#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(textCmd.fontSize) / m_font.getBaseSize(); // Extract color components uint32_t color = textCmd.color; float r = static_cast((color >> 24) & 0xFF) / 255.0f; float g = static_cast((color >> 16) & 0xFF) / 255.0f; float b = static_cast((color >> 8) & 0xFF) / 255.0f; float a = static_cast(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(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(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(m_glyphInstances.size() * sizeof(SpriteInstance))); cmd.setVertexBuffer(m_quadVB); cmd.setIndexBuffer(m_quadIB); cmd.setInstanceBuffer(m_instanceBuffer, 0, static_cast(m_glyphInstances.size())); cmd.setTexture(0, m_font.getTexture(), m_textureSampler); cmd.drawInstanced(6, static_cast(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(m_glyphInstances.size() * sizeof(SpriteInstance))); cmd.setVertexBuffer(m_quadVB); cmd.setIndexBuffer(m_quadIB); cmd.setInstanceBuffer(m_instanceBuffer, 0, static_cast(m_glyphInstances.size())); cmd.setTexture(0, m_font.getTexture(), m_textureSampler); cmd.drawInstanced(6, static_cast(m_glyphInstances.size())); cmd.submit(0, m_shader, 0); } } } // namespace grove