Broken UI and Loading screens

This commit is contained in:
Jan Racek
2026-03-26 19:14:37 +01:00
parent 37ba4c85fe
commit 19bb2254f3
8 changed files with 630 additions and 6 deletions

View File

@@ -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

405
src/loadingscreen.cpp Normal file
View File

@@ -0,0 +1,405 @@
#include "loadingscreen.hh"
#include "sceneloader.hh"
#include "renderer.hh"
#include <psyqo/primitives/rectangles.hh>
#include <psyqo/primitives/misc.hh>
#include <psyqo/primitives/triangles.hh>
#include <psyqo/primitives/sprites.hh>
#include <psyqo/primitives/control.hh>
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<const LoaderPackHeader*>(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<const LoaderPackHeader*>(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<const LoaderPackAtlas*>(ptr);
ptr += sizeof(LoaderPackAtlas);
if (atlas->pixelDataOffset != 0 && atlas->width > 0 && atlas->height > 0) {
const uint16_t* pixels = reinterpret_cast<const uint16_t*>(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<const LoaderPackClut*>(ptr);
ptr += sizeof(LoaderPackClut);
if (clut->clutDataOffset != 0 && clut->length > 0) {
const uint16_t* palette = reinterpret_cast<const uint16_t*>(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

125
src/loadingscreen.hh Normal file
View File

@@ -0,0 +1,125 @@
#pragma once
#include <stdint.h>
#include <psyqo/font.hh>
#include <psyqo/gpu.hh>
#include <psyqo/primitives/common.hh>
#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

View File

@@ -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);
}
}

View File

@@ -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() {

View File

@@ -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)

View File

@@ -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

View File

@@ -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;