Drawing row regarding future scrolling

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

Post Reply
User avatar
Raccoon
Posts: 22
Joined: Tue Nov 30, 2010 9:22 am
Location: France

Drawing row regarding future scrolling

Post by Raccoon »

Hello everyone. I'm just a new member, and I read lost of posts.

I need your help about method, I know how to do, but I don't know what is the most optimized method.

It's just about drawing one row per NMI event, that's for begin able to do scrolling.
For the moment, I use a Buffer of 30 bytes in RAM which represents 30 tiles of a PAL row.
This is part of my code :

Code: Select all

	DrawPPURow:
		lda PPU_DoDrawBuffer	; Need to Draw a Row ?
		beq Return_DrawPPURow	; If PPU_DoDrawBuffer=0 dont draw a row and goto Return_DrawPPURow
		
		lda #$20
		sta PPUADDR
		lda #$00
		sta PPUADDR
		
		sta $FF			; DEBUG = just for read write at $FF to know when execute this
		
		ldx #00
		-
		lda Buffer_Data,x
		sta $2007
		inx
		cpx #30
		bne -
		
		dec PPU_DoDrawBuffer	; Drawing is made

		Return_DrawPPURow:

I have seen in the SBM sources at ROOMHACKING, and I expected it use vectors for drawing ? (I'm not sure of this, because I think I saw CHR data in RAM)
Also I see our great post : Scrollin'

My problem is regarding using vector or PPUADDR addressing like you do. (I think it will be useless, because it keep free lots of RAM spaces)

But how clear a row? Or how clear parts where there is nothing ?
With buffer it is easy to do it (just clear the buffer).
But with addressing, for an already written row, how you do it ?

Maybe I can use 1 NMI event for clear the entire row, and 1 NMI event to draw my CHRs datas (using addressing).

Thanks for you Helps, I will continue my search.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Drawing row regarding future scrolling

Post by tokumaru »

Raccoon wrote:I know how to do, but I don't know what is the most optimized method.
Since you don't have a lot of data to update, don't worry about the "optimal" way yet, just get it working however you can.
For the moment, I use a Buffer of 30 bytes in RAM which represents 30 tiles of a PAL row.
Don't you mean "column" instead of "row"? A row is horizontal, and there are 32 tiles in a name table row, columns are vertical, and there are 30 tiles in each one. The numbers are the same for PAL and NTSC by the way, there is no difference regarding screen sizes.
This is part of my code :
The problem with your code is that it always writes to the address $2000, which is the first column/row of the screen. As the screen scrolls you have to draw your column/row to other addresses, the one that is near the edge of the screen.

Code: Select all

		lda Buffer_Data,x
		sta $2007
		inx
		cpx #30
		bne -
A simple optimization that saves a good amount of time is counting down instead of up. You'd load X with #$29 instead of #$00 and DEX instead of INX. This way, after you have copied the byte at index 0, the 0 will decrement to $FF (255) and set the N flag (because 255 is the same as -1), so you can use BPL instead of BNE and get rid of the CPX, which is why you save time. You just have to fill your buffer backwards as well.

In 6502 assembly, whenever possible we count down instead of up, because you can detect when indexes reach 0 or -1 without having to compare anything.
I expected it use vectors for drawing ? (I'm not sure of this, because I think I saw CHR data in RAM)
I have no idea why you are talking about vectors and CHR-RAM, and I don't see how that has anything to do with scrolling.
But how clear a row? Or how clear parts where there is nothing ?
You can just overwrite name table data, there is no need to clear anything first.

What you have to do is calculate the correct PPU address for the row/column you want to update, based on the scrolling position. If you are drawing columns, the first one begins at $2000, the second at $2001, the third at $2002, and so on, until $201F, the 32nd column. For rows, the first one begins at $2000, the second at $2020, the third at $2040, and so on, until $23A0, the 30th row.

Also note that depending on the mirroring, you will also have to draw columns/rows to the other name table, the one at $2400.
User avatar
Raccoon
Posts: 22
Joined: Tue Nov 30, 2010 9:22 am
Location: France

Post by Raccoon »

Since you don't have a lot of data to update
That's right for the moment. I was thinking that I can do one column drawing during VblankTime. This must happen when scroll !
Don't you mean "column" instead of "row"?
Ho sorry for my understanding, it's a bad french translation from my part. It's ok, I mean "column". Thanks to point this :)
The problem with your code is that it always writes to the address $2000
Yes, I know, it will be just for test redraw at the same column. I want to implement probably an indirect addressing.
A simple optimization that saves a good amount of time is counting down instead of up.
I know but this reverse my buffer no ?:

Code: Select all

ldx #29
-
lda Buffer_Data,x          ; first time read the 29° byte of the buffer
sta $2007
dex
bne -
Maybe you thinking I will reverse my buffer during it's build function.

Ho sorry I don't see you say this :
You just have to fill your buffer backwards as well.
I have no idea why you are talking about vectors
It's because , i was thinking a vector is for example:

Code: Select all

Block : .byte $AB, $CH
$AB: the n line position of my block ( 1 -> 15 )
$CH: the CHR byte to write

It's not clear (and not optimized like this), but you can imagine, it's like a command line I will send in my NMI PPU draw function.

You can just overwrite name table data, there is no need to clear anything first.
Yest I know, but I was thinking without an buffer. But you said me : "just get it working however you can."
I realize that Dwedit use buffer in his thread : Working on a new game for that compo


Thanks for your time.
Last edited by Raccoon on Tue Nov 30, 2010 1:44 pm, edited 1 time in total.
mic_
Posts: 922
Joined: Thu Oct 05, 2006 6:29 am

Post by mic_ »

Maybe you thinking I will reverse my buffer during it's build function.
Either that, or you can offset your counter so that it ends at zero when counting upward (i.e. start at 256-30), and offset your base address accordingly. I don't remember whether the 6502 has a penalty for crossing a page boundary with an absolute-indexed address, so this may or may not be a good idea.
User avatar
Raccoon
Posts: 22
Joined: Tue Nov 30, 2010 9:22 am
Location: France

Post by Raccoon »

For sure, it's a good idea. I will simply use a backwards buffer.

Thanks for your helps
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

mic_ wrote:Either that, or you can offset your counter so that it ends at zero when counting upward (i.e. start at 256-30), and offset your base address accordingly.
Yes, you can do that too, but there is a cycle penalty if you cross a page while reading the data. Instead of the Z flag you can use the N flag to detect the end of the buffer, so you start at 128-30 instead, reducing the chances of crossing a page. The code would look like this:

Code: Select all

	ldx #(128-30)
-
	lda Buffer-(128-30), x
	sta $2007
	inx
	bpl -
As long as the buffer is smaller than 129 bytes and starts after 128-30 in a memory page, that will not cause a cycle penalty. This way the buffer doesn't need to be reversed.
Raccoon wrote:I was thinking that I can do one column drawing during VblankTime. This must happen when scroll !
Yes, all updates must be done during VBlank, and by using this type of basic loop you can transfer about 150 bytes to the PPU per VBlank. Since a column is only 30 bytes, there's still a lot of time left. Remember that you also have to use that time to update the sprites (a sprite DMA takes 513 cycles plus the time needed to trigger it) and the attribute tables (so that your tiles use the correct palettes).
I want to implement probably an indirect addressing.
If by "indirect addressing" you mean something like "STA $2007, X", that's not going work. What you have to do is calculate where in the PPU your new column will start, and when the time comes write that to $2006 before copying the column. Like I said in my last message, the first column starts at $2000, the second at $2001, the third at $2002, and so on.

Code: Select all

ldx #29
-
lda Buffer_Data,x          ; first time read the 29° byte of the buffer
sta $2007
dex
bne -
This is almost correct... the only problem is that it will not copy the byte at position 0, because of the BNE you used at the end. Change that to BPL and it will work, because the loop will only finish when the index reaches 255, after you've copied the byte at position 0.
It's because , i was thinking a vector is for example:

Code: Select all

Block : .byte $AB, $CH
$AB: the n line position of my block ( 1 -> 15 )
$CH: the CHR byte to write
Sorry, I still can't see the purpose of this. What is this position that ranges between 1 and 15?
Yest I know, but I was thinking without an buffer.
Without a buffer? Do you mean copying data directly from the level map to the screen? That's not a good idea, because decoding data from the map usually takes time, and you can't spend your precious VBlank time doing that kind of stuff. The rule is that you do all the reading/decoding/converting outside of VBlank and store all the results into buffers, so that when the time comes you can just copy that data to the PPU as fast as you can.
User avatar
Raccoon
Posts: 22
Joined: Tue Nov 30, 2010 9:22 am
Location: France

Post by Raccoon »

Sure it will help me.
It's true reverse my buffer need just more time like this (it isn't in my NMI loop):

Code: Select all

		; Buffer_Shift = Line position

		; Fix for backwards write
		lda #30		; load one column
		sec         		; set the carry
		sbc Buffer_Shift   	; subtract Buffer_Shift
		sta Buffer_Shift	; save the low byte

I try it to offset base address, but don't know how do this.
Thanks for the code, and good explains.
This is almost correct... the only problem is that it will not copy the byte at position 0
Yes I realize it, i have try this, there just one minute last.
I read my txt doc 6502guid about BPL.
If by "indirect addressing" you mean something like "STA $2007, X"
No I mean exactly "Indirect Indexed Addressing", like this :
lda (PPU_Address),y
But it doesn't work or it will be complicated for nothing.
I just need to calculate PPU_Address_low and PPU_Address_high (you answer me thanks)
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

Raccoon wrote:I just need to calculate PPU_Address_low and PPU_Address_high (you answer me thanks)
Yes, that's what you have to do.

I can only help you if you tell me what kind of mirroring you are using, vertical (the two name tables are side by side) or horizontal (one name table on top of the other). Vertical mirroring is better when scrolling horizontally, since there are no visible glitches. If you use horizontal mirroring, there will be color glitches at the sides of the screen (like in Kirby's adventure or SMB3, for example).

I also need to know how you are updating the scroll during VBlank (I need to see the code that writes to $2000 and $2005).
User avatar
Raccoon
Posts: 22
Joined: Tue Nov 30, 2010 9:22 am
Location: France

Post by Raccoon »

For the moment, I use just MMC1 mapper with vertical mirroring.
I will do some basics test before doing all the great stuff.

Tomorrow, i show you my code regarding $2000 and $2005. (it is very late at night in France !)

Thanks for your help again :)
User avatar
Raccoon
Posts: 22
Joined: Tue Nov 30, 2010 9:22 am
Location: France

Post by Raccoon »

I continue, I am just in reflexion about reading my level table, and build my buffer. ( I think it's the hard part)


Just for show my RAM part :

Code: Select all

;---------------------------------------------------------------------------------------
; RAM 											
;---------------------------------------------------------------------------------------

;	; BUFFER									
	Buffer_Data	EQU $00			; 60 bytes = 2 Columns of 15*2 tiles
	Buffer_Shift	EQU $3F			; shift write data

;	; JOYPAD									
	Joypad_State	EQU $50		; Hold states of each buttons

;	; SCROLL									
	Scroll_x 	EQU $51
	Scroll_y 	EQU $52
	acc 		EQU $53

;	; NAMETABLE									
	TableData_Shift	EQU $60			; Shift bytes in NameTableScreen
	
;	; PPU DRAW									
	PPU_ShiftColumn		EQU $61			; PPU Column Position from 0 to 32
	PPU_Address		EQU $62	; 2 BYTES	; PPU Column Position Address
	PPU_DoDrawBuffer	EQU $64			; To know when Buffer need to be draw

	VBlank_nCycles		EQU $6F			; Count n Cycles for one VblankTime
My main, and my NIM:

Code: Select all

;---------------------------------------------------------------------------------------
;MAIN LOOP										
;---------------------------------------------------------------------------------------



MainLoop:			; Loop Forever			

	include "Joypad.asm"
	

	WaitNextVblankTime:
		lda VBlank_nCycles
		-
		cmp VBlank_nCycles
		beq -

	jmp MainLoop		; jump to Main_Loop

;---------------------------------------------------------------------------------------
; NMI											
;---------------------------------------------------------------------------------------


NMI:	; Signal generated by PPU for a VBlank Time
	
	SaveRegisters:
		pha	; Push A on Stack
		txa	
		pha	; Push X on Stack
		tya
		pha	; Push Y on Stack
		
	DrawPPURow:
		lda PPU_DoDrawBuffer	; Need to Draw a Row ?
		beq Return_DrawPPURow	; PPU_DoDrawBuffer=0 goto Return_DrawPPURow
		
		lda PPU_Address+1
		sta PPUADDR		; PPUADDR = $2000
		lda PPU_Address
		sta PPUADDR
		
		sta $FF			; DEBUG = just for read write at $FF to know when execute this
	
		ldx #(128-30) 
		- 
		lda Buffer_Data-(128-30), x 
		sta $2007 
		inx 
		bpl -
		
		dec PPU_DoDrawBuffer	; Drawing is made

		Return_DrawPPURow:
	
	
	Scrooling:		
		ldx Scroll_x
		stx $2005
		ldx #00
		stx $2005
	
	CountVBlankCycles:
		inc VBlank_nCycles	; from 0 to 255 one VblankTime ?

	RetrieveRegisters:
		pla	
		tay	; Pull Stack on Y
		pla	
		tax	; Pull Stack on X
		pla	; Pull Stack on A
		
		
	rti		; Return from Interrupt

And just before Main Loop, my some tests wich draw only 2 colums (I use MetaTile 2x2 tiles) :

Code: Select all

;---------------------------------------------------------------------------------------
; TEST RENDER BUFFER										
;---------------------------------------------------------------------------------------

	jsr Render			;draw one column x1 tile
	
		WaitNextVblankTime2:
		lda VBlank_nCycles
		-
		cmp VBlank_nCycles
		beq -
	
	jsr IncrementColumnAddress
	jsr WriteBuffer

;---------------------------------------------------------------------------------------
; RENDER										
;---------------------------------------------------------------------------------------
	Render:

	jsr Initializes		
	jsr ReadObject
	jsr ClearBuffer
	jsr WriteBuffer

	Return_Render:
		rts
Maybe you need to see my Level Data, and my Metatiles (I name this tileSet)

Code: Select all

; OBJECT DEFINITION
;Byte n°1 	$RL = nRow, nLine		POSITION	
;Byte n°2 	$TS = nTileSet of Bank		CHR		
;nColumn  Position : #0 -> 16 (32 tiles)
;nLine 	  Position : #0 -> 15 (30 tiles), #16 = $F = New column increment

TableData:.byte $02,$00


	;BANK of TILESET (64 max X 4 bytes = 256 = $FF)
	BankOfTileSet:
	Plateform_TopRight	: .byte $00,$10,$01,$11		; $00
	Plateform_TopMiddle	: .byte $02,$12,$01,$11		; $01
	Plateform_TopLeft	: .byte $02,$12,$03,$13		; $02
	
	Plateform_BottomRight	: .byte $10,$10,$11,$11		; $03
	Plateform_BottomMiddle	: .byte $12,$12,$11,$11		; $04
	Plateform_BottomLeft	: .byte $13,$13,$12,$12		; $05
It's not really clear like this, if you want see more, I can do an entire ZIP.

I will post screenshot of my 2 columns in fews minutes:

Image

You can see the RAM getting in FCEUX.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

There are a few inconsistencies in your code that are bothering be a bit. For example, in the part where yoy set the PPU address before writing tiles, there's a comment that says "PPUADDR = $2000 ". That got me confused because the PPU address register is actually $2006, and in the rest of the code you are not using registers' names you are using their addresses ($2007, $2005, etc), so you should either use only their addresses, or give them all names, making sure that they point to the correct addresses.

Another problem is that I don't see any writes to register $2000 (PPUCTRL). There should be a write to it next to the $2005 writes, because the lower 2 bits of PPUCTRL have an important part in the scrolling too. Since you are using vertical mirroring, and your name tables are arranged side by side, there will be times when you will be showing the one on the left and there will be times when you'll be showing the one on the right, and you will need to use PPUCTRL to indicate that.

About calculating the target address for your columns: you will have to use the "Scroll_x" variable for that. One important thing to change is that since your level is longer than 256 pixels, that variable must be 16-bits large. I assume you know how to handle 16-bit values.

Anyway, the tiles are 8 pixels wide, so you must detect when the camera crosses an 8 pixel barrier in order to know when to draw a new column. Look at the binary numbers and you will see that 0 is %00000000, 7 is %00000111, and 8 is %00001000. If you pay attention, you'll see that numbers 1 through 7 have the 4th bit as 0, and only when the number becomes 8 that bit changes to 1. Because of that, that is the bit that will tells us that the scroll crosses an 8 pixel barrier. The code could look something like this:

Code: Select all

	lda Scroll_x+0 ;load the lower byte of Scroll_x
	pha ;save it to the stack

	;UPDATE Scroll_x HERE

	pla ;get the old Scroll_x
	eor Scroll_x ;XOR it with the new Scroll_x
	and #%00001000
	beq +Skip

	;DECODE A NEW COLUMN HERE

+Skip:
Once you have decided that a new column must be rendered, it's time to find out where in the level map this column is and where in the name tables it should be rendered to. Scroll_x will also help you with this.

Scroll_x tells you which part of the name tables should be displayed at the leftmost side of the screen, so if you you scrolled left, that's the column that has to be updated. If you scrolled right (to detect whether you scrolled right or left you have to compare the old Scroll_x to the new one and see which one is larger), you have to add 256 to Scroll_x to find which column needs updating, because the right side of the screen is 256 pixels to the right of the left side.

I don't know how your level map is stored, so it's up to you to convert Scroll_x to a format that allows you to read from it, but I can tell you about calculating the destination address in the name tables. Once you have defined the X coordinate of the column (it's either Scroll_x or Scroll_x + 256), you have to get rid of the lower 3 bits to convert it from pixel units to tile units. Then, the next 5 bits (a number between 0 and 31) will indicate what name table column to use. The next bit will tell you if you should write the column to the first name table ($2000) or to the second ($2400).

When setting the scroll, you have to write the lower byte of Scroll_x to $2005, and the first bit of the high byte to bit 0 of PPUCTRL, in order to select the name table where scrolling should start.

I know that's a lot of information, and I don't think I can make it any simpler. This stuff isn't so trivial, so if you are having a hard time with this maybe you should consider not using scrolling for your first project.
User avatar
Raccoon
Posts: 22
Joined: Tue Nov 30, 2010 9:22 am
Location: France

Post by Raccoon »

That got me confused because the PPU address register is actually $2006
Yes, you all right it's effectively $2006. It's just an error from my part.

My code is confusing I know, that's why I reorganize it.
I remake the buffer function, for being able to draw an amount of columns. Like this I could pass for initial 32 columns for the first nametable.


Another problem is that I don't see any writes to register $2000 (PPUCTRL)
Yes that's because, I do just initialize in the head of the program (before main loop). This refers to an separate file for subroutines:

Code: Select all

ScreenDisplayOFF EQU #%11110111	; With AND force ScreenDisplay 	bit to 0 = DisplayOFF
DisableNMI	 EQU #%01111111 ; With AND force Execute NMI 	bit to 0 = DisableNMI

Register2000 	 EQU  #%10001100
;			76543210
;			||||||||
;			||||||++----$10	Name Table Select					
;			||||||		00=$2000  10=$2800					
;			||||||		01=$2400  11=$2C00					
;			||||||
;			|||||+-------$2 PPU Address Read/Write Increment			
;			|||||		= 1 Increment by 1					
;			|||||		= 0 Increment by 32					
;			|||||
;			||||+--------$3 Sprite Pattern Table Address				
;			||||		0=$0000  1=$1000					
;			||||
;			|||+---------$4 Screen Pattern Table Address				
;			|||		0=$0000 1=$1000						
;			|||
;			||+----------$5 Sprite Size						
;			||		0=8x8  1=8x16						
;			||
;			|+-----------$6 Execute NMI on Sprite Hit				
;			|		0=Disable  1=Enable					
;			|
;			+------------$7 Execute NMI on VBlank					
;					0=Disable 1=Enable					

Register2001 	 EQU  #%00001110
;			76543210
;			||||||||
;			|||||||+-----$0	Color Display						
;			|||||||		0=All colors						
;			|||||||		1=Mono Color						
;			|||||||
;			||||||+------$1 Image Clip						
;			||||||		0 = Don't show the left 8 pixels of the screen		
;			||||||		1 = Show the left 8 pixels				
;			||||||
;			|||||+-------$2 Sprite Clip						
;			|||||		0 = Don't Show sprites in the left 8-pixel column 	
;			|||||		1 = Show Everywhere 					
;			|||||
;			||||+--------$3 Screen Display						
;			||||		0=OFF 1=ON						
;			||||
;			|||+---------$4 Sprite Display 						
;			|||		0=Hide 1=Show						
;			|||
;			+++--------$765 Full Background Color					
;					000 = NONE	010 = Green				
;					001 = Red	100 = Blue				

;	PPU_CTRL	= $2000
;	PPU_MASK	= $2001
;	PPU_STATUS	= $2002
;	PPU_SCROLL	= $2005
;	PPU_ADDR	= $2006
;	PPU_DATA	= $2007



;---------------------------------------------------------------------------------------
;VBlankWait										
;---------------------------------------------------------------------------------------
VBlankWait:      		; wait for Vblank, PPU is ready after this		
	bit PPUSTATUS		; if %2002 = LastPaletteByte then N = 1			
	bpl VBlankWait		; if  N = 0 goto VblankWait				
	rts			; return form subroutine				

;---------------------------------------------------------------------------------------
;SetNameTableAt2000									
;---------------------------------------------------------------------------------------

SetNameTable$2000:
	
	; write Address NameTable1 > $2000
	lda #$20
	sta PPUADDR
	lda #$00
	sta PPUADDR
	
	; fix first line place
	lda #$00
	sta PPUADDR
	rts

;---------------------------------------------------------------------------------------
;Screen Turn On										
;---------------------------------------------------------------------------------------
DisplayScreen:
	jsr VBlankWait		; Wait VBlank Routine	
	lda Register2000	; execute NMI on Vblank & Sprite Pattern Table Address = $1000		
	sta PPUCTRL		; set Control Register #1						
        lda Register2001	; screen display & Show sprites everywhere & Show the left 8 pixels	
	sta PPUMASK		; set Control Register #2						
	rts

;---------------------------------------------------------------------------------------
;DisableScreen										
;---------------------------------------------------------------------------------------
DisableScreen:			;DisableDisplay needed before writing NameTable

	lda DisableNMI		; Bit Force to DisableNMI
	and Register2000
	sta PPUCTRL

	lda ScreenDisplayOFF	; Bit Force to ScreenDisplayOFF	
	and Register2001
	sta PPUMASK
	rts

;---------------------------------------------------------------------------------------
;WaitNextVblankTime									
;---------------------------------------------------------------------------------------

WaitNextVblankTime:

	lda VBlank_nCycles
	-
	cmp VBlank_nCycles
	beq -
		
	rts

since your level is longer than 256 pixels, that variable must be 16-bits large. I assume you know how to handle 16-bit values.
Yes I will use it !
Anyway, the tiles are 8 pixels wide, so you must detect when the camera crosses an 8 pixel barrier in order to know when to draw a new column.
Hum I think of that a little. Yes, good tips to use the 3th bit detect to see when the number becomes 8. I will use same thing for 31 to swap nametables, yes it's ok, I understand.

Thanks for the code

Code: Select all

so if you are having a hard time with this maybe you should consider not using scrolling for your first project.
No, even if that take some times to do, I want do that.
And I will thanks you again to help, for sure I will share my entire scrolling system with other when it will work!

It's not my real first project, I have made some stuff before.
I have made an drawing system which build large objects: sorts of Meta-tiles where I can chose predefined parts sizes for start, "repeated" middle, end parts. With a "level" table of different objects positions, and sizes, it will calculate all tiles and render. It's difficult to explain, but this is not optimized for scrolling and work for only one nametable.
Post Reply