GroveEngine/modules/BgfxRenderer/Text/BitmapFont.cpp
StillHammer 613283d75c feat(BgfxRenderer): Phase 7-8 - Text, Tilemap, Multi-texture, Resize
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>
2025-11-27 22:09:48 +08:00

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