Somewhat fixed ui

This commit is contained in:
Jan Racek
2026-03-25 17:14:08 +01:00
parent f485ec36a8
commit 37ba4c85fe
2 changed files with 135 additions and 27 deletions

View File

@@ -4,6 +4,7 @@
#include <psyqo/primitives/common.hh> #include <psyqo/primitives/common.hh>
#include <psyqo/primitives/misc.hh> #include <psyqo/primitives/misc.hh>
#include <psyqo/primitives/rectangles.hh> #include <psyqo/primitives/rectangles.hh>
#include <psyqo/primitives/sprites.hh>
#include <psyqo/primitives/triangles.hh> #include <psyqo/primitives/triangles.hh>
namespace psxsplash { namespace psxsplash {
@@ -38,7 +39,9 @@ void UISystem::loadFromSplashpack(uint8_t* data, uint16_t canvasCount,
uint8_t* ptr = data + tableOffset; 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; if (fontCount > UI_MAX_FONTS - 1) fontCount = UI_MAX_FONTS - 1;
m_fontCount = fontCount; m_fontCount = fontCount;
for (int fi = 0; fi < fontCount; fi++) { 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<uint32_t*>(ptr + 8); uint32_t dataOff = *reinterpret_cast<uint32_t*>(ptr + 8);
fd.pixelDataSize = *reinterpret_cast<uint32_t*>(ptr + 12); fd.pixelDataSize = *reinterpret_cast<uint32_t*>(ptr + 12);
fd.pixelData = (dataOff != 0) ? (data + dataOff) : nullptr; 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 ── // ── Parse canvas descriptors ──
@@ -61,7 +87,7 @@ void UISystem::loadFromSplashpack(uint8_t* data, uint16_t canvasCount,
// Canvas descriptor table: 12 bytes per entry // Canvas descriptor table: 12 bytes per entry
// struct { uint32_t dataOffset; uint8_t nameLen; uint8_t sortOrder; // struct { uint32_t dataOffset; uint8_t nameLen; uint8_t sortOrder;
// uint8_t elementCount; uint8_t flags; uint32_t nameOffset; } // 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_canvasCount = canvasCount;
m_elementCount = 0; m_elementCount = 0;
@@ -190,7 +216,7 @@ void UISystem::loadFromSplashpack(uint8_t* data, uint16_t canvasCount,
void UISystem::resolveLayout(const UIElement& el, void UISystem::resolveLayout(const UIElement& el,
int16_t& outX, int16_t& outY, int16_t& outX, int16_t& outY,
int16_t& outW, int16_t& outH) const { 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 ax = ((int)el.anchorMinX * VRAM_RES_WIDTH) >> 8;
int ay = ((int)el.anchorMinY * VRAM_RES_HEIGHT) >> 8; int ay = ((int)el.anchorMinY * VRAM_RES_HEIGHT) >> 8;
outX = (int16_t)(ax + el.x); outX = (int16_t)(ax + el.x);
@@ -227,7 +253,7 @@ psyqo::PrimPieces::TPageAttr UISystem::makeTPage(const UIImageData& img) {
psyqo::PrimPieces::TPageAttr tpage; psyqo::PrimPieces::TPageAttr tpage;
tpage.setPageX(img.texpageX); tpage.setPageX(img.texpageX);
tpage.setPageY(img.texpageY); tpage.setPageY(img.texpageY);
// Color mode from bitDepth: 0Tex4Bits, 1Tex8Bits, 2Tex16Bits // Color mode from bitDepth: 0->Tex4Bits, 1->Tex8Bits, 2->Tex16Bits
switch (img.bitDepth) { switch (img.bitDepth) {
case 0: case 0:
tpage.set(psyqo::Prim::TPageAttr::Tex4Bits); tpage.set(psyqo::Prim::TPageAttr::Tex4Bits);
@@ -332,13 +358,17 @@ void UISystem::renderElement(UIElement& el,
} }
case UIElementType::Text: { case UIElementType::Text: {
// Queue text for phase 2 (after gpu.chain) uint8_t fi = el.textData.fontIndex;
if (m_pendingTextCount < UI_MAX_ELEMENTS) { if (fi > 0 && fi <= m_fontCount) {
uint8_t fi = (el.type == UIElementType::Text) ? el.textData.fontIndex : 0; // 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++] = { m_pendingTexts[m_pendingTextCount++] = {
x, y, x, y,
el.colorR, el.colorG, el.colorB, el.colorR, el.colorG, el.colorB,
fi,
el.textBuf el.textBuf
}; };
} }
@@ -372,9 +402,7 @@ void UISystem::renderOT(psyqo::GPU& gpu,
void UISystem::renderText(psyqo::GPU& gpu) { void UISystem::renderText(psyqo::GPU& gpu) {
for (int i = 0; i < m_pendingTextCount; i++) { for (int i = 0; i < m_pendingTextCount; i++) {
auto& pt = m_pendingTexts[i]; auto& pt = m_pendingTexts[i];
psyqo::FontBase* font = resolveFont(pt.fontIndex); m_systemFont->chainprintf(gpu,
if (!font) continue;
font->chainprintf(gpu,
{{.x = pt.x, .y = pt.y}}, {{.x = pt.x, .y = pt.y}},
{{.r = pt.r, .g = pt.g, .b = pt.b}}, {{.r = pt.r, .g = pt.g, .b = pt.b}},
"%s", pt.text); "%s", pt.text);
@@ -386,8 +414,8 @@ void UISystem::renderText(psyqo::GPU& gpu) {
// ============================================================================ // ============================================================================
psyqo::FontBase* UISystem::resolveFont(uint8_t fontIndex) { psyqo::FontBase* UISystem::resolveFont(uint8_t fontIndex) {
if (fontIndex == 0 || fontIndex > m_fontCount) return m_systemFont; // Only used for system font now; custom fonts go through renderProportionalText
return &m_customFonts[fontIndex - 1]; return m_systemFont;
} }
void UISystem::uploadFonts(psyqo::GPU& gpu) { void UISystem::uploadFonts(psyqo::GPU& gpu) {
@@ -402,13 +430,88 @@ void UISystem::uploadFonts(psyqo::GPU& gpu) {
(int16_t)fd.vramX, (int16_t)fd.vramY, (int16_t)fd.vramX, (int16_t)fd.vramY,
64, (int16_t)fd.textureH); 64, (int16_t)fd.textureH);
// Initialize the Font<2> instance for this custom font // Upload white CLUT at font CLUT position (entry 0=transparent, entry 1=white).
m_customFonts[i].initialize(gpu, // Sprite color tinting will produce the desired text color.
{{.x = (int16_t)fd.vramX, .y = (int16_t)fd.vramY}}, static const uint16_t whiteCLUT[2] = { 0x0000, 0x7FFF };
{{.x = (int16_t)fd.glyphW, .y = (int16_t)fd.glyphH}}); 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<Renderer::ORDERING_TABLE_SIZE>& ot,
psyqo::BumpAllocator<Renderer::BUMP_ALLOCATOR_SIZE>& 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<psyqo::Prim::Sprite>();
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<psyqo::Prim::TPage>();
tpFrag.primitive.attr = tpageAttr;
ot.insert(tpFrag, 0);
}
// ============================================================================ // ============================================================================
// Canvas API // Canvas API
// ============================================================================ // ============================================================================

View File

@@ -66,6 +66,7 @@ struct UIFontDesc {
uint16_t textureH; uint16_t textureH;
const uint8_t* pixelData; // raw 4bpp, points into splashpack const uint8_t* pixelData; // raw 4bpp, points into splashpack
uint32_t pixelDataSize; uint32_t pixelDataSize;
uint8_t advanceWidths[96]; // per-char advance (ASCII 0x20-0x7F) in pixels
}; };
class UISystem { class UISystem {
@@ -77,17 +78,17 @@ public:
void loadFromSplashpack(uint8_t* data, uint16_t canvasCount, void loadFromSplashpack(uint8_t* data, uint16_t canvasCount,
uint8_t fontCount, uint32_t tableOffset); 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. /// Must be called AFTER loadFromSplashpack and BEFORE first render.
void uploadFonts(psyqo::GPU& gpu); 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. // Called BEFORE gpu.chain(ot) from inside the renderer.
void renderOT(psyqo::GPU& gpu, void renderOT(psyqo::GPU& gpu,
psyqo::OrderingTable<Renderer::ORDERING_TABLE_SIZE>& ot, psyqo::OrderingTable<Renderer::ORDERING_TABLE_SIZE>& ot,
psyqo::BumpAllocator<Renderer::BUMP_ALLOCATOR_SIZE>& balloc); psyqo::BumpAllocator<Renderer::BUMP_ALLOCATOR_SIZE>& balloc);
// Phase 2: Emit text via psyqo font chaining. // Phase 2: Emit system font text via psyqo font chaining.
// Called AFTER gpu.chain(ot). // Called AFTER gpu.chain(ot).
void renderText(psyqo::GPU& gpu); void renderText(psyqo::GPU& gpu);
@@ -96,7 +97,7 @@ public:
void setCanvasVisible(int idx, bool v); void setCanvasVisible(int idx, bool v);
bool isCanvasVisible(int idx) const; 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; int findElement(int canvasIdx, const char* name) const;
void setElementVisible(int handle, bool v); void setElementVisible(int handle, bool v);
bool isElementVisible(int handle) const; bool isElementVisible(int handle) const;
@@ -119,8 +120,6 @@ public:
private: private:
psyqo::Font<>* m_systemFont = nullptr; 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 UIFontDesc m_fontDescs[UI_MAX_FONTS - 1]; // descriptors from splashpack
int m_fontCount = 0; // number of custom fonts (0-3) int m_fontCount = 0; // number of custom fonts (0-3)
@@ -129,12 +128,12 @@ private:
int m_canvasCount = 0; int m_canvasCount = 0;
int m_elementCount = 0; int m_elementCount = 0;
// Per-frame pending text list (filled during renderOT, flushed in renderText) // Pending text for system font only (custom fonts render in OT)
struct PendingText { int16_t x, y; uint8_t r, g, b; uint8_t fontIndex; const char* text; }; struct PendingText { int16_t x, y; uint8_t r, g, b; const char* text; };
PendingText m_pendingTexts[UI_MAX_ELEMENTS]; PendingText m_pendingTexts[UI_MAX_ELEMENTS];
int m_pendingTextCount = 0; 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); psyqo::FontBase* resolveFont(uint8_t fontIndex);
void resolveLayout(const UIElement& el, void resolveLayout(const UIElement& el,
@@ -145,6 +144,12 @@ private:
psyqo::OrderingTable<Renderer::ORDERING_TABLE_SIZE>& ot, psyqo::OrderingTable<Renderer::ORDERING_TABLE_SIZE>& ot,
psyqo::BumpAllocator<Renderer::BUMP_ALLOCATOR_SIZE>& balloc); psyqo::BumpAllocator<Renderer::BUMP_ALLOCATOR_SIZE>& 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<Renderer::ORDERING_TABLE_SIZE>& ot,
psyqo::BumpAllocator<Renderer::BUMP_ALLOCATOR_SIZE>& balloc);
static psyqo::PrimPieces::TPageAttr makeTPage(const UIImageData& img); static psyqo::PrimPieces::TPageAttr makeTPage(const UIImageData& img);
}; };