GroveEngine/modules/BgfxRenderer/Passes/TextPass.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

194 lines
6.4 KiB
C++

#include "TextPass.h"
#include "../RHI/RHIDevice.h"
#include "../Frame/FramePacket.h"
namespace grove {
TextPass::TextPass(rhi::ShaderHandle shader)
: m_shader(shader)
{
m_glyphInstances.reserve(MAX_GLYPHS_PER_BATCH);
}
void TextPass::setup(rhi::IRHIDevice& device) {
// Initialize default bitmap font
if (!m_font.initDefault(device)) {
// Font init failed - text rendering will be disabled
return;
}
// Create quad vertex buffer (unit quad, instanced) - same as SpritePass
float quadVertices[] = {
// pos.x, pos.y, pos.z, r, g, b, a
0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, // bottom-left
1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, // bottom-right
1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, // top-right
0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, // top-left
};
rhi::BufferDesc vbDesc;
vbDesc.type = rhi::BufferDesc::Vertex;
vbDesc.size = sizeof(quadVertices);
vbDesc.data = quadVertices;
vbDesc.dynamic = false;
vbDesc.layout = rhi::BufferDesc::PosColor;
m_quadVB = device.createBuffer(vbDesc);
// Create index buffer
uint16_t quadIndices[] = {
0, 1, 2,
0, 2, 3
};
rhi::BufferDesc ibDesc;
ibDesc.type = rhi::BufferDesc::Index;
ibDesc.size = sizeof(quadIndices);
ibDesc.data = quadIndices;
ibDesc.dynamic = false;
m_quadIB = device.createBuffer(ibDesc);
// Create dynamic instance buffer for glyphs
rhi::BufferDesc instDesc;
instDesc.type = rhi::BufferDesc::Instance;
instDesc.size = MAX_GLYPHS_PER_BATCH * sizeof(SpriteInstance);
instDesc.data = nullptr;
instDesc.dynamic = true;
m_instanceBuffer = device.createBuffer(instDesc);
// Create texture sampler uniform
m_textureSampler = device.createUniform("s_texColor", 1);
}
void TextPass::shutdown(rhi::IRHIDevice& device) {
device.destroy(m_quadVB);
device.destroy(m_quadIB);
device.destroy(m_instanceBuffer);
device.destroy(m_textureSampler);
m_font.shutdown(device);
}
void TextPass::execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi::RHICommandBuffer& cmd) {
if (frame.textCount == 0 || !m_font.isValid()) {
return;
}
// Set render state for text (alpha blending, no depth)
rhi::RenderState state;
state.blend = rhi::BlendMode::Alpha;
state.cull = rhi::CullMode::None;
state.depthTest = false;
state.depthWrite = false;
cmd.setState(state);
m_glyphInstances.clear();
// Convert each TextCommand into glyph instances
for (size_t i = 0; i < frame.textCount; ++i) {
const TextCommand& textCmd = frame.texts[i];
if (!textCmd.text) continue;
// Calculate scale factor based on font size
float scale = static_cast<float>(textCmd.fontSize) / m_font.getBaseSize();
// Extract color components
uint32_t color = textCmd.color;
float r = static_cast<float>((color >> 24) & 0xFF) / 255.0f;
float g = static_cast<float>((color >> 16) & 0xFF) / 255.0f;
float b = static_cast<float>((color >> 8) & 0xFF) / 255.0f;
float a = static_cast<float>(color & 0xFF) / 255.0f;
float cursorX = textCmd.x;
float cursorY = textCmd.y;
const char* ptr = textCmd.text;
while (*ptr) {
char ch = *ptr++;
// Handle newline
if (ch == '\n') {
cursorX = textCmd.x;
cursorY += m_font.getLineHeight() * scale;
continue;
}
const GlyphInfo& glyph = m_font.getGlyph(static_cast<uint8_t>(ch));
// Create sprite instance for this glyph
SpriteInstance inst;
// Position (top-left of glyph)
inst.x = cursorX + glyph.offsetX * scale;
inst.y = cursorY + glyph.offsetY * scale;
// Scale to glyph size
inst.scaleX = glyph.width * scale;
inst.scaleY = glyph.height * scale;
// No rotation
inst.rotation = 0.0f;
// UVs from font atlas
inst.u0 = glyph.u0;
inst.v0 = glyph.v0;
inst.u1 = glyph.u1;
inst.v1 = glyph.v1;
// Texture ID (font atlas = 0)
inst.textureId = 0.0f;
// Layer
inst.layer = static_cast<float>(textCmd.layer);
// Padding/reserved
inst.padding0 = 0.0f;
inst.reserved[0] = 0.0f;
inst.reserved[1] = 0.0f;
inst.reserved[2] = 0.0f;
inst.reserved[3] = 0.0f;
// Color
inst.r = r;
inst.g = g;
inst.b = b;
inst.a = a;
m_glyphInstances.push_back(inst);
// Advance cursor
cursorX += glyph.advance * scale;
// Check batch limit
if (m_glyphInstances.size() >= MAX_GLYPHS_PER_BATCH) {
// Flush current batch
device.updateBuffer(m_instanceBuffer, m_glyphInstances.data(),
static_cast<uint32_t>(m_glyphInstances.size() * sizeof(SpriteInstance)));
cmd.setVertexBuffer(m_quadVB);
cmd.setIndexBuffer(m_quadIB);
cmd.setInstanceBuffer(m_instanceBuffer, 0, static_cast<uint32_t>(m_glyphInstances.size()));
cmd.setTexture(0, m_font.getTexture(), m_textureSampler);
cmd.drawInstanced(6, static_cast<uint32_t>(m_glyphInstances.size()));
cmd.submit(0, m_shader, 0);
m_glyphInstances.clear();
}
}
}
// Submit remaining glyphs
if (!m_glyphInstances.empty()) {
device.updateBuffer(m_instanceBuffer, m_glyphInstances.data(),
static_cast<uint32_t>(m_glyphInstances.size() * sizeof(SpriteInstance)));
cmd.setVertexBuffer(m_quadVB);
cmd.setIndexBuffer(m_quadIB);
cmd.setInstanceBuffer(m_instanceBuffer, 0, static_cast<uint32_t>(m_glyphInstances.size()));
cmd.setTexture(0, m_font.getTexture(), m_textureSampler);
cmd.drawInstanced(6, static_cast<uint32_t>(m_glyphInstances.size()));
cmd.submit(0, m_shader, 0);
}
}
} // namespace grove