From 37ba4c85fe9102fdc5c2ef8bad26dd52f181080e Mon Sep 17 00:00:00 2001 From: Jan Racek Date: Wed, 25 Mar 2026 17:14:08 +0100 Subject: [PATCH] Somewhat fixed ui --- src/uisystem.cpp | 139 +++++++++++++++++++++++++++++++++++++++++------ src/uisystem.hh | 23 +++++--- 2 files changed, 135 insertions(+), 27 deletions(-) diff --git a/src/uisystem.cpp b/src/uisystem.cpp index 8b392d5..4049f24 100644 --- a/src/uisystem.cpp +++ b/src/uisystem.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace psxsplash { @@ -38,7 +39,9 @@ void UISystem::loadFromSplashpack(uint8_t* data, uint16_t canvasCount, uint8_t* ptr = data + tableOffset; - // ── Parse font descriptors (16 bytes each, before canvas data) ── + // ── Parse font descriptors (112 bytes each, before canvas data) ── + // Layout: glyphW(1) glyphH(1) vramX(2) vramY(2) textureH(2) + // dataOffset(4) dataSize(4) advanceWidths(96) if (fontCount > UI_MAX_FONTS - 1) fontCount = UI_MAX_FONTS - 1; m_fontCount = fontCount; for (int fi = 0; fi < fontCount; fi++) { @@ -51,7 +54,30 @@ void UISystem::loadFromSplashpack(uint8_t* data, uint16_t canvasCount, uint32_t dataOff = *reinterpret_cast(ptr + 8); fd.pixelDataSize = *reinterpret_cast(ptr + 12); fd.pixelData = (dataOff != 0) ? (data + dataOff) : nullptr; - ptr += 16; + // Read 96 advance width bytes + for (int i = 0; i < 96; i++) { + fd.advanceWidths[i] = ptr[16 + i]; + } + ptr += 112; + } + + // ── Skip past font pixel data to reach canvas descriptors ── + // The binary layout is: [font descriptors] [font pixel data] [canvas descriptors] + // ptr is currently past the font descriptors. We need to skip the pixel data block. + // Pixel data positions are stored as absolute offsets in the descriptors. + if (fontCount > 0) { + uint32_t fontDataEnd = 0; + for (int fi = 0; fi < fontCount; fi++) { + if (m_fontDescs[fi].pixelData != nullptr && m_fontDescs[fi].pixelDataSize > 0) { + uint32_t endPos = (uint32_t)(m_fontDescs[fi].pixelData - data) + m_fontDescs[fi].pixelDataSize; + if (endPos > fontDataEnd) fontDataEnd = endPos; + } + } + if (fontDataEnd > 0) { + // Align to 4 bytes (matching the writer's AlignToFourBytes) + fontDataEnd = (fontDataEnd + 3) & ~3u; + ptr = data + fontDataEnd; + } } // ── Parse canvas descriptors ── @@ -61,7 +87,7 @@ void UISystem::loadFromSplashpack(uint8_t* data, uint16_t canvasCount, // Canvas descriptor table: 12 bytes per entry // struct { uint32_t dataOffset; uint8_t nameLen; uint8_t sortOrder; // uint8_t elementCount; uint8_t flags; uint32_t nameOffset; } - uint8_t* tablePtr = ptr; // starts right after font descriptors + uint8_t* tablePtr = ptr; // starts after font descriptors AND pixel data m_canvasCount = canvasCount; m_elementCount = 0; @@ -190,7 +216,7 @@ void UISystem::loadFromSplashpack(uint8_t* data, uint16_t canvasCount, void UISystem::resolveLayout(const UIElement& el, int16_t& outX, int16_t& outY, int16_t& outW, int16_t& outH) const { - // Anchor gives the origin point in screen space (8.8 fixed → pixel) + // Anchor gives the origin point in screen space (8.8 fixed -> pixel) int ax = ((int)el.anchorMinX * VRAM_RES_WIDTH) >> 8; int ay = ((int)el.anchorMinY * VRAM_RES_HEIGHT) >> 8; outX = (int16_t)(ax + el.x); @@ -227,7 +253,7 @@ psyqo::PrimPieces::TPageAttr UISystem::makeTPage(const UIImageData& img) { psyqo::PrimPieces::TPageAttr tpage; tpage.setPageX(img.texpageX); tpage.setPageY(img.texpageY); - // Color mode from bitDepth: 0→Tex4Bits, 1→Tex8Bits, 2→Tex16Bits + // Color mode from bitDepth: 0->Tex4Bits, 1->Tex8Bits, 2->Tex16Bits switch (img.bitDepth) { case 0: tpage.set(psyqo::Prim::TPageAttr::Tex4Bits); @@ -332,13 +358,17 @@ void UISystem::renderElement(UIElement& el, } case UIElementType::Text: { - // Queue text for phase 2 (after gpu.chain) - if (m_pendingTextCount < UI_MAX_ELEMENTS) { - uint8_t fi = (el.type == UIElementType::Text) ? el.textData.fontIndex : 0; + uint8_t fi = el.textData.fontIndex; + if (fi > 0 && fi <= m_fontCount) { + // Custom font: render proportionally into OT + renderProportionalText(fi - 1, x, y, + el.colorR, el.colorG, el.colorB, + el.textBuf, ot, balloc); + } else if (m_pendingTextCount < UI_MAX_ELEMENTS) { + // System font: queue for renderText phase (chainprintf) m_pendingTexts[m_pendingTextCount++] = { x, y, el.colorR, el.colorG, el.colorB, - fi, el.textBuf }; } @@ -372,9 +402,7 @@ void UISystem::renderOT(psyqo::GPU& gpu, void UISystem::renderText(psyqo::GPU& gpu) { for (int i = 0; i < m_pendingTextCount; i++) { auto& pt = m_pendingTexts[i]; - psyqo::FontBase* font = resolveFont(pt.fontIndex); - if (!font) continue; - font->chainprintf(gpu, + m_systemFont->chainprintf(gpu, {{.x = pt.x, .y = pt.y}}, {{.r = pt.r, .g = pt.g, .b = pt.b}}, "%s", pt.text); @@ -386,8 +414,8 @@ void UISystem::renderText(psyqo::GPU& gpu) { // ============================================================================ psyqo::FontBase* UISystem::resolveFont(uint8_t fontIndex) { - if (fontIndex == 0 || fontIndex > m_fontCount) return m_systemFont; - return &m_customFonts[fontIndex - 1]; + // Only used for system font now; custom fonts go through renderProportionalText + return m_systemFont; } void UISystem::uploadFonts(psyqo::GPU& gpu) { @@ -402,13 +430,88 @@ void UISystem::uploadFonts(psyqo::GPU& gpu) { (int16_t)fd.vramX, (int16_t)fd.vramY, 64, (int16_t)fd.textureH); - // Initialize the Font<2> instance for this custom font - m_customFonts[i].initialize(gpu, - {{.x = (int16_t)fd.vramX, .y = (int16_t)fd.vramY}}, - {{.x = (int16_t)fd.glyphW, .y = (int16_t)fd.glyphH}}); + // Upload white CLUT at font CLUT position (entry 0=transparent, entry 1=white). + // Sprite color tinting will produce the desired text color. + static const uint16_t whiteCLUT[2] = { 0x0000, 0x7FFF }; + Renderer::GetInstance().VramUpload( + whiteCLUT, + (int16_t)fd.vramX, (int16_t)fd.vramY, + 2, 1); } } +// ============================================================================ +// Proportional text rendering (custom fonts) +// ============================================================================ + +void UISystem::renderProportionalText(int fontIdx, int16_t x, int16_t y, + uint8_t r, uint8_t g, uint8_t b, + const char* text, + psyqo::OrderingTable& ot, + psyqo::BumpAllocator& balloc) { + UIFontDesc& fd = m_fontDescs[fontIdx]; + int glyphsPerRow = 256 / fd.glyphW; + uint8_t baseV = fd.vramY & 0xFF; + + // TPage for this font's texture page + psyqo::PrimPieces::TPageAttr tpageAttr; + tpageAttr.setPageX(fd.vramX >> 6); + tpageAttr.setPageY(fd.vramY >> 8); + tpageAttr.set(psyqo::Prim::TPageAttr::Tex4Bits); + tpageAttr.setDithering(false); + + // CLUT reference for this font + 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}; + + // First: insert all glyph sprites at depth 0 + 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); + + auto& frag = balloc.allocateFragment(); + frag.primitive.position = {.x = cursorX, .y = y}; + // Use advance as sprite width for proportional sizing. + // The glyph is left-aligned in the cell, so showing advance-width + // pixels captures the glyph content with correct spacing. + int16_t spriteW = (advance > 0 && advance < fd.glyphW) ? (int16_t)advance : (int16_t)fd.glyphW; + frag.primitive.size = {.x = spriteW, .y = (int16_t)fd.glyphH}; + frag.primitive.setColor(color); + psyqo::PrimPieces::TexInfo texInfo; + texInfo.u = u; + texInfo.v = v; + texInfo.clut = clutIdx; + frag.primitive.texInfo = texInfo; + ot.insert(frag, 0); + + cursorX += advance; + } + + // Then: insert TPage AFTER sprites at the same depth. + // OT uses head insertion (LIFO), so TPage ends up rendering BEFORE the sprites. + // This ensures each text element's TPage is active for its own sprites only, + // even when multiple fonts are on screen simultaneously. + auto& tpFrag = balloc.allocateFragment(); + tpFrag.primitive.attr = tpageAttr; + ot.insert(tpFrag, 0); +} + // ============================================================================ // Canvas API // ============================================================================ diff --git a/src/uisystem.hh b/src/uisystem.hh index 73c0bcb..82cb6dd 100644 --- a/src/uisystem.hh +++ b/src/uisystem.hh @@ -66,6 +66,7 @@ struct UIFontDesc { uint16_t textureH; const uint8_t* pixelData; // raw 4bpp, points into splashpack uint32_t pixelDataSize; + uint8_t advanceWidths[96]; // per-char advance (ASCII 0x20-0x7F) in pixels }; class UISystem { @@ -77,17 +78,17 @@ public: void loadFromSplashpack(uint8_t* data, uint16_t canvasCount, uint8_t fontCount, uint32_t tableOffset); - /// Upload custom font textures to VRAM and initialize Font<> instances. + /// Upload custom font textures to VRAM. /// Must be called AFTER loadFromSplashpack and BEFORE first render. void uploadFonts(psyqo::GPU& gpu); - // Phase 1: Insert OT primitives for boxes, images, progress bars. + // Phase 1: Insert OT primitives for boxes, images, progress bars, and custom font text. // Called BEFORE gpu.chain(ot) from inside the renderer. void renderOT(psyqo::GPU& gpu, psyqo::OrderingTable& ot, psyqo::BumpAllocator& balloc); - // Phase 2: Emit text via psyqo font chaining. + // Phase 2: Emit system font text via psyqo font chaining. // Called AFTER gpu.chain(ot). void renderText(psyqo::GPU& gpu); @@ -96,7 +97,7 @@ public: void setCanvasVisible(int idx, bool v); bool isCanvasVisible(int idx) const; - // Element API — returns flat handle into m_elements, or -1 + // Element API - returns flat handle into m_elements, or -1 int findElement(int canvasIdx, const char* name) const; void setElementVisible(int handle, bool v); bool isElementVisible(int handle) const; @@ -119,8 +120,6 @@ public: private: psyqo::Font<>* m_systemFont = nullptr; - // Custom fonts: up to 3, each with 4 fragments for DMA chaining - psyqo::Font<4> m_customFonts[UI_MAX_FONTS - 1]; UIFontDesc m_fontDescs[UI_MAX_FONTS - 1]; // descriptors from splashpack int m_fontCount = 0; // number of custom fonts (0-3) @@ -129,12 +128,12 @@ private: int m_canvasCount = 0; int m_elementCount = 0; - // Per-frame pending text list (filled during renderOT, flushed in renderText) - struct PendingText { int16_t x, y; uint8_t r, g, b; uint8_t fontIndex; const char* text; }; + // Pending text for system font only (custom fonts render in OT) + struct PendingText { int16_t x, y; uint8_t r, g, b; const char* text; }; PendingText m_pendingTexts[UI_MAX_ELEMENTS]; int m_pendingTextCount = 0; - /// Resolve which Font to use for a given fontIndex. + /// Resolve which Font to use for system font (fontIndex 0). psyqo::FontBase* resolveFont(uint8_t fontIndex); void resolveLayout(const UIElement& el, @@ -145,6 +144,12 @@ private: psyqo::OrderingTable& ot, psyqo::BumpAllocator& balloc); + void renderProportionalText(int fontIdx, int16_t x, int16_t y, + uint8_t r, uint8_t g, uint8_t b, + const char* text, + psyqo::OrderingTable& ot, + psyqo::BumpAllocator& balloc); + static psyqo::PrimPieces::TPageAttr makeTPage(const UIImageData& img); };