aboutsummaryrefslogtreecommitdiff
path: root/examples/beginner/hellocpp
diff options
context:
space:
mode:
authorspicyjpeg <thatspicyjpeg@gmail.com>2023-05-21 18:17:01 +0200
committerspicyjpeg <thatspicyjpeg@gmail.com>2023-05-21 18:17:01 +0200
commit4b9369e9084a7f3fee47c40a1bb71e50529eefd3 (patch)
treed9932f1c2eb7882fe5937417a671c74c6af5d4c9 /examples/beginner/hellocpp
parent1e058d15e29adc482074d7225b4c1ed34e63568d (diff)
downloadpsn00bsdk-4b9369e9084a7f3fee47c40a1bb71e50529eefd3.tar.gz
Refactor hello and cppdemo examples, fix cdxa
Diffstat (limited to 'examples/beginner/hellocpp')
-rw-r--r--examples/beginner/hellocpp/CMakeLists.txt18
-rw-r--r--examples/beginner/hellocpp/main.cpp232
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;
+}