Phase 7 - Text Rendering: - Add BitmapFont with embedded 8x8 CP437 font (ASCII 32-126) - Add TextPass for instanced glyph rendering - Fix SceneCollector::parseText() to copy strings to FrameAllocator Phase 8A - Multi-texture Support: - Add numeric texture ID system in ResourceCache - SpritePass sorts by textureId and batches per texture - Flush batch on texture change for efficient rendering Phase 8B - Tilemap Rendering: - Add TilemapPass for grid-based tile rendering - Support tileData as comma-separated string - Tiles rendered as instanced quads Window Resize: - Handle window resize via process() input - Call bgfx::reset() on size change 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
331 lines
10 KiB
C++
331 lines
10 KiB
C++
#include "BitmapFont.h"
|
|
#include "../RHI/RHIDevice.h"
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
namespace grove {
|
|
|
|
// ============================================================================
|
|
// Embedded 8x8 Monospace Font Data
|
|
// Classic CP437 style bitmap font covering ASCII 32-126
|
|
// Each character is 8x8 pixels, stored as 8 bytes (1 bit per pixel)
|
|
// ============================================================================
|
|
|
|
// clang-format off
|
|
static const uint8_t g_fontData8x8[] = {
|
|
// 32 SPACE
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 33 !
|
|
0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00,
|
|
// 34 "
|
|
0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 35 #
|
|
0x6C, 0x6C, 0xFE, 0x6C, 0xFE, 0x6C, 0x6C, 0x00,
|
|
// 36 $
|
|
0x18, 0x7E, 0xC0, 0x7C, 0x06, 0xFC, 0x18, 0x00,
|
|
// 37 %
|
|
0x00, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0x00,
|
|
// 38 &
|
|
0x38, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0x76, 0x00,
|
|
// 39 '
|
|
0x30, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 40 (
|
|
0x0C, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00,
|
|
// 41 )
|
|
0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00,
|
|
// 42 *
|
|
0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00,
|
|
// 43 +
|
|
0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00,
|
|
// 44 ,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30,
|
|
// 45 -
|
|
0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
|
// 46 .
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00,
|
|
// 47 /
|
|
0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00,
|
|
// 48 0
|
|
0x7C, 0xCE, 0xDE, 0xF6, 0xE6, 0xC6, 0x7C, 0x00,
|
|
// 49 1
|
|
0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00,
|
|
// 50 2
|
|
0x7C, 0xC6, 0x06, 0x7C, 0xC0, 0xC0, 0xFE, 0x00,
|
|
// 51 3
|
|
0xFC, 0x06, 0x06, 0x3C, 0x06, 0x06, 0xFC, 0x00,
|
|
// 52 4
|
|
0x0C, 0xCC, 0xCC, 0xCC, 0xFE, 0x0C, 0x0C, 0x00,
|
|
// 53 5
|
|
0xFE, 0xC0, 0xFC, 0x06, 0x06, 0xC6, 0x7C, 0x00,
|
|
// 54 6
|
|
0x7C, 0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0x7C, 0x00,
|
|
// 55 7
|
|
0xFE, 0x06, 0x06, 0x0C, 0x18, 0x18, 0x18, 0x00,
|
|
// 56 8
|
|
0x7C, 0xC6, 0xC6, 0x7C, 0xC6, 0xC6, 0x7C, 0x00,
|
|
// 57 9
|
|
0x7C, 0xC6, 0xC6, 0x7E, 0x06, 0x06, 0x7C, 0x00,
|
|
// 58 :
|
|
0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00,
|
|
// 59 ;
|
|
0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x30,
|
|
// 60 <
|
|
0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00,
|
|
// 61 =
|
|
0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00,
|
|
// 62 >
|
|
0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00,
|
|
// 63 ?
|
|
0x7C, 0xC6, 0x0C, 0x18, 0x18, 0x00, 0x18, 0x00,
|
|
// 64 @
|
|
0x7C, 0xC6, 0xDE, 0xDE, 0xDE, 0xC0, 0x7C, 0x00,
|
|
// 65 A
|
|
0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0x00,
|
|
// 66 B
|
|
0xFC, 0xC6, 0xC6, 0xFC, 0xC6, 0xC6, 0xFC, 0x00,
|
|
// 67 C
|
|
0x7C, 0xC6, 0xC0, 0xC0, 0xC0, 0xC6, 0x7C, 0x00,
|
|
// 68 D
|
|
0xF8, 0xCC, 0xC6, 0xC6, 0xC6, 0xCC, 0xF8, 0x00,
|
|
// 69 E
|
|
0xFE, 0xC0, 0xC0, 0xF8, 0xC0, 0xC0, 0xFE, 0x00,
|
|
// 70 F
|
|
0xFE, 0xC0, 0xC0, 0xF8, 0xC0, 0xC0, 0xC0, 0x00,
|
|
// 71 G
|
|
0x7C, 0xC6, 0xC0, 0xCE, 0xC6, 0xC6, 0x7E, 0x00,
|
|
// 72 H
|
|
0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0x00,
|
|
// 73 I
|
|
0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00,
|
|
// 74 J
|
|
0x06, 0x06, 0x06, 0x06, 0xC6, 0xC6, 0x7C, 0x00,
|
|
// 75 K
|
|
0xC6, 0xCC, 0xD8, 0xF0, 0xD8, 0xCC, 0xC6, 0x00,
|
|
// 76 L
|
|
0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFE, 0x00,
|
|
// 77 M
|
|
0xC6, 0xEE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0x00,
|
|
// 78 N
|
|
0xC6, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0xC6, 0x00,
|
|
// 79 O
|
|
0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00,
|
|
// 80 P
|
|
0xFC, 0xC6, 0xC6, 0xFC, 0xC0, 0xC0, 0xC0, 0x00,
|
|
// 81 Q
|
|
0x7C, 0xC6, 0xC6, 0xC6, 0xD6, 0xDE, 0x7C, 0x06,
|
|
// 82 R
|
|
0xFC, 0xC6, 0xC6, 0xFC, 0xD8, 0xCC, 0xC6, 0x00,
|
|
// 83 S
|
|
0x7C, 0xC6, 0xC0, 0x7C, 0x06, 0xC6, 0x7C, 0x00,
|
|
// 84 T
|
|
0xFE, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00,
|
|
// 85 U
|
|
0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00,
|
|
// 86 V
|
|
0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0x00,
|
|
// 87 W
|
|
0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00,
|
|
// 88 X
|
|
0xC6, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0xC6, 0x00,
|
|
// 89 Y
|
|
0xC6, 0xC6, 0x6C, 0x38, 0x18, 0x18, 0x18, 0x00,
|
|
// 90 Z
|
|
0xFE, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFE, 0x00,
|
|
// 91 [
|
|
0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00,
|
|
// 92 backslash
|
|
0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00,
|
|
// 93 ]
|
|
0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00,
|
|
// 94 ^
|
|
0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00,
|
|
// 95 _
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE,
|
|
// 96 `
|
|
0x18, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 97 a
|
|
0x00, 0x00, 0x7C, 0x06, 0x7E, 0xC6, 0x7E, 0x00,
|
|
// 98 b
|
|
0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0xFC, 0x00,
|
|
// 99 c
|
|
0x00, 0x00, 0x7C, 0xC6, 0xC0, 0xC6, 0x7C, 0x00,
|
|
// 100 d
|
|
0x06, 0x06, 0x7E, 0xC6, 0xC6, 0xC6, 0x7E, 0x00,
|
|
// 101 e
|
|
0x00, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0x7C, 0x00,
|
|
// 102 f
|
|
0x1C, 0x36, 0x30, 0x78, 0x30, 0x30, 0x30, 0x00,
|
|
// 103 g
|
|
0x00, 0x00, 0x7E, 0xC6, 0xC6, 0x7E, 0x06, 0x7C,
|
|
// 104 h
|
|
0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0xC6, 0x00,
|
|
// 105 i
|
|
0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3C, 0x00,
|
|
// 106 j
|
|
0x0C, 0x00, 0x0C, 0x0C, 0x0C, 0x0C, 0xCC, 0x78,
|
|
// 107 k
|
|
0xC0, 0xC0, 0xCC, 0xD8, 0xF0, 0xD8, 0xCC, 0x00,
|
|
// 108 l
|
|
0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00,
|
|
// 109 m
|
|
0x00, 0x00, 0xEC, 0xFE, 0xD6, 0xC6, 0xC6, 0x00,
|
|
// 110 n
|
|
0x00, 0x00, 0xFC, 0xC6, 0xC6, 0xC6, 0xC6, 0x00,
|
|
// 111 o
|
|
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x00,
|
|
// 112 p
|
|
0x00, 0x00, 0xFC, 0xC6, 0xC6, 0xFC, 0xC0, 0xC0,
|
|
// 113 q
|
|
0x00, 0x00, 0x7E, 0xC6, 0xC6, 0x7E, 0x06, 0x06,
|
|
// 114 r
|
|
0x00, 0x00, 0xDC, 0xE6, 0xC0, 0xC0, 0xC0, 0x00,
|
|
// 115 s
|
|
0x00, 0x00, 0x7E, 0xC0, 0x7C, 0x06, 0xFC, 0x00,
|
|
// 116 t
|
|
0x30, 0x30, 0x7C, 0x30, 0x30, 0x36, 0x1C, 0x00,
|
|
// 117 u
|
|
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, 0x00,
|
|
// 118 v
|
|
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00,
|
|
// 119 w
|
|
0x00, 0x00, 0xC6, 0xC6, 0xD6, 0xFE, 0x6C, 0x00,
|
|
// 120 x
|
|
0x00, 0x00, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0x00,
|
|
// 121 y
|
|
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x7C,
|
|
// 122 z
|
|
0x00, 0x00, 0xFE, 0x0C, 0x38, 0x60, 0xFE, 0x00,
|
|
// 123 {
|
|
0x0E, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0E, 0x00,
|
|
// 124 |
|
|
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00,
|
|
// 125 }
|
|
0x70, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x70, 0x00,
|
|
// 126 ~
|
|
0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
// clang-format on
|
|
|
|
static constexpr int FONT_FIRST_CHAR = 32; // Space
|
|
static constexpr int FONT_CHAR_COUNT = 95; // 32-126
|
|
static constexpr int FONT_GLYPH_SIZE = 8; // 8x8 pixels
|
|
|
|
bool BitmapFont::initDefault(rhi::IRHIDevice& device) {
|
|
// Create 128x64 atlas (16 chars per row, 8 rows = 128 chars max)
|
|
// We only use 95 chars (ASCII 32-126)
|
|
m_atlasWidth = 128; // 16 chars * 8 pixels
|
|
m_atlasHeight = 64; // 8 rows * 8 pixels
|
|
|
|
// Create RGBA texture data
|
|
const int atlasPixels = m_atlasWidth * m_atlasHeight;
|
|
std::vector<uint32_t> atlasData(atlasPixels, 0);
|
|
|
|
// Render each character into the atlas
|
|
for (int charIdx = 0; charIdx < FONT_CHAR_COUNT; ++charIdx) {
|
|
int col = charIdx % 16;
|
|
int row = charIdx / 16;
|
|
int baseX = col * FONT_GLYPH_SIZE;
|
|
int baseY = row * FONT_GLYPH_SIZE;
|
|
|
|
const uint8_t* glyphData = &g_fontData8x8[charIdx * 8];
|
|
|
|
for (int y = 0; y < 8; ++y) {
|
|
uint8_t rowBits = glyphData[y];
|
|
for (int x = 0; x < 8; ++x) {
|
|
// MSB first: bit 7 is leftmost pixel
|
|
bool pixel = (rowBits & (0x80 >> x)) != 0;
|
|
int atlasX = baseX + x;
|
|
int atlasY = baseY + y;
|
|
int idx = atlasY * m_atlasWidth + atlasX;
|
|
// White pixel with alpha = 255 if set, 0 if not
|
|
atlasData[idx] = pixel ? 0xFFFFFFFF : 0x00000000;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create GPU texture
|
|
rhi::TextureDesc texDesc;
|
|
texDesc.width = m_atlasWidth;
|
|
texDesc.height = m_atlasHeight;
|
|
texDesc.format = rhi::TextureDesc::RGBA8;
|
|
texDesc.data = atlasData.data();
|
|
texDesc.dataSize = atlasPixels * sizeof(uint32_t);
|
|
m_texture = device.createTexture(texDesc);
|
|
|
|
if (!m_texture.isValid()) {
|
|
return false;
|
|
}
|
|
|
|
// Generate glyph info
|
|
generateDefaultGlyphs();
|
|
|
|
m_baseSize = 8.0f;
|
|
m_lineHeight = 8.0f;
|
|
|
|
return true;
|
|
}
|
|
|
|
void BitmapFont::generateDefaultGlyphs() {
|
|
float invW = 1.0f / m_atlasWidth;
|
|
float invH = 1.0f / m_atlasHeight;
|
|
|
|
for (int charIdx = 0; charIdx < FONT_CHAR_COUNT; ++charIdx) {
|
|
uint32_t codepoint = FONT_FIRST_CHAR + charIdx;
|
|
|
|
int col = charIdx % 16;
|
|
int row = charIdx / 16;
|
|
|
|
GlyphInfo glyph;
|
|
glyph.u0 = col * FONT_GLYPH_SIZE * invW;
|
|
glyph.v0 = row * FONT_GLYPH_SIZE * invH;
|
|
glyph.u1 = (col + 1) * FONT_GLYPH_SIZE * invW;
|
|
glyph.v1 = (row + 1) * FONT_GLYPH_SIZE * invH;
|
|
glyph.width = FONT_GLYPH_SIZE;
|
|
glyph.height = FONT_GLYPH_SIZE;
|
|
glyph.offsetX = 0.0f;
|
|
glyph.offsetY = 0.0f;
|
|
glyph.advance = FONT_GLYPH_SIZE; // Monospace: fixed advance
|
|
|
|
m_glyphs[codepoint] = glyph;
|
|
}
|
|
|
|
// Default glyph for unknown characters (use '?' which is ASCII 63)
|
|
m_defaultGlyph = m_glyphs['?'];
|
|
}
|
|
|
|
bool BitmapFont::loadBMFont(rhi::IRHIDevice& device, const std::string& fntPath, const std::string& pngPath) {
|
|
// TODO: Implement BMFont loader if needed
|
|
// For now, fall back to default font
|
|
return initDefault(device);
|
|
}
|
|
|
|
void BitmapFont::shutdown(rhi::IRHIDevice& device) {
|
|
if (m_texture.isValid()) {
|
|
device.destroy(m_texture);
|
|
m_texture = rhi::TextureHandle();
|
|
}
|
|
m_glyphs.clear();
|
|
}
|
|
|
|
const GlyphInfo& BitmapFont::getGlyph(uint32_t codepoint) const {
|
|
auto it = m_glyphs.find(codepoint);
|
|
if (it != m_glyphs.end()) {
|
|
return it->second;
|
|
}
|
|
return m_defaultGlyph;
|
|
}
|
|
|
|
float BitmapFont::measureWidth(const char* text) const {
|
|
if (!text) return 0.0f;
|
|
|
|
float width = 0.0f;
|
|
while (*text) {
|
|
const GlyphInfo& glyph = getGlyph(static_cast<uint8_t>(*text));
|
|
width += glyph.advance;
|
|
++text;
|
|
}
|
|
return width;
|
|
}
|
|
|
|
} // namespace grove
|