Somewhat fixed ui
This commit is contained in:
139
src/uisystem.cpp
139
src/uisystem.cpp
@@ -4,6 +4,7 @@
|
||||
#include <psyqo/primitives/common.hh>
|
||||
#include <psyqo/primitives/misc.hh>
|
||||
#include <psyqo/primitives/rectangles.hh>
|
||||
#include <psyqo/primitives/sprites.hh>
|
||||
#include <psyqo/primitives/triangles.hh>
|
||||
|
||||
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<uint32_t*>(ptr + 8);
|
||||
fd.pixelDataSize = *reinterpret_cast<uint32_t*>(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<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
|
||||
// ============================================================================
|
||||
|
||||
@@ -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<Renderer::ORDERING_TABLE_SIZE>& ot,
|
||||
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).
|
||||
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<Renderer::ORDERING_TABLE_SIZE>& ot,
|
||||
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);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user