Migrating code from NMI & handling gameloop code timing.

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
User avatar
rychan
Posts: 65
Joined: Wed Jun 21, 2017 1:51 pm
Contact:

Migrating code from NMI & handling gameloop code timing.

Post by rychan »

Hi there fellow NES Development Newbies!

I'm Rychan and I've started to get into NES development recently after having spent a year or two coding up Gameboy games in GBDK and C. There's a link to a few of them here if you're interested BTW: http://gamejolt.com/@refreshgames/games

I've never played with assembly until about a month ago and have finally plucked up the cpourage to take on the beast that is NESASM.

So far I've been able to get the basics of a game working well inside the FCEUX NES Emulator. Wrote some collision detection, faux randomness, played with sprites and moving from the title screen to the main screen and you can probably tell where this is going :)

Before we get there though, here's a link to an animation of the main game screen in it's current state via emulation:

https://twitter.com/refreshgamesuk/stat ... 6661805057

So, I purchased a flash cart and with the help of a friend got my old NES up and running with the thing. Which is when I noticed a few fundamental flaws with my code haha! Yes joypad button detection and also running out of vblank time by putting EVERYTHING into NMI, like a noob which is where I am with ASM I guess :)

I've updated my joypad handling code which now works on device and understand that I now need to separate my game logic out into the main game loop and minimise the amount of stuff in NMI but I'm a little unsure how to both perform large background loads safely and how to inform my code that it needs to wait until vBlank is done for safe looping.

I mean, is it possible to OAM a whole background over in a single vblank amount of time? I don't know the cycle count for this but I imagine it's a lot less flexible then the gameboy :)

Any advice would be really handy, I'm still sorta dreading moving my code from NMI into the main game loop but understand that it needs to be done in order to persue my dreams of owning a NES cart which I made, 8 year old me would be so happy also :)
Probably The "Most Filthy Casual" NES ASM Developer https://refreshgames.co.uk/
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: Migrating code from NMI & handling gameloop code timing.

Post by unregistered »

rychan wrote:I mean, is it possible to OAM a whole background over in a single vblank amount of time? I don't know the cycle count for this but I imagine it's a lot less flexible then the gameboy :)

Any advice would be really handy, I'm still sorta dreading moving my code from NMI into the main game loop but understand that it needs to be done in order to persue my dreams of owning a NES cart which I made, 8 year old me would be so happy also :)
OAM is Object Attribute Memory, I think, and I also believe that deals with sprites. OAM a whole background? :? For me, writing nametables 00 and 01 requires disabling rendering and it takes multiple frames, so I doubt it's possible to draw an entire background in one vblank (NMI).

You are 8 years old and interested in the NES? That's pretty fantastic! :D
User avatar
rychan
Posts: 65
Joined: Wed Jun 21, 2017 1:51 pm
Contact:

Re: Migrating code from NMI & handling gameloop code timing.

Post by rychan »

Eh heh, nope, I'm way over 8 years old unregistered (more like 35 currently, although my son, who happens to be 8 years old loves the NES now!) :) But I remember my 8 year old self wishing to make a game that runs on the NES :)

I take it that's why my game crashes to black on actual hardware then? when I'm disabling rendering then attempting to restart it later on, I'm still inside NMI, perhaps it just isn't making it to the point (due to NMI being re-run)w here it's requesting it to be turned back on then! That would make sense now I think about it.
Probably The "Most Filthy Casual" NES ASM Developer https://refreshgames.co.uk/
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Migrating code from NMI & handling gameloop code timing.

Post by tokumaru »

Short reply because I can't be here long: No, you can't update a full background in a single vblank. The fastest you can copy a byte (from ROM or normal RAM) is 8 cycles (LDA + STA), and a full background is 1024 bytes, meaning you'd need at least 8192 cycles (not considering the overhead of loops or the NMI handler) but vblank only lasts ~2273 cycles. In the best case, you're looking at 4 frames for a full background.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Migrating code from NMI & handling gameloop code timing.

Post by tokumaru »

Is the structure of your program copied from the Nerdy Nights tutorial? The problem there isn't exactly that everything is in the NMI handler (which although not optimal, is a valid program structure - SMB for example uses it), but the fact that the game logic is placed before the PPU updates in said NMI handler. Simply inverting this order should allow you to make full use of a frame's time, with PPU updates using the first 20 scanlines (vblank) and the game logic using the remaining 240 scanlines.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: Migrating code from NMI & handling gameloop code timing.

Post by unregistered »

rychan wrote:Eh heh, nope, I'm way over 8 years old unregistered (more like 35 currently, although my son, who happens to be 8 years old loves the NES now!) :) But I remember my 8 year old self wishing to make a game that runs on the NES :)

I take it that's why my game crashes to black on actual hardware then? when I'm disabling rendering then attempting to restart it later on, I'm still inside NMI, perhaps it just isn't making it to the point (due to NMI being re-run)w here it's requesting it to be turned back on then! That would make sense now I think about it.
Ah ok, I'm about the same age as you. :) Sorry for misunderstanding.

Yes, that sounds about right since tokumaru said it takes ~ 2273 cycles for a vblank; it's not at all.
User avatar
rychan
Posts: 65
Joined: Wed Jun 21, 2017 1:51 pm
Contact:

Re: Migrating code from NMI & handling gameloop code timing.

Post by rychan »

Thanks for the tips everyone. Going to look into re-writing my code into the main loop tomorrow night now. I'm assuming that so long as my code per frame doesn't go over the start of vblank if can BIT test $2002 at the end of the main loop to prevent execution from looping too many times.

I'm thinking I need to pre-prepare the screen to be off then use frames to load in the new data over 4-5 frames worth of time before re-enabling it again, should be fun!

Will go through the rewrites tomorrow anyhoo, thanks once again, I'll post my results when I have them!
Probably The "Most Filthy Casual" NES ASM Developer https://refreshgames.co.uk/
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Migrating code from NMI & handling gameloop code timing.

Post by tokumaru »

rychan wrote:I'm assuming that so long as my code per frame doesn't go over the start of vblank if can BIT test $2002 at the end of the main loop to prevent execution from looping too many times.
Not really. Reading $2002 at the exact time the vblank flag gets set cancels the NMI (and the flag returns clear). But even if this quirk didn't exist, the vblank flag is cleared when vblank ends, so if your NMI handler used all of the vblank time you'd miss the flag anyway. You have to implement a flag in software to properly detect vblanks.
I'm thinking I need to pre-prepare the screen to be off then use frames to load in the new data over 4-5 frames worth of time before re-enabling it again, should be fun!
Most games do indeed turn rendering off between levels or any screens that require large video updates, to gain unrestricted access to VRAM. If a blank screen isn't a problem, that's way easier than spreading large updates across several vblanks.
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Migrating code from NMI & handling gameloop code timing.

Post by rainwarrior »

As tokumaru said, polling $2002 is unreliable for a vblank wait; even in the best conditions it misses a frame occasionally and you will wait a whole extra frame.

If you want all your code outside the NMI, the simplest way to poll for the next vblank is something like this:

Code: Select all

nmi:
	inc nmi_counter
	rti

wait_for_nmi:
	lda nmi_counter
@loop:
	cmp nmi_counter
	beq @loop
	rts

; ...

main_loop:
	; ...
	jsr wait_for_nmi
	; ... do all PPU updates right here, immediately after the wait
	; ...
	jmp main_loop
In this case you have an NMI handler that does nothing except change a value (nmi), and when you want to wait for it you can just spin testing that value until it changes (wait_for_nmi). Immediately following the wait test you'll be close to the start of vblank and can freely use the PPU for a short time.

Edit: fixed a mistake spotted by tepples.
Last edited by rainwarrior on Wed Jun 21, 2017 5:50 pm, edited 1 time in total.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Migrating code from NMI & handling gameloop code timing.

Post by tepples »

rainwarrior wrote:

Code: Select all

nmi:
	inc nmi_counter
	rti

wait_for_nmi:
	lda nmi_counter
@loop:
	cmp nmi_counter
	bne @loop
	rts
Shouldn't that be beq @loop? You want to keep waiting while the frame count stays the same.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Migrating code from NMI & handling gameloop code timing.

Post by tokumaru »

rainwarrior wrote:

Code: Select all

nmi:
	inc nmi_counter
	rti

wait_for_nmi:
	lda nmi_counter
@loop:
	cmp nmi_counter
	beq @loop
	rts

; ...

main_loop:
	; ...
	jsr wait_for_nmi
	; ... do all PPU updates right here, immediately after the wait
	; ...
	jmp main_loop
The simplicity of this approach is great, but I want to take this opportunity to say that in practice, this offers no advantage over the all-in-NMI method, since it doesn't provide any means to deal with lag frames. If the game logic takes too long, the music will slow down, and raster effects (e.g. status bars, parallax effects) on the next frame might glitch. If lag frames aren't an issue, this is probably the simplest structure you can use, and also versatile in the sense that you can easily have different vblank handlers for different parts of the program, or you can simply JSR to a common one if that works best for you.

The all-in-NMI method, when done the correct way (i.e. PPU updates first, game logic second - not how Nerdy Nights presents it), at least offers the possibility of doing something about lag frames, since the NMI will interrupt the game logic, at which point you can choose to handle some high priority tasks such as music and raster effects before the new frame begins, even if the logic for the previous frame hasn't finished yet.
calima
Posts: 1745
Joined: Tue Oct 06, 2015 10:16 am

Re: Migrating code from NMI & handling gameloop code timing.

Post by calima »

Just in case you're not aware, you can program for the nes in C just fine. For simpler games you don't need to touch asm at all, for more complex ones you only need to write performance-sensitive parts in it.
User avatar
rychan
Posts: 65
Joined: Wed Jun 21, 2017 1:51 pm
Contact:

Re: Migrating code from NMI & handling gameloop code timing.

Post by rychan »

Oh, there's a C compiler out there? I honestly wasn't aware of that. I might fall back to that should I need to, but I feel that, having only ever used high level languages, it's one of those things which after many years of coding I should at least have a go at making something in. Even though, yup, simplistic game using mapper 0, it's one of those challenges I want to beat :)

Does the @ prefix change anything about a label? Or is it one of those good practises to help organise code better.

Got a couple of hours now to play around with things in code again, yay!
Probably The "Most Filthy Casual" NES ASM Developer https://refreshgames.co.uk/
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Migrating code from NMI & handling gameloop code timing.

Post by tokumaru »

rychan wrote:Does the @ prefix change anything about a label?
It depends on the assembler, but that normally means the label is local, so you can reuse names if there's a global label between them:

Code: Select all

@LocalLabel:
	jmp @LocalLabel

GlobalLabel:

@LocalLabel:
	jmp @LocalLabel
EDIT: You mentioned you're using NESASM, and looking up its documentation reveals that local labels in that assembler begin with a dot instead. Just one of the many things NESASM does differently from everything else.
User avatar
rychan
Posts: 65
Joined: Wed Jun 21, 2017 1:51 pm
Contact:

Re: Migrating code from NMI & handling gameloop code timing.

Post by rychan »

Cool, will investigate into using those later on, I'll stick to global labels for the moment to keep things simpler for me :)

Also, this happened! Yay!

https://youtu.be/gc1q4Ph1-x8

and there's even an hour left for me this morning to re-enable those missing features, so psyched!
Probably The "Most Filthy Casual" NES ASM Developer https://refreshgames.co.uk/
Post Reply