aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/README.md6
-rw-r--r--examples/beginner/cppdemo/CMakeLists.txt18
-rw-r--r--examples/beginner/cppdemo/main.cpp151
-rw-r--r--examples/beginner/hello/main.c251
-rw-r--r--examples/beginner/hellocpp/CMakeLists.txt18
-rw-r--r--examples/beginner/hellocpp/main.cpp232
-rw-r--r--examples/cdrom/cdxa/main.c242
-rw-r--r--template/main.c251
8 files changed, 676 insertions, 493 deletions
diff --git a/examples/README.md b/examples/README.md
index ae601f1..5bdd4f1 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -8,8 +8,8 @@ Additional information may be found in the source code of each example.
| Path | Description | Type | Notes |
| :--------------------------------------------- | :---------------------------------------------------- | :--: | :---: |
-| [`beginner/cppdemo`](./beginner/cppdemo) | Simple demonstration of (dynamic) C++ classes | EXE | |
-| [`beginner/hello`](./beginner/hello) | The obligatory "Hello World" example program | EXE | |
+| [`beginner/hello`](./beginner/hello) | A simple "hello world" example/project template | EXE | |
+| [`beginner/hellocpp`](./beginner/hellocpp) | C++ version of the example above | EXE | |
| [`cdrom/cdbrowse`](./cdrom/cdbrowse) | File browser using libpsxcd's directory functions | CD | |
| [`cdrom/cdxa`](./cdrom/cdxa) | CD-XA ADPCM audio player | CD | 1 |
| [`demos/n00bdemo`](./demos/n00bdemo) | The premiere demonstration program of PSn00bSDK | EXE | 2 |
@@ -86,4 +86,4 @@ are for rebuilding the examples *after* the SDK has been installed.
CD images for each example.
-----------------------------------------
-_Last updated on 2022-10-27 by spicyjpeg_
+_Last updated on 2023-05-21 by spicyjpeg_
diff --git a/examples/beginner/cppdemo/CMakeLists.txt b/examples/beginner/cppdemo/CMakeLists.txt
deleted file mode 100644
index 11a35ed..0000000
--- a/examples/beginner/cppdemo/CMakeLists.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-# PSn00bSDK example CMake script
-# (C) 2021 spicyjpeg - MPL licensed
-
-cmake_minimum_required(VERSION 3.21)
-
-project(
- cppdemo
- LANGUAGES CXX
- VERSION 1.0.0
- DESCRIPTION "PSn00bSDK basic C++ example"
- HOMEPAGE_URL "http://lameguy64.net/?page=psn00bsdk"
-)
-
-file(GLOB _sources *.cpp)
-psn00bsdk_add_executable(cppdemo GPREL ${_sources})
-#psn00bsdk_add_cd_image(cppdemo_iso cppdemo iso.xml DEPENDS cppdemo)
-
-install(FILES ${PROJECT_BINARY_DIR}/cppdemo.exe TYPE BIN)
diff --git a/examples/beginner/cppdemo/main.cpp b/examples/beginner/cppdemo/main.cpp
deleted file mode 100644
index f55f952..0000000
--- a/examples/beginner/cppdemo/main.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/* Work in progress example, need to add comments.
- *
- * Basically a quick little example that showcases C++ classes are
- * functioning in PSn00bSDK. - Lameguy64
- *
- * First written in December 18, 2020.
- *
- * Changelog:
- *
- * May 11, 2023 - Updated the example to use C++ standard library headers,
- * renamed the class and cleaned up some methods.
- * May 10, 2021 - Variable types updated for psxgpu.h changes.
- *
- */
-
-#include <cstddef>
-#include <cstdint>
-#include <cstdio>
-#include <cstdlib>
-#include <psxgte.h>
-#include <psxgpu.h>
-
-/* Example class */
-
-class RenderContext {
-private:
- std::uint32_t *_ot[2];
- std::uint8_t *_pri[2];
- std::uint8_t *_nextpri;
-
- int _ot_count, _db;
-
- DISPENV _disp[2];
- DRAWENV _draw[2];
-
-public:
- RenderContext(std::size_t ot_len = 8, std::size_t pri_len = 8192);
- ~RenderContext(void);
- void setupBuffers(int w, int h, int r, int g, int b);
- void flip(void);
-
- template<typename T> inline T *addPrimitive(void) {
- auto pri = reinterpret_cast<T *>(_nextpri);
- addPrim(_ot[_db], pri);
-
- _nextpri += sizeof(T);
- return pri;
- }
-};
-
-RenderContext::RenderContext(std::size_t ot_len, std::size_t pri_len) {
- _ot[0] = new std::uint32_t[ot_len];
- _ot[1] = new std::uint32_t[ot_len];
-
- _db = 0;
- _ot_count = ot_len;
- ClearOTagR(_ot[0], _ot_count);
- ClearOTagR(_ot[1], _ot_count);
-
- _pri[0] = new std::uint8_t[pri_len];
- _pri[1] = new std::uint8_t[pri_len];
-
- _nextpri = _pri[0];
-
- std::printf("RenderContext::RenderContext: Buffers allocated.\n");
-}
-
-RenderContext::~RenderContext(void) {
- delete[] _ot[0];
- delete[] _ot[1];
-
- delete[] _pri[0];
- delete[] _pri[1];
-
- std::printf("RenderContext::RenderContext: Buffers freed.\n");
-}
-
-void RenderContext::setupBuffers(int w, int h, int r, int g, int b) {
- SetDefDispEnv(&_disp[0], 0, h, w, h);
- SetDefDispEnv(&_disp[1], 0, 0, w, h);
- SetDefDrawEnv(&_draw[0], 0, 0, w, h);
- SetDefDrawEnv(&_draw[1], 0, h, w, h);
-
- setRGB0(&_draw[0], r, g, b);
- _draw[0].isbg = 1;
- _draw[0].dtd = 1;
-
- setRGB0(&_draw[1], r, g, b);
- _draw[1].isbg = 1;
- _draw[1].dtd = 1;
-
- PutDispEnv(&_disp[0]);
- PutDrawEnv(&_draw[0]);
-}
-
-void RenderContext::flip(void) {
- DrawSync(0);
- VSync(0);
-
- _db ^= 1;
-
- PutDispEnv(&_disp[_db]);
- PutDrawEnv(&_draw[_db]);
-
- DrawOTag(_ot[_db ^ 1] + _ot_count - 1);
- ClearOTagR(_ot[_db], _ot_count);
-
- _nextpri = _pri[_db];
-}
-
-/* Main */
-
-static constexpr int SCREEN_XRES = 320;
-static constexpr int SCREEN_YRES = 240;
-
-static constexpr int BGCOLOR_R = 63;
-static constexpr int BGCOLOR_G = 0;
-static constexpr int BGCOLOR_B = 127;
-
-int main(int argc, const char **argv) {
- ResetGraph(0);
- SetDispMask(1);
-
- RenderContext ctx;
- ctx.setupBuffers(SCREEN_XRES, SCREEN_YRES, BGCOLOR_R, BGCOLOR_G, BGCOLOR_B);
-
- 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.
- auto tile = ctx.addPrimitive<TILE>();
- setTile(tile);
- setXY0 (tile, x, y);
- setWH (tile, 64, 64);
- setRGB0(tile, 255, 255, 0);
-
- ctx.flip();
- }
-
- return 0;
-}
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;
}
diff --git a/examples/beginner/hellocpp/CMakeLists.txt b/examples/beginner/hellocpp/CMakeLists.txt
new file mode 100644
index 0000000..85f0fde
--- /dev/null
+++ b/examples/beginner/hellocpp/CMakeLists.txt
@@ -0,0 +1,18 @@
+# PSn00bSDK example CMake script
+# (C) 2021 spicyjpeg - MPL licensed
+
+cmake_minimum_required(VERSION 3.21)
+
+project(
+ hellocpp
+ LANGUAGES CXX
+ VERSION 1.0.0
+ DESCRIPTION "PSn00bSDK C++ hello world example"
+ HOMEPAGE_URL "http://lameguy64.net/?page=psn00bsdk"
+)
+
+file(GLOB _sources *.cpp)
+psn00bsdk_add_executable(hellocpp GPREL ${_sources})
+#psn00bsdk_add_cd_image(hellocpp_iso hellocpp iso.xml DEPENDS hellocpp)
+
+install(FILES ${PROJECT_BINARY_DIR}/hellocpp.exe TYPE BIN)
diff --git a/examples/beginner/hellocpp/main.cpp b/examples/beginner/hellocpp/main.cpp
new file mode 100644
index 0000000..20923be
--- /dev/null
+++ b/examples/beginner/hellocpp/main.cpp
@@ -0,0 +1,232 @@
+/*
+ * PSn00bSDK C++ basic graphics example
+ * (C) 2020-2023 Lameguy64, spicyjpeg - MPL licensed
+ *
+ * A C++ variant of the beginner/hello example showcasing the use of classes and
+ * templates in place of structures, making the code more readable and less
+ * error-prone. The OT and primitive buffer are now allocated on the heap and
+ * automatically freed when the RenderContext class is destroyed or goes out of
+ * scope.
+ *
+ * See the original example for more details.
+ */
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <psxgpu.h>
+
+static constexpr size_t DEFAULT_OT_LENGTH = 15;
+static constexpr size_t DEFAULT_BUFFER_LENGTH = 8192;
+
+/* RenderBuffer class */
+
+class RenderBuffer {
+private:
+ DISPENV _disp_env;
+ DRAWENV _draw_env;
+
+ std::uint32_t *_ot;
+ std::uint8_t *_buffer;
+ std::size_t _ot_length, _buffer_length;
+
+public:
+ RenderBuffer(std::size_t ot_length, std::size_t buffer_length);
+ ~RenderBuffer(void);
+ void setup(int x, int y, int w, int h, int r, int g, int b);
+
+ inline uint8_t *buffer_start(void) const {
+ return _buffer;
+ }
+ inline uint8_t *buffer_end(void) const {
+ return &_buffer[_buffer_length];
+ }
+ inline uint32_t *ot_entry(int z) const {
+ //assert((z >= 0) && (z < _ot_length));
+ return &_ot[z];
+ }
+
+ inline void clear_ot(void) {
+ ClearOTagR(_ot, _ot_length);
+ }
+ inline void draw(void) {
+ DrawOTagEnv(&_ot[_ot_length - 1], &_draw_env);
+ }
+ inline void display(void) const {
+ PutDispEnv(&_disp_env);
+ }
+};
+
+RenderBuffer::RenderBuffer(std::size_t ot_length, std::size_t buffer_length)
+: _ot_length(ot_length), _buffer_length(buffer_length) {
+ // Initializing the OT in a constructor is unsafe, since ClearOTagR()
+ // requires DMA to be enabled and may fail if called before ResetGraph() or
+ // ResetCallback() (which can easily happen as constructors can run before
+ // main()). Thus, this constructor is only going to allocate the buffers and
+ // clearing is deferred to RenderContext::setup().
+ _ot = new uint32_t[ot_length];
+ _buffer = new uint8_t[buffer_length];
+
+ assert(_ot && _buffer);
+
+ //std::printf("Allocated buffer, ot=0x%08x, buffer=0x%08x\n", ot, buffer);
+}
+
+RenderBuffer::~RenderBuffer(void) {
+ delete[] _ot;
+ delete[] _buffer;
+
+ //std::printf("Freed buffer, ot=0x%08x, buffer=0x%08x\n", ot, buffer);
+}
+
+void RenderBuffer::setup(int x, int y, int w, int h, int r, int g, int b) {
+ // Set the framebuffer's VRAM coordinates.
+ SetDefDrawEnv(&_draw_env, x, y, w, h);
+ SetDefDispEnv(&_disp_env, x, y, w, h);
+
+ // Set the default background color and enable auto-clearing.
+ setRGB0(&_draw_env, r, g, b);
+ _draw_env.isbg = 1;
+}
+
+/* RenderContext class */
+
+class RenderContext {
+private:
+ RenderBuffer _buffers[2];
+ std::uint8_t *_next_packet;
+ int _active_buffer;
+
+ // These functions are simply shorthands for _buffers[_active_buffer] and
+ // _buffers[_active_buffer ^ 1] respectively. They are only used internally.
+ inline RenderBuffer &_draw_buffer(void) {
+ return _buffers[_active_buffer];
+ }
+ inline RenderBuffer &_disp_buffer(void) {
+ return _buffers[_active_buffer ^ 1];
+ }
+
+public:
+ RenderContext(
+ std::size_t ot_length = DEFAULT_OT_LENGTH,
+ std::size_t buffer_length = DEFAULT_BUFFER_LENGTH
+ );
+ void setup(int w, int h, int r, int g, int b);
+ void flip(void);
+
+ // This is a "factory function" that allocates a new primitive within the
+ // currently active buffer. It is a template method, meaning T will get
+ // replaced at compile time by the type of the primitive we are going to
+ // allocate (and sizeof(T) will change accordingly!).
+ template<typename T> inline T *new_primitive(int z = 0) {
+ // Place the primitive after all previously allocated primitives, then
+ // insert it into the OT and bump the allocation pointer.
+ auto prim = reinterpret_cast<T *>(_next_packet);
+
+ addPrim(_draw_buffer().ot_entry(z), prim);
+ _next_packet += sizeof(T);
+
+ // Make sure we haven't yet run out of space for future primitives.
+ assert(_next_packet <= _draw_buffer().buffer_end());
+
+ return 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().
+ inline void draw_text(int x, int y, int z, const char *text) {
+ _next_packet = reinterpret_cast<uint8_t *>(
+ FntSort(_draw_buffer().ot_entry(z), _next_packet, x, y, text)
+ );
+
+ assert(_next_packet <= _draw_buffer().buffer_end());
+ }
+};
+
+RenderContext::RenderContext(std::size_t ot_length, std::size_t buffer_length)
+: _buffers{
+ RenderBuffer(ot_length, buffer_length),
+ RenderBuffer(ot_length, buffer_length)
+} {}
+
+void RenderContext::setup(int w, int h, int r, int g, int b) {
+ // Place the two framebuffers vertically in VRAM.
+ _buffers[0].setup(0, 0, w, h, r, g, b);
+ _buffers[1].setup(0, h, w, h, r, g, b);
+
+ // Initialize the first buffer and clear its OT so that it can be used for
+ // drawing.
+ _active_buffer = 0;
+ _next_packet = _draw_buffer().buffer_start();
+ _draw_buffer().clear_ot();
+
+ // Turn on the video output.
+ SetDispMask(1);
+}
+
+void RenderContext::flip(void) {
+ // Wait for the GPU to finish drawing, then wait for vblank in order to
+ // prevent screen tearing.
+ DrawSync(0);
+ VSync(0);
+
+ // Display the framebuffer the GPU has just finished drawing and start
+ // rendering the display list that was filled up in the main loop.
+ _disp_buffer().display();
+ _draw_buffer().draw();
+
+ // Switch over to the next buffer, clear it and reset the packet allocation
+ // pointer.
+ _active_buffer ^= 1;
+ _next_packet = _draw_buffer().buffer_start();
+ _draw_buffer().clear_ot();
+}
+
+/* Main */
+
+static constexpr int SCREEN_XRES = 320;
+static constexpr int 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;
+ ctx.setup(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.
+ auto tile = ctx.new_primitive<TILE>(1);
+
+ 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).
+ ctx.draw_text(8, 16, 0, "Hello from C++!");
+
+ ctx.flip();
+ }
+
+ return 0;
+}
diff --git a/examples/cdrom/cdxa/main.c b/examples/cdrom/cdxa/main.c
index 94e6d45..253f540 100644
--- a/examples/cdrom/cdxa/main.c
+++ b/examples/cdrom/cdxa/main.c
@@ -1,4 +1,4 @@
-/*
+/*
* LibPSn00b Example Programs
*
* CD-XA Audio Example
@@ -50,12 +50,6 @@
* header of the received sector if it belongs to the channel currently
* being played.
*
- * Additionally, XA audio data must be encoded with video sectors at
- * the end of each XA stream to serve as a terminator marker which
- * triggers CdReadyCallback() during playback. From within the callback
- * a CdlReadS command with the location of the XA data can be issued again
- * to repeat the track or CdlPause to stop playback.
- *
*
* Tips:
*
@@ -106,7 +100,7 @@
*
*
* Example by Lameguy64
- *
+ * (TODO: clean up/rewrite this example)
*
* Changelog:
*
@@ -115,7 +109,7 @@
* November 22, 2019 - Initial version
*
*/
-
+
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -171,94 +165,92 @@ TIM_IMAGE tim;
/* XA audio handling stuff */
volatile int num_loops=0; /* Loop counter */
volatile int xa_play_channel; /* Currently playing channel number */
-CdlLOC xa_loc; /* XA data start location
+CdlLOC xa_loc; /* XA data start location */
+/* Pad input buffer*/
+char padbuff[2][34];
-/* Sector header structure for video sector terminator */
-typedef struct SECTOR_HEAD
-{
- uint16_t id;
- uint16_t chan;
- uint8_t pad[28];
-} SECTOR_HEAD;
+// https://problemkaputt.de/psx-spx.htm#cdromxasubheaderfilechannelinterleave
+typedef struct {
+ uint8_t file, channel;
+ uint8_t submode, coding_info;
+} XA_Header;
-/* Pad input buffer*/
-char padbuff[2][34];
+typedef enum {
+ XA_END_OF_RECORD = 1 << 0,
+ XA_TYPE_VIDEO = 1 << 1,
+ XA_TYPE_AUDIO = 1 << 2,
+ XA_TYPE_DATA = 1 << 3,
+ XA_TRIGGER = 1 << 4,
+ XA_FORM2 = 1 << 5,
+ XA_REAL_TIME = 1 << 6,
+ XA_END_OF_FILE = 1 << 7
+} XA_SubmodeFlag;
-char xa_sector_buff[2048];
+// https://problemkaputt.de/psx-spx.htm#cdromsectorencoding
+typedef struct {
+ CdlLOC pos;
+ XA_Header xa_header[2];
+ uint8_t data[2048];
+ uint32_t edc;
+ uint8_t ecc[276];
+} Sector;
-/* Callback for detecting end of channel (hooked by CdReadyCallback) */
-void xa_callback(CdlIntrResult intr, unsigned char *result)
-{
- SECTOR_HEAD *sec;
-
- /* Only respond to data ready callbacks */
- if (intr == CdlDataReady)
- {
- /* Fetch data sector */
- CdGetSector(&xa_sector_buff, 512);
-
- /* Quirk: This CdGetSector() implementation must fetch 2048 bytes */
- /* or more otherwise the following sectors will be read in an */
- /* incorrect byte order, probably due to stray data in the data */
- /* FIFO. Trying to flush remaining bytes in the FIFO through */
- /* memory reads after DMA transfer yields random deadlocking on */
- /* real hardware for some reason. CdGetSector() probably reads */
- /* the data FIFO in software rather than DMA transfer whereas */
- /* CdGetSector2() uses DMA transfer in the official SDK. */
-
- /* Check if sector belongs to the currently playing channel */
- sec = (SECTOR_HEAD*)xa_sector_buff;
-
- if( sec->id == 352 )
- {
- // Debug
- //printf("ID=%d CHAN=%d PL=%d\n", sec->id, (sec->chan>>10)&0xF, xa_play_channel);
-
- /* Check if sector is of the currently playing channel */
- if( ((sec->chan>>10)&0xF) == xa_play_channel )
- {
- num_loops++;
-
- /* Retry playback by seeking to start of XA data and stream */
- CdControlF(CdlReadS, &xa_loc);
-
- /* Stop playback */
- //CdControlF(CdlPause, 0);
- }
- }
+// This buffer is used by cd_event_handler() as a temporary area for sectors
+// read from the CD. Due to DMA limitations it can't be allocated on the stack
+// (especially not in the interrupt callbacks' stack, whose size is very
+// limited).
+Sector sector;
+
+void cd_event_handler(CdlIntrResult event, uint8_t *payload) {
+ // Ignore all events other than a sector being ready.
+ if (event != CdlDataReady)
+ return;
+
+ // Fetch the sector and check if it is part of the audio file by checking
+ // its XA flags. If it isn't an audio sector, restart playback.
+ CdGetSector(&sector, sizeof(Sector) / 4);
+
+ if (
+ !(sector.xa_header[0].submode & XA_TYPE_AUDIO) &&
+ !(sector.xa_header[0].submode & XA_TYPE_AUDIO)
+ ) {
+ // Seek back to the beginning of the file.
+ CdControlF(CdlReadS, &xa_loc);
+
+ num_loops++;
}
}
void init()
-{
+{
int i;
-
+
/* Uncomment to direct tty messages to serial */
//AddSIO(115200);
-
+
/* Reset GPU (also installs event handler for VSync) */
printf("Init GPU... ");
ResetGraph( 0 );
printf("Done.\n");
-
-
+
+
/* Initialize SPU and CD-ROM */
printf("Initializing CD-ROM... ");
SpuInit();
CdInit();
printf("Done.\n");
-
-
+
+
/* Set display and draw environment parameters */
SetDefDispEnv(&disp[0], 0, 0, SCREEN_XRES, SCREEN_YRES);
SetDefDispEnv(&disp[1], 0, SCREEN_YRES, SCREEN_XRES, SCREEN_YRES);
-
+
SetDefDrawEnv(&draw[0], 0, SCREEN_YRES, SCREEN_XRES, SCREEN_YRES);
SetDefDrawEnv(&draw[1], 0, 0, SCREEN_XRES, SCREEN_YRES);
-
-
+
+
/* Set clear color, area clear and dither processing */
setRGB0(&draw[0], 63, 0, 127);
draw[0].isbg = 1;
@@ -266,13 +258,13 @@ void init()
setRGB0(&draw[1], 63, 0, 127);
draw[1].isbg = 1;
draw[1].dtd = 1;
-
-
+
+
/* Load and open font stream */
FntLoad(960, 0);
FntOpen(32, 32, 256, 176, 2, 200);
-
-
+
+
/* Upload the ball texture */
GetTimInfo(ball16c, &tim); /* Get TIM parameters */
LoadImage(tim.prect, tim.paddr); /* Upload texture to VRAM */
@@ -280,13 +272,13 @@ void init()
{
LoadImage(tim.crect, tim.caddr); /* Upload CLUT if present */
}
-
-
+
+
/* Calculate ball positions */
printf("Calculating balls... ");
-
+
for(i=0; i<MAX_BALLS; i++)
- {
+ {
balls[i].x = (rand()%304);
balls[i].y = (rand()%224);
balls[i].xdir = 1-(rand()%3);
@@ -299,17 +291,17 @@ void init()
balls[i].ydir *= 2;
balls[i].r = (rand()%256);
balls[i].g = (rand()%256);
- balls[i].b = (rand()%256);
+ balls[i].b = (rand()%256);
}
-
+
printf("Done.\n");
-
-
+
+
/* Initialize pad */
InitPAD(padbuff[0], 34, padbuff[1], 34);
StartPAD();
ChangeClearPAD(0);
-
+
}
@@ -323,12 +315,12 @@ int main(int argc, const char* argv[])
CdlFILE file;
CdlFILTER filter;
-
+
int i,counter=0;
int sel_channel=0;
int p_up=0,p_down=0,p_right=0,p_cross=0,p_circle=0;
-
-
+
+
/* Init graphics and stuff before doing anything else */
init();
@@ -344,27 +336,27 @@ int main(int argc, const char* argv[])
sec = CdPosToInt(&file.pos);
printf("XA located at sector %d size %d.\n", sec, file.size);
}
-
+
/* Save file location as XA location */
xa_loc = file.pos;
-
+
/* Hook XA callback function to CdReadyCallback (for auto stop/loop */
EnterCriticalSection();
- CdReadyCallback(xa_callback);
+ CdReadyCallback(&cd_event_handler);
ExitCriticalSection();
/* Set CD mode for XA streaming (2x speed, send XA to SPU, enable filter */
i = CdlModeSpeed|CdlModeRT|CdlModeSF;
CdControl(CdlSetmode, &i, 0);
-
- /* Set file 1 on filter for channels 0-7 */
+
+ /* Set file 1 on filter for channels 0-31 */
filter.file = 1;
-
+
/* Main loop */
printf("Entering loop...\n");
-
+
while(1) {
-
+
pad = ((PADTYPE*)padbuff[0]);
if( pad->stat == 0 )
@@ -387,12 +379,12 @@ int main(int argc, const char* argv[])
{
p_up = 0;
}
-
+
if( !(pad->btn&PAD_DOWN) )
{
if( !p_down )
{
- if( sel_channel < 7 )
+ if( sel_channel < 31 )
{
sel_channel++;
}
@@ -403,7 +395,7 @@ int main(int argc, const char* argv[])
{
p_down = 0;
}
-
+
/* Play selected XA channel from start */
if( !(pad->btn&PAD_CROSS) )
{
@@ -420,7 +412,7 @@ int main(int argc, const char* argv[])
{
p_cross = 0;
}
-
+
/* Stop playback */
if( !(pad->btn&PAD_CIRCLE) )
{
@@ -434,7 +426,7 @@ int main(int argc, const char* argv[])
{
p_circle = 0;
}
-
+
/* Change XA channel */
if( !(pad->btn&PAD_RIGHT) )
{
@@ -450,15 +442,15 @@ int main(int argc, const char* argv[])
{
p_right = 0;
}
-
+
}
}
-
-
+
+
/* Display information */
FntPrint(-1, "\n PSN00BSDK XA AUDIO EXAMPLE\n\n");
FntPrint(-1, " CHANNEL:\n");
-
+
for(i=0; i<8; i++)
{
if( i == sel_channel )
@@ -470,83 +462,83 @@ int main(int argc, const char* argv[])
FntPrint(-1, " %d\n", i);
}
}
-
- FntPrint(-1, "\n CURRENT=%d STATUS=%x LOOPS=%d\n",
+
+ FntPrint(-1, "\n CURRENT=%d STATUS=%x LOOPS=%d\n",
xa_play_channel, CdStatus(), num_loops);
FntPrint(-1, "\n <X>-PLAY (START) <O>-STOP\n <R>-SET CHANNEL\n");
-
-
+
+
/* Clear ordering table and set start address of primitive buffer */
ClearOTagR(ot[db], OT_LEN);
nextpri = pribuff[db];
-
-
+
+
/* Sort the balls */
sprt = (SPRT_16*)nextpri;
for( i=0; i<MAX_BALLS; i++ ) {
-
+
setSprt16(sprt);
setXY0(sprt, balls[i].x, balls[i].y);
setRGB0(sprt, balls[i].r, balls[i].g, balls[i].b);
setUV0(sprt, 0, 0);
setClut(sprt, tim.crect->x, tim.crect->y);
-
+
addPrim(ot[db]+(OT_LEN-1), sprt);
sprt++;
-
+
balls[i].x += balls[i].xdir;
balls[i].y += balls[i].ydir;
-
+
if( ( balls[i].x+16 ) > SCREEN_XRES ) {
balls[i].xdir = -2;
} else if( balls[i].x < 0 ) {
balls[i].xdir = 2;
}
-
+
if( ( balls[i].y+16 ) > SCREEN_YRES ) {
balls[i].ydir = -2;
} else if( balls[i].y < 0 ) {
balls[i].ydir = 2;
}
-
+
}
nextpri = (char*)sprt;
-
-
+
+
/* Sort a TPage primitive so the sprites will draw pixels from */
/* the correct texture page in VRAM */
tpri = (DR_TPAGE*)nextpri;
setDrawTPage(tpri, 0, 0, getTPage(0, 0, tim.prect->x, tim.prect->y));
addPrim(ot[db]+(OT_LEN-1), tpri);
nextpri += sizeof(DR_TPAGE);
-
+
/* Draw font */
FntFlush(-1);
-
+
/* Wait for GPU and VSync */
DrawSync(0);
VSync(0);
-
+
/* Since draw.isbg is non-zero this clears the screen */
PutDispEnv(&disp[db]);
PutDrawEnv(&draw[db]);
SetDispMask(1);
-
+
/* Begin drawing the new frame */
DrawOTag( ot[db]+(OT_LEN-1) );
-
+
/* Alternate to the next buffer */
db = !db;
-
+
/* Periodically issue CdlNop every second to update CdStatus() */
counter++;
if( (counter%60) == 59 )
{
CdControl(CdlNop, 0, 0);
}
-
+
}
-
+
return 0;
}
diff --git a/template/main.c b/template/main.c
index 1f02f0b..7f738d3 100644
--- a/template/main.c
+++ b/template/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;
}