Keeping track of registers

The place for codemasters or beginners to talk about programming any language for the Spectrum.
Dr_Dave
Drutt
Posts: 32
Joined: Wed Feb 05, 2020 9:58 am

Keeping track of registers

Post by Dr_Dave »

Hi all!

After procrastinating on learning Z80 Assembly language for about 35 years, I'm finally taking the plunge and trying to figure it all out. I'm a coder by trade, so I figured "how hard could it be?" Proves what I know...

I am making some progress, mostly by writing broken code then interating each instruction until something basically kind of works. I'm trying to build a very basic game, and you can see the progress here:

https://www.youtube.com/watch?v=WyFkG-fZoHo

But I'm kind of getting to the point where I need to step back and rethink my approach. Specifically, how I keep track of and use all of my registers. I'm finding a lot of my bugs arise because I've overwritten a value I expected to find, or some unexpected value still exists from somewhere else in one of my registers. I don't really feel as if my rather haphazard approach will be scalable to something more extensive, so want to get hold of it now - in truth, the lack of variables, as such, has been the hardest concept to get my head around. I'm finding that it sometimes feels like I'm running out of fingers to count on.

So - a general topic for discussion. What is your approach to tracking your registers? How do you manage them cleanly in the most efficient way possible? Does this question even make sense?

Other than that - other than the brain-aching frustration - I'm actually really enjoying this little exercise and it's been great discovering this forum! No idea how coders in the 80s did it without PCs to code on and the internet to post dumb questions to :)
User avatar
djnzx48
Manic Miner
Posts: 730
Joined: Wed Dec 06, 2017 2:13 am
Location: New Zealand

Re: Keeping track of registers

Post by djnzx48 »

The game looks good so far!

One method that I found helpful in the past was to write a routine using arbitrary variable names instead of registers. Then after the general structure of the code was complete, I could replace them with actual Z80 registers depending on how they were used (e.g. for accessing memory, or arithmetic). I would also check whether any variable lifetimes were mutually exclusive, and if so, merge them into a single register.
User avatar
Morkin
Bugaboo
Posts: 3266
Joined: Mon Nov 13, 2017 8:50 am
Location: Bristol, UK

Re: Keeping track of registers

Post by Morkin »

Dr_Dave wrote: Wed Feb 05, 2020 10:07 am I'm finding a lot of my bugs arise because I've overwritten a value I expected to find, or some unexpected value still exists from somewhere else in one of my registers.
Haha..! I reckon that accounts for roughly 90% of my entire z80 coding activity time... :lol:
Other than that - other than the brain-aching frustration - I'm actually really enjoying this little exercise and it's been great discovering this forum! No idea how coders in the 80s did it without PCs to code on and the internet to post dumb questions to :)
Indeed, am not a 'modern' coder myself but feel a lot better about posting z80 questions here, you usually get some good suggestions for faster/smaller code and people aren't overly critical.. :)

Game's shaping up well I reckon. 8-)
My Speccy site: thirdharmoniser.com
User avatar
Joefish
Rick Dangerous
Posts: 2058
Joined: Tue Nov 14, 2017 10:26 am

Re: Keeping track of registers

Post by Joefish »

You need to get organised and use all the structured programming ideas you've seen in other languages.

Specifically, break your program down into functions, each with a very specific set of input parameters. They needn't be on the stack like compiled C; you can simply say that e.g. on entry to my PRINT_ONE_CHARACTER function, B is the X-position, C is the Y-position and A is the character to print. Or maybe HL points to the character in memory. Then CALL it and RETurn at the end.

Also pick a strategy for preserving other registers, as you may be calling your function from inside a loop, which is counted down by other registers. Either you PUSH the values you want to preserve BEFORE you call your function, or your function PUSHes all the registers it's going to need to use itself and POPs them at the end to restore them just before the RET. You could also, for example, end that print fuction with C the same and B one higher so the next print is to the adjacent character, or just return with A, B and C the same as at the start of the function. Then if your calling function is actually printing a string, it should INC B, put the next character in A, and call the print function again.

That way, you're not chasing register values from one function to the next. You still might need to faff about deciding whether you're best using HL, DE, IX etc. as an address pointer inside a function, and whether to store A temporaraily in another register or PUSH AF or something, but at least the problem is contained within that function.

It doesn't give you the fastest and most efficient code possible, but it'll make a lot more sense. Just concentrate your optimisation on any function that's used the most often, to get the most out of it.

For slightly longer storage, define your own variables. Pick a spot of memory and say something like VARS EQU 60000
The define your variables as PLAYER_X EQU VARS // PLAYER_Y EQU VARS+1 // PLAYER_SPRITE EQU VARS+2 etc. (Just remember pointers are worth 2 bytes!) Now store your data there with functions like LD A,(PLAYER_X) or LD (PLAYER_SPRITE),A

If you really want to go as far as structured data, you can do that too. e.g. SPRITE EQU VARS for the main structure, then define offsets within that structure _X_POS EQU 0 // _Y_POS EQU 1 // _IMG_ID EQU 2 and overall SPRITE_SIZE EQU 3
Then use registers with offset, e.g. LD IX,SPRITE to point to the overall structure then LD A,(IX+_X_POS) // LD (IX+_IMG_ID),A to access its member data. And because you're now using an address register (instead of a hard-coded address), there are instructions for reading/writing other 8-bit registers e.g. LD E,(IX+_Y_POS).
Just remember you need to do something like LD BC,SPRITE_SIZE // ADD IX,BC to move to the next structure in a series.
Last edited by Joefish on Wed Feb 05, 2020 12:18 pm, edited 1 time in total.
User avatar
Joefish
Rick Dangerous
Posts: 2058
Joined: Tue Nov 14, 2017 10:26 am

Re: Keeping track of registers

Post by Joefish »

Just out of interest, what are you using to capture your Speccy screen and turn it into YouTube-able video?
Dr_Dave
Drutt
Posts: 32
Joined: Wed Feb 05, 2020 9:58 am

Re: Keeping track of registers

Post by Dr_Dave »

Joefish wrote: Wed Feb 05, 2020 12:17 pm Just out of interest, what are you using to capture your Speccy screen and turn it into YouTube-able video?
Thanks for all your advice... all makes perfect sense. I figured it was just a case of being more organised and structured, but wondered if I was missing something somewhere. It felt a lot like juggling.

For capture, I'm just using OBS to capture the output of a window and save to an MP4. I found that using the capture from Speculator emulator, for example, resulted in a tiny video that didn't scale up very well on YouTube.
catmeows
Manic Miner
Posts: 716
Joined: Tue May 28, 2019 12:02 pm
Location: Prague

Re: Keeping track of registers

Post by catmeows »

Dr_Dave wrote: Wed Feb 05, 2020 10:07 am

So - a general topic for discussion. What is your approach to tracking your registers? How do you manage them cleanly in the most efficient way possible? Does this question even make sense?
DO NOT TRACK :)
Track it only for small portions of code like small, single purpose subroutine or small loop. And by small I mean 20 to 30 instructions at most. That are the only valid cases when you should map registers to logical vars. Otherwise load values from memory on demand and store to memory as soon you are done.
Optimize register allocation when everything is working and there Is need for improvement. Even then start with bottlenecks.
Proud owner of Didaktik M
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2641
Joined: Mon Nov 13, 2017 3:16 pm

Re: Keeping track of registers

Post by Ast A. Moore »

Yeah, it’s all about the overall structure of your code. Each subroutine you write should be self-sufficient. However, that’s not always practical, particularly when speed is a concern. It’s okay to use actual variables (that is, values stored in memory). As a memory/speed-saving measure, you can use self-modifying code, but that’s a fairly advanced technique that makes code slightly more difficult to follow. For example, instead of

Code: Select all

	ld a,(lives)
	or a
	jp nz,somewhere_else

lives	defb 3
you could refactor your code into something like

Code: Select all

lives	ld a,0			;self-modifying code
	or a
	jp nz,somewhere_else
And then elsewhere in your code, where you need to read and modify the lives variable, instead of

Code: Select all

	ld hl,lives
	dec (hl)
you do

Code: Select all

	ld hl,lives+1
	dec (hl)
That will save you two bytes and four T states (LD A,(**) is 13 T states; LD A,* is 7 T states).
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
User avatar
Joefish
Rick Dangerous
Posts: 2058
Joined: Tue Nov 14, 2017 10:26 am

Re: Keeping track of registers

Post by Joefish »

Yes, self-modifying code can be a nightmare at times, but you can also use it to free up variables. e.g.

Code: Select all

  LD A,99 ;I'm going to loop 99 times, but I'm not going to hold up the A register, and PUSH/POP is for amateurs...
loop
  LD (loop_end+1),A
  ...
  Do stuff... with A if you like...
  ...
loop_end
  LD A,0  ;This instruction has just been modified to LD A,99
  DEC A
  JP NZ,loop
Dr_Dave
Drutt
Posts: 32
Joined: Wed Feb 05, 2020 9:58 am

Re: Keeping track of registers

Post by Dr_Dave »

I think a big realisation for me - highlighted in the examples above - is that (if I can explain this properly) the code that you write maps onto the memory you work with. So a big part of coding in assembly is shifting around that memory/code to other places. As a high level coder, I'm not used to being so deep down "in it", so to speak. But I can see how it could be very powerful.
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2641
Joined: Mon Nov 13, 2017 3:16 pm

Re: Keeping track of registers

Post by Ast A. Moore »

Oh, and don’t be shy of using the undocumented instructions for manipulating index register halves. I adopted this technique years ago, and it’s been a huge boon. You get four additional 8-bit registers for free. (Four, Carl!) Just remember that if you do ROM calls, you need to reload the IY register with 0x5C3A—that’s the base address of the system variables area in the Spectrum. Not all ROM routines use it, but plenty do.

The use of index register halves is particularly useful in loops. Instead of pushing and popping values onto and from the stack (which will cost you 21 T states), you do LD IXl,* . . . DEC IXl. That’s only 19 T states. Three doesn’t seem like a lot of T states at first glance, but in a loop, they add up pretty quickly.

Prime registers can also be pretty useful. EX AF,AF′ and EXX are only 4 T states each, but will free you all the main registers to work with. Just be mindful of the flags register. You don’t want to do an EX AF,AF′ before a conditional instruction accidentally. (Although I do use that particular trick or saving and restoring both the accumulator and the flags register in tight sprite drawing routines sometimes, that only happens in a very specific and very controlled environment.)
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
User avatar
Joefish
Rick Dangerous
Posts: 2058
Joined: Tue Nov 14, 2017 10:26 am

Re: Keeping track of registers

Post by Joefish »

Dr_Dave wrote: Wed Feb 05, 2020 12:58 pm I think a big realisation for me - highlighted in the examples above - is that (if I can explain this properly) the code that you write maps onto the memory you work with. So a big part of coding in assembly is shifting around that memory/code to other places. As a high level coder, I'm not used to being so deep down "in it", so to speak. But I can see how it could be very powerful.
It can be fiddly - you have to keep track of how much memory your program code takes, to watch out for it overlapping data or variables or workspace. And decide whether something like your graphics data should come immediately after your code, or be loaded to a fixed address. Best not to copy code around though. You'd either have to re-calculate all the jumps, or keep them all small enough to use relative jumps - which gets really hard after a while.

Then there's the speed code runs at. Anything in the address range 16384-32767 is sharing a RAM chip with the screen, so memory access and program execution is slowed down in a complicated contention pattern by the ULA access. But once you're in machine code, if you don't want to use any ROM calls, you can overwrite the System Variables and BASIC area with your own data. I usually load a font, for example, into higher memory, but copy it down to lower memory as soon as the code runs because I only use it on title screens so don't care if it's a bit slow to access. That frees up some higher memory for buffering, etc.

The other problem with everything in RAM is it can make compressed data a bit redundant, unless you're getting really good compression (e.g. 50% or better). Because as well as the compressed data, you need spare RAM to uncompress it into for working with, which can all add up to more memory than just storing the plain unpacked stuff.
User avatar
Einar Saukas
Bugaboo
Posts: 3097
Joined: Wed Nov 15, 2017 2:48 pm

Re: Keeping track of registers

Post by Einar Saukas »

Dr_Dave wrote: Wed Feb 05, 2020 10:07 amWhat is your approach to tracking your registers?
Good documentation.

There's no need to avoid complex optimizations in your code. As long as you document everything properly, you will be fine.

For instance, the entire COMPLICA DX source code is commented like this:

Code: Select all

; -----------------------------------------------------------------------------
; Specialized routine to process the lowest tree node (depth zero) of the
; Minimax search tree with alpha-beta pruning, when it requires MINIMIZING
; player's score (for opponent move).
;
; Technically "heuristic_min" works exactly like "alphabeta_min_loop", except
; it directly evaluates board for each child node instead of calling
; "alphabeta_max" recursively. Although it would be easier to just let it
; invoke "alphabeta_max" again to obtain the evaluated board, this extra
; level of recursion would represent an overhead of another 108 T-states per
; leaf node. In level 1 for instance, it would mean a negligible overhead of
; 4*108 = 432 T-states only. But in level 7, it could provide an overhead of
; (4^7)*108 = 1769472 T-states. Condering BRAINIAC only has about 20K T-states
; available per frame (multicolor rendering consumes most CPU resources), this
; simple optimization is responsible for making BRAINIAC almost 2 seconds
; faster.
;
; Parameters:
;   B: must be 4
;   C: opponent (1 or 2)
;   D: alphaMax
;   E: betaMin
;
; Returns:
;   A: "improved" betaMin
;   D: alphaMax
;   E: "improved" betaMin
;   A': opponent (1 or 2)
;
; Uses:
;   ai_depth: current recursion depth
;   ai_player: current player
;
; Destroys:
;   F, B, HL, F'
; -----------------------------------------------------------------------------
heuristic_min:                          ; for each column {
        ld      a, b
        dec     a                       ;     column (0-3)
        push    de
        push    bc
        call    BRAINIAC_play           ;     insert opponent piece at column
        push    hl
        push    af
        call    ai_eval_board           ;     A = eval_board
        ld      c, a
        pop     af
        pop     hl
        call    BRAINIAC_undo           ;     remove opponent piece from column
        ld      a, c
        pop     bc
        pop     de

        cp      e
        jr      nc, heuristic_min_next  ;     if (A >= betaMin) continue

        cp      d
        jr      c, heuristic_min_exit
        jr      z, heuristic_min_exit   ;     if (A <= alphaMax) { depth++; return A }

        ld      e, a                    ;     betaMin = A

heuristic_min_next:
        djnz    heuristic_min           ; }
        ld      a, e
heuristic_min_exit:
        ld      hl, ai_depth
        inc     (hl)                    ; depth++
        ret                             ; return betaMin
Each routine contains a header that provides an overall explanation, a list of input parameters, output results, accessed global variables and modified registers. Also the code itself provides more detailed comments organized as pseudo-code.

It may seem overkill, but it's much better to spend time on documentation instead of tracking bugs.
Last edited by Einar Saukas on Fri Feb 07, 2020 3:57 pm, edited 1 time in total.
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2641
Joined: Mon Nov 13, 2017 3:16 pm

Re: Keeping track of registers

Post by Ast A. Moore »

True. Profuse commenting is key. It’s easy to forget to comment your code in the spur of the moment. The problem is, if you don’t, you’re almost guaranteed to forget what your code does in a matter of days.
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
User avatar
uglifruit
Manic Miner
Posts: 703
Joined: Thu Jan 17, 2019 12:41 pm
Location: Leicester
Contact:

Re: Keeping track of registers

Post by uglifruit »

I absolutely totally positively agree with this. Comment comment comment. Then comment some more.*

My ability to forget what a subroutine does and how it works straight after I've written never ceases to amaze me (and is often the reason for my things ending up unfinished). I think it's a Cthulhu type reaction to my brain has to trying to cope with the unimaginable horrors I've faced.


* There is a fair amount of hypocritical "do as I say, not as I do" here.
CLEAR 23855
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2641
Joined: Mon Nov 13, 2017 3:16 pm

Re: Keeping track of registers

Post by Ast A. Moore »

uglifruit wrote: Fri Feb 07, 2020 5:24 pm * There is a fair amount of hypocritical "do as I say, not as I do" here.
It’s mostly the “Eh, I’ll comment it all later; get off my case and just let me code now” excuse that comes back and bites you in the heinie.
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
User avatar
uglifruit
Manic Miner
Posts: 703
Joined: Thu Jan 17, 2019 12:41 pm
Location: Leicester
Contact:

Re: Keeping track of registers

Post by uglifruit »

Actually one of my dirty stock routines that I regularly copy and paste is a "push everything, print all the registers, pop everything, return" that I call upon when I can't figure out what the heck I am doing wrong. And it's normally something as stupid as resetting a counter inside a loop that it's supposed the be tracking, or somesuch daftness.

Yesterday I was pleased to add a "rotate each of the characters by 90 degrees" in my font designer program.
At one point I sat with pen and paper to do the first couple of iterations of loops, tracking the registers so I was confident that I was rr ing the right thing into the carry flag and then conditionally or-ring the right value of a different register (that started at 128, then itself got rr'd to 64, 32, etc). I was pretty shocked when it actually worked as I hoped it would!
CLEAR 23855
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2641
Joined: Mon Nov 13, 2017 3:16 pm

Re: Keeping track of registers

Post by Ast A. Moore »

It’s surprising how essential the good old pen and paper are in coding. I also like to visualize bit shifts by placing eight fingers on the keycaps, without pressing the keys, and moving them around, each hand representing a nibble. You can also use one of the thumbs as the carry flag.
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
User avatar
1024MAK
Bugaboo
Posts: 3118
Joined: Wed Nov 15, 2017 2:52 pm
Location: Sunny Somerset in the U.K. in Europe

Re: Keeping track of registers

Post by 1024MAK »

Ahh, lack of good detail in comments in the code. Guaranteed to cause problems weeks, months or years later....

Mark
:!: Standby alert :!:
“There are four lights!”
Step up to red alert. Sir, are you absolutely sure? It does mean changing the bulb :dance
Looking forward to summer later in the year.
User avatar
1024MAK
Bugaboo
Posts: 3118
Joined: Wed Nov 15, 2017 2:52 pm
Location: Sunny Somerset in the U.K. in Europe

Re: Keeping track of registers

Post by 1024MAK »

Ast A. Moore wrote: Fri Feb 07, 2020 8:13 pm It’s surprising how essential the good old pen and paper are in coding. I also like to visualize bit shifts by placing eight fingers on the keycaps, without pressing the keys, and moving them around, each hand representing a nibble. You can also use one of the thumbs as the carry flag.
Do you have to take your socks off to do a sixteen bit shift? :shock: :lol:

Mark
:!: Standby alert :!:
“There are four lights!”
Step up to red alert. Sir, are you absolutely sure? It does mean changing the bulb :dance
Looking forward to summer later in the year.
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2641
Joined: Mon Nov 13, 2017 3:16 pm

Re: Keeping track of registers

Post by Ast A. Moore »

1024MAK wrote: Sat Feb 08, 2020 1:57 am Do you have to take your socks off to do a sixteen bit shift? :shock: :lol:
Oh, now you’re just being silly. For that purpose precisely, I never wear socks. :D
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
User avatar
Cosmium
Microbot
Posts: 156
Joined: Tue Dec 04, 2018 10:20 pm
Location: USA

Re: Keeping track of registers

Post by Cosmium »

1024MAK wrote: Sat Feb 08, 2020 1:54 am Ahh, lack of good detail in comments in the code. Guaranteed to cause problems weeks, months or years later....

Mark
Or even days or hours later.. :)

I actually sometimes wonder if it's possible to over comment the code. Occasionally it feels as though I'm having to wade through a ton of verbose explanation when just leaving a line alone, uncommented, may've sufficed!? I spose when in doubt it's best to comment rather than not, though. At least document what a piece of code does overall, even if it's not on a line by line basis.
AndyC
Dynamite Dan
Posts: 1405
Joined: Mon Nov 13, 2017 5:12 am

Re: Keeping track of registers

Post by AndyC »

Cosmium wrote: Sun Feb 09, 2020 4:16 am
1024MAK wrote: Sat Feb 08, 2020 1:54 am Ahh, lack of good detail in comments in the code. Guaranteed to cause problems weeks, months or years later....

Mark
Or even days or hours later.. :)

I actually sometimes wonder if it's possible to over comment the code. Occasionally it feels as though I'm having to wade through a ton of verbose explanation when just leaving a line alone, uncommented, may've sufficed!? I spose when in doubt it's best to comment rather than not, though. At least document what a piece of code does overall, even if it's not on a line by line basis.
There's an art to it. Don't document what a line does, you can usually see that in small enough blocks of code. Instead document what larger blocks are intended to do, so that if bugs arise you can at least see if it is because the implementation is flawed or whether calling code is dependent upon something you never actually intended to guarantee.
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2641
Joined: Mon Nov 13, 2017 3:16 pm

Re: Keeping track of registers

Post by Ast A. Moore »

AndyC wrote: Sun Feb 09, 2020 9:21 am Don't document what a line does, you can usually see that in small enough blocks of code. Instead document what larger blocks are intended to do, so that if bugs arise you can at least see if it is because the implementation is flawed or whether calling code is dependent upon something you never actually intended to guarantee.
Yeah, you’d think that, eh? But no. I have a piece of very clever code I wrote some time ago. It’s rather complex and brilliant (but of course :roll: ). I wrote and fine-tuned it over a course of a couple of days; was really into it and had no time to comment everything (well, anything, actually) as I went along. When I was done, I told myself (as I always do) that I needed to comment it. Sure thing, I replied to myself, tomorrow I will.

Well, guess what? I know exactly what the “larger blocks are intended to do,” I just have absolutely no clue how. ;)

On a more serious note, it’s the nature of assembly language that even if everything is profusely and neatly commented, there are still cases when you really have to sit and try to wrap your head around it. That’s because in addition to the list of instructions you’re staring at, you need to visualize the state the CPU is in while executing each one of them. This is not something you do on a regular basis—or at all—when coding in a high-level programming language.
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
Dr_Dave
Drutt
Posts: 32
Joined: Wed Feb 05, 2020 9:58 am

Re: Keeping track of registers

Post by Dr_Dave »

I feel as though I'm making progress with this... I'm feeling a lot more confident now that I've moved to more a structured codebase rather than some monolithic nightmare. Applying good programming practice like modular functions and plenty of commenting has helped enormously in this regard. I'm also feeling a lot more confident with using push/pop and exx - though a forgotten pop here and there has caused more than one moment of perfect confusion!

Best of all, I've discovered the Z80 debugger (https://marketplace.visualstudio.com/it ... .z80-debug) for VS Code. Despite the fact that I have a problem with it crashing fairly frequently, this has proved indispensible for getting to the bottom of issues.

I totally agree with the points made above about about pen and paper being a useful tool... I found myself drawing diagrams of the screen memory to try to visualise how stepping through pixels translated to memory locations for transferring a screen buffer around. I honestly can't think of a better tool for doing this... my aging brain is just too rusty to hold things like this in my mind!
Post Reply