140 lines
6.7 KiB
Fennel
140 lines
6.7 KiB
Fennel
; IIgs graphical architecture:
|
|
; PREMISE: All small-scale bitmapped graphics are encoded as code that pushes the graphics onto the stack,
|
|
; which has been aligned to the appropriate place to draw said graphics.
|
|
; Jump tables are stored in the main code segment so that eg. tiles can be easily calculated by index, and
|
|
; so we don't have to explicitly pass longs to a regular function.
|
|
|
|
(import-macros {:sss ! : compile} :ssc.macros)
|
|
(local lume (require :lib.lume))
|
|
(local {: countiter} (require :lib.util))
|
|
|
|
; Our bitmap compiler is based on MrSprite - http://brutaldeluxe.fr/products/crossdevtools/mrspritetech/
|
|
; sprite is a Lua string, where each byte is made up of two nibbles - the low nibble
|
|
; should be a 16-bit value representing the colour, and the high nibble should be
|
|
; 0 if the pixel is opaque, or f if the pixel is transparent.
|
|
; width should be a multiple of 4.
|
|
(fn preprocess-sprite [sprite w h]
|
|
; splits up each horizontal line into two kinds of "runs":
|
|
; :solid - each word can be directly written to memory; there is no transparency
|
|
; :masked - the word at this location must be bitwise ANDed by :mask and ORed by :word
|
|
; words containing nothing but transparent pixels are removed.
|
|
; Also determines the most frequently-occurring solid words and distributes them
|
|
; to registers.
|
|
(let [rows [] frequencies {}]
|
|
(var word 0)
|
|
(var mask 0)
|
|
(var isprite 1)
|
|
(for [y 0 (- h 1)]
|
|
(let [row []]
|
|
(var solidrun nil)
|
|
(for [x 0 (- w 1)]
|
|
(let [b (string.byte (sprite:sub isprite isprite))
|
|
pixcolour (bit.band b 0x0f)
|
|
pixmask (bit.rshift (bit.band b 0xf0) 4)
|
|
pixshift (match (% x 4) 0 4 1 0 2 12 3 8)]
|
|
(set word (bit.bor word (bit.lshift pixcolour pixshift)))
|
|
(set mask (bit.bor mask (bit.lshift pixmask pixshift)))
|
|
(when (= (% x 4) 3)
|
|
(when (not= mask 0) (set solidrun nil))
|
|
(when (= mask 0) ; fully opaque word
|
|
(when (= solidrun nil)
|
|
(set solidrun {:run :solid :x (/ (- x 3) 2) :words []})
|
|
(table.insert row solidrun))
|
|
(table.insert solidrun.words word)
|
|
(tset frequencies word (+ (or (. frequencies word) 0) 1)))
|
|
(when (and (not= mask 0) (not= mask 0xffff))
|
|
(table.insert row {:run :masked :x (/ (- x 3) 2) : word : mask}))
|
|
(set word 0)
|
|
(set mask 0))
|
|
(set isprite (+ isprite 1))))
|
|
(table.insert rows row)))
|
|
(local top-frequencies (icollect [word freq (pairs frequencies)] {: word : freq}))
|
|
(table.sort top-frequencies #(> $1.freq $2.freq))
|
|
{: rows
|
|
:registers {:x (?. top-frequencies 1 :word)
|
|
:y (?. top-frequencies 2 :word)
|
|
:d (?. top-frequencies 3 :word)}}))
|
|
|
|
(fn compile-row [block row registers]
|
|
(each [_ run (ipairs row)]
|
|
(match run.run
|
|
:solid
|
|
(let [s-target (+ run.x (* (length run.words) 2) -1)]
|
|
(lume.push block [:tsc] [:adc (- s-target registers.s)] [:tcs])
|
|
(set registers.s (- run.x 1))
|
|
(for [iword (length run.words) 1 -1]
|
|
(lume.push block
|
|
(match (. run.words iword)
|
|
registers.x [:phx]
|
|
registers.y [:phy]
|
|
registers.d [:phd]
|
|
word [:pea word]))))
|
|
:masked
|
|
(do (var s-offset (- run.x registers.s))
|
|
(when (> s-offset 127)
|
|
(lume.push block [:tsc] [:adc s-offset] [:tcs])
|
|
(set registers.s run.x)
|
|
(set s-offset 0))
|
|
(lume.push block [:lda s-offset :s] [:and run.mask] [:ora run.word] [:sta s-offset :s])))))
|
|
|
|
(fn compile-sprite [sprite w h]
|
|
(let [{: rows : registers} (preprocess-sprite sprite w h)
|
|
block (lume.concat [:block]
|
|
(when registers.x [[:ldx registers.x]])
|
|
(when registers.y [[:ldy registers.y]])
|
|
(when registers.d [[:lda registers.d] [:tcd]]))]
|
|
(set registers.s 0)
|
|
(each [_ row (ipairs rows)]
|
|
(compile-row block row registers)
|
|
(set registers.s (- registers.s 160)))
|
|
block))
|
|
|
|
#(compile $1
|
|
(fn enable-shadow-writes () (set! (word-at (ref :0xc035)) (& (word-at (ref :0xc035)) 0xfff1)))
|
|
(fn disable-shadow-writes () (set! (word-at (ref :0xc035)) (| (word-at (ref :0xc035)) 0x000e)))
|
|
|
|
; The fastest way to draw any graphics on the IIgs is to map the stack pointer to
|
|
; video memory, and use stack-pushing instructions to write values. draw-object
|
|
; takes a location in video memory and a pointer to a machine code routine called a "drawfn"
|
|
; that performs the drawing
|
|
(global word draw-object-saved-stack 0)
|
|
(fn draw-object (screen object)
|
|
(asm (sei) ; disable interrupts
|
|
(lda object) (sta [{:abs #(+ ($1:lookup-addr :draw-object-current-object-jump) 1)}]) ; self-modifying code! rewrite the jump target
|
|
(phd) ; save direct page register
|
|
(tsc) (sta draw-object-saved-stack) ; save stack
|
|
(lda screen 2) ; we offset by 2 because we just pushed a word onto the stack and the compiler doesn't know about it
|
|
(tcs) ; drawfns expect the current screen pointer to be stored in the stack register
|
|
(lda :0xc068) (ora 0x30) (sta :0xc068) ; move bank 1 to bank 0
|
|
(clc) ; clear carry - all drawfns will add to the stack pointer and then walk it back
|
|
draw-object-current-object-jump
|
|
(jmp draw-object) ; will actually jump to "object"
|
|
draw-object-finished (export draw-object-finished)
|
|
(lda :0xc068) (and 0xffcf) (sta :0xc068) ; move bank 1 back to bank 1
|
|
(lda draw-object-saved-stack) (tcs) ; restore the stack pointer
|
|
(pld) ; restore direct page register
|
|
(cli))) ; enable interrupts
|
|
|
|
(macrobarrier drawfn)
|
|
(form drawfn [(lambda [ssc name ...]
|
|
(let [expr (lume.concat [:do ...] [[:asm [:jmp :draw-object-finished]]])]
|
|
(ssc:define-fn name nil #(do
|
|
(local fname (.. "<drawfn " name ">"))
|
|
(local asm (ssc:expr-poly expr))
|
|
(ssc:expr-poly [:form name (fn [ssc] (assert (= ssc.locals nil) (.. name " must be called from a drawfn")) [:jmp fname])])
|
|
(ssc:expr-poly [:define name [:ref fname]])
|
|
(ssc.org:append fname asm)))))])
|
|
|
|
(drawfn pei-slam-tile
|
|
(asm (tsc) (tcd) (adc 7) (tcs)
|
|
[(lume.concat [:block] (icollect [_ (countiter 16)]
|
|
[(! block (pei (:d6)) (pei (:d4)) (pei (:d2)) (pei (:d0))
|
|
(tsc) (adc 161) (tcd) (adc 7) (tcs))]))]))
|
|
|
|
(drawfn pei-slam-scanline
|
|
(asm (tsc) (tcd) (adc 159) (tcs)
|
|
[(lume.concat [:block] (icollect [offset (countiter 158 0 -2)] [:pei [(.. :d offset)]]))]))
|
|
|
|
(form compile-sprite [(lambda [ssc name sprite w h] (ssc:expr-poly [:drawfn name [:asm (compile-sprite sprite w h)]]))])
|
|
)
|