Broken UI and Loading screens
This commit is contained in:
3
Makefile
3
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
|
||||
|
||||
|
||||
405
src/loadingscreen.cpp
Normal file
405
src/loadingscreen.cpp
Normal 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
125
src/loadingscreen.hh
Normal 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
|
||||
19
src/main.cpp
19
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user