aboutsummaryrefslogtreecommitdiff
path: root/examples/beginner/hello/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/beginner/hello/main.c')
-rw-r--r--examples/beginner/hello/main.c251
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;
}