Writing to video memory
For most things to do with graphics, we need to write to video memory, so learning how to do it is one of the basic steps you need to learn. This page explains that, and other pages will refer here in case you need it later.
What we need
Before we begin, we need some of the constants that were explained in the page about setting up the video hardware. Two important addresses are the VDP control and data ports, which is where we write to do everything:
VdpCtrl: equ $C00004 ; VDP control port
VdpData: equ $C00000 ; VDP data port
But also importantly, when we cleared video memory we defined a few constants that were used to write to memory in the first place, we're gonna need them:
VRAM_ADDR_CMD: equ $40000000
CRAM_ADDR_CMD: equ $C0000000
VSRAM_ADDR_CMD: equ $40000010
- To write video RAM (most things), we use
VRAM_ADDR_CMD
- To write color RAM (i.e. palettes), we use
CRAM_ADDR_CMD
- To write vertical scroll RAM, we use
VSRAM_ADDR_CMD
Setting the address
Fixed addresses
To tell the VDP into which address (and which memory) we want to write,
we need to send a 32-bit value (a longword) to VdpCtrl
. The
value must be as follows:
- Bits 13-0 of address go in bits 29-16 of the command
- Bits 15-14 of address go in bits 1-0 of the command
- Then you need to OR with one of the
*_ADDR_CMD
constants
Written as a calculation it becomes the following mess:
((address AND $3FFF
) << 16) OR
((address AND $C000
) >> 14) OR
constant
Doing this manually every time would be annoying, so we're gonna wrap this neatly into macros (also note the use of a generic macro that all the other macros call, to make it even easier):
SetXramAddr: macro addr, cmd
move.l #(((addr)&$3FFF)<<16))|(((addr)&$C000)>>14)|(cmd), (VdpCtrl)
endm
SetVramAddr: macro addr
SetXramAddr addr, VRAM_ADDR_CMD
endm
SetCramAddr: macro addr
SetXramAddr addr, CRAM_ADDR_CMD
endm
SetVsramAddr: macro addr
SetXramAddr addr, VSRAM_ADDR_CMD
endm
To set to VRAM address 1234 (for example):
SetVramAddr 1234
Variable addresses
What if the address isn't fixed? Say we want to make a generic routine to load data into video memory (e.g. tiles), it won't know ahead of time what address it has to write to. Worse yet, if you try to do it like in the macros above but using registers, the resulting code will be an awful mess.
Thankfully, there's a neat trick: bits 13-0 are left in the first word while bits 15-14 are in the second word. A register contains two words. The trick here is that a longword bit shift will affect the whole register, but a word bit shift will only affect the low word (and leave the other word alone). So the idea is:
- Clear higher word if needed
- Push bits 15-14 into the high word using a longword shift
- Push bits 13-0 back in place using a word shift
- Use
swap
to flip the words into correct order - Do the rest
The result is like follows:
; The following modifies the register with
; the address (one of d0-d7), but nothing else
SetXramAddrReg: macro reg, cmd
and.l #$FFFF, reg
lsl.l #2, reg
lsr.w #2, reg
swap reg
or.l #cmd, reg
move.l reg, (VdpCtrl)
endm
SetVramAddrReg: macro reg
SetXramAddrReg reg, VRAM_ADDR_CMD
endm
SetCramAddrReg: macro reg
SetXramAddrReg reg, CRAM_ADDR_CMD
endm
SetVsramAddrReg: macro reg
SetXramAddrReg reg, VSRAM_ADDR_CMD
endm
Then you could do stuff like:
move.w #1234, d0
SetVramAddrReg d0
Writing the data
Now that we've set the address we need to write the data into video
memory. This part is easier: write everything to VdpData
(do not increment the address, i.e. always write to the same
location). Note that you must write words or longwords (not
bytes).
Simple example to copy arbitrary data to VRAM:
; d0.w = VRAM address
; d1.w = number of words (*not* bytes)
; a0.l = pointer to data
CopyToVram:
; Tell VDP where we'll write
SetVramAddrReg d0
; Copy the data to VRAM
; Note the substract because DBF
; stops at -1 instead of 0 and
; how we do *not* increment a1
subq.w #1, d1
blo.s @End
lea (VdpData), a1
@Loop:
move.w (a0)+, (a1)
dbf d1, @Loop
@End:
; We're done
rts
Further optimization tips:
- Writing a longword to
VdpData
is the same as writing two consecutive words to it. - Unrolling the loop if you know the data will be a multiple of a given size of course also helps.