diff options
| author | spicyjpeg <thatspicyjpeg@gmail.com> | 2023-05-21 18:17:01 +0200 |
|---|---|---|
| committer | spicyjpeg <thatspicyjpeg@gmail.com> | 2023-05-21 18:17:01 +0200 |
| commit | 4b9369e9084a7f3fee47c40a1bb71e50529eefd3 (patch) | |
| tree | d9932f1c2eb7882fe5937417a671c74c6af5d4c9 /examples/beginner/hellocpp | |
| parent | 1e058d15e29adc482074d7225b4c1ed34e63568d (diff) | |
| download | psn00bsdk-4b9369e9084a7f3fee47c40a1bb71e50529eefd3.tar.gz | |
Refactor hello and cppdemo examples, fix cdxa
Diffstat (limited to 'examples/beginner/hellocpp')
| -rw-r--r-- | examples/beginner/hellocpp/CMakeLists.txt | 18 | ||||
| -rw-r--r-- | examples/beginner/hellocpp/main.cpp | 232 |
2 files changed, 250 insertions, 0 deletions
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; +} |
