Log inRegister an accountBrowse CSDbHelp & documentationFacts & StatisticsThe forumsAvailable RSS-feeds on CSDbSupport CSDb Commodore 64 Scene Database
You are not logged in - nap
CSDb User Forums


Forums > C64 Coding > Sprite multiplexing *with* priorities
2009-01-24 14:40
Bregalad
Account closed

Registered: Jul 2008
Posts: 42
Sprite multiplexing *with* priorities

I'm willing to port a game on the C64 but for sure I'll need more than 8 sprites per screen. The game uses a top-down perspective so sprites that are southern on the screen must show above the sprites that are nothern, else it will look weird.
Unfortunately, most sprite multiplexing tehniques I've seen arround the net seems to kill any priorities, and that's not good.

I guess I can come up with a solution, but I'm not sure how good it is :
- The screen is sepearated into 10 areas of 20 pixel tall
- There is a raster interrupt each 20 pixels that will handle the multiplexing, and the interrupt at line 250 will also be used for the other usual stuff (screen updates, sound code, etc...)
- Each sprite always crosses 2 areas no matter where they are positionned (this assumes Y expanding is never used)

Now during the frame :
In the first area (lines 0-20), the sprites are mapped to hardware sprites with regards to their sorted priorities. If there is more than 8 then the lowest pirority is discarded.

In the second area, the sprites that were already used in the first area cannot be used, so I map the sprites that are still unused to the sprites starting in the second area, regarding to their priorities.

In the third area, all sprites that were used in the first are done being displayed and I can re-map them to sprites of the third area according to their priorities, etc....

Now the priorities are not matched between areas, but at least they are inside each area. Also the number of interrupts is constant, and everything can be pre-calculed during the non-display time, so that interrupts just do a few writes each 20 scanlines which is not too bothersome and allow it to be mixed with other raster split without too much trouble.

I know Cadavers said it was a bad idea to split the screen with areas, but I can't come with anything else. I could use the generic method in a way so that top sprites are lowest priorities than bottom sprites, and hoping it won't look too weird.
 
... 61 posts hidden. Click here to view all posts....
 
2009-02-02 18:18
Bregalad
Account closed

Registered: Jul 2008
Posts: 42
Yeah I write something to $d011 with bit 7 clear so that I always trigger interrupts in the visible part of the screen (or on the bottom of the screen). Do I have to re-write $d011 regularly to prevent interrupts to be triggered during VBlank ? And yes I do a cli instruction so that after the sprites are sorted and ready to be displayed, the VBlank IRQ can continue and be interrupted by sprite IRQ if it needs so. Without the cli, the sprite IRQ would be blocked as long as the VBlank IRQ does not return.

I can post the IRQ handler if you want :
IRQ	inc $d019	;Acknownledge the raster IRQ
	pha
	txa
	pha
	tya
	pha		;Check if the IRQ is caused by the sprite multiplexing
	ldx SpriteRasterIndex
	bpl SpriteIRQ	;Or by the end of the frame (raster position $ff)
MainIRQ
	jsr SpriteMultiplexer
	jsr InitSprites
	jsr SetSprites
;	jsr MoveStars
	jsr MoveCenter
	lda FrameCtr
	eor #$01
	sta FrameCtr
	pla
	tay
	pla
	tax
	pla
NMI	rti

SpriteIRQ
	ldy SpriteHWAssign.w,X
	lda SpriteSortedFlags.w,X
	sta $d027,Y			;Color
	lda SpriteSortedTileNmr.w,X
	sta $7f8,Y			;Tile number
	tya
	asl A
	tay
	inc $d020
	lda SpriteSortedPosY.w,X
	sta $d001,Y
	lda SpritePosXL.w,X
	sta $d000,Y
	lda D017Buffer.w,X
	sta $d017
	lda D01dBuffer.w,X
	sta $d01d
	lda D01bBuffer.w,X	;Copy all flags
	sta $d01b
	lda D01cBuffer.w,X
	sta $d01c
	lda D015Buffer.w,X
	sta $d015
	lda D010Buffer.w,X
	sta $d010
	dec $d020

-	inx
	cpx #SpriteAmount	;Last sprite ?
	bcs _lastInt
	lda InterruptLine.w,X
	beq -			;If raster line is 0, we should ignore it
	stx SpriteRasterIndex	;Copy valid index and rasterline
	sta $d012
	cmp #$ff
	beq _lastInt		;If value is $ff, then it's the last interrupt of the frame
	sec
	sbc #$02		;If a new interrupt should occur in less than 2 scanlines
	cmp $d012		;Do it straight away
	bcs +
	jmp SpriteIRQ

--	lda #$ff
	cmp $d012
	bne _lastInt
-	jmp MainIRQ

_lastInt
	bit $d011
	bmi -		;If we somehow missed the main IRQ make sure we jump to it right away
	lda #$fd
	cmp $d012	;If rasterline is $fe or $ff go to main IRQ after a small delay (I don't want to miss it)
	bcc --
	lda #$ff
	sta $d012
	sta SpriteRasterIndex
+	pla
	tay
	pla
	tax
	pla
	rti
2009-02-02 19:47
Oswald

Registered: Apr 2002
Posts: 5094
d011 works like d012 when it comes to the raster counter, so once you cleared the topmost bit its fine.

looks like the bug is caused by the complicated irq handling. I'd remove all the logics which differentiates vblank and makes sure it happens. instead simply fire the vblank irq at a position where it will surely not interfere with a sprite irq (bottom border+some lines).

hmm after you set the d012 for the next sprite irq why do you jump back to mainirq (vblank?) you should just restore regs and rti I guess.

maybe you forget that you can change the irq vector inside irqs, so after the last sprite irq you can simply point it to the vblank irq to happen. you could also have 8 irqs chained for writing the 8 hw sprites instead of indexing the vic spr regs.

CIA interrupts:

http://unusedino.de/ec64/technical/aay/c64/ciamain.htm

you have two timers A&B in each CIA. they countdown 1 with each clock cycle (if you havent set them to count the underflow of nother timer). you can use the control registers to start stop the timers & the irq register to ask cia to generate IRQ when a timer underflows.

CIA at dc00 will generate IRQS the one at $DD00 will generate NMIS.

by careful timing you can use cia#2 as a 2nd raster interrupt source.
2009-02-02 21:58
Cruzer

Registered: Dec 2001
Posts: 1048
You could also mask the unwanted gfx away software-wise from the sprites with wrong priority.
2009-02-02 22:32
Bregalad
Account closed

Registered: Jul 2008
Posts: 42
Quote:

looks like the bug is caused by the complicated irq handling. I'd remove all the logics which differentiates vblank and makes sure it happens. instead simply fire the vblank irq at a position where it will surely not interfere with a sprite irq (bottom border+some lines).

Well the logic is a little complicated as you say but it's cadaver who said in his doc that you must make sure not to be late from the next IRQ else the screen will flicker and I belive he's right. If two sprites are on the exact same line the programm will try to do a second IRQ on the same line, which will probably not happen correctly and it will wait the IRQ of the next frame, and this is really bad.

However you are right that the logic that detects if I'm late from the VBlank interrupt and jump to it is probably not necessary, as the lower position possible for sprites is $f9 (so the IRQ should happen arround $f5), and the NMI is at $ff. Even if there is like 8 sprites at $f9 this shouldn't happen, so I might as well remove it. Maybe I messed something up and this added bugs. I will try that right away and edit that post to say the results.
2009-02-02 22:50
Bregalad
Account closed

Registered: Jul 2008
Posts: 42
Oh it seems it is impossible to edit posts. This is a shame. Anyway, I've removed the useless logic to simplfiy things a little, but it changes nothing, there is still something wrong. But you were right that I had to remove that. Here you are the new code :
	ldy SpriteHWAssign.w,X
	lda SpriteSortedFlags.w,X
	sta $d027,Y			;Color
	lda SpriteSortedTileNmr.w,X
	sta $7f8,Y			;Tile number
	tya
	asl A
	tay
	inc $d020
	lda SpriteSortedPosY.w,X
	sta $d001,Y
	lda SpritePosXL.w,X
	sta $d000,Y
	lda D017Buffer.w,X
	sta $d017
	lda D01dBuffer.w,X
	sta $d01d
	lda D01bBuffer.w,X	;Copy all flags
	sta $d01b
	lda D01cBuffer.w,X
	sta $d01c
	lda D015Buffer.w,X
	sta $d015
	lda D010Buffer.w,X
	sta $d010
	dec $d020

-	inx
	cpx #SpriteAmount	;Last sprite ?
	bcs _lastInt
	lda InterruptLine.w,X
	beq -			;If raster line is 0, we should ignore it
	cmp #$ff
	beq _lastInt		;If value is $ff, then it's the last interrupt of the frame
	stx SpriteRasterIndex	;Copy valid index and rasterline
	sta $d012
	sec
	sbc #$02		;If a new interrupt should occur in less than 2 scanlines
	cmp $d012		;Do it straight away
	bcc SpriteIRQ
	bcs +

_lastInt
	lda #$ff
	sta $d012
	sta SpriteRasterIndex
+	pla
	tay
	pla
	tax
	pla
	rti


I'd like to know a bit more about acknownledging. Do you have to wait that the raster moves to the next scanline to acknownledge, or else the IRQ will remain low because it it still to the same line as the compare value ? If so it would explain all my bugs.

The CIA timer 2 seems good for raster effects, so that I could have the sprite multiplexer (if I ever get it working) always enabled, and NMIs would be used for other possible effects (and take the higest priority). Not putting sprites right after the split point is the responsability of the main programm (else they won't be displayed because the IRQ will be fired too late).

About changing the IRQ vector in the IRQ routine, yeah this is possible but I don't know what I'd really gain from it. I could possibly make something more reliable with that method, I'll think about it if I ever found my bug. It would not change the logic but just move it to another place.

Writing a different piece of code for each hardware sprite is possible, but I think it's really a bad idea. It would make the code much bigger to load into RAM and you gain nothing from it. LDA is the same number of cycles when indexed or not as long as no page boundary is crossed. STA is one more cycle when indexed, but I only have 4 indexed STA in the IRQ code. It would save 4 cycles, but you'd have to change an jump instruction or the IRQ vector each time, which would take more than 4 cycles so you'd end up with a bigger *and* slower code !

For the priorities I guess I came with a good algorithm. The first sprite is always #7, and for the following one, I detect if it overlaps with the previous (with some margin of about 4 pixels). If it doesn't overlap, it's index it #7. If it does overlap, then the index is one less than the previous index, ANDded with #7. Then I detect if it overlaps with the prveious sprite using the same idex. If it does, the sprite is rejected and not displayed (#0 is written for it's IRQ line to say it's not a valid number).
2009-02-03 02:33
Graham
Account closed

Registered: Dec 2002
Posts: 990
Acknowledging raster IRQs is done by setting bit 0 of $D019. The clean way would be LDA $D019 STA $D019, but on 6502 you can also use INC $D019.

Acknowledging can be done anywhere in the IRQ, but you might kill some raster IRQs if you do it late. The routine you show up there already eats ~3 rasterlines alone, so if you have "an IRQ the next rasterline" and acknowledge it at the end of that IRQ, the next-rasterline IRQ will never happen. If you do it early, the IRQ will happen but atleast 2 rasterlines too late due to RTI being so late.

You can allow IRQs within another IRQ by clearing the IRQ-flag with CLI. Sometimes that might be useful, but in your case it will probably lead to a number of side effects.
2009-02-03 06:23
Oswald

Registered: Apr 2002
Posts: 5094
I see no possible bugs now. the next candidate is the routine setting up InterruptLine.w. I'd set up sprites at fixed positions and check the code at strategic breakpoints if it sets up d012 correctly, if its running on the rasterline its supposed to be, spriterasterindex is crucial aswell. a peek at $d011 if topmost bit is cleared could be done aswell. hm, also cpx #lastsprite is a suspect, usually you dont have a constant nr of sprites displayed in a multiplexer.

I'd still prefer 'chained' irqs as it makes the flow of your code more understandable: no check for vblank at each irq, just change vector to vblank after the last sprite, should spare some cycles if enough sprite irqs are present.

hmm also checking both sprite nr and interruptline.w for last sprite looks to be too much, one of them should be enough.
2009-02-03 21:51
Bregalad
Account closed

Registered: Jul 2008
Posts: 42
Quote:

Acknowledging can be done anywhere in the IRQ, but you might kill some raster IRQs if you do it late. The routine you show up there already eats ~3 rasterlines alone, so if you have "an IRQ the next rasterline" and acknowledge it at the end of that IRQ, the next-rasterline IRQ will never happen. If you do it early, the IRQ will happen but atleast 2 rasterlines too late due to RTI being so late.

Oh my god I just realised this was sadly true. The C64 is really slugglish slow !

So yeah I had to modify my IRQ so that it becomes as fast as possible. I save the reigsters to Z-Page variables instead of the stack, and instead of doing double-comparaison for the last IRQ, I ended up increasing the size of InterruptLine by one and make sure the last element is always $ff, so that if all sprites are used it detects the last sprites the same way as usual. I could always move some more stuff to zeropage, but it will end up eating too much of it and that's not good.
I also change the IRQ vector so that it points to either routine I need and this works great. I now only have VBlank interrupts at SL 255 which is great (I had false VBlanks before).

Unfortunately it's not working as I'd like it to. Some sprites seems completely misplaced on the X axis. It could be a bug in the main programm, but I'm pretty sure it worked fine with only 8 sprites, so there is no reason the number of sprites would add bugs. Maybe I write to the registers in the wrong order ? I haven't tested to individually play with the flags such as multicolor or X/Y expansion, but I plan to.

Here you are the latest version, with two possible IRQ entry points :
IRQ	inc $d019	;Acknownledge the raster IRQ
	pha
	txa
	pha
	tya
	pha
	jsr SpriteMultiplexer
	jsr InitSprites
	jsr SetSprites
	jsr MoveStars
	jsr MoveCenter
	lda FrameCtr
	eor #$01
	sta FrameCtr
	pla
	tay
	pla
	tax
	pla
NMI	rti

SpriteIRQ
	inc $d019
	sta SpriteStoreA
	stx SpriteStoreX
	sty SpriteStoreY

	ldx SpriteRasterIndex
_loop
	ldy SpriteHWAssign.w,X
	lda SpriteSortedFlags.w,X
	sta $d027,Y			;Color
	lda SpriteSortedTileNmr.w,X
	sta $7f8,Y			;Tile number
	tya
	asl A
	tay
	lda D017Buffer.w,X
	sta $d017
	lda D01dBuffer.w,X
	sta $d01d
	lda D01bBuffer.w,X	;Copy all flags
	sta $d01b
	lda D01cBuffer.w,X
	sta $d01c
	lda D015Buffer.w,X
	sta $d015
	lda D010Buffer.w,X
	sta $d010
	lda SpritePosXL.w,X
	sta $d000,Y
	lda SpriteSortedPosY.w,X
	sta $d001,Y
-	inx
	lda InterruptLine,X
	beq -			;If raster line is 0, we should ignore it
	sta $d012
	cmp #$ff
	beq _lastInt		;If value is $ff, then it's the last interrupt of the frame
	stx SpriteRasterIndex	;Copy valid index and rasterline
	sec
	sbc #$03		;If a new interrupt should occur in less than 2 scanlines
	cmp $d012		;Do it straight away
	bcc _loop
	bcs +

_lastInt
	lda #<IRQ
	sta $fffe
	lda #>IRQ
	sta $ffff
+	lda SpriteStoreA
	ldx SpriteStoreX
	ldy SpriteStoreY
	rti
2009-02-04 03:21
Graham
Account closed

Registered: Dec 2002
Posts: 990
You should limit the IRQs to something like every 8th rasterline. That way you have enough time to finish the IRQ before the next one will start and avoid a lot of problems. And yes, the C64 is very slow :)
2009-02-04 06:58
JackAsser

Registered: Jun 2002
Posts: 2014
Completely unrelated to sprite multiplexing, but saving clobbered registers to the ZP is kinda wasting ZP-space. Simply selfmod the code instead:
sta areg+1
stx xreg+1
xty yreg+1
...
areg: lda #$00
xreg: ldx #$00
yreg: ldy #$00
rti


Also, a HCL 'classic' is to just update $fffe if you know the two interrupt service routines fit into the same page.

Also since you catch the BEQ case after the CMP #$ff you know that the carry will always be clear. Simply remove the SEC and adjust the SBC-value accordingly.
Previous - 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 - Next
RefreshSubscribe to this thread:

You need to be logged in to post in the forum.

Search the forum:
Search   for   in  
All times are CET.
Search CSDb
Advanced
Users Online
CreaMD/React
crayon/Hokuto Force
apprentix
Guests online: 103
Top Demos
1 Next Level  (9.7)
2 13:37  (9.7)
3 Mojo  (9.7)
4 Coma Light 13  (9.6)
5 Edge of Disgrace  (9.6)
6 What Is The Matrix 2  (9.6)
7 The Demo Coder  (9.6)
8 Uncensored  (9.6)
9 Comaland 100%  (9.6)
10 Wonderland XIV  (9.6)
Top onefile Demos
1 No Listen  (9.6)
2 Layers  (9.6)
3 Cubic Dream  (9.6)
4 Party Elk 2  (9.6)
5 Copper Booze  (9.6)
6 X-Mas Demo 2024  (9.5)
7 Dawnfall V1.1  (9.5)
8 Rainbow Connection  (9.5)
9 Onscreen 5k  (9.5)
10 Morph  (9.5)
Top Groups
1 Performers  (9.3)
2 Booze Design  (9.3)
3 Oxyron  (9.3)
4 Censor Design  (9.3)
5 Triad  (9.3)
Top Graphicians
1 Mirage  (9.8)
2 Archmage  (9.7)
3 Pal  (9.6)
4 Carrion  (9.6)
5 Sulevi  (9.6)

Home - Disclaimer
Copyright © No Name 2001-2024
Page generated in: 0.058 sec.