Machine Code: Moving from Attribute scrolling to pixel scrolling

The place for codemasters or beginners to talk about programming any language for the Spectrum.
User avatar
Hedge1970
Manic Miner
Posts: 388
Joined: Mon Feb 18, 2019 2:41 pm

Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by Hedge1970 »

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?
User avatar
Stefan
Manic Miner
Posts: 809
Joined: Mon Nov 13, 2017 9:51 pm
Location: Belgium
Contact:

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by Stefan »

Hedge1970 wrote: Thu Mar 21, 2024 3:49 am 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.
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.
Hedge1970 wrote: Thu Mar 21, 2024 3:49 am 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.
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.
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?
I would first finish what you have. Have another look at pixel scrolling with your next game.
Timmy
Manic Miner
Posts: 230
Joined: Sat Apr 23, 2022 7:13 pm
Location: The Netherlands

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by Timmy »

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.
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by ketmar »

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.
Ralf
Rick Dangerous
Posts: 2289
Joined: Mon Nov 13, 2017 11:59 am
Location: Poland

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by Ralf »

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.
User avatar
Joefish
Rick Dangerous
Posts: 2059
Joined: Tue Nov 14, 2017 10:26 am

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by Joefish »

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.
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by ParadigmShifter »

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.

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
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.

Image

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
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by ParadigmShifter »

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.
User avatar
Hedge1970
Manic Miner
Posts: 388
Joined: Mon Feb 18, 2019 2:41 pm

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by Hedge1970 »

Timmy 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.
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 sprite

LD B,$08
LOOP_FRUIT
LD A,(HL)
LD (DE),A
INC D
INC HL
DJNZ LOOP_FRUIT

Hope that makes sense.
User avatar
Hedge1970
Manic Miner
Posts: 388
Joined: Mon Feb 18, 2019 2:41 pm

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by Hedge1970 »

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.
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.


Thanks
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by ParadigmShifter »

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.
User avatar
Hedge1970
Manic Miner
Posts: 388
Joined: Mon Feb 18, 2019 2:41 pm

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by Hedge1970 »

ParadigmShifter wrote: Thu Mar 21, 2024 2:43 pm It still shouldn't flicker as long as you are drawing ahead of the raster.
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.
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by ParadigmShifter »

Here's some info about the raster (timings for 48K model)
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
User avatar
Hedge1970
Manic Miner
Posts: 388
Joined: Mon Feb 18, 2019 2:41 pm

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by Hedge1970 »

I’ll definitely be coming back to this post once I am ready. Thank you.
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by ParadigmShifter »

Forgot to post the link where I got the info about the raster from

https://worldofspectrum.org/faq/referen ... erence.htm
User avatar
Joefish
Rick Dangerous
Posts: 2059
Joined: Tue Nov 14, 2017 10:26 am

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by Joefish »

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.
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by ParadigmShifter »

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
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by ketmar »

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! ;-)
User avatar
Joefish
Rick Dangerous
Posts: 2059
Joined: Tue Nov 14, 2017 10:26 am

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by Joefish »

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.
Timmy
Manic Miner
Posts: 230
Joined: Sat Apr 23, 2022 7:13 pm
Location: The Netherlands

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by Timmy »

Usually I'd just explain this flicker problem like this:

https://archive.org/details/sinclair-us ... ew=theater
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by ParadigmShifter »

That link (like most others on archive.org) does not work for me (SSL error).
Timmy
Manic Miner
Posts: 230
Joined: Sat Apr 23, 2022 7:13 pm
Location: The Netherlands

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by Timmy »

ParadigmShifter wrote: Thu Mar 21, 2024 10:09 pm That link (like most others on archive.org) does not work for me (SSL error).
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.
But it's probably a better discussion in some separate thread.

EDIT: Try this one? https://spectrumcomputing.co.uk/page.ph ... 91&page=78
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by ParadigmShifter »

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 :)
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by ketmar »

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.
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Machine Code: Moving from Attribute scrolling to pixel scrolling

Post by ParadigmShifter »

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')

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
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 ;)
Last edited by ParadigmShifter on Fri Mar 22, 2024 1:17 pm, edited 2 times in total.
Post Reply