diff options
Diffstat (limited to 'examples/beginner/hello/main.c')
| -rw-r--r-- | examples/beginner/hello/main.c | 251 |
1 files changed, 153 insertions, 98 deletions
diff --git a/examples/beginner/hello/main.c b/examples/beginner/hello/main.c index 1f02f0b..7f738d3 100644 --- a/examples/beginner/hello/main.c +++ b/examples/beginner/hello/main.c @@ -1,118 +1,173 @@ -/* - * LibPSn00b Example Programs +/* + * PSn00bSDK basic graphics example + * (C) 2020-2023 Lameguy64, spicyjpeg - MPL licensed * - * Hello World Example - * 2019-2020 Meido-Tek Productions / PSn00bSDK Project + * A comprehensive "advanced hello world" example showing how to set up the + * screen with double buffering, draw basic graphics (a bouncing square) and use + * PSn00bSDK's debug font API to quickly print some text, all while following + * best practices. This is not necessarily the simplest hello world example and + * may look daunting at first glance, but it is a good starting point for more + * complex programs. * - * The obligatory hello world example normally included in nearly every - * SDK package. This example should also get you started in how to manage - * the display using psxgpu. + * In order to avoid cluttering the program with global variables (as many Sony + * SDK examples and other PSn00bSDK examples written before this one do) two + * custom structures are employed: * - * Example by Lameguy64 + * - a RenderBuffer structure containing the DISPENV and DRAWENV objects that + * represent the location of the framebuffer in VRAM, as well as the ordering + * table (OT) used to sort GPU commands/primitives by their Z index and the + * actual buffer commands will be written to; + * - a RenderContext structure holding two RenderBuffer instances plus some + * variables to keep track of which buffer is currently being drawn and how + * much of its primitive buffer has been filled up so far. * - * - * Changelog: - * - * January 1, 2020 - Initial version + * A C++ version of this example is also available (see examples/hellocpp). */ - -#include <stdio.h> -#include <sys/types.h> -#include <psxetc.h> -#include <psxgte.h> + +#include <assert.h> +#include <stddef.h> +#include <stdint.h> #include <psxgpu.h> +// Length of the ordering table, i.e. the range Z coordinates can have, 0-15 in +// this case. Larger values will allow for more granularity with depth (useful +// when drawing a complex 3D scene) at the expense of RAM usage and performance. +#define OT_LENGTH 16 -// Define display/draw environments for double buffering -DISPENV disp[2]; -DRAWENV draw[2]; -int db; +// Size of the buffer GPU commands and primitives are written to. If the program +// crashes due to too many primitives being drawn, increase this value. +#define BUFFER_LENGTH 8192 +/* Framebuffer/display list class */ -// Init function -void init(void) -{ - // This not only resets the GPU but it also installs the library's - // ISR subsystem to the kernel - ResetGraph(0); - - // Define display environments, first on top and second on bottom - SetDefDispEnv(&disp[0], 0, 0, 320, 240); - SetDefDispEnv(&disp[1], 0, 240, 320, 240); - - // Define drawing environments, first on bottom and second on top - SetDefDrawEnv(&draw[0], 0, 240, 320, 240); - SetDefDrawEnv(&draw[1], 0, 0, 320, 240); - - // Set and enable clear color - setRGB0(&draw[0], 0, 96, 0); - setRGB0(&draw[1], 0, 96, 0); - draw[0].isbg = 1; - draw[1].isbg = 1; - - // Clear double buffer counter - db = 0; - - // Apply the GPU environments - PutDispEnv(&disp[db]); - PutDrawEnv(&draw[db]); - - // Load test font - FntLoad(960, 0); - - // Open up a test font text stream of 100 characters - FntOpen(0, 8, 320, 224, 0, 100); +typedef struct { + DISPENV disp_env; + DRAWENV draw_env; + + uint32_t ot[OT_LENGTH]; + uint8_t buffer[BUFFER_LENGTH]; +} RenderBuffer; + +typedef struct { + RenderBuffer buffers[2]; + uint8_t *next_packet; + int active_buffer; +} RenderContext; + +void setup_context(RenderContext *ctx, int w, int h, int r, int g, int b) { + // Place the two framebuffers vertically in VRAM. + SetDefDrawEnv(&(ctx->buffers[0].draw_env), 0, 0, w, h); + SetDefDispEnv(&(ctx->buffers[0].disp_env), 0, 0, w, h); + SetDefDrawEnv(&(ctx->buffers[1].draw_env), 0, h, w, h); + SetDefDispEnv(&(ctx->buffers[1].disp_env), 0, h, w, h); + + // Set the default background color and enable auto-clearing. + setRGB0(&(ctx->buffers[0].draw_env), r, g, b); + setRGB0(&(ctx->buffers[1].draw_env), r, g, b); + ctx->buffers[0].draw_env.isbg = 1; + ctx->buffers[1].draw_env.isbg = 1; + + // Initialize the first buffer and clear its OT so that it can be used for + // drawing. + ctx->active_buffer = 0; + ctx->next_packet = ctx->buffers[0].buffer; + ClearOTagR(ctx->buffers[0].ot, OT_LENGTH); + + // Turn on the video output. + SetDispMask(1); } -// Display function -void display(void) -{ - // Flip buffer index - db = !db; - - // Wait for all drawing to complete +void flip_buffers(RenderContext *ctx) { + // Wait for the GPU to finish drawing, then wait for vblank in order to + // prevent screen tearing. DrawSync(0); - - // Wait for vertical sync to cap the logic to 60fps (or 50 in PAL mode) - // and prevent screen tearing VSync(0); - // Switch pages - PutDispEnv(&disp[db]); - PutDrawEnv(&draw[db]); - - // Enable display output, ResetGraph() disables it by default - SetDispMask(1); - + RenderBuffer *draw_buffer = &(ctx->buffers[ctx->active_buffer]); + RenderBuffer *disp_buffer = &(ctx->buffers[ctx->active_buffer ^ 1]); + + // Display the framebuffer the GPU has just finished drawing and start + // rendering the display list that was filled up in the main loop. + PutDispEnv(&(disp_buffer->disp_env)); + DrawOTagEnv(&(draw_buffer->ot[OT_LENGTH - 1]), &(draw_buffer->draw_env)); + + // Switch over to the next buffer, clear it and reset the packet allocation + // pointer. + ctx->active_buffer ^= 1; + ctx->next_packet = disp_buffer->buffer; + ClearOTagR(disp_buffer->ot, OT_LENGTH); } -// Main function, program entrypoint -int main(int argc, const char *argv[]) -{ - int counter; - - // Init stuff - init(); - - // Main loop - counter = 0; - while(1) - { - - // Print the obligatory hello world and counter to show that the - // program isn't locking up to the last created text stream - FntPrint(-1, "HELLO WORLD\n"); - FntPrint(-1, "COUNTER=%d\n", counter); - - // Draw the last created text stream - FntFlush(-1); - - // Update display - display(); - - // Increment the counter - counter++; +void *new_primitive(RenderContext *ctx, int z, size_t size) { + // Place the primitive after all previously allocated primitives, then + // insert it into the OT and bump the allocation pointer. + RenderBuffer *buffer = &(ctx->buffers[ctx->active_buffer]); + uint8_t *prim = ctx->next_packet; + + addPrim(&(buffer->ot[z]), prim); + ctx->next_packet += size; + + // Make sure we haven't yet run out of space for future primitives. + assert(ctx->next_packet <= &(buffer->buffer[BUFFER_LENGTH])); + + return (void *) prim; +} + +// A simple helper for drawing text using PSn00bSDK's debug font API. Note that +// FntSort() requires the debug font texture to be uploaded to VRAM beforehand +// by calling FntLoad(). +void draw_text(RenderContext *ctx, int x, int y, int z, const char *text) { + RenderBuffer *buffer = &(ctx->buffers[ctx->active_buffer]); + + ctx->next_packet = (uint8_t *) + FntSort(&(buffer->ot[z]), ctx->next_packet, x, y, text); + + assert(ctx->next_packet <= &(buffer->buffer[BUFFER_LENGTH])); +} + +/* Main */ + +#define SCREEN_XRES 320 +#define SCREEN_YRES 240 + +int main(int argc, const char **argv) { + // Initialize the GPU and load the default font texture provided by + // PSn00bSDK at (960, 0) in VRAM. + ResetGraph(0); + FntLoad(960, 0); + + // Set up our rendering context. + RenderContext ctx; + setup_context(&ctx, SCREEN_XRES, SCREEN_YRES, 63, 0, 127); + + int x = 0, y = 0; + int dx = 1, dy = 1; + + for (;;) { + // Update the position and velocity of the bouncing square. + if (x < 0 || x > (SCREEN_XRES - 64)) + dx = -dx; + if (y < 0 || y > (SCREEN_YRES - 64)) + dy = -dy; + + x += dx; + y += dy; + + // Draw the square by allocating a TILE (i.e. untextured solid color + // rectangle) primitive at Z = 1. + TILE *tile = (TILE *) new_primitive(&ctx, 1, sizeof(TILE)); + + setTile(tile); + setXY0 (tile, x, y); + setWH (tile, 64, 64); + setRGB0(tile, 255, 255, 0); + + // Draw some text in front of the square (Z = 0, primitives with higher + // Z indices are drawn first). + draw_text(&ctx, 8, 16, 0, "Hello world!"); + + flip_buffers(&ctx); } - + return 0; } |
