diff options
| -rw-r--r-- | examples/README.md | 6 | ||||
| -rw-r--r-- | examples/beginner/cppdemo/CMakeLists.txt | 18 | ||||
| -rw-r--r-- | examples/beginner/cppdemo/main.cpp | 151 | ||||
| -rw-r--r-- | examples/beginner/hello/main.c | 251 | ||||
| -rw-r--r-- | examples/beginner/hellocpp/CMakeLists.txt | 18 | ||||
| -rw-r--r-- | examples/beginner/hellocpp/main.cpp | 232 | ||||
| -rw-r--r-- | examples/cdrom/cdxa/main.c | 242 | ||||
| -rw-r--r-- | template/main.c | 251 |
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(§or, 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; } |
