GroveEngine/modules/BgfxRenderer/Shaders/ShaderManager.cpp
StillHammer 262eef377e feat(BgfxRenderer): Phase 5.5 - Sprite shader with instancing and texture support
- Create proper sprite shader (vs_sprite.sc, fs_sprite.sc) with GPU instancing
- SpriteInstance struct now 80 bytes (5 x vec4) for GPU layout compatibility
- Add BufferDesc::Layout enum (Raw, PosColor, InstanceData) for proper vertex layouts
- BgfxDevice creates correct VertexLayout based on buffer type
- SpritePass uses PosColor layout for quad vertices (Position + Color0)
- Instance buffer uses 5 x vec4 layout (TexCoord7-3) for i_data0-4
- Add texture support with v_texcoord0 interpolation from instance UVs
- Create default white 1x1 texture fallback
- Compile shaders for SPIRV (Vulkan), GLSL (OpenGL), and Metal

Visual test confirms sprites render correctly at ~545 FPS.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 19:27:25 +08:00

149 lines
4.6 KiB
C++

#include "ShaderManager.h"
#include "../RHI/RHIDevice.h"
// Embedded shader bytecode
#include "vs_color.bin.h"
#include "fs_color.bin.h"
#include "vs_sprite.bin.h"
#include "fs_sprite.bin.h"
namespace grove {
ShaderManager::~ShaderManager() {
// Note: shutdown() must be called explicitly with device before destruction
// We can't call it here because we don't have the device reference
}
void ShaderManager::init(rhi::IRHIDevice& device, const std::string& rendererName) {
if (m_initialized) {
return;
}
loadBuiltinShaders(device, rendererName);
m_initialized = true;
}
void ShaderManager::shutdown(rhi::IRHIDevice& device) {
for (auto& [name, handle] : m_programs) {
if (handle.isValid()) {
device.destroy(handle);
}
}
m_programs.clear();
m_initialized = false;
}
rhi::ShaderHandle ShaderManager::getProgram(const std::string& name) const {
auto it = m_programs.find(name);
if (it != m_programs.end()) {
return it->second;
}
return rhi::ShaderHandle{}; // Invalid handle
}
bool ShaderManager::hasProgram(const std::string& name) const {
return m_programs.find(name) != m_programs.end();
}
void ShaderManager::loadBuiltinShaders(rhi::IRHIDevice& device, const std::string& rendererName) {
// Select shader bytecode based on renderer
const uint8_t* vsData = nullptr;
uint32_t vsSize = 0;
const uint8_t* fsData = nullptr;
uint32_t fsSize = 0;
if (rendererName == "OpenGL") {
vsData = vs_drawstress_glsl;
vsSize = sizeof(vs_drawstress_glsl);
fsData = fs_drawstress_glsl;
fsSize = sizeof(fs_drawstress_glsl);
} else if (rendererName == "OpenGL ES") {
vsData = vs_drawstress_essl;
vsSize = sizeof(vs_drawstress_essl);
fsData = fs_drawstress_essl;
fsSize = sizeof(fs_drawstress_essl);
} else if (rendererName == "Vulkan") {
vsData = vs_drawstress_spv;
vsSize = sizeof(vs_drawstress_spv);
fsData = fs_drawstress_spv;
fsSize = sizeof(fs_drawstress_spv);
} else if (rendererName == "Direct3D 11" || rendererName == "Direct3D 12") {
vsData = vs_drawstress_dx11;
vsSize = sizeof(vs_drawstress_dx11);
fsData = fs_drawstress_dx11;
fsSize = sizeof(fs_drawstress_dx11);
} else if (rendererName == "Metal") {
vsData = vs_drawstress_mtl;
vsSize = sizeof(vs_drawstress_mtl);
fsData = fs_drawstress_mtl;
fsSize = sizeof(fs_drawstress_mtl);
} else {
// Fallback to Vulkan (most common in WSL2)
vsData = vs_drawstress_spv;
vsSize = sizeof(vs_drawstress_spv);
fsData = fs_drawstress_spv;
fsSize = sizeof(fs_drawstress_spv);
}
// Create color shader via RHI
rhi::ShaderDesc shaderDesc;
shaderDesc.vsData = vsData;
shaderDesc.vsSize = vsSize;
shaderDesc.fsData = fsData;
shaderDesc.fsSize = fsSize;
rhi::ShaderHandle colorProgram = device.createShader(shaderDesc);
if (colorProgram.isValid()) {
m_programs["color"] = colorProgram;
m_programs["debug"] = colorProgram;
}
// Load sprite instancing shader
loadSpriteShader(device, rendererName);
}
void ShaderManager::loadSpriteShader(rhi::IRHIDevice& device, const std::string& rendererName) {
const uint8_t* vsData = nullptr;
uint32_t vsSize = 0;
const uint8_t* fsData = nullptr;
uint32_t fsSize = 0;
if (rendererName == "OpenGL") {
vsData = vs_sprite_glsl;
vsSize = sizeof(vs_sprite_glsl);
fsData = fs_sprite_glsl;
fsSize = sizeof(fs_sprite_glsl);
} else if (rendererName == "Vulkan") {
vsData = vs_sprite_spv;
vsSize = sizeof(vs_sprite_spv);
fsData = fs_sprite_spv;
fsSize = sizeof(fs_sprite_spv);
} else if (rendererName == "Metal") {
vsData = vs_sprite_mtl;
vsSize = sizeof(vs_sprite_mtl);
fsData = fs_sprite_mtl;
fsSize = sizeof(fs_sprite_mtl);
} else {
// Fallback to Vulkan (most common in WSL2)
vsData = vs_sprite_spv;
vsSize = sizeof(vs_sprite_spv);
fsData = fs_sprite_spv;
fsSize = sizeof(fs_sprite_spv);
}
rhi::ShaderDesc shaderDesc;
shaderDesc.vsData = vsData;
shaderDesc.vsSize = vsSize;
shaderDesc.fsData = fsData;
shaderDesc.fsSize = fsSize;
rhi::ShaderHandle spriteProgram = device.createShader(shaderDesc);
if (spriteProgram.isValid()) {
m_programs["sprite"] = spriteProgram;
}
}
} // namespace grove