Performance Profiling on the NES
One of the things that makes game development different from other software development is how critical timing is. To keep animation and motion feeling smooth and continuous, you need to make sure frames are displayed at a high and regular rate. When you can't keep a stable framerate the game begins to feel choppy, and responds unevenly to your input. If your gameplay requires quick and precise responses from the player, this can be a very bad thing. If your target is 60 frames per second, this means you have less than 17 milliseconds to respond to the player, update all the action in the game, and draw the results on the screen for them to see. If your computer's power is excessive compared to your game's computational needs, this may not be much of an issue, but for most game development it is a constant struggle to keep a game running smoothly within its timing budget. This is especially true for a CPU as limited as the one in the NES. You need to keep an watchful eye on the time your code takes to run: an act that is often called performance profiling.
The emulator FCEUX has a very powerful debugger, and you can use it to carefully count CPU clock cycles. It even has a Lua scripting integration that you can use to overlay information on the screen. Emulator debugging tools can give you X-ray vision to see inside a game, and beyond development and romhacking it's also very handy for things like Tool-Assisted Speedrun videos.
What if it was 1988 and you didn't have an emulator, though? Is there some way to get good timing information that will work on the NES itself? Modern console developers can work with devkit versions of the console that come equipped with special hardware to monitor what's going on, but this never existed for the NES. Unlike modern hardware, though, the NES has ways to directly control the continuous stream of video coming out.
The video signal for a frame traces line by line along the television screen, left to right in rows (known as scanlines), top to bottom, like text on a page. The signal for a single frame takes a little over 15 milliseconds to cover the whole screen, and then spends about 1.5 milliseconds returning to the top left for the next frame. This means if we can interrupt the signal at a specific time during a frame, we can see that interruption at a specific location on the screen.
The NES has a way to tint the screen red, green, or blue in a very immediate way. Not many games put this to use, notably Noah's Ark used it to tint the bottom of the screen blue for a rising flood. Because it's instantaneous, it gives us a very good way to mark timing of the code with a visual change to the video signal.
Here's an image of one frame of Lizard with my performance profiling instrumentation turned on. One frame on the NTSC NES essentially lasts for 260 scanlines, the first 20 in vertical blank, and the following 240 drawing the image on the screen.
A frame begins on the NES when the signal has just reached the bottom right of the screen, and it returning to the top left. During this time the PPU (picture processing unit, analogous to the modern GPU or graphics card) sits idle, giving me a brief window of opportunity to upload visual data to it for the next frame. This window of opportunity is brief, so the code here must be very efficient. During this time I set new colour palettes, place sprites, and make any changes to the background that will be shown this frame. If I tried to send graphics data at any other time there will be a conflict with the active PPU causing visual corruption of the screen, so it is imperative that I finish before the end of vertical blank.
When the graphics update is finished, the music and sound code is run. This doesn't have to be done here, specifically, but it is good to run it at the same place each frame so that it plays back evenly.
This green region represents the time spent responding to player input, moving and updating the lizard character. Controlling and animating the lizard is significantly more complex than most other things in the game, so it's allowed a large part of the timing budget just for itself.
In the source code for this game, I call all the interactive things "dogs". Every engine has a different word for this (e.g. object, entity, actor, pawn, mob), but in Lizard everything is a dog. This red band represents all the time spent updating these dogs during the frame. In the depicted frame, there are two jellyfish, a snail, and a coin.
A typical update for a jellyfish, for example, would be something like this:
- Test to see if it's touching the player (if yes, turns you to bones).
- Update a timer to see if it's ready to puff yet. If it is, change the velocity and animate the puff.
- Adjust velocity for gravity and drag.
- Move the jellyfish, and resolve any collision with the world.
This blue band represents the time building sprite data into a buffer that will be sent during the next vertical blank. Every sprite is a collection pieced together from 8x8 pixel tiles, each of 3 colours. The lizard character, for example, is usually 5 tiles: 4 for the body, and 1 for the head and eye (which use a different colour set than the body).
Importantly, I also draw the dogs in a pseudo-random order. This is done because the NES has a strict limit on sprites for each scanline; if there are more than 8 sprite tiles on a single line, any after the first 8 will disappear. By drawing them in a random order, conflicting sprites will flicker in and out, as an alternative to having some drop out completely. The sprite flickering commonly seen in NES games is a deliberately programmed effect to make sure enemies don't become completely invisible when the limit is reached.
The more of this there is, the better. In this particular frame, we've still got about half a frame worth of time to spend. When I'm laying out a room in the game, I can add more dogs as long as there is still free time left over. If the updates take too much time, they spill into the next frame and the game will slow down to half speed. I do performance profiling to make sure this never happens.
So, that's the anatomy of a frame of Lizard, and some rough insight as to what the NES' computer is doing with its time. If you have technical questions about NES or game development, or there's something you'd like to hear more about, feel free to ask. I will try to make updates on topics that people are interested in.
Thanks again to everyone who has supported me. I am very happy to be able to make this game for you.