Examples
Examples can be found within the example subdirectory of the SH4ZAM repo, and new examples are always welcome to be contributed. If you installed SH4ZAM through kos-ports, the examples are installed to kos-ports/examples/sh4zam; type make in an example's folder to build it.
| Example | Description |
|---|---|
| Bruce's Balls | Pushes up to 4.5 million PPS, rendering Bruce's balls. |
| PVR DMA | Real-world, complex renderer using KOS's PVR DMA API. |
Bruce's Balls
Section titled “Bruce's Balls”Source: example/bruces_balls/bruces_balls.c (C23).
The program renders a series of high-polygon balls (93 by default, 20 stacks by 20 slices each) and reports back the rendering performance in polygons per second. It uses KOS's "Direct Rendering" PVR API, which submits geometry to the PowerVR GPU by filling and flushing the two store queues to the TA in alternation. This is the fastest rendering path, and with the right build flags it pushes 4.5 million polygons per second.
Cache-line-sized data
Section titled “Cache-line-sized data”The secret to high performance is keeping your data cache-friendly. Each ball is exactly one 32-byte cache line:
typedef struct { alignas(32) // Align to cache-line sizes shz_vec3_t pos; //!< 3D Position of a ball. shz_vec2_t rot; //!< X/Y orientation of a ball. shz_vec2_t vel; //!< X/Y velocity of a ball. uint32_t color; //!< ARGB8888 color of a ball.} Ball;
static_assert(sizeof(Ball) == 32); // Ensure we are exactly the size of a cache-line.static_assert(alignof(Ball) == 32); // Ensure we are exactly aligned to start on cache-line boundaries.Because a Ball fills a whole line, initialization calls shz_dcache_alloc_line(&balls[idx]) first: it allocates the cache line without fetching the old contents from RAM. You can only do this when your data fits perfectly into cache lines, or you will corrupt whatever else shares the line.
Projection matrix, built once
Section titled “Projection matrix, built once”static void setup_projection_view(shz_mat4x4_t* mat) { constexpr float screen_width = 640.0f; constexpr float screen_height = 480.0f; constexpr float near_z = 0.0f; constexpr float fov = SHZ_DEG_TO_RAD(60.0f); constexpr float aspect = screen_width / screen_height;
shz_dcache_alloc_line(mat); shz_xmtrx_init_permutation_wxyz(); shz_xmtrx_apply_screen(screen_width, screen_height); shz_dcache_alloc_line(((void*)mat) + 32); shz_xmtrx_apply_perspective(fov, aspect, near_z); shz_xmtrx_store_4x4(mat);}XMTRX starts as a permutation matrix, so every vector transformed by it comes out with W first, which is what perspective division needs immediately. A screen-space transform and the perspective projection are applied on top, and the result is stored once. (KOS's mat_perspective() does the screen mapping for you; SH4ZAM does it explicitly for fine-grained control.)
Per ball: load, translate, apply
Section titled “Per ball: load, translate, apply”static void apply_model_matrix(shz_vec3_t pos, shz_vec2_t rot, const shz_mat4x4_t* proj_view) { shz_xmtrx_load_4x4(proj_view); shz_xmtrx_translate(pos.x, pos.y, -pos.z); shz_xmtrx_apply_rotation_x(rot.x); shz_xmtrx_apply_rotation_y(rot.y);}Per vertex: sincos, FTRV, FSRRA, store queues
Section titled “Per vertex: sincos, FTRV, FSRRA, store queues”shz_sincos_t sc_slice = shz_sincosf(sliceAngle);shz_vec3_t local_pos1 = shz_vec3_scale(shz_vec3_init(sc_slice.cos * r2, sc_slice.sin * r2, z2), radius);
shz_vec4_t trans_pos1 = shz_xmtrx_transform_vec4(shz_vec3_vec4(local_pos1, 1.0f));// Undo the WXYZ permutation baked into the projection matrix.trans_pos1 = shz_vec4_swizzle(trans_pos1, 1, 2, 3, 0);
pvr_vertex_t* vert1 = pvr_dr_target(*dr_state);SHZ_MEMORY_BARRIER_SOFT();
trans_pos1.w = shz_invf_fsrra(trans_pos1.w);vert1->x = trans_pos1.x * trans_pos1.w;vert1->y = trans_pos1.y * trans_pos1.w;vert1->z = trans_pos1.w;pvr_dr_commit(vert1);shz_sincosf()gets sine and cosine from oneFSCA.shz_xmtrx_transform_vec4()is oneFTRVagainst the matrix held in XMTRX.SHZ_MEMORY_BARRIER_SOFT()stops GCC from reordering memory accesses so it doesn't read the transformed position before it's ready.shz_invf_fsrra()is the fast inverse. Upstream's comment is worth repeating: this inversion trick only returns the ABSOLUTE value, so it has the wrong sign when inverting negatives. It is fine here because Z is positive.- Polygon headers go out with
SHZ_PREFETCH()followed byshz_sq_memcpy32_1(), a single 32-byte copy through the store queues.
Build flags
Section titled “Build flags”CFLAGS += -std=gnu2x -O3 \ -fomit-frame-pointer -flto -fbuiltin -ffast-math -ffp-contract=fast -mfsrra -mfsca \ -fmerge-all-constants -funroll-loops -fno-PIC -fipa-pta \ -ftree-vectorize -I$(KOS_BASE)/utilsIt links with -lsh4zam -lkosutils -lm -Wl,--gc-sections.
PVR DMA
Section titled “PVR DMA”Source: example/pvr_dma/, by jnmartin64.
A real-world renderer that loads an OBJ model with textures from a romdisk and draws it through KOS's PVR DMA path:
- Uses KOS's
pvr_vertbuf_tail()/pvr_vertbuf_written()API for DMA scene submission. - Uses SH4ZAM to perform all matrix math and transforms.
- Provides a real-world tested implementation of near-Z clipping.
- Shows off PVR hardware fog.
To build: compile and install KOS, install sh4zam, zlib, libjpeg, libpng and libkmg from kos-ports, then run make.
Controls: analog stick moves and turns, d-pad up/down moves vertically, d-pad left/right and the L/R triggers adjust the fog far and near planes, A toggles texturing off for debug vertex coloring, Y resets the scene, Start exits.
More references
Section titled “More references”The projects on the Showcase page have been accelerated with SH4ZAM and make great references, especially SH4ZAM PVR, a set of advanced PVR Direct Rendering examples.