It took me a little while to understand what I was doing so wrong last time. It was almost hilarious in hindsight.
So to recap, I’m using the Layer 2 port ($123b) to switch which bits of the video screen are accessible in the region of memory from $0000 to $3fff, and this is doing some funky things with the MMU to make it happen. I’m also doing funky things because my little routine is receiving its parameters from the ZX Basic compiler via the IX registers.
It turned out that several things were wrong in my original code, but they’re all stupid things.
First up, if you’re going to use $123b to bank up memory to write to Layer 2, you have to actually put it in write mode. This, it turns out, tends to help. Bit 4 should be set on the instruction to switch banks.
Secondly, I’d misunderstood how the ZX Basic compiler sends parameters across to the code. I’d understood from samples that the first parameter was available from IX+5, what I hadn’t clocked is which way around the bytes are in this; I’m passing in a 16 bit number and I want to treat it as such, but it’s little endian – so I get the high byte first. If I’m passing in 16 bits, bits 15-8 are in IX+5 and bits 7-0 are in IX+4. The docs don’t make this clear that this is what is happening.
Thirdly, and possibly most hilariously. I’m passing in a number ostensibly from 0 to 639 to represent the X coordinate. I divest myself of the relevant high bits to bank switch but somehow along the way I forgot I needed to do a shift-right to halve that number. My routine accepts a single byte to write two pixels, and I don’t need to do anything else except ensure that what I pass in gets turned into this format! That’s the stupid thing here: the screen is in 128 pixel slices, but only 64 bytes represent a row of each slice, I need to halve the number (rounding down) to get to the right byte.
I’m going to chalk this up mostly to a combination of tiredness from work and a general lack of thinking in bytes. But this is essentially the routine we end up with, including all my nonsense comments for now.
SUB PlotPixelPair(byval x as uinteger, byval y as ubyte, byval colour as ubyte)
ASM
; Turn off interrupts interrupting us.
#ifndef IM2
call _checkints
di
#endif
; Preserve all registers coming in.
push bc
push de
push hl
; Get the current thing banked in 0000-3FFF, and push that onto the stack.
ld bc, LAYER2_ACCESS_P_123B
in a,(c)
push af
ld a, 1
out (c), a
; So this is where it gets interesting.
; IX+4/IX+5 = 0-638 x coordinate
; IX+7 = 0-255 y coordinate
; Now, if we ignore everything but the bottom 2 bits in (IX+4), shift it left, setting bit 0 based
; on bit 7 of (IX+5) we can get the bank value, then (IX+5) AND $7F gets us the x coord within the bank.
; At which point all you need to do is switch bank, ld l, (IX+7); ld h, (IX+5); ld (hl), (IX+9)
ld d, (IX+9)
determinebank:
ld a, (IX+5) ; Get the MSB of X coord
and %00000011 ; Collect only the bits we care about for banking purposes.
sla a ; Shift left, leaving space to identify even or odd bank.
or %00010000 ; Make sure to set the bit that controls this bank behaviour.
ld h, (IX+4) ; Get the LSB of X coord
bit 7, h ; Transport the top bit of it into A
jr z, evenbank
inc a
evenbank:
res 7, h ; Nuke the top bit of H, so at this point A is our bank, H is the X byte inside the bank
srl h
out (c), a ; Switch bank
ld l, (IX+7) ; Put our Y into the right part of things
ld (hl), d ; Write the pixel pair
pop af
out (c), a ; Return to previous bank state
; Reset all registers ready to go out.
pop hl
pop de
pop bc
#ifndef IM2
ReenableInts
#endif
END ASM
END SUB
This at least lets me draw in the little Spectrum-like swoosh in the top right corner, which as silly as it is, represents progress.
The colours aren’t quite what I’d like but I’m bound by the limits of the Next’s palette here and honestly, that’s OK. It has the look I wanted in general and I’ll take that.
Next time, we really must get around to drawing some text on the screen.