From 19bb2254f37cf449512efbbd90c420b050058a2f Mon Sep 17 00:00:00 2001 From: Jan Racek Date: Thu, 26 Mar 2026 19:14:37 +0100 Subject: [PATCH] Broken UI and Loading screens --- Makefile | 3 +- src/loadingscreen.cpp | 405 ++++++++++++++++++++++++++++++++++++++++++ src/loadingscreen.hh | 125 +++++++++++++ src/main.cpp | 19 +- src/scenemanager.cpp | 39 +++- src/scenemanager.hh | 6 +- src/uisystem.cpp | 32 ++++ src/uisystem.hh | 7 + 8 files changed, 630 insertions(+), 6 deletions(-) create mode 100644 src/loadingscreen.cpp create mode 100644 src/loadingscreen.hh diff --git a/Makefile b/Makefile index dedf413..5b835ce 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,8 @@ src/profiler.cpp \ src/collision.cpp \ src/bvh.cpp \ src/cutscene.cpp \ -src/uisystem.cpp +src/uisystem.cpp \ +src/loadingscreen.cpp CPPFLAGS += -DPCDRV_SUPPORT=1 diff --git a/src/loadingscreen.cpp b/src/loadingscreen.cpp new file mode 100644 index 0000000..63cb435 --- /dev/null +++ b/src/loadingscreen.cpp @@ -0,0 +1,405 @@ +#include "loadingscreen.hh" +#include "sceneloader.hh" +#include "renderer.hh" + +#include +#include +#include +#include +#include + +namespace psxsplash { + +// ──────────────────────────────────────────────────────────────── +// Bare-metal string helpers (no libc) +// ──────────────────────────────────────────────────────────────── +static void int_to_str(int val, char* buf) { + if (val < 0) { *buf++ = '-'; val = -val; } + char tmp[12]; + int len = 0; + do { tmp[len++] = '0' + (val % 10); val /= 10; } while (val > 0); + for (int i = len - 1; i >= 0; i--) *buf++ = tmp[i]; + *buf = '\0'; +} + +// ──────────────────────────────────────────────────────────────── +// Load +// ──────────────────────────────────────────────────────────────── +bool LoadingScreen::load(psyqo::GPU& gpu, psyqo::Font<>& systemFont, int sceneIndex) { + // Build filename: "scene_N.loading" + char filename[32] = "scene_"; + char numBuf[8]; + int_to_str(sceneIndex, numBuf); + int i = 6; + for (int j = 0; numBuf[j]; j++) filename[i++] = numBuf[j]; + filename[i++] = '.'; + filename[i++] = 'l'; + filename[i++] = 'o'; + filename[i++] = 'a'; + filename[i++] = 'd'; + filename[i++] = 'i'; + filename[i++] = 'n'; + filename[i++] = 'g'; + filename[i] = '\0'; + + int fileSize = 0; + uint8_t* data = SceneLoader::LoadFile(filename, fileSize); + if (!data || fileSize < (int)sizeof(LoaderPackHeader)) { + if (data) SceneLoader::FreeFile(data); + return false; + } + + auto* header = reinterpret_cast(data); + if (header->magic[0] != 'L' || header->magic[1] != 'P') { + SceneLoader::FreeFile(data); + return false; + } + + m_data = data; + m_dataSize = fileSize; + m_font = &systemFont; + m_active = true; + m_resW = (int16_t)header->resW; + m_resH = (int16_t)header->resH; + + // Upload texture atlases and CLUTs to VRAM (before setting DrawingOffset) + uploadTextures(gpu); + + // Initialize UISystem with the system font and parse the loader pack + m_ui.init(systemFont); + m_ui.loadFromSplashpack(data, header->canvasCount, header->fontCount, header->tableOffset); + m_ui.uploadFonts(gpu); + + // Ensure canvas 0 is visible + if (m_ui.getCanvasCount() > 0) { + m_ui.setCanvasVisible(0, true); + } + + // Find the progress bar named "loading" + findProgressBar(); + + return true; +} + +// ──────────────────────────────────────────────────────────────── +// Upload atlas/CLUT data to VRAM (RAM → VRAM blit) +// ──────────────────────────────────────────────────────────────── +void LoadingScreen::uploadTextures(psyqo::GPU& gpu) { + auto* header = reinterpret_cast(m_data); + + // Atlas and CLUT entries start right after the 16-byte header + const uint8_t* ptr = m_data + sizeof(LoaderPackHeader); + + for (int ai = 0; ai < header->atlasCount; ai++) { + auto* atlas = reinterpret_cast(ptr); + ptr += sizeof(LoaderPackAtlas); + + if (atlas->pixelDataOffset != 0 && atlas->width > 0 && atlas->height > 0) { + const uint16_t* pixels = reinterpret_cast(m_data + atlas->pixelDataOffset); + psyqo::Rect region{.a = {.x = (int16_t)atlas->x, .y = (int16_t)atlas->y}, + .b = {(int16_t)atlas->width, (int16_t)atlas->height}}; + gpu.uploadToVRAM(pixels, region); + } + } + + for (int ci = 0; ci < header->clutCount; ci++) { + auto* clut = reinterpret_cast(ptr); + ptr += sizeof(LoaderPackClut); + + if (clut->clutDataOffset != 0 && clut->length > 0) { + const uint16_t* palette = reinterpret_cast(m_data + clut->clutDataOffset); + psyqo::Rect region{.a = {.x = (int16_t)(clut->clutX * 16), .y = (int16_t)clut->clutY}, + .b = {(int16_t)clut->length, 1}}; + gpu.uploadToVRAM(palette, region); + } + } +} + +// ──────────────────────────────────────────────────────────────── +// Find progress bar +// ──────────────────────────────────────────────────────────────── +void LoadingScreen::findProgressBar() { + m_hasProgressBar = false; + + int canvasCount = m_ui.getCanvasCount(); + for (int ci = 0; ci < canvasCount; ci++) { + int handle = m_ui.findElement(ci, "loading"); + if (handle >= 0 && m_ui.getElementType(handle) == UIElementType::Progress) { + m_hasProgressBar = true; + + m_ui.getPosition(handle, m_barX, m_barY); + m_ui.getSize(handle, m_barW, m_barH); + m_ui.getColor(handle, m_barFillR, m_barFillG, m_barFillB); + m_ui.getProgressBgColor(handle, m_barBgR, m_barBgG, m_barBgB); + break; + } + } +} + +// ──────────────────────────────────────────────────────────────── +// Draw a filled rectangle (immediate mode, no DMA chain). +// Coordinates are logical — DrawingOffset shifts them to the target buffer. +// ──────────────────────────────────────────────────────────────── +void LoadingScreen::drawRect(psyqo::GPU& gpu, int16_t x, int16_t y, + int16_t w, int16_t h, + uint8_t r, uint8_t g, uint8_t b) { + if (w <= 0 || h <= 0) return; + psyqo::Prim::Rectangle rect; + rect.setColor(psyqo::Color{.r = r, .g = g, .b = b}); + rect.position = {.x = x, .y = y}; + rect.size = {.x = w, .y = h}; + rect.setOpaque(); + gpu.sendPrimitive(rect); +} + +// ──────────────────────────────────────────────────────────────── +// Draw a textured image (two triangles, sendPrimitive). +// GouraudTexturedTriangle carries its own TPage attribute. +// DrawingOffset shifts logical coordinates to the correct framebuffer. +// ──────────────────────────────────────────────────────────────── +void LoadingScreen::drawImage(psyqo::GPU& gpu, int handle, + int16_t x, int16_t y, int16_t w, int16_t h, + uint8_t r, uint8_t g, uint8_t b) { + const UIImageData* img = m_ui.getImageData(handle); + if (!img) return; + + // Build TPage attribute (same as UISystem::makeTPage) + psyqo::PrimPieces::TPageAttr tpage; + tpage.setPageX(img->texpageX); + tpage.setPageY(img->texpageY); + switch (img->bitDepth) { + case 0: tpage.set(psyqo::Prim::TPageAttr::Tex4Bits); break; + case 1: tpage.set(psyqo::Prim::TPageAttr::Tex8Bits); break; + case 2: default: tpage.set(psyqo::Prim::TPageAttr::Tex16Bits); break; + } + tpage.setDithering(false); + + psyqo::PrimPieces::ClutIndex clut(img->clutX, img->clutY); + psyqo::Color tint = {.r = r, .g = g, .b = b}; + + // Triangle 0: top-left, top-right, bottom-left + { + psyqo::Prim::GouraudTexturedTriangle tri; + tri.pointA.x = x; tri.pointA.y = y; + tri.pointB.x = x + w; tri.pointB.y = y; + tri.pointC.x = x; tri.pointC.y = y + h; + tri.uvA.u = img->u0; tri.uvA.v = img->v0; + tri.uvB.u = img->u1; tri.uvB.v = img->v0; + tri.uvC.u = img->u0; tri.uvC.v = img->v1; + tri.tpage = tpage; + tri.clutIndex = clut; + tri.setColorA(tint); + tri.setColorB(tint); + tri.setColorC(tint); + tri.setOpaque(); + gpu.sendPrimitive(tri); + } + // Triangle 1: top-right, bottom-right, bottom-left + { + psyqo::Prim::GouraudTexturedTriangle tri; + tri.pointA.x = x + w; tri.pointA.y = y; + tri.pointB.x = x + w; tri.pointB.y = y + h; + tri.pointC.x = x; tri.pointC.y = y + h; + tri.uvA.u = img->u1; tri.uvA.v = img->v0; + tri.uvB.u = img->u1; tri.uvB.v = img->v1; + tri.uvC.u = img->u0; tri.uvC.v = img->v1; + tri.tpage = tpage; + tri.clutIndex = clut; + tri.setColorA(tint); + tri.setColorB(tint); + tri.setColorC(tint); + tri.setOpaque(); + gpu.sendPrimitive(tri); + } +} + +// ──────────────────────────────────────────────────────────────── +// Draw custom-font text via sendPrimitive (TPage + Sprite per glyph). +// DrawingOffset shifts logical coordinates to the correct framebuffer. +// ──────────────────────────────────────────────────────────────── +void LoadingScreen::drawCustomText(psyqo::GPU& gpu, int handle, + int16_t x, int16_t y, + uint8_t r, uint8_t g, uint8_t b) { + uint8_t fontIdx = m_ui.getTextFontIndex(handle); + if (fontIdx == 0) return; // system font, not custom + const UIFontDesc* fd = m_ui.getFontDesc(fontIdx - 1); // 1-based → 0-based + if (!fd) return; + + const char* text = m_ui.getText(handle); + if (!text || !text[0]) return; + + // Set TPage for this font's texture page + psyqo::Prim::TPage tpCmd; + tpCmd.attr.setPageX(fd->vramX >> 6); + tpCmd.attr.setPageY(fd->vramY >> 8); + tpCmd.attr.set(psyqo::Prim::TPageAttr::Tex4Bits); + tpCmd.attr.setDithering(false); + gpu.sendPrimitive(tpCmd); + + // CLUT reference (same as renderProportionalText in UISystem) + psyqo::Vertex clutPos = {{.x = (int16_t)fd->vramX, .y = (int16_t)fd->vramY}}; + psyqo::PrimPieces::ClutIndex clutIdx(clutPos); + psyqo::Color color = {.r = r, .g = g, .b = b}; + + int glyphsPerRow = 256 / fd->glyphW; + uint8_t baseV = fd->vramY & 0xFF; + + int16_t cursorX = x; + while (*text) { + uint8_t c = (uint8_t)*text++; + if (c < 32 || c > 127) c = '?'; + uint8_t charIdx = c - 32; + uint8_t advance = fd->advanceWidths[charIdx]; + + if (c == ' ') { + cursorX += advance; + continue; + } + + int charRow = charIdx / glyphsPerRow; + int charCol = charIdx % glyphsPerRow; + uint8_t u = (uint8_t)(charCol * fd->glyphW); + uint8_t v = (uint8_t)(baseV + charRow * fd->glyphH); + + int16_t spriteW = (advance > 0 && advance < fd->glyphW) ? (int16_t)advance : (int16_t)fd->glyphW; + + psyqo::Prim::Sprite sprite; + sprite.setColor(color); + sprite.position = {.x = cursorX, .y = y}; + sprite.size = {.x = spriteW, .y = (int16_t)fd->glyphH}; + psyqo::PrimPieces::TexInfo texInfo; + texInfo.u = u; + texInfo.v = v; + texInfo.clut = clutIdx; + sprite.texInfo = texInfo; + gpu.sendPrimitive(sprite); + + cursorX += advance; + } +} + +// ──────────────────────────────────────────────────────────────── +// Render ALL elements to ONE framebuffer at the given VRAM Y offset. +// Uses DrawingOffset so all draw functions use logical coordinates. +// ──────────────────────────────────────────────────────────────── +void LoadingScreen::renderToBuffer(psyqo::GPU& gpu, int16_t yOffset) { + // Configure GPU drawing area for this framebuffer + gpu.sendPrimitive(psyqo::Prim::DrawingAreaStart(psyqo::Vertex{{.x = 0, .y = yOffset}})); + gpu.sendPrimitive(psyqo::Prim::DrawingAreaEnd(psyqo::Vertex{{.x = m_resW, .y = (int16_t)(yOffset + m_resH)}})); + gpu.sendPrimitive(psyqo::Prim::DrawingOffset(psyqo::Vertex{{.x = 0, .y = yOffset}})); + + // Clear this buffer to black (Rectangle is shifted by DrawingOffset) + drawRect(gpu, 0, 0, m_resW, m_resH, 0, 0, 0); + + int canvasCount = m_ui.getCanvasCount(); + for (int ci = 0; ci < canvasCount; ci++) { + if (!m_ui.isCanvasVisible(ci)) continue; + int elemCount = m_ui.getCanvasElementCount(ci); + + for (int ei = 0; ei < elemCount; ei++) { + int handle = m_ui.getCanvasElementHandle(ci, ei); + if (handle < 0 || !m_ui.isElementVisible(handle)) continue; + + UIElementType type = m_ui.getElementType(handle); + int16_t x, y, w, h; + m_ui.getPosition(handle, x, y); + m_ui.getSize(handle, w, h); + uint8_t r, g, b; + m_ui.getColor(handle, r, g, b); + + switch (type) { + case UIElementType::Box: + drawRect(gpu, x, y, w, h, r, g, b); + break; + + case UIElementType::Progress: { + uint8_t bgr, bgg, bgb; + m_ui.getProgressBgColor(handle, bgr, bgg, bgb); + drawRect(gpu, x, y, w, h, bgr, bgg, bgb); + + uint8_t val = m_ui.getProgress(handle); + int fillW = (int)val * w / 100; + if (fillW > 0) + drawRect(gpu, x, y, (int16_t)fillW, h, r, g, b); + break; + } + + case UIElementType::Image: + drawImage(gpu, handle, x, y, w, h, r, g, b); + break; + + case UIElementType::Text: { + uint8_t fontIdx = m_ui.getTextFontIndex(handle); + if (fontIdx > 0) { + drawCustomText(gpu, handle, x, y, r, g, b); + } else if (m_font) { + m_font->print(gpu, m_ui.getText(handle), + {{.x = x, .y = y}}, + {{.r = r, .g = g, .b = b}}); + } + break; + } + } + } + } +} + +// ──────────────────────────────────────────────────────────────── +// Render to both framebuffers, then FREE all loaded data +// ──────────────────────────────────────────────────────────────── +void LoadingScreen::renderInitialAndFree(psyqo::GPU& gpu) { + if (!m_data) return; + + // Render to framebuffer 0 (Y = 0) + renderToBuffer(gpu, 0); + gpu.pumpCallbacks(); + + // Render to framebuffer 1 (Y = m_resH, typically 240) + renderToBuffer(gpu, m_resH); + gpu.pumpCallbacks(); + + // Restore normal scissor for the active framebuffer + gpu.enableScissor(); + + // FREE all loaded data — the splashpack needs this memory + SceneLoader::FreeFile(m_data); + m_data = nullptr; + m_dataSize = 0; + // m_ui now points into freed memory — don't use it again. + // m_active remains true so updateProgress keeps working. +} + +// ──────────────────────────────────────────────────────────────── +// Update progress bar in BOTH framebuffers +// ──────────────────────────────────────────────────────────────── +void LoadingScreen::updateProgress(psyqo::GPU& gpu, uint8_t percent) { + if (!m_hasProgressBar || !m_active) return; + if (percent > 100) percent = 100; + + int fillW = (int)percent * m_barW / 100; + + // Draw into both framebuffers using DrawingOffset + for (int buf = 0; buf < 2; buf++) { + int16_t yOff = (buf == 0) ? 0 : m_resH; + + gpu.sendPrimitive(psyqo::Prim::DrawingAreaStart(psyqo::Vertex{{.x = 0, .y = yOff}})); + gpu.sendPrimitive(psyqo::Prim::DrawingAreaEnd(psyqo::Vertex{{.x = m_resW, .y = (int16_t)(yOff + m_resH)}})); + gpu.sendPrimitive(psyqo::Prim::DrawingOffset(psyqo::Vertex{{.x = 0, .y = yOff}})); + + // Background + drawRect(gpu, m_barX, m_barY, m_barW, m_barH, + m_barBgR, m_barBgG, m_barBgB); + + // Fill + if (fillW > 0) { + drawRect(gpu, m_barX, m_barY, (int16_t)fillW, m_barH, + m_barFillR, m_barFillG, m_barFillB); + } + } + + // Restore normal scissor + gpu.enableScissor(); + gpu.pumpCallbacks(); +} + +} // namespace psxsplash diff --git a/src/loadingscreen.hh b/src/loadingscreen.hh new file mode 100644 index 0000000..b35ef2f --- /dev/null +++ b/src/loadingscreen.hh @@ -0,0 +1,125 @@ +#pragma once + +#include +#include +#include +#include +#include "uisystem.hh" + +namespace psxsplash { + +/// Loader pack header — matches the binary written by PSXLoaderPackWriter.cs. +struct LoaderPackHeader { + char magic[2]; // "LP" + uint16_t version; // 2 + uint8_t fontCount; + uint8_t canvasCount; // always 1 + uint16_t resW; + uint16_t resH; + uint8_t atlasCount; // texture atlases for UI images + uint8_t clutCount; // CLUTs for indexed-color images + uint32_t tableOffset; +}; +static_assert(sizeof(LoaderPackHeader) == 16, "LoaderPackHeader must be 16 bytes"); + +/// Atlas entry in the loader pack (matches SPLASHPACKTextureAtlas layout). +struct LoaderPackAtlas { + uint32_t pixelDataOffset; // absolute offset in file + uint16_t width, height; + uint16_t x, y; // VRAM position +}; +static_assert(sizeof(LoaderPackAtlas) == 12, "LoaderPackAtlas must be 12 bytes"); + +/// CLUT entry in the loader pack (matches SPLASHPACKClut layout). +struct LoaderPackClut { + uint32_t clutDataOffset; // absolute offset in file + uint16_t clutX; // VRAM X (in 16-pixel units × 16) + uint16_t clutY; // VRAM Y + uint16_t length; // number of palette entries + uint16_t pad; +}; +static_assert(sizeof(LoaderPackClut) == 12, "LoaderPackClut must be 12 bytes"); + +/// Loading screen controller. +/// +/// Loads a .loading file (tiny UI-only binary), renders the screen, +/// and provides progress updates during the main splashpack load. +/// +/// Strategy: +/// 1. Blank the screen immediately (user sees black, not frozen frame). +/// 2. Load the .loading file (small, fast). +/// 3. Upload texture atlases, CLUTs, and custom font textures to VRAM. +/// 4. Render ALL elements to BOTH framebuffers using direct GPU commands: +/// - Images via sendPrimitive(GouraudTexturedTriangle) +/// - Custom font text via sendPrimitive(TPage) + sendPrimitive(Sprite) +/// - System font text via font.print() +/// - Boxes and progress bars via sendPrimitive(Rectangle) +/// 5. FREE all loaded data (loader pack) so the splashpack has room. +/// 6. During splashpack loading, call updateProgress() to redraw ONLY the +/// progress bar (simple rectangles in both framebuffers — no VRAM needed). +/// +/// The progress bar is found by looking for a PSXUIProgressBar element named "loading". +class LoadingScreen { +public: + /// Try to load a loader pack from a file. + /// Returns true if a loading screen was successfully loaded. + /// @param gpu GPU reference for rendering. + /// @param systemFont System font used if no custom font for text. + /// @param sceneIndex Scene index to derive the filename (scene_N.loading). + bool load(psyqo::GPU& gpu, psyqo::Font<>& systemFont, int sceneIndex); + + /// Render all loading screen elements to BOTH framebuffers, + /// then FREE all loaded data. After this, only updateProgress works. + void renderInitialAndFree(psyqo::GPU& gpu); + + /// Update the progress bar to the given percentage (0-100). + /// Redraws the progress bar rectangles in both framebuffers. + /// Safe after data is freed — uses only cached layout values. + void updateProgress(psyqo::GPU& gpu, uint8_t percent); + + /// Returns true if a loading screen was loaded (even after data freed). + bool isActive() const { return m_active; } + +private: + /// Render a filled rectangle at an absolute VRAM position. + void drawRect(psyqo::GPU& gpu, int16_t x, int16_t y, int16_t w, int16_t h, + uint8_t r, uint8_t g, uint8_t b); + + /// Render an image element (two textured triangles). + /// Assumes DrawingOffset is already configured for the target buffer. + void drawImage(psyqo::GPU& gpu, int handle, int16_t x, int16_t y, + int16_t w, int16_t h, uint8_t r, uint8_t g, uint8_t b); + + /// Render custom-font text via sendPrimitive (TPage + Sprite per glyph). + /// Assumes DrawingOffset is already configured for the target buffer. + void drawCustomText(psyqo::GPU& gpu, int handle, int16_t x, int16_t y, + uint8_t r, uint8_t g, uint8_t b); + + /// Render ALL elements to a single framebuffer at the given VRAM Y offset. + void renderToBuffer(psyqo::GPU& gpu, int16_t yOffset); + + /// Upload atlas/CLUT data to VRAM. + void uploadTextures(psyqo::GPU& gpu); + + /// Find the "loading" progress bar element and cache its layout. + void findProgressBar(); + + uint8_t* m_data = nullptr; + int m_dataSize = 0; + psyqo::Font<>* m_font = nullptr; + bool m_active = false; + + // Temporary UISystem to parse the loader pack's canvas/element data + UISystem m_ui; + + // Cached layout for the "loading" progress bar (resolved at load time) + bool m_hasProgressBar = false; + int16_t m_barX = 0, m_barY = 0, m_barW = 0, m_barH = 0; + uint8_t m_barFillR = 255, m_barFillG = 255, m_barFillB = 255; + uint8_t m_barBgR = 0, m_barBgG = 0, m_barBgB = 0; + + // Resolution from the loader pack + int16_t m_resW = 320, m_resH = 240; +}; + +} // namespace psxsplash diff --git a/src/main.cpp b/src/main.cpp index f082eee..3eca16f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include "scenemanager.hh" #include "sceneloader.hh" #include "pcdrv_handler.hh" +#include "loadingscreen.hh" namespace { @@ -29,6 +30,9 @@ class MainScene final : public psyqo::Scene { psxsplash::SceneManager m_sceneManager; + // Loading screen (persists between scenes; owned here, passed to SceneManager) + psxsplash::LoadingScreen m_loadingScreen; + // PCdrv-loaded scene data (owned) uint8_t* m_sceneData = nullptr; }; @@ -64,6 +68,15 @@ void MainScene::start(StartReason reason) { // Initialize PCdrv (break instructions - handled by emulator or our break handler) psxsplash::SceneLoader::Init(); + // Blank display immediately so the user never sees a frozen frame + gpu().clear(psyqo::Color{.r = 0, .g = 0, .b = 0}); + gpu().pumpCallbacks(); + + // Try to load a loading screen for scene 0 + if (m_loadingScreen.load(gpu(), app.m_font, 0)) { + m_loadingScreen.renderInitialAndFree(gpu()); + } + // Load the first scene via PCdrv. // Files are relative to the pcdrvbase directory (PSXBuild/). int fileSize = 0; @@ -74,8 +87,12 @@ void MainScene::start(StartReason reason) { m_sceneData = psxsplash::SceneLoader::LoadFile("output.bin", fileSize); } + if (m_loadingScreen.isActive()) { + m_loadingScreen.updateProgress(gpu(), 30); + } + if (m_sceneData) { - m_sceneManager.InitializeScene(m_sceneData); + m_sceneManager.InitializeScene(m_sceneData, &m_loadingScreen); } } diff --git a/src/scenemanager.cpp b/src/scenemanager.cpp index d1a6c82..5178aae 100644 --- a/src/scenemanager.cpp +++ b/src/scenemanager.cpp @@ -7,6 +7,7 @@ #include "renderer.hh" #include "splashpack.hh" #include "luaapi.hh" +#include "loadingscreen.hh" #include "lua.h" @@ -19,7 +20,9 @@ using namespace psxsplash; // Static member definition psyqo::Font<>* psxsplash::SceneManager::s_font = nullptr; -void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) { +void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingScreen* loading) { + auto& gpu = Renderer::GetInstance().getGPU(); + L.Reset(); // Initialize audio system @@ -35,6 +38,8 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) { SplashpackSceneSetup sceneSetup; m_loader.LoadSplashpack(splashpackData, sceneSetup); + if (loading && loading->isActive()) loading->updateProgress(gpu, 40); + m_luaFiles = std::move(sceneSetup.luaFiles); m_gameObjects = std::move(sceneSetup.objects); m_objectNames = std::move(sceneSetup.objectNames); @@ -76,6 +81,8 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) { m_audio.loadClip((int)i, clip.adpcmData, clip.sizeBytes, clip.sampleRate, clip.loop); } + if (loading && loading->isActive()) loading->updateProgress(gpu, 55); + // Copy cutscene data into scene manager storage (sceneSetup is stack-local) m_cutsceneCount = sceneSetup.cutsceneCount; for (int i = 0; i < m_cutsceneCount; i++) { @@ -99,6 +106,8 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) { m_uiSystem.uploadFonts(Renderer::GetInstance().getGPU()); Renderer::GetInstance().SetUISystem(&m_uiSystem); + if (loading && loading->isActive()) loading->updateProgress(gpu, 70); + // Resolve UI track handles: the splashpack loader stored raw name pointers // in CutsceneTrack.target for UI tracks. Now that UISystem is loaded, resolve // those names to canvas indices / element handles. @@ -196,6 +205,8 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) { L.LoadLuaFile(luaFile->luaCode, luaFile->length, i); } + if (loading && loading->isActive()) loading->updateProgress(gpu, 85); + L.RegisterSceneScripts(sceneSetup.sceneLuaFileIndex); L.OnSceneCreationStart(); @@ -209,6 +220,8 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) { Renderer::GetInstance().SetCamera(m_currentCamera); L.OnSceneCreationEnd(); + + if (loading && loading->isActive()) loading->updateProgress(gpu, 100); } void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) { @@ -606,10 +619,24 @@ void psxsplash::SceneManager::processPendingSceneLoad() { int targetIndex = m_pendingSceneIndex; m_pendingSceneIndex = -1; + auto& gpu = Renderer::GetInstance().getGPU(); + // Build filename: scene_N.splashpack char filename[32]; snprintf(filename, sizeof(filename), "scene_%d.splashpack", targetIndex); + // Blank the screen immediately so the user doesn't see a frozen frame + gpu.clear(psyqo::Color{.r = 0, .g = 0, .b = 0}); + gpu.pumpCallbacks(); + + // Try to load a loading screen for the target scene + LoadingScreen loading; + if (s_font) { + if (loading.load(gpu, *s_font, targetIndex)) { + loading.renderInitialAndFree(gpu); + } + } + // 1. Tear down EVERYTHING in the current scene first — // Lua VM, vector backing storage, audio. This returns as much // heap memory as possible before any new allocation. @@ -624,16 +651,22 @@ void psxsplash::SceneManager::processPendingSceneLoad() { m_currentSceneData = nullptr; } + if (loading.isActive()) loading.updateProgress(gpu, 20); + // 3. Allocate new scene data (heap is now maximally consolidated) int fileSize = 0; uint8_t* newData = SceneLoader::LoadFile(filename, fileSize); - if (!newData) return; + if (!newData) { + return; + } + + if (loading.isActive()) loading.updateProgress(gpu, 30); m_currentSceneData = newData; m_currentSceneIndex = targetIndex; // 4. Initialize with new data (creates fresh Lua VM inside) - InitializeScene(newData); + InitializeScene(newData, loading.isActive() ? &loading : nullptr); } void psxsplash::SceneManager::clearScene() { diff --git a/src/scenemanager.hh b/src/scenemanager.hh index 1d1e38c..c453563 100644 --- a/src/scenemanager.hh +++ b/src/scenemanager.hh @@ -23,9 +23,13 @@ #include "uisystem.hh" namespace psxsplash { + +// Forward-declare; full definition in loadingscreen.hh +class LoadingScreen; + class SceneManager { public: - void InitializeScene(uint8_t* splashpackData); + void InitializeScene(uint8_t* splashpackData, LoadingScreen* loading = nullptr); void GameTick(psyqo::GPU &gpu); // Font access (set from main.cpp after uploadSystemFont) diff --git a/src/uisystem.cpp b/src/uisystem.cpp index 4049f24..dc2e12d 100644 --- a/src/uisystem.cpp +++ b/src/uisystem.cpp @@ -681,4 +681,36 @@ int UISystem::getCanvasElementHandle(int canvasIdx, int elementIndex) const { return (int)(cv.elements + elementIndex - m_elements); } +void UISystem::getProgressBgColor(int handle, uint8_t& r, uint8_t& g, uint8_t& b) const { + if (handle < 0 || handle >= m_elementCount) { r = g = b = 0; return; } + const UIElement& el = m_elements[handle]; + if (el.type != UIElementType::Progress) { r = g = b = 0; return; } + r = el.progress.bgR; + g = el.progress.bgG; + b = el.progress.bgB; +} + +int UISystem::getCanvasCount() const { + return m_canvasCount; +} + +const UIImageData* UISystem::getImageData(int handle) const { + if (handle < 0 || handle >= m_elementCount) return nullptr; + const UIElement& el = m_elements[handle]; + if (el.type != UIElementType::Image) return nullptr; + return &el.image; +} + +const UIFontDesc* UISystem::getFontDesc(int fontIdx) const { + if (fontIdx < 0 || fontIdx >= m_fontCount) return nullptr; + return &m_fontDescs[fontIdx]; +} + +uint8_t UISystem::getTextFontIndex(int handle) const { + if (handle < 0 || handle >= m_elementCount) return 0; + const UIElement& el = m_elements[handle]; + if (el.type != UIElementType::Text) return 0; + return el.textData.fontIndex; +} + } // namespace psxsplash diff --git a/src/uisystem.hh b/src/uisystem.hh index 82cb6dd..f2c32c1 100644 --- a/src/uisystem.hh +++ b/src/uisystem.hh @@ -116,6 +116,13 @@ public: UIElementType getElementType(int handle) const; int getCanvasElementCount(int canvasIdx) const; int getCanvasElementHandle(int canvasIdx, int elementIndex) const; + void getProgressBgColor(int handle, uint8_t& r, uint8_t& g, uint8_t& b) const; + int getCanvasCount() const; + + // Raw accessors for loading-screen direct rendering + const UIImageData* getImageData(int handle) const; + const UIFontDesc* getFontDesc(int fontIdx) const; // fontIdx = 0-based custom font index + uint8_t getTextFontIndex(int handle) const; private: psyqo::Font<>* m_systemFont = nullptr;