Machine Code: Moving from Attribute scrolling to pixel scrolling
Machine Code: Moving from Attribute scrolling to pixel scrolling
I’ve taken a long time - some 2 months to develop an attribute scrolling “game engine”. In the centre of the screen is my Sprite, it never moves, it is the screen around it that moves. As you press the “up” key the screen looks for the next line of data and draw direct to screen starting at top left and printing each attribute square left to right. If you press down it looks for next set of data and draws from left to right. If you press right it looks for next set of data and draws from top right down, finally left look for next line of data and draws from top right down.
Within selected 8x8 attribute squares are UDGs which get printed to the screen but because its Attribute scrolling as the UDGs get redrawn there is a flicker. As I understand it I can mitigate this flicker if I do pixel scrolling. I’ve steered clear of this because I don’t really understand how to deal with moving between the 1/3rds of the screen (top, middle and bottom), I’ve seen code samples that say this is how you do it but it’s still very confusing as I am new to Machine Code.
My Game is well underway but I feel to really make it feel proper it would need pixel scrolling but I am concerned it will be too hard to implement as I am too new and not really a very good programmer. Am I over estimating the task and should I just go for it? Or is it going to add too much complexity to an already tough ask for a first game?
Within selected 8x8 attribute squares are UDGs which get printed to the screen but because its Attribute scrolling as the UDGs get redrawn there is a flicker. As I understand it I can mitigate this flicker if I do pixel scrolling. I’ve steered clear of this because I don’t really understand how to deal with moving between the 1/3rds of the screen (top, middle and bottom), I’ve seen code samples that say this is how you do it but it’s still very confusing as I am new to Machine Code.
My Game is well underway but I feel to really make it feel proper it would need pixel scrolling but I am concerned it will be too hard to implement as I am too new and not really a very good programmer. Am I over estimating the task and should I just go for it? Or is it going to add too much complexity to an already tough ask for a first game?
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
No, flicker is caused by not being aware of when you are updating something and where the raster beam is.
Attribute scrolling, done correctly, is at most jerky since it is moving around 8 pixels at a time.
If you look at the binary values, it is not that bad. Also a look up table with addresses per pixel line can help, both for sanity and speed.
I would first finish what you have. Have another look at pixel scrolling with your next game.Hedge1970 wrote: ↑Thu Mar 21, 2024 3:49 am My Game is well underway but I feel to really make it feel proper it would need pixel scrolling but I am concerned it will be too hard to implement as I am too new and not really a very good programmer. Am I over estimating the task and should I just go for it? Or is it going to add too much complexity to an already tough ask for a first game?
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
I can only agree with the answer that you should probably wait for your next game to do pixel scrolling.
Finish this one first. It's not a big deal if there's some flicker. It's more important that you finish first.
A question, when you mean "attribute scrolling" you mean 8-pixel scrolling including pixels, and not just attributes, right? In that case n-pixel scrolling won't solve your problem with flicker, because the amount of screen (in bytes) that is being updated has not changed.
Finish this one first. It's not a big deal if there's some flicker. It's more important that you finish first.
A question, when you mean "attribute scrolling" you mean 8-pixel scrolling including pixels, and not just attributes, right? In that case n-pixel scrolling won't solve your problem with flicker, because the amount of screen (in bytes) that is being updated has not changed.
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
btw. if you are scrolling by 8 pixels a time, and you see attribute flicker, then you need to rewrite your scoll routines. do not scroll attrs and gfx separately: copy one char, and then immediately copy its attribute. that's if i understood you right.
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
Don't rewrite your game. People are right saying that you can try pixel scroll in your next game.
Just remember, pixel scroll is much harder to do than attribute scroll and requires much more CPU power. Quite often the
only reasonable solution is to use a screen buffer for it.
Just remember, pixel scroll is much harder to do than attribute scroll and requires much more CPU power. Quite often the
only reasonable solution is to use a screen buffer for it.
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
Actually moving the data in the screen memory area is nearly always going to flicker, unless you get very, very good at timing exactly what you're updating where on the screen with how the TV picture is redrawn.
Most games will draw the game to somewhere else in memory first, with all the scrolling done, sprites added, then copy that to the screen in one go. It's not as fast as drawing straight to screen memory, but it cuts out a lot of flicker. Also you can define your in-memory screen however you want. Maybe bigger or smaller than the real screen; maybe the same size, but you don't go right to the edges. Most useful is to store it line-by-line rather than in the odd order of the screen memory, then swap the lines around when you copy to the main screen.
As for scrolling, scrolling vertically pixel-by-pixel is relatively easy, as it's just copying lines around. Horizontal is harder, as it's meddling with the content of bytes and that's a lot slower. Single-pixel scrolling is bearable. Shifting by two pixels is going to be slow. Many games resort to actually redrawing the whole background from a tile-map, using tile blocks that are pre-shifted horizontally in 2, 4 and 6 pixel steps. But that limits the number of different tiles you can afford to hold in memory. And gives you a whole new set of problems where they butt up against each other, particularly if you want them in different colours.
Most games will draw the game to somewhere else in memory first, with all the scrolling done, sprites added, then copy that to the screen in one go. It's not as fast as drawing straight to screen memory, but it cuts out a lot of flicker. Also you can define your in-memory screen however you want. Maybe bigger or smaller than the real screen; maybe the same size, but you don't go right to the edges. Most useful is to store it line-by-line rather than in the odd order of the screen memory, then swap the lines around when you copy to the main screen.
As for scrolling, scrolling vertically pixel-by-pixel is relatively easy, as it's just copying lines around. Horizontal is harder, as it's meddling with the content of bytes and that's a lot slower. Single-pixel scrolling is bearable. Shifting by two pixels is going to be slow. Many games resort to actually redrawing the whole background from a tile-map, using tile blocks that are pre-shifted horizontally in 2, 4 and 6 pixel steps. But that limits the number of different tiles you can afford to hold in memory. And gives you a whole new set of problems where they butt up against each other, particularly if you want them in different colours.
- ParadigmShifter
- Manic Miner
- Posts: 699
- Joined: Sat Sep 09, 2023 4:55 am
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
I had a go at doing per pixel scrolling a while back but never finished it. It uses quite a lot of tricks to try and speed it up though. It scrolls 32x22 character cells directly drawn to the screen.
It uses the stack and a lot of unrolling to draw each line in raster order. It only supported 1 tile per row as well.
The map is mirrored in X because it draws each line right to left instead of left to right (via PUSH to copy 16 bits at a time).
Map format is the opcodes for the pushes It builds the pushes each frame Joffa style (like in Cobra).
Tile types are BL - blank. WL - left side of wall. WR - right side of wall. WM - middle of wall.
It prescrolls the graphics when the program starts and each line is offset 256 bytes from each other instead of being contiguous IIRC. (That's so moving to the next row of data is just INC H rather than LD DE, 32 / ADD HL, DE)
I was unable to draw full screen without flicker though And there's no time left for sprites or anything, which is when I gave up.
I agree that using an off screen buffer is a lot easier (as is vertical scrolling, since you don't have to rotate the graphics then).
EDIT: More discussion and a false start here
https://worldofspectrum.org/forums/disc ... y-thing/p1
Code: Select all
ORG #8000
SCROLLEDTILESTRIDE EQU 8
TIMING EQU 1
WL EQU #F5
WM EQU #D5
WR EQU #C5
BL EQU #E5
UNROLL_MAPTOPUSHES_LDIR EQU 1
MACRO UNROLLED_ROW scraddr, tileaddr, pushops, pushret, tileentry
ld ix, scraddr
exx
tileentry:
ld hl, tileaddr
ld c, e ; 2
.loopouter
ld b, d ; 8
.loop
ld sp, hl
exx
pop bc
pop de
pop af
ld sp, ix
jp pushops
pushret:
inc ixh
exx
inc h
djnz .loop
dec c
exx
jr z, .done
ld ix, scraddr + #20
exx
ld b, d ; 8
jp .loopouter
.done
ENDM
MACRO DOPUSH mylabel, myret
mylabel:
REPT 0;5 ; only 30 columsn drawn
push af
push de
push bc
ENDR
REPT 4
push af
push de
;push de
push bc
push hl
ENDR
REPT 0;8
push af
push bc
ENDR
jp myret
ENDM
MACRO SCROLL tile
ld hl, (tile+1)
ld a, l
and ~#3F
ld b, a
ld a, c
or b
ld (tile+1), a
ENDM
; will need something like this if length of the push code is not fixed at 16 bytes long
MACRO DEFPUSHBLOCK retto, retoffset
ALIGN 64
BLOCK 61
retoffset:
jp retto
ENDM
MACRO MAPTOPUSHES_STACK pushaddr
ld sp, hl
pop hl
pop bc
pop de
pop af
exx
ex af, af'
pop hl
pop bc
pop de
pop af
ld sp, pushaddr+16
push af
push de
push bc
push hl
exx
ex af, af'
push af
push de
push bc
push hl
ENDM
MACRO BUILDPUSHES leveladdroffset, pushrow
; build the PUSHes for the screen from the map
ld de, (mapxoffset)
ld hl, levelmap+leveladdroffset
add hl, de
MAPTOPUSHES_STACK pushrow
ENDM
main:
di
ld (restoresp+1), sp
ld de, wall16x16
ld hl, prescrolledtiles
call prescrollgfx
ld de, checkers16x16
ld hl, prescrolledtiles+SCROLLEDTILESTRIDE*8
call prescrollgfx
;jp nextframe
nextframe:
IF TIMING
ld a, 2
out (#FE), a
ENDIF
.brkhere
ld a, (scrollxoffset)
inc a
and 15
ld c, a ; scrolloffset in C
ld (scrollxoffset), a
rlca
rlca
rlca
ld b, a
and 8<<3
jr z, .noincscrolladj
inc b
.noincscrolladj
ld a, b
and #3F
ld (scrolladjust), a
ld a, c
and 15
jr nz, .noupdatemapoffset
ld a, (mapxoffset)
dec a
and 127
ld (mapxoffset), a
.noupdatemapoffset
BUILDPUSHES #0000, pushrow1
BUILDPUSHES #0100, pushrow2
BUILDPUSHES #0200, pushrow3
BUILDPUSHES #0300, pushrow4
BUILDPUSHES #0400, pushrow5
BUILDPUSHES #0500, pushrow6
BUILDPUSHES #0600, pushrow7
BUILDPUSHES #0700, pushrow8
BUILDPUSHES #0800, pushrow9
BUILDPUSHES #0900, pushrow10
BUILDPUSHES #0A00, pushrow11
IF TIMING
ld a, 3
out (#FE), a
ENDIF
ld a, (scrolladjust)
ld c, a
;SCROLL tile0
SCROLL tile1
SCROLL tile2
SCROLL tile3
SCROLL tile4
SCROLL tile5
SCROLL tile6
SCROLL tile7
SCROLL tile8
SCROLL tile9
SCROLL tile10
SCROLL tile11
IF TIMING
ld a, 4
out (#FE), a
ENDIF
restoresp:
ld sp, 0
ei
halt
IF TIMING
ld a, 1
out (#FE), a
ENDIF
ld (restoresp+1), sp
di
ld de, #0802
exx
ld hl, 0;(bgtile)
;UNROLLED_ROW #4000+32, prescrolledtiles, pushrow0, retrow0, tile0
UNROLLED_ROW #4040+32, prescrolledtiles, pushrow1, retrow1, tile1
UNROLLED_ROW #4080+32, prescrolledtiles+64, pushrow2, retrow2, tile2
UNROLLED_ROW #40C0+32, prescrolledtiles, pushrow3, retrow3, tile3
UNROLLED_ROW #4800+32, prescrolledtiles+64, pushrow4, retrow4, tile4
UNROLLED_ROW #4840+32, prescrolledtiles, pushrow5, retrow5, tile5
UNROLLED_ROW #4880+32, prescrolledtiles+64, pushrow6, retrow6, tile6
UNROLLED_ROW #48C0+32, prescrolledtiles, pushrow7, retrow7, tile7
UNROLLED_ROW #5000+32, prescrolledtiles+64, pushrow8, retrow8, tile8
UNROLLED_ROW #5040+32, prescrolledtiles, pushrow9, retrow9, tile9
UNROLLED_ROW #5080+32, prescrolledtiles+64, pushrow10, retrow10, tile10
UNROLLED_ROW #50C0+32, prescrolledtiles, pushrow11, retrow11, tile11
jp nextframe
;DOPUSH pushrow0, retrow0
DOPUSH pushrow1, retrow1
DOPUSH pushrow2, retrow2
DOPUSH pushrow3, retrow3
DOPUSH pushrow4, retrow4
DOPUSH pushrow5, retrow5
DOPUSH pushrow6, retrow6
DOPUSH pushrow7, retrow7
DOPUSH pushrow8, retrow8
DOPUSH pushrow9, retrow9
DOPUSH pushrow10, retrow10
DOPUSH pushrow11, retrow11
; prescroll the graphics tile
; DE = 16x16 tile
; HL = destination base addr
prescrollgfx:
xor a
push hl ; push destination address
ld b, 32
.loop
push hl
push bc
; 2 bytes of padding to the left
ld (hl), a
inc hl
ld (hl), a
inc hl
; copy image to right hand side graphic
ex de, hl
ldi
ldi
dec hl
dec hl
; copy image to repeating block graphic
ldi
ldi
ex de, hl
pop bc
pop hl
inc h ; next row offest by 256 bytes
djnz .loop
pop hl ; dest address
ld a, 7
.nextscroll
push hl
ld b, 16
.nextrow
push hl
push de
push bc
ld d, h
ld e, l
ld bc, SCROLLEDTILESTRIDE
add hl, bc ; next scrolled tile address
ld bc, SCROLLEDTILESTRIDE
ex de, hl
push de
ldir
pop hl
ld de, 5 ; start at the right hand side of the tile data (5 chars along)
add hl, de
sla (hl)
dec hl
rl (hl)
dec hl
rl (hl)
dec hl
rl (hl)
dec hl
rl (hl)
dec hl
rl (hl)
pop bc
pop de
inc d
pop hl
inc h
djnz .nextrow
dec a
pop hl
ret z
ld bc, SCROLLEDTILESTRIDE
add hl, bc
jr .nextscroll
ret
scrollxoffset db 0
scrolladjust db 0
mapxoffset dw 0
bgtile dw #0808
IF 1;0
wall16x16 db #FE, #FE
db #FE, #FE
db #FE, #FE
db #00, #00
db #EF, #EF
db #EF, #EF
db #EF, #EF
db #00, #00
db #FE, #FE
db #FE, #FE
db #FE, #FE
db #00, #00
db #EF, #EF
db #EF, #EF
db #EF, #EF
db #00, #00
ELSE
; solid block for debugging purposes
wall16x16 db #FF, #FF
db #FF, #FF
db #FF, #FF
db #FF, #FF
db #FF, #FF
db #FF, #FF
db #FF, #FF
db #FF, #FF
db #FF, #FF
db #FF, #FF
db #FF, #FF
db #FF, #FF
db #FF, #FF
db #FF, #FF
db #FF, #FF
db #FF, #FF
ENDIF
girder16x16 db #00, #00, #FF, #FF, #EE, #EE, #FF, #FF, #00, #00, #E0, #0E, #70, #1C, #38, #38
db #1C, #70, #0E, #E0, #07, #C0, #00, #00, #FF, #FF, #EE, #EE, #FF, #FF, #00, #00
checkers16x16 db #55, #55, #AA, #AA
db #55, #55, #AA, #AA
db #55, #55, #AA, #AA
db #55, #55, #AA, #AA
db #55, #55, #AA, #AA
db #55, #55, #AA, #AA
db #55, #55, #AA, #AA
db #55, #55, #AA, #AA
IF 0
;DEFPUSHBLOCK retrow0, jploc0
DEFPUSHBLOCK retrow1, jploc1
DEFPUSHBLOCK retrow2, jploc2
DEFPUSHBLOCK retrow3, jploc3
DEFPUSHBLOCK retrow4, jploc4
DEFPUSHBLOCK retrow5, jploc5
DEFPUSHBLOCK retrow6, jploc6
DEFPUSHBLOCK retrow7, jploc7
DEFPUSHBLOCK retrow8, jploc8
DEFPUSHBLOCK retrow9, jploc9
DEFPUSHBLOCK retrow10, jploc10
DEFPUSHBLOCK retrow11, jploc11
ENDIF
ALIGN 256
levelmap:
REPT 16
db WM, WM, WM, WM, WM, WM, WM, WM, WM, WM, WM, WM, WM, WM, WM, WM
ENDR
REPT 16
db WR, BL, WL, WR, BL, BL, BL, BL, BL, BL, WL, WR, WL, WR, BL, WL
ENDR
REPT 16
db WR, BL, WL, WM, WR, BL, BL, BL, BL, BL, WL, WR, WL, WR, BL, WL
ENDR
REPT 16
db WR, BL, WL, WM, WM, WR, BL, BL, BL, BL, WL, WR, WL, WR, BL, WL
ENDR
REPT 16
db WR, BL, BL, BL, BL, BL, BL, BL, BL, BL, WL, WR, WL, WR, BL, WL
ENDR
REPT 16
db WR, BL, BL, WL, WR, BL, BL, BL, BL, BL, WL, WR, WL, WR, BL, WL
ENDR
REPT 16
db WR, BL, BL, WL, WM, WR, BL, BL, BL, BL, WL, WR, WL, WR, BL, WL
ENDR
REPT 16
db WR, BL, BL, WL, WM, WM, WR, BL, BL, BL, WL, WR, WL, WR, BL, WL
ENDR
REPT 16
db WR, BL, BL, BL, BL, BL, BL, BL, BL, BL, WL, WR, WL, WR, BL, WL
ENDR
REPT 16
db WR, BL, BL, BL, BL, BL, BL, BL, BL, BL, WL, WR, WL, WR, BL, WL
ENDR
REPT 16
db WM, WM, WM, WM, WM, WM, WM, WM, WM, WM, WM, WM, WM, WM, WM, WM
ENDR
ALIGN 256
prescrolledtiles
The map is mirrored in X because it draws each line right to left instead of left to right (via PUSH to copy 16 bits at a time).
Map format is the opcodes for the pushes It builds the pushes each frame Joffa style (like in Cobra).
Tile types are BL - blank. WL - left side of wall. WR - right side of wall. WM - middle of wall.
It prescrolls the graphics when the program starts and each line is offset 256 bytes from each other instead of being contiguous IIRC. (That's so moving to the next row of data is just INC H rather than LD DE, 32 / ADD HL, DE)
I was unable to draw full screen without flicker though And there's no time left for sprites or anything, which is when I gave up.
I agree that using an off screen buffer is a lot easier (as is vertical scrolling, since you don't have to rotate the graphics then).
EDIT: More discussion and a false start here
https://worldofspectrum.org/forums/disc ... y-thing/p1
- ParadigmShifter
- Manic Miner
- Posts: 699
- Joined: Sat Sep 09, 2023 4:55 am
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
Anyway the discussion on WoS has a link to info about Ghosts N' Goblins graphics routines and a link to a disassembly of Cobra (warning: Joffa's code uses a lot of self modifying code and so is hard to follow what is going on). Cobra also uses the floating bus to start drawing at the start of the bottom border area rather than starting drawing after a HALT like in my code. Cobra also runs at 25fps but I believe it spends 1 frame drawing and the next frame updating the sprite positions, building the next scroll commands (sequence of PUSH opcodes), and playing the sound effects.
Some other stuff worth considering:
Only scroll parts of the screen which aren't blank (my code writes every pixel even blank ones) - R-Type did that IIRC. If you are careful with how you do that you can get away without erasing anything as well, just set the attribs to same ink and paper colour.
Ghosts N' Goblins has unrolled routines for drawing a strip of tiles from 1 to 30 cells wide. The cells are also made up of repeating 32x16 pixel patterns.
Some other stuff worth considering:
Only scroll parts of the screen which aren't blank (my code writes every pixel even blank ones) - R-Type did that IIRC. If you are careful with how you do that you can get away without erasing anything as well, just set the attribs to same ink and paper colour.
Ghosts N' Goblins has unrolled routines for drawing a strip of tiles from 1 to 30 cells wide. The cells are also made up of repeating 32x16 pixel patterns.
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
I am mostly printing the data direct to the attribute memory LD DE,$5820 but some data means my code prints to screen memory and prints a spriteTimmy wrote: ↑Thu Mar 21, 2024 10:39 am A question, when you mean "attribute scrolling" you mean 8-pixel scrolling including pixels, and not just attributes, right? In that case n-pixel scrolling won't solve your problem with flicker, because the amount of screen (in bytes) that is being updated has not changed.
LD B,$08
LOOP_FRUIT
LD A,(HL)
LD (DE),A
INC D
INC HL
DJNZ LOOP_FRUIT
Hope that makes sense.
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
The book I learned M/C from used this method, build the new screen in memory then copy to screen memory. However for some reason I just went straight to screen, by the time I had my vertical and horizontal code sorted I felt it was so many steps that adding the additional in-memory screen would kill the refresh rate. But having said that I don’t currently know how much “work” my current code is doing. I’ve heard of T states but not dug into them enough to be able to work out how many T states my code currently uses. Maybe that’s my next step along with figuring out where the Rasta scan is so I can try and execute my screen refresh after it’s started. On the plus side as I am new to this I assume (maybe correctly) there is a lot of optimisation available for my code so I can smooth it out later.Joefish wrote: ↑Thu Mar 21, 2024 12:34 pm Most games will draw the game to somewhere else in memory first, with all the scrolling done, sprites added, then copy that to the screen in one go. It's not as fast as drawing straight to screen memory, but it cuts out a lot of flicker. Also you can define your in-memory screen however you want. Maybe bigger or smaller than the real screen; maybe the same size, but you don't go right to the edges. Most useful is to store it line-by-line rather than in the odd order of the screen memory, then swap the lines around when you copy to the main screen.
Thanks
- ParadigmShifter
- Manic Miner
- Posts: 699
- Joined: Sat Sep 09, 2023 4:55 am
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
It still shouldn't flicker as long as you are drawing ahead of the raster.
You should probably unroll that loop instead of DJNZ. 8xDJNZ takes 7x13 + 8 = 99T states. The code isn't very long so unrolling is fine there.
You should align your graphics data to a multiple of the graphics size (so 8x8 would be align 8, 16 by 16 align 32, etc.) then you can do INC L instead of INC HL saving 2 jiffies per 8 pixels drawn.
If you are drawing direct to the screen the most important thing to do is start all your drawing as soon as the HALT finishes (if you are using one. If you aren't using a HALT or the floating bus it will flicker since you aren't synced to the VBlank). So draw everything before reading the keyboard and updating the logic (which updates the sprrite positions so they can be drawn immediately at the start of the next frame).
EDIT: Tstates here
https://map.grauw.nl/resources/z80instr.php
I only count TStates when optimising really... most of the time I just change the border colour at various points in the frame to see how long each routine is taking.
You should probably unroll that loop instead of DJNZ. 8xDJNZ takes 7x13 + 8 = 99T states. The code isn't very long so unrolling is fine there.
You should align your graphics data to a multiple of the graphics size (so 8x8 would be align 8, 16 by 16 align 32, etc.) then you can do INC L instead of INC HL saving 2 jiffies per 8 pixels drawn.
If you are drawing direct to the screen the most important thing to do is start all your drawing as soon as the HALT finishes (if you are using one. If you aren't using a HALT or the floating bus it will flicker since you aren't synced to the VBlank). So draw everything before reading the keyboard and updating the logic (which updates the sprrite positions so they can be drawn immediately at the start of the next frame).
EDIT: Tstates here
https://map.grauw.nl/resources/z80instr.php
I only count TStates when optimising really... most of the time I just change the border colour at various points in the frame to see how long each routine is taking.
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
That’s good to know, once I’ve sorted the main game features I’ll look at understanding the Rasta and hope that it smooths the whole thing out.ParadigmShifter wrote: ↑Thu Mar 21, 2024 2:43 pm It still shouldn't flicker as long as you are drawing ahead of the raster.
- ParadigmShifter
- Manic Miner
- Posts: 699
- Joined: Sat Sep 09, 2023 4:55 am
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
Here's some info about the raster (timings for 48K model)
So you get 14336T before it starts drawing the visible screen area (before that it is drawing the top border). Then the screen is in contended memory so everything slows down a bit. EDIT: Put your stack in non-contended memory as well, I set SP=0 so the stack starts at address 65535.
Ideally you want to draw everything in the top third of the screen before the raster hits the visible area, then the middle third while it is drawing the top third of the screen, and then the final third of the screen while it is drawing the middle third. If you can do that it will not flicker. If you can't manage that it may be worth trying to sort your sprites for the next frame drawing so they are drawn in order of their Y position on screen. You might be able to just draw everything in each third in a separate pass though.
I've posted code for how I draw sprites before (using an erase list and a draw list which I build at the end of each frame for drawing next frame) I will add a link in a mo. EDIT: Here viewtopic.php?p=138706#p138706
After an interrupt occurs, 64 line times (14336 T states; see below for exact timings) pass before the first byte of the screen (16384) is displayed. At least the last 48 of these are actual border-lines; the others may be either border or vertical retrace.
Then the 192 screen+border lines are displayed, followed by 56 border lines again. Note that this means that a frame is (64+192+56)*224=69888 T states long, which means that the '50 Hz' interrupt is actually a 3.5MHz/69888=50.08 Hz interrupt. This fact can be seen by taking a clock program, and running it for an hour, after which it will be the expected 6 seconds fast. However, on a real Spectrum, the frequency of the interrupt varies slightly as the Spectrum gets hot; the reason for this is unknown, but placing a cooler onto the ULA has been observed to remove this effect.
Now for the timings of each line itself: define a screen line to start with 256 screen pixels, then border, then horizontal retrace, and then border again. All this takes 224 T states. Every half T state a pixel is written to the CRT, so if the ULA is reading bytes it does so each 4 T states (and then it reads two: a screen and an ATTR byte). The border is 48 pixels wide at each side. A video screen line is therefore timed as follows: 128 T states of screen, 24 T states of right border, 48 T states of horizontal retrace and 24 T states of left border.
So you get 14336T before it starts drawing the visible screen area (before that it is drawing the top border). Then the screen is in contended memory so everything slows down a bit. EDIT: Put your stack in non-contended memory as well, I set SP=0 so the stack starts at address 65535.
Ideally you want to draw everything in the top third of the screen before the raster hits the visible area, then the middle third while it is drawing the top third of the screen, and then the final third of the screen while it is drawing the middle third. If you can do that it will not flicker. If you can't manage that it may be worth trying to sort your sprites for the next frame drawing so they are drawn in order of their Y position on screen. You might be able to just draw everything in each third in a separate pass though.
I've posted code for how I draw sprites before (using an erase list and a draw list which I build at the end of each frame for drawing next frame) I will add a link in a mo. EDIT: Here viewtopic.php?p=138706#p138706
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
I’ll definitely be coming back to this post once I am ready. Thank you.
- ParadigmShifter
- Manic Miner
- Posts: 699
- Joined: Sat Sep 09, 2023 4:55 am
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
Forgot to post the link where I got the info about the raster from
https://worldofspectrum.org/faq/referen ... erence.htm
https://worldofspectrum.org/faq/referen ... erence.htm
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
Although it means an additional delay at the start, it's easier to update just behind the raster. If you're ahead, you have to keep ahead, and you just can't do that unless you're only updating a small area of the screen. Otherwise, the raster will catch up and overtake your copy function, leaving an obvious 'tear' in the screen, where the old image is still visible below a certain point. If you start out behind, then it doesn't matter if you lag a bit further behind.
If redrawing the screen then adding sprites, you've got a problem doing that ahead of the raster because even if you can redraw the background in time, by the time you come to drawing the sprites, the raster may have already passed the point where the sprites go, so you'll never actually see them drawn.
If redrawing the screen then adding sprites, you've got a problem doing that ahead of the raster because even if you can redraw the background in time, by the time you come to drawing the sprites, the raster may have already passed the point where the sprites go, so you'll never actually see them drawn.
- ParadigmShifter
- Manic Miner
- Posts: 699
- Joined: Sat Sep 09, 2023 4:55 am
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
Yeah it's best to intermingle drawing of rows and drawing of sprites which is how Ghosts N' Goblins does it. It's probably worth changing the border colour every time you draw a row then you'll get an idea of how fine grained the sorting of the sprites need to be, it may be possible to draw one third of the screen then draw sprites in that third, or you might need more fine grained sorting. But do the sorting after drawing ready for the next frame, don't do it before drawing.
Dunno if this link will work, how Ghosts N' Goblins draws a frame
https://www.emix8.org/ggdisasm/screenpaint2.mp4
Dunno if this link will work, how Ghosts N' Goblins draws a frame
https://www.emix8.org/ggdisasm/screenpaint2.mp4
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
yep, i agree: drawing behind the raster is way easier. you'll have ~14K tstates to prepare: jit-compile your render code, sort sprites, play some music, and so on. and then you can render everything without worrying about raster at all. even if your render code will take more than 69K tstates, it doesn't matter much, because the raster will draw the top part of the new image while you're finishing with the bottom.
the only problem here is to time your preparation code so it will take slightly more than 14K tstates. but again, you don't have to start at the exact time: if you'll spend 20K tstates preparing, for example, it should still be ok as long as you finish the rest in time.
so it is basically:
HALT
prepare everything, spending about 14K-16K tstates.
render as fast as you can! ;-)
and if you'll limit playing area to 2/3 of the screen, you'll easily hit 25 FPS (you'll have plenty of time for your game logic code). maybe even 50 FPS, but i'd say that 50 FPS usually doesn't worth the efforts. even modern AAA titles are ok with 30 FPS, so 25 is fine too! ;-)
the only problem here is to time your preparation code so it will take slightly more than 14K tstates. but again, you don't have to start at the exact time: if you'll spend 20K tstates preparing, for example, it should still be ok as long as you finish the rest in time.
so it is basically:
HALT
prepare everything, spending about 14K-16K tstates.
render as fast as you can! ;-)
and if you'll limit playing area to 2/3 of the screen, you'll easily hit 25 FPS (you'll have plenty of time for your game logic code). maybe even 50 FPS, but i'd say that 50 FPS usually doesn't worth the efforts. even modern AAA titles are ok with 30 FPS, so 25 is fine too! ;-)
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
You can always use the time after a HALT to draw some more sprites, or maybe paint a scrolling area that the player and other sprites can't cross (maybe a bit of parallax foreground), so long as it's all based on data from the previous frame of your game update. Then once the raster has got past the first line of the screen display, you can start repainting the next frame of the game just behind it. But whatever you do, you have to have a rough measure of ensuring it takes a similar amount of time each time it's used, to ensure you always end up behind the raster.
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
Usually I'd just explain this flicker problem like this:
https://archive.org/details/sinclair-us ... ew=theater
https://archive.org/details/sinclair-us ... ew=theater
- ParadigmShifter
- Manic Miner
- Posts: 699
- Joined: Sat Sep 09, 2023 4:55 am
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
That link (like most others on archive.org) does not work for me (SSL error).
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
It's probably off topic, but considering you also have problems on some other sites, I'd be safe to bet that it's a browser/OS error at your side.ParadigmShifter wrote: ↑Thu Mar 21, 2024 10:09 pm That link (like most others on archive.org) does not work for me (SSL error).
But it's probably a better discussion in some separate thread.
EDIT: Try this one? https://spectrumcomputing.co.uk/page.ph ... 91&page=78
- ParadigmShifter
- Manic Miner
- Posts: 699
- Joined: Sat Sep 09, 2023 4:55 am
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
Nope does not work still, all the pages show up as missing images :/
It's probably my ISP on my phone internet which blocks certain sites (if I remove the s from https it thinks it's an adult site lol) and to unlock it I'd need a credit card number which I don't have.
Oh well.
EDIT: This link works https://sinclairuser.com/091/sprite_routine1.htm
Ok that's talking about double buffering or copying the existing background to a working buffer which is an easy way to do it. I think you need to use the stack if you want a full screen buffer since I think LDIR of all the screen takes longer than a frame, even if you unroll a lot of the loop like 32 LDIs in a row? Also uses a lot of memory.
I erase all my sprites before the raster hits the end of the top border area then draw them (which also finishes before the top of the border is drawn except when scrolling a column) but then again I'm not drawing very much in my current project. I am aiming for 50fps at all times when the player is in control of their pieces though (physics and scrolling of many columns at once can cause it to slow down a bit I think)
For my Manic Miner (unfinished but all levels playable) thing (first machine code programming I did) I did use a double buffer but I had a dirty list of up to 64 cells which needed copying from the backbuffer so I did not transfer the entire buffer per frame. That obviously does not work for scrolling games though.
EDIT2: Ghosts N' Goblins and Cobra both draw directly to the screen and don't use a double buffer though. Ghosts N' Goblins occasionally flickers and Cobra uses the floating bus and is rock solid on 48K anyway (128K floating bus trick was not discovered by a poster on this forum yet) - but then Joffa was kind of a mad genius, RIP. Both draw the entire screen (blank space and all) every frame. I think GNG is more impressive really since the scrolling window is 8 directional and much larger than in Cobra at 30x22 cells. Game is too hard and the graphics are crap though of course
It's probably my ISP on my phone internet which blocks certain sites (if I remove the s from https it thinks it's an adult site lol) and to unlock it I'd need a credit card number which I don't have.
Oh well.
EDIT: This link works https://sinclairuser.com/091/sprite_routine1.htm
Ok that's talking about double buffering or copying the existing background to a working buffer which is an easy way to do it. I think you need to use the stack if you want a full screen buffer since I think LDIR of all the screen takes longer than a frame, even if you unroll a lot of the loop like 32 LDIs in a row? Also uses a lot of memory.
I erase all my sprites before the raster hits the end of the top border area then draw them (which also finishes before the top of the border is drawn except when scrolling a column) but then again I'm not drawing very much in my current project. I am aiming for 50fps at all times when the player is in control of their pieces though (physics and scrolling of many columns at once can cause it to slow down a bit I think)
For my Manic Miner (unfinished but all levels playable) thing (first machine code programming I did) I did use a double buffer but I had a dirty list of up to 64 cells which needed copying from the backbuffer so I did not transfer the entire buffer per frame. That obviously does not work for scrolling games though.
EDIT2: Ghosts N' Goblins and Cobra both draw directly to the screen and don't use a double buffer though. Ghosts N' Goblins occasionally flickers and Cobra uses the floating bus and is rock solid on 48K anyway (128K floating bus trick was not discovered by a poster on this forum yet) - but then Joffa was kind of a mad genius, RIP. Both draw the entire screen (blank space and all) every frame. I think GNG is more impressive really since the scrolling window is 8 directional and much larger than in Cobra at 30x22 cells. Game is too hard and the graphics are crap though of course
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
eh. just take a look at Firefly. 8-direction scrolling (actually, there is no "scrolling" there, the engine can draw the map from the arbitrary coordinates). 25 FPS, JIT-compiled blitter, and many, many different maps, all in 48k. i once extracted that engine and turned it into reusable, with map editor. have to find that project and publish it again, i guess.
by the way, you cannot copy the whole screen in one frame, not even using stack tricks. there is simply not enough tstates. you may be able to build it from several tiles, but copying fullscreen backbuffer and keeping 50 FPS is physically impossible.
by the way, you cannot copy the whole screen in one frame, not even using stack tricks. there is simply not enough tstates. you may be able to build it from several tiles, but copying fullscreen backbuffer and keeping 50 FPS is physically impossible.
- ParadigmShifter
- Manic Miner
- Posts: 699
- Joined: Sat Sep 09, 2023 4:55 am
Re: Machine Code: Moving from Attribute scrolling to pixel scrolling
Yeah I was thinking about clearing the screen/full screen backbuffer which is just about possible in a frame but only if you use stack tricks.
The fastest you can copy is 204T/16 = 12.75T/byte using this (I believe this is the fastest possible way to copy anyway, if you only copy 14 bytes you don't need to ex af, af' but I think it's still faster to do that than not use AF')
which if you do all 192 lines takes longer than 69888T (it takes 408*192 = 78336T) which is the number of T states in a frame (48K).
As a comparison, 16xLDI takes 256T just for the copy and 20T for setting the source and destination from a hard coded value. LDIR is even slower at 21*15+16 = 331T and you have to ld bc, 16 as well.
Also you have to expand that macro 192*2 times which is mahoosive. Otherwise you have to self modify the address or waste registers for the source and the destination. (Or use IX and IY, which are slower and you have to keep adding 16 to them).
So I guess the Hewson article was only talking about smaller screen buffers.
With tiles or clearing the screen you can fill the screen faster as ketmar said because you don't have to read as much data (which is what the pushes pops in that macro are doing) as often.
My scrolling routine which just about beats the raster for 22 of the 24 lines only reads 6 bytes per screen row (left hand side of a wall, right hand side of a wall, middle of a wall, 2 bytes each), and the spare register is always 0 (for clearing 2 bytes of screen data). There's faster ways to do what I was doing though I think, it was my first attempt
The fastest you can copy is 204T/16 = 12.75T/byte using this (I believe this is the fastest possible way to copy anyway, if you only copy 14 bytes you don't need to ex af, af' but I think it's still faster to do that than not use AF')
Code: Select all
MACRO MEMCPY16 dest, src
ld sp, src ; 10T
pop af
pop bc
pop de
pop hl ; 40T
exx
ex af, af' ; 8T
pop af
pop bc
pop de
pop hl ; 40T
ld sp, dest+16 ; 10T
push hl
push de
push bc
push af ; 44T
exx
ex af, af' ; 8T
push hl
push de
push bc
push af ; 44T = 204T
ENDM
As a comparison, 16xLDI takes 256T just for the copy and 20T for setting the source and destination from a hard coded value. LDIR is even slower at 21*15+16 = 331T and you have to ld bc, 16 as well.
Also you have to expand that macro 192*2 times which is mahoosive. Otherwise you have to self modify the address or waste registers for the source and the destination. (Or use IX and IY, which are slower and you have to keep adding 16 to them).
So I guess the Hewson article was only talking about smaller screen buffers.
With tiles or clearing the screen you can fill the screen faster as ketmar said because you don't have to read as much data (which is what the pushes pops in that macro are doing) as often.
My scrolling routine which just about beats the raster for 22 of the 24 lines only reads 6 bytes per screen row (left hand side of a wall, right hand side of a wall, middle of a wall, 2 bytes each), and the spare register is always 0 (for clearing 2 bytes of screen data). There's faster ways to do what I was doing though I think, it was my first attempt
Last edited by ParadigmShifter on Fri Mar 22, 2024 1:17 pm, edited 2 times in total.