Experimenting with 32x sprite drawing and scaling techniques
Posted: Fri Jun 25, 2021 7:57 pm
Intro
I'm releasing this before feature creep makes me go crazy.
Following my Afterburner/Space Harrier 32x investigations, (viewtopic.php?f=4&t=3234) I wanted to use the knowledge I gained to start writing my own routines using different techniques and to setup a project to compare them. I've created a benchmark app project for this purpose.
In the benchmark app project you'll find scenes and functions that cover drawing:
And there are versions of drawing functions for:
These are just experiments so these aren't intended to be the be-all and end-all of 32x sprite drawing. I'm sure somebody can write faster versions (in assembly, even!). This project is simply a base to start from.
32X Application
The project has a main menu that lets you choose different scenes. Different scenes test different techniques and video modes. Each scene has an FPS counter to measure performance.
Scene Instructions
In scenes with one sprite
In scenes with multiple sprites
Drawing Techniques Used
I've tried different techniques for drawing sprites, to be able to compare them in terms of performance. They unfortunately don't cover every technique combination. I also limited myself to 32x32 source sprites because the number of combinations of techniques, sprite sizes and different video modes was getting crazy.
Here are the different drawing functions, with descriptions:
The scaling functions take the index of the scaling functions to use as opposed to the destination size. A quality of life improvement would be to add a way to specify the destination size to look up which scale function to use.
About scenes drawing 8bit sprites in words
I've added tests for this because Afterburner 32x proved that you can draw a lot more sprites this way. The boost in performance comes with limitations, however. The SH2 doesn't write words on odd bytes so the destination x position has to be a multiple of 2. Destination sizes can also only be multiples of 2. Of course this can be worked around, but it'll naturally increase the complexity of drawing sprites in this mode.
Also, because scaling is done per word instead of per byte, the resulting scaled sprite looks very "grainy". This is noticeable in Afterburner 32x in the opening spinning ball scene. For sprites that don't have clean lines, like vegetation, it won't be as noticeable.
About scenes drawing 16bit sprites
There's not enough video ram in the frame buffer to support a resolution of 320x224 in 16 bit color mode. It's more like 320x203. Line 204 in the frame buffer is set to black and lines 205 to 224 in the line table point to it.
Drawing words to the frame buffer doesn't use transparency, so transparent areas of 16bit sprites will actually overwrite already-drawn pixels with black. If you write to the overwrite area instead, individual bytes won't be written. Which is fine for 16bit pixels that are 0 but a problem for pixels where either byte is 0. For example, for a color value of 0x0006, only the 06 will be written, leaving the frame buffer unchanged for the pixel intended for 00. This will give odd colors. To avoid this, for any 16 pixels where one of the bytes is 0, add 1 to the byte. So for 0x0006, use 0x0406 instead.
Benchmarks
For scenes with multiple sprites, I've noted the maximum number of sprites can be on screen to acheive 30 fps on actual hardware. It's not exact, but it'll give a general idea. A real scene will have a combination of clipped and non-clipped scaled and non-scaled sprites.
Also note that every frame is cleared using the 32x fill function.
8 bit sprites, byte writes
16 bit sprites, word writes
Code
The project is built against marsdev (https://github.com/andwn/marsdev) but should work with Chilly Willy's 32x SDK and other variants.
Every scene has a Scene struct that contains the description of the scene, an init function and an update function. They're collected in mainScene.c into an array. Simply add a new scene to this array to add it to the main menu. The screen is pretty packed so you might want to rework the way the menu is organized.
There is one file per scene. The file (and Scene struct) is named for the features that it tests.
The SceneManager handles running the scenes.
Commonly used code is in utils. Sprite resources are in sprite_data. Joystick handles input.
Screenshots
Downloads
Benchmark app https://www.dropbox.com/s/5sr5yer545a3t ... p.32x?dl=0
Benchmark app source https://www.dropbox.com/s/c1jc7kz3anp0v ... p.zip?dl=0
I'm releasing this before feature creep makes me go crazy.
Following my Afterburner/Space Harrier 32x investigations, (viewtopic.php?f=4&t=3234) I wanted to use the knowledge I gained to start writing my own routines using different techniques and to setup a project to compare them. I've created a benchmark app project for this purpose.
In the benchmark app project you'll find scenes and functions that cover drawing:
- fixed-sized 32x32 sprites
- clipped fixed-sized 32x32 sprites
- scaled 32x32 sprites
- clipped scaled 32x32 sprites
And there are versions of drawing functions for:
- 8bit sprites written in bytes
- 8bit sprites written in words
- 16 bit sprites written in words
These are just experiments so these aren't intended to be the be-all and end-all of 32x sprite drawing. I'm sure somebody can write faster versions (in assembly, even!). This project is simply a base to start from.
32X Application
The project has a main menu that lets you choose different scenes. Different scenes test different techniques and video modes. Each scene has an FPS counter to measure performance.
Scene Instructions
In scenes with one sprite
- Dpad moves the sprite
- A changes sprite
- B goes back
- C changes from framebuffer to overwrite area
- X/Y changes scale (when supported)
In scenes with multiple sprites
- Left/right on the dpad changes the number of columns
- up/down on the dpad changes the number of rows
- A changes sprite
- B goes back
- C changes from framebuffer to overwrite area
- X/Y changes scale (when supported)
Drawing Techniques Used
I've tried different techniques for drawing sprites, to be able to compare them in terms of performance. They unfortunately don't cover every technique combination. I also limited myself to 32x32 source sprites because the number of combinations of techniques, sprite sizes and different video modes was getting crazy.
Here are the different drawing functions, with descriptions:
- Unscaled, unclipped sprites. A drawing function made for 32x32 sprites
- Unscaled, clipped sprites. A general drawing function made for any sized sprites
- Scaled, unclipped sprites. Uses premade functions that use premade scaling tables. There is one function and one table for every destination size of a sprite. In the examples, functions have been generated for 32x32 sprites going from a width of 1 pixel to 64 pixels. Obviously this technique uses more memory than the others. One technique to try is use a more general drawing function that only uses scale tables.
- Scaled, clipped sprites. A drawing function that uses the scale tables
The scaling functions take the index of the scaling functions to use as opposed to the destination size. A quality of life improvement would be to add a way to specify the destination size to look up which scale function to use.
About scenes drawing 8bit sprites in words
I've added tests for this because Afterburner 32x proved that you can draw a lot more sprites this way. The boost in performance comes with limitations, however. The SH2 doesn't write words on odd bytes so the destination x position has to be a multiple of 2. Destination sizes can also only be multiples of 2. Of course this can be worked around, but it'll naturally increase the complexity of drawing sprites in this mode.
Also, because scaling is done per word instead of per byte, the resulting scaled sprite looks very "grainy". This is noticeable in Afterburner 32x in the opening spinning ball scene. For sprites that don't have clean lines, like vegetation, it won't be as noticeable.
About scenes drawing 16bit sprites
There's not enough video ram in the frame buffer to support a resolution of 320x224 in 16 bit color mode. It's more like 320x203. Line 204 in the frame buffer is set to black and lines 205 to 224 in the line table point to it.
Drawing words to the frame buffer doesn't use transparency, so transparent areas of 16bit sprites will actually overwrite already-drawn pixels with black. If you write to the overwrite area instead, individual bytes won't be written. Which is fine for 16bit pixels that are 0 but a problem for pixels where either byte is 0. For example, for a color value of 0x0006, only the 06 will be written, leaving the frame buffer unchanged for the pixel intended for 00. This will give odd colors. To avoid this, for any 16 pixels where one of the bytes is 0, add 1 to the byte. So for 0x0006, use 0x0406 instead.
Benchmarks
For scenes with multiple sprites, I've noted the maximum number of sprites can be on screen to acheive 30 fps on actual hardware. It's not exact, but it'll give a general idea. A real scene will have a combination of clipped and non-clipped scaled and non-scaled sprites.
Also note that every frame is cleared using the 32x fill function.
8 bit sprites, byte writes
- Multiple 32x32 sprites ~84 sprites
- Multiple 32x32 sprites, with clipping ~48 sprites
- Multiple scaled 32x32 sprites ~84 sprites
- Multiple scaled 32x32 sprites, with clipping ~36 sprites
- Multiple 32x32 sprites ~192 sprites
- Multiple 32x32 sprites, with clipping ~85 sprites
- Multiple scaled 32x32 sprites ~150 sprites
- Multiple scaled 32x32 sprites, with clipping ~60 sprites
16 bit sprites, word writes
- Multiple 32x32 sprites ~72 sprites
- Multiple 32x32 sprites, with clipping ~39 sprites
- Multiple scaled 32x32 sprites ~70 sprites
- Multiple scaled 32x32 sprites, with clipping ~27 sprites
Code
The project is built against marsdev (https://github.com/andwn/marsdev) but should work with Chilly Willy's 32x SDK and other variants.
Every scene has a Scene struct that contains the description of the scene, an init function and an update function. They're collected in mainScene.c into an array. Simply add a new scene to this array to add it to the main menu. The screen is pretty packed so you might want to rework the way the menu is organized.
There is one file per scene. The file (and Scene struct) is named for the features that it tests.
The SceneManager handles running the scenes.
Commonly used code is in utils. Sprite resources are in sprite_data. Joystick handles input.
Screenshots
Downloads
Benchmark app https://www.dropbox.com/s/5sr5yer545a3t ... p.32x?dl=0
Benchmark app source https://www.dropbox.com/s/c1jc7kz3anp0v ... p.zip?dl=0