feat(BgfxRenderer): Add layer sorting for correct sprite Z-order

Sprites are now sorted by layer (ascending) then by textureId for batching.
Batches are flushed when layer OR texture changes to maintain correct draw order.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
StillHammer 2025-11-28 07:13:34 +08:00
parent 613283d75c
commit 5795bbb37e

View File

@ -101,32 +101,40 @@ void SpritePass::execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi:
state.depthWrite = false; state.depthWrite = false;
cmd.setState(state); cmd.setState(state);
// Build sorted indices by textureId for batching // Build sorted indices by layer (primary) and textureId (secondary) for batching
m_sortedIndices.clear(); m_sortedIndices.clear();
m_sortedIndices.reserve(frame.spriteCount); m_sortedIndices.reserve(frame.spriteCount);
for (size_t i = 0; i < frame.spriteCount; ++i) { for (size_t i = 0; i < frame.spriteCount; ++i) {
m_sortedIndices.push_back(static_cast<uint32_t>(i)); m_sortedIndices.push_back(static_cast<uint32_t>(i));
} }
// Sort by textureId (stable sort to preserve layer order within same texture) // Sort by layer first (ascending: layer 0 = background, rendered first)
std::stable_sort(m_sortedIndices.begin(), m_sortedIndices.end(), // 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) { [&frame](uint32_t a, uint32_t b) {
return frame.sprites[a].textureId < frame.sprites[b].textureId; 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 // Process sprites in batches by layer and texture
// Flush batch when layer OR texture changes to maintain correct draw order
std::vector<SpriteInstance> batchData; std::vector<SpriteInstance> batchData;
batchData.reserve(MAX_SPRITES_PER_BATCH); batchData.reserve(MAX_SPRITES_PER_BATCH);
uint16_t currentTextureId = UINT16_MAX; uint16_t currentTextureId = UINT16_MAX;
float currentLayer = -1e9f; // Use a value that won't match any real layer
rhi::TextureHandle currentTexture; rhi::TextureHandle currentTexture;
for (uint32_t idx : m_sortedIndices) { for (uint32_t idx : m_sortedIndices) {
const SpriteInstance& sprite = frame.sprites[idx]; const SpriteInstance& sprite = frame.sprites[idx];
uint16_t texId = static_cast<uint16_t>(sprite.textureId); uint16_t texId = static_cast<uint16_t>(sprite.textureId);
// Check if texture changed // Check if texture OR layer changed (must flush to maintain layer order)
if (texId != currentTextureId) { if (texId != currentTextureId || sprite.layer != currentLayer) {
// Flush previous batch // Flush previous batch
if (!batchData.empty()) { if (!batchData.empty()) {
device.updateBuffer(m_instanceBuffer, batchData.data(), device.updateBuffer(m_instanceBuffer, batchData.data(),
@ -135,8 +143,9 @@ void SpritePass::execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi:
batchData.clear(); batchData.clear();
} }
// Update current texture // Update current state
currentTextureId = texId; currentTextureId = texId;
currentLayer = sprite.layer;
if (texId == 0 || !m_resourceCache) { if (texId == 0 || !m_resourceCache) {
// Use default/active texture for textureId=0 // Use default/active texture for textureId=0
currentTexture = m_activeTexture.isValid() ? m_activeTexture : m_defaultTexture; currentTexture = m_activeTexture.isValid() ? m_activeTexture : m_defaultTexture;