More sprites on screen: Slower or same time?

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
More sprites on screen: Slower or same time?
by on (#153118)
Does the NES need more time to process the graphics if there are more sprites in the visible area of the screen or doesn't this matter?

After all, there are always 64 sprites.
So, when a game lags because there are too many characters on the screen, is this because of the actual update done by the PPU or is it just because the game logic code for i = 0 to 63 UpdateSpriteAttribute(i) takes longer than for i = 0 to 11 UpdateSpriteAttribute(i)?

I can imagine both versions because I imagine that if the PPU detects that the Y position is outside the visible area, it doesn't do any drawing at all and therefore saves time.


Also, what about changing the sprites? If I have 10 sprites on screen that don't move at all, is this quicker for the PPU than 10 sprites that change their position and attribute in every frame?

The PPU reads the sprites from a specified location via DMA. So, I would assume that non-movement between two frames takes the same amount of time than movement. Because the PPU reads the sprites anew every time and therefore cannot know beforehand which sprites have changed and which have not. (Unlike the background where you manually choose which tiles to change by setting the PPU address that you want to draw to.)


What do you say?
Re: More sprites on screen: Slower or same time?
by on (#153120)
More sprites doesn't affect rendering time. Neither does stationary/moving sprites. Every frame takes exactly the same time (minus the "missing dot" on every 2nd frame depending on whether rendering is enabled or not).

Games lag because they can't get the frame ready fast enough. They have about 16 ms per frame (minus time needed for vblank updates) to do it if they want to achieve 60 FPS. If they miss the deadline (vblank), PPU will start rendering with data from the previous frame and thus the frame rate gets halved (assuming the game logic gets its job done in 2 frames).

Some good reading: http://wiki.nesdev.com/w/index.php/The_frame_and_NMIs
Re: More sprites on screen: Slower or same time?
by on (#153121)
Slowdowns are caused by the game engine trying to do too much in a single frame, which is often caused by lots of active objects. We sometimes make the assumption that more sprites = slowdown because objects are usually represented by sprites, but the PPU has nothing to do with this, it always does its thing in the same amount of time regardless of how many sprites there are.
Re: More sprites on screen: Slower or same time?
by on (#153122)
On frame buffer systems, doesn't it generally work like this?

Not enough CPU time: The game runs slower to finish the frame.

Not enough GPU time: The image on the screen is choppier, but the game runs at the same speed.

That makes me wonder, can things like the Sega super scaller boards actually run choppier? I know some of them have a fixed "sprite" limit, but I imagine that if they are all scaled to be fullscreen, the GPU may not be able to keep up. The Sega System 32 supposedly supports unlimited sprites, so I suppose it would have to.

You know, one thing I've always wondered is that if the GPU cannot keep up, could you actually have it to where it just halts the CPU to make it to where it is slower but not choppy? I'd much prefer this.
Re: More sprites on screen: Slower or same time?
by on (#153129)
If a frame buffer based system such as the Lynx or Nintendo 64 can't keep up, it does halt the CPU by not clearing the GPU's busy flag. New display lists can't be sent while the GPU is busy.

What you're talking about might be closer to an "integrated graphics" platform, which shares RAM between the CPU and GPU. This happens in N64, low-end PCs, Xbox, Xbox 360, Xbox One, and PlayStation 4. The CPU can't access RAM while the GPU is busy with it. Likewise, the Atari 7800's MARIA GPU is line buffer based and halts the CPU for part of each scanline to fetch the sprites on that line. More sprites means more cycles.

Slowdown happens on major third- and fourth-generation consoles because the CPU (not the GPU) takes longer to perform for i = 0 to 63 UpdateSpriteAttribute(i) than for i = 0 to 11 UpdateSpriteAttribute(i). Even if your 10 sprites don't move each frame, they might still move in the sprite table, especially on NES and SMS where you have to cycle priorities to transform dropout into flicker. The exception is external frame buffer GPUs on the cartridge (Star Fox; Virtua Racing), which use BLAST PROCESSING (i.e. DMA) to get the fully composited frame buffer to the PPU.
Re: More sprites on screen: Slower or same time?
by on (#153130)
tepples wrote:
If a frame buffer based system such as the Lynx or Nintendo 64 can't keep up, it does halt the CPU by not clearing the GPU's busy flag.

Hm... I thought that the games actually still did run full speed, but that it just doesn't bother rendering the next frame and instead takes the time of both frames to render the one, like the game itself is running at 60fps, but the display is running at 30fps. I guess I'm mistaken.
Re: More sprites on screen: Slower or same time?
by on (#153131)
Let me correct myself: it doesn't actually halt the CPU unless the CPU spins on the GPU's busy flag and doesn't update physics until it's released. Star Fox is known to spin on the busy flag, which is why running it on a GSU in double speed mode or on an emulator that isn't cycle accurate causes the game to run faster.
Re: More sprites on screen: Slower or same time?
by on (#153132)
Alright, so I don't have to worry about having too many sprites on screen. That's a good thing.

thefox wrote:

Thanks. It was very insightful.

It even helped me with a specific problem that I still had: I didn't know that rendering should also only be enabled and disabled in NMI. I enabled it right after the startup code and wondered why my whole scenery is in the middle of the screen for the first frame after a reset and then jumps to the correct position: I enabled rendering right after the initialization code.

Also, the idea that you should leave NMI enabled at every moment was new to me. Otherwise, I would have always enabled and disables $2000 and $2001 together.

And I guess I'll move some other things out of NMI into the game logic. Like switching the nametable variable: The whole if (scrollingPosition == 0) SwitchNametable() was in the NMI until now. But the NMI should only set the nametable bit in $2000 according to that variable, and not caclulate if that variable itself gets set to 0 or to 1.
Re: More sprites on screen: Slower or same time?
by on (#153148)
Espozo wrote:
Not enough CPU time: The game runs slower to finish the frame.

Not enough GPU time: The image on the screen is choppier, but the game runs at the same speed.

That's not the usual approach. It can be done (e.g. see emulators with "frameskip" option), but it's got some drawbacks that make it an unpopular way to do things.

In most games where a slow framerate doesn't appear to affect the actual speed of gameplay it's because the game is using a variable time-step simulation (as oppose to a fixed time-step). This means every frame the game measures how long that frame took. If the frame took longer, objects in the game will move farther during the frame. This generally means that the speed of play looks even even if the framerate gets slow or has momentary hiccups, though it also has the problem that the game gets inaccurate at low framerates.

I don't wanna ramble on all day about why you would do one instead of the other, but variable time-step is the "usual" solution, so far as I've seen. Frameskip style is done sometimes, but it's usually not good to be processing frames continually with the player blind to them; visual feedback is important.

Also, it doesn't have to be dependent on GPU. Either the rendering or gameplay processing can be taking too long (or both), and the variable time-step treats these as the same problem.
Re: More sprites on screen: Slower or same time?
by on (#153150)
Quote:
And I guess I'll move some other things out of NMI into the game logic.

I think that some logic like reading controllers and advancing your sound engine another frame should be kept in NMI to make sure they run once per frame. As long as they come after all the graphic updating that needs to be in a vblank.

DRW wrote:
I didn't know that rendering should also only be enabled and disabled in NMI.

Is this right? I disable rendering outside of NMI when I want to draw an entirely new screen, and nothing jumps around. For Game Boy, Nintendo doesn't allow games that turn off the LCD outside of vblank since doing that apparently damages the LCD, but that doesn't apply for NES.
If the scenery jumps I guess you didn't set scroll at the correct place? Writing to PPU registers sometimes messes with the scroll I think.
Re: More sprites on screen: Slower or same time?
by on (#153152)
Pokun wrote:
DRW wrote:
I didn't know that rendering should also only be enabled and disabled in NMI.

Is this right?


Disabling rendering mid-screen means that you'll get one frame only half displayed. This may or may not be a problem, depending on your point of view.

Enabling rendering mid-screen means that you'll get one frame half displayed, most likely at the wrong scroll position and with corrupt sprites (until the next NMI where you OAM DMA and set the scroll).

There are some other minor potential issues with it, but these are really the most glaring. Recommended practice is to do this in the NMI, and just set a variable in your main thread that "requests" that rendering turn on or off in the next NMI (then waits for the NMI before proceeding).
Re: More sprites on screen: Slower or same time?
by on (#153153)
Pokun wrote:
I think that some logic like reading controllers and advancing your sound engine another frame should be kept in NMI to make sure they run once per frame.

Reading the controllers during lag frames is actually not a good idea. If the NMI hits between two different parts of the game engine that process input, you might end up processing 2 different sets of button states in the same logical frame, which is an inconsistency. It might not be a problem in most cases, but since it has no advantages either, it's better to read the controllers only once for the entire logical frame and keep the input state consistent all the way through it.

If you want, you may update the "low level" controller state every frame, but have part of the game that's susceptible to lag make a copy of the button states at the beginning of the logical frame and use only that until the end of the frame calculations.

Music and sound should indeed be processed steadily at 60Hz, so that there are no hiccups or slowdowns no matter what the game is doing.

Quote:
As long as they come after all the graphic updating that needs to be in a vblank.

Yes, that's a very important detail.

Another important thing to keep in the NMI are raster effects, since these must be performed even in case of slowdown, so that status bars and other kinds of splits don't jump and glitch out.
Re: More sprites on screen: Slower or same time?
by on (#153171)
tokumaru wrote:
Pokun wrote:
I think that some logic like reading controllers and advancing your sound engine another frame should be kept in NMI to make sure they run once per frame.

Reading the controllers during lag frames is actually not a good idea. If the NMI hits between two different parts of the game engine that process input, you might end up processing 2 different sets of button states in the same logical frame, which is an inconsistency. It might not be a problem in most cases, but since it has no advantages either, it's better to read the controllers only once for the entire logical frame and keep the input state consistent all the way through it.

Oh thank you I just moved my jsr read_con out to the main loop and it actually solved a problem I couldn't understand why it happened. I had a title screen state that waits for the start button to be pressed then it starts the game. But my game started anyway without pressing start, and I couldn't figure out why. Probably the NMI didn't happen in time (and thus the controller was never read in time) for the button check, so the buttons variable contained random data.
Re: More sprites on screen: Slower or same time?
by on (#153174)
tokumaru wrote:
Another important thing to keep in the NMI are raster effects, since these must be performed even in case of slowdown, so that status bars and other kinds of splits don't jump and glitch out.

And if the raster effect handling needs any kind of helper variables (e.g. a scanline number, or a pointer to a routine that handles the effect), they should be kept separated from the variables of the main thread and only updated when it's safe to do so (when the new frame is being switched in). Failing to do so could lead to visual anomalies in the best case (using data from two separate frames), and to crashes in the worst case (if operating on pointers).

Pokun wrote:
so the buttons variable contained random data.

I'd move the controller reading back to NMI, initialize the variable, and after making sure that it fixed the problem, think about moving the controller reading outside NMI.

Reading the controllers at 60 Hz has at least one benefit: button transitions (pressed -> not pressed and vice versa) can be detected without lag.
Re: More sprites on screen: Slower or same time?
by on (#153177)
thefox wrote:
And if the raster effect handling needs any kind of helper variables (e.g. a scanline number, or a pointer to a routine that handles the effect), they should be kept separated from the variables of the main thread and only updated when it's safe to do so (when the new frame is being switched in).

Yes, much like PPU updates, changes to these parameters must be buffered, and only overwrite the active parameters in the NMI, when the logical frame is flagged as "complete".

Quote:
Reading the controllers at 60 Hz has at least one benefit: button transitions (pressed -> not pressed and vice versa) can be detected without lag.

That's in fact a reason NOT to do it: if you detect the transition during a lag frame after input has already been processed, nothing will be done about it that frame, and when the next NMI fires, it won't detect a transition, so when the next logical frame starts it will have completely missed it. It's much better to let the logical frame detect the transitions with 1 frame of lag than to completely miss it.
Re: More sprites on screen: Slower or same time?
by on (#153179)
But once you get into Micronics-class lag (12 to 20 fps), it becomes possible to press and release a button between one game logic frame and the next.
Re: More sprites on screen: Slower or same time?
by on (#153180)
So how do you propose we read the controllers without inconsistencies and without dropping any presses?

Updating at 60Hz, saving the states in a stack, and at the start of the logic frame pull all the states from this stack and combine them into one?

I guess that no matter what you do, you can't have a responsive game at such low framerates.
Re: More sprites on screen: Slower or same time?
by on (#153182)
I tried moving it back in the NMI after having initialized all controller variables and indeed it worked (I moved it back in the main loop now though to avoid trouble).
I wonder why these controller variables weren't initialized in my init code where I initialize all RAM though. I usually initialize all variables again before my main loop, but I hadn't done it with the controller variables for some reason.

rainwarrior wrote:
Pokun wrote:
DRW wrote:
I didn't know that rendering should also only be enabled and disabled in NMI.

Is this right?


Disabling rendering mid-screen means that you'll get one frame only half displayed. This may or may not be a problem, depending on your point of view.

Enabling rendering mid-screen means that you'll get one frame half displayed, most likely at the wrong scroll position and with corrupt sprites (until the next NMI where you OAM DMA and set the scroll).

There are some other minor potential issues with it, but these are really the most glaring. Recommended practice is to do this in the NMI, and just set a variable in your main thread that "requests" that rendering turn on or off in the next NMI (then waits for the NMI before proceeding).

I noticed now that I do indeed have graphical glitches when disabling and enabling rendering outside NMI. I made such a flag and enabled/disabled rendering in NMI according to the state of this flag. And indeed no more half frames, thanks!
Re: More sprites on screen: Slower or same time?
by on (#153230)
tokumaru wrote:
Quote:
Reading the controllers at 60 Hz has at least one benefit: button transitions (pressed -> not pressed and vice versa) can be detected without lag.

That's in fact a reason NOT to do it: if you detect the transition during a lag frame after input has already been processed, nothing will be done about it that frame, and when the next NMI fires, it won't detect a transition, so when the next logical frame starts it will have completely missed it. It's much better to let the logical frame detect the transitions with 1 frame of lag than to completely miss it.

Yeah the transition(s) (button press/release event) should be buffered somehow, otherwise it will be missed. A simple way to buffer the input would be for the main logic to tell the NMI routine whether the event has been read. If not, the NMI routine would hold onto it. Don't know if it's worth trying to buffer more than one event (even one might not be worth it if the game doesn't lag much).

In any case it's important to be mindful about where you're reading the controller. I used to have a bug in STREEMERZ where I didn't read the controller during screen transitions (this was natural, because I used to read the controller in the main logic, and there was no natural place in the screen transition code to place the controller reading). Since it decompresses the map to RAM, the transition takes a significant amount of time (at least 100-200ms). In this time frame it was possible to release and press the grapple button, and if the game didn't read the controller during that time, the transition was missed. This was a problem because it's quite common to move upwards in the game by rapidly tapping the grapple button.

I guess I was wrong to say "can be detected without lag" when I really meant that the transition wouldn't be missed regardless of lag.