#include "SpritePass.h" #include "../RHI/RHIDevice.h" #include "../Frame/FramePacket.h" #include "../Resources/ResourceCache.h" #include #include namespace grove { SpritePass::SpritePass(rhi::ShaderHandle shader) : m_shader(shader) { m_sortedIndices.reserve(MAX_SPRITES_PER_BATCH); } void SpritePass::setup(rhi::IRHIDevice& device) { // Create quad vertex buffer (unit quad, instanced) // Layout must match shader: a_position (vec3) + a_color0 (vec4) // Note: Color is white (1,1,1,1) - actual color comes from instance data 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; // Match shader: a_position + a_color0 m_quadVB = device.createBuffer(vbDesc); // Create index buffer uint16_t quadIndices[] = { 0, 1, 2, // first triangle 0, 2, 3 // second triangle }; rhi::BufferDesc ibDesc; ibDesc.type = rhi::BufferDesc::Index; ibDesc.size = sizeof(quadIndices); ibDesc.data = quadIndices; ibDesc.dynamic = false; m_quadIB = device.createBuffer(ibDesc); // Note: We no longer create a persistent instance buffer since we use transient buffers // But keep it for fallback if transient allocation fails rhi::BufferDesc instDesc; instDesc.type = rhi::BufferDesc::Instance; instDesc.size = MAX_SPRITES_PER_BATCH * sizeof(SpriteInstance); instDesc.data = nullptr; instDesc.dynamic = true; m_instanceBuffer = device.createBuffer(instDesc); // Create texture sampler uniform (must match shader: s_texColor) m_textureSampler = device.createUniform("s_texColor", 1); // Create default white 1x1 texture (used when no texture is bound) uint32_t whitePixel = 0xFFFFFFFF; // RGBA white rhi::TextureDesc texDesc; texDesc.width = 1; texDesc.height = 1; texDesc.format = rhi::TextureDesc::RGBA8; texDesc.data = &whitePixel; texDesc.dataSize = sizeof(whitePixel); m_defaultTexture = device.createTexture(texDesc); } void SpritePass::shutdown(rhi::IRHIDevice& device) { device.destroy(m_quadVB); device.destroy(m_quadIB); device.destroy(m_instanceBuffer); device.destroy(m_textureSampler); device.destroy(m_defaultTexture); // Note: m_shader is owned by ShaderManager, not destroyed here } void SpritePass::flushBatch(rhi::IRHIDevice& device, rhi::RHICommandBuffer& cmd, rhi::TextureHandle texture, uint32_t count) { if (count == 0) return; cmd.setVertexBuffer(m_quadVB); cmd.setIndexBuffer(m_quadIB); // Note: Instance buffer should be set before calling this cmd.setTexture(0, texture, m_textureSampler); cmd.drawInstanced(6, count); cmd.submit(0, m_shader, 0); } void SpritePass::execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi::RHICommandBuffer& cmd) { if (frame.spriteCount == 0) { return; } // Set render state for sprites (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); // Build sorted indices by layer (primary) and textureId (secondary) for batching m_sortedIndices.clear(); m_sortedIndices.reserve(frame.spriteCount); for (size_t i = 0; i < frame.spriteCount; ++i) { m_sortedIndices.push_back(static_cast(i)); } // Sort by layer first (ascending: layer 0 = background, rendered first) // Then by textureId to batch sprites on the same layer std::sort(m_sortedIndices.begin(), m_sortedIndices.end(), [&frame](uint32_t a, uint32_t b) { const SpriteInstance& sa = frame.sprites[a]; const SpriteInstance& sb = frame.sprites[b]; if (sa.layer != sb.layer) { return sa.layer < sb.layer; } return sa.textureId < sb.textureId; }); // Process sprites in batches by texture // Use transient buffers for proper multi-batch rendering uint32_t batchStart = 0; while (batchStart < frame.spriteCount) { // Find the end of current batch (same texture) uint16_t currentTexId = static_cast(frame.sprites[m_sortedIndices[batchStart]].textureId); uint32_t batchEnd = batchStart + 1; while (batchEnd < frame.spriteCount) { uint16_t nextTexId = static_cast(frame.sprites[m_sortedIndices[batchEnd]].textureId); if (nextTexId != currentTexId) { break; // Texture changed, flush this batch } ++batchEnd; } uint32_t batchCount = batchEnd - batchStart; // Resolve texture handle for this batch rhi::TextureHandle batchTexture; if (currentTexId == 0 || !m_resourceCache) { batchTexture = m_activeTexture.isValid() ? m_activeTexture : m_defaultTexture; } else { batchTexture = m_resourceCache->getTextureById(currentTexId); if (!batchTexture.isValid()) { batchTexture = m_activeTexture.isValid() ? m_activeTexture : m_defaultTexture; } } // Allocate transient instance buffer for this batch rhi::TransientInstanceBuffer transientBuffer = device.allocTransientInstanceBuffer(batchCount); if (transientBuffer.isValid()) { // Copy sprite data to transient buffer SpriteInstance* dest = static_cast(transientBuffer.data); for (uint32_t i = 0; i < batchCount; ++i) { dest[i] = frame.sprites[m_sortedIndices[batchStart + i]]; } // Re-set state for each batch to ensure clean state cmd.setState(state); // Set buffers and draw cmd.setVertexBuffer(m_quadVB); cmd.setIndexBuffer(m_quadIB); cmd.setTransientInstanceBuffer(transientBuffer, 0, batchCount); cmd.setTexture(0, batchTexture, m_textureSampler); cmd.drawInstanced(6, batchCount); cmd.submit(0, m_shader, 0); } else { // Fallback: use dynamic buffer (may have issues with multiple batches) // This should only happen if GPU runs out of transient memory std::vector batchData; batchData.reserve(batchCount); for (uint32_t i = 0; i < batchCount; ++i) { batchData.push_back(frame.sprites[m_sortedIndices[batchStart + i]]); } device.updateBuffer(m_instanceBuffer, batchData.data(), static_cast(batchData.size() * sizeof(SpriteInstance))); cmd.setInstanceBuffer(m_instanceBuffer, 0, batchCount); flushBatch(device, cmd, batchTexture, batchCount); } batchStart = batchEnd; } } } // namespace grove