iain's development activities. May contain z80, Cocoa, or whatever.
27 January 2023
Final touches at the moment to the scrolling code was to move the map data and the drawing code into the graphics page so I didn’t have to switch pages and change stack twice for every tile. Now it changes to the graphics page when it wants to draw the map and back to the main page once the map is drawn. This is about 150t less per tile. So it’s worked out as a pretty big performance boost.
And here it is - it doesn’t tear on the screen just in the gif, thanks giphy capture.
Next I think I’ll tackle either animation or character movement
25 January 2023
Victory. Solved the memory corruption bug.
The problem was in the interrupt handling code which I borrowed along with most of the rest of the basic setup code to get things going from Howard Price’s Flappy Bird clone.
The interrupt handlers are stored as a linked list of three values: the line number for a line interrupt, the address of the handler, and the address of the next entry in the list. As I’m not using the line interrupts (yet) this list is a single entry that points back to the start of itself.
At initialisation int.reset
is set to the start of this linked list, and after every interrupt the list moves to the next value and sets two memory locations int.jump
to the address that contains the address of the jump handler in the list and int.next
to the address that contains the address of the next entry in the list. Because there’s only one entry in this list these values never change. I wonder if the extra level of indirection here is going to be important later.
The problematic code in question is this routine
@check_int: ; Make sure this is the frame interrupt
int.status: ld a,0
bit FrameIntBit,a
jp z,@+save_regs
@reset_int_manager: ; If not, reset to start properly next time
ld hl,(int.reset)
ld (int.next),hl
ret
What this routine does is load the status value into a
(the 0 has been replaced at runtime by the actual value) and checks if the status is a frame interrupt. If it is not, it resets the int.next
to point at the start of the interrupt handler linked list to go back to the start. But that’s not what int.next
is supposed to contain: int.next
is supposed to contain the address of the memory containing the address, not the address itself.
So the correct thing is to set int.next
to int.reset + 3
@reset_int_manager:
ld hl,(int.reset)
FOR 3, inc hl
ld (int.next),hl
The only question I have is why bit FrameIntBit, a
is failing and putting us into the @reset_int_manager
code in the first place. There’s no non-frame interrupts set, and the status value is FF
which my vague reading of the Sam Coupé development manual would be invalid. So, I dunno. It works now and that’s all that matters.
The hardest part of it was to find a simple way to reproduce the bug: at one point the reproduction steps were to add a breakpoint and skip it 157 times, but even then there were still thousands of instuctions to step through before the bug triggered. Once I worked out the bug was happening inside the interrupt handler, I added a frame interrupt breakpoint and could step the code there and saw funny things happening after the @reset_int_manager
code ran. Then lots more staring trying to work out how the indirection worked.
I could delete this code and hardcode it all, but I like having the flexibility of having different interrupt handlers available if I need them. Now I can work on something more interesting now I have scrolling working again.
22 January 2023
Been tracking down a weird memory corruption bug related to the interrupts. Because of the use of pop/push to copy the tiles from memory to the screen, I’ve disabled interrupts while that is happening. This shouldn’t really be necessary as the interrupts use their own stack, but if I don’t disable interrupts then memory corruption appears on the screen, which is confusing due to the separate stack.
In the working version, if I disable interrupts once for the whole map drawing then it works fine, if I only disable interrupts around the code that manipulates the stack for each tile then the interrupt handler eventually gets messed up and jumps into invalid memory.
Been getting to know the SimCoupe debugger much better, but haven’t tracked the bug down yet.
20 January 2023
My time has been split between various different things, so I’ve not had as much time as I’d have liked to work on the scrolling, but what time I did have has been spent trying to fix niggling little bugs in the various different odd and even scrolling routines, sharing code and things like that. I got one pixel per update working, but it still felt slow and the code was very complex with the start of each routine having to set up the different values and subroutines necessary to share some code with lots of SMC code.
So, I had a think and put in some profiling code, and I think it was taking a frame and a half to draw the screen each time. If this code is to ever become something more than an experiment, if it would turn into a game, then that’s far too much, because we still need all the other sprites drawn, all the animation handled, all the game logic handled, keyboard processing, music and sfx processing… and there doesn’t seem like there’s time left for that.
The outcome is that I’ve simplified the code so now it only draws the even frames: 2 pixels per update. This makes things so much simpler, the odd offset code means it never needs to merge nibbles from screen with sprite nibbles and one path for drawing every time. It also reduces the memory usage as the map tiles don’t need to be shifted. It also makes the scrolling move at double speed, so I can make it only update the screen once every 2 frames to get the same speed as 1 pixel per update and suddenly I’ve regained an entire screen refresh for logic stuff.
11 January 2023
Slow few days. Have the even clip code done and am now adjusting it to work for the odd clips as well. Most of the last few days has been spent agonising about how to improve the speed and keep the code maintainable. For example, the code for drawing the even/odd maps are identical except for three subroutine calls, and I was trying to find the best way to not duplicate this code, but not add too much extra time to each frame. I tried jump tables and vector tables and finally the thing that worked was just using self modifying code to change the addresses that get called every frame. But that added about 60 t-states to each frame over just having the same code duplicated twice.
I had resigned myself to just maintaining two copies of slightly complex code, but then I realised that if I can move the memory page switching code to just switch pages once at the start of each frame then I’ll save 100 t-states per tile, which for the level I’m using to test works out as a saving of about 15,000 t-states, so I shouldn’t worry too much about adding 60 t-states per frame.