From cb0a420242501f8f1a39c0e53691fe632ba7ccdc Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Wed, 7 Jan 2026 17:53:30 +0000 Subject: [PATCH] feat(shader): Integrate bgfx_tools for shader compilation and add PipelineCompilerService --- CMakeLists.txt | 12 +- conanfile.py | 2 +- src/bgfx_tools/shaderc/shaderc_mem.cpp | 171 +++++++++++++ src/bgfx_tools/shaderc/shaderc_mem.h | 24 ++ src/services/impl/bgfx_graphics_backend.cpp | 89 +++---- src/services/impl/bgfx_graphics_backend.hpp | 4 +- src/services/impl/bgfx_shader_compiler.cpp | 238 ++++++++++-------- src/services/impl/bgfx_shader_compiler.hpp | 5 +- .../impl/pipeline_compiler_service.cpp | 42 ++++ .../i_pipeline_compiler_service.hpp | 28 +++ 10 files changed, 452 insertions(+), 163 deletions(-) create mode 100644 src/bgfx_tools/shaderc/shaderc_mem.cpp create mode 100644 src/bgfx_tools/shaderc/shaderc_mem.h create mode 100644 src/services/impl/pipeline_compiler_service.cpp create mode 100644 src/services/interfaces/i_pipeline_compiler_service.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0aa77ef..dab87eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,6 +233,7 @@ if(BUILD_SDL3_APP) src/services/impl/null_gui_service.cpp src/services/impl/bgfx_gui_service.cpp src/services/impl/bgfx_shader_compiler.cpp + src/services/impl/pipeline_compiler_service.cpp src/services/impl/bullet_physics_service.cpp src/services/impl/scene_service.cpp src/services/impl/graphics_service.cpp @@ -265,7 +266,7 @@ if(BUILD_SDL3_APP) elseif(TARGET shaderc::shaderc_combined) target_link_libraries(sdl3_app PRIVATE shaderc::shaderc_combined) else() - message(FATAL_ERROR "shaderc CMake target not found") + message(WARNING "shaderc CMake target not found; skipping link. Using local bgfx_tools for shader compilation at runtime.") endif() endif() target_compile_definitions(sdl3_app PRIVATE SDL_MAIN_HANDLED @@ -325,6 +326,7 @@ add_executable(script_engine_tests src/services/impl/scene_service.cpp src/events/event_bus.cpp src/stb_image.cpp + src/services/impl/pipeline_compiler_service.cpp ) target_include_directories(script_engine_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") target_link_libraries(script_engine_tests PRIVATE @@ -344,7 +346,7 @@ if(TARGET shaderc::shaderc) elseif(TARGET shaderc::shaderc_combined) target_link_libraries(script_engine_tests PRIVATE shaderc::shaderc_combined) else() - message(FATAL_ERROR "shaderc CMake target not found") + message(WARNING "shaderc CMake target not found; skipping link for script_engine_tests.") endif() add_test(NAME script_engine_tests COMMAND script_engine_tests) @@ -353,6 +355,7 @@ add_executable(bgfx_gui_service_tests src/services/impl/bgfx_gui_service.cpp src/services/impl/bgfx_shader_compiler.cpp src/services/impl/materialx_shader_generator.cpp + src/services/impl/pipeline_compiler_service.cpp ) target_include_directories(bgfx_gui_service_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") target_link_libraries(bgfx_gui_service_tests PRIVATE @@ -366,7 +369,7 @@ if(TARGET shaderc::shaderc) elseif(TARGET shaderc::shaderc_combined) target_link_libraries(bgfx_gui_service_tests PRIVATE shaderc::shaderc_combined) else() - message(FATAL_ERROR "shaderc CMake target not found") + message(WARNING "shaderc CMake target not found; skipping link for bgfx_gui_service_tests.") endif() add_test(NAME bgfx_gui_service_tests COMMAND bgfx_gui_service_tests) @@ -382,6 +385,7 @@ add_executable(vulkan_shader_linking_test src/services/impl/sdl_window_service.cpp src/events/event_bus.cpp src/stb_image.cpp + src/services/impl/pipeline_compiler_service.cpp ) target_include_directories(vulkan_shader_linking_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") target_link_libraries(vulkan_shader_linking_test PRIVATE @@ -398,7 +402,7 @@ if(TARGET shaderc::shaderc) elseif(TARGET shaderc::shaderc_combined) target_link_libraries(vulkan_shader_linking_test PRIVATE shaderc::shaderc_combined) else() - message(FATAL_ERROR "shaderc CMake target not found") + message(WARNING "shaderc CMake target not found; skipping link for vulkan_shader_linking_test.") endif() add_test(NAME vulkan_shader_linking_test COMMAND vulkan_shader_linking_test) endif() diff --git a/conanfile.py b/conanfile.py index 7e2e4d0..16e813c 100644 --- a/conanfile.py +++ b/conanfile.py @@ -17,7 +17,7 @@ class SDL3CppConan(ConanFile): BASE_REQUIRES = ( "lua/5.4.8", "sdl/3.2.20", - "shaderc/2025.3", + # shaderc removed; using local bgfx_tools shaderc executable instead "cpptrace/1.0.4", "ogg/1.3.5", "theora/1.1.1", diff --git a/src/bgfx_tools/shaderc/shaderc_mem.cpp b/src/bgfx_tools/shaderc/shaderc_mem.cpp new file mode 100644 index 0000000..59a2156 --- /dev/null +++ b/src/bgfx_tools/shaderc/shaderc_mem.cpp @@ -0,0 +1,171 @@ +#include "shaderc_mem.h" +#include "shaderc.h" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +// Writer that captures binary output into a std::vector +class MemoryWriter : public bx::FileWriter +{ +public: + MemoryWriter() = default; + virtual ~MemoryWriter() {} + + virtual int32_t write(const void* _data, int32_t _size, bx::Error*) override + { + const uint8_t* d = static_cast(_data); + m_buffer.insert(m_buffer.end(), d, d + _size); + return _size; + } + + const std::vector& data() const { return m_buffer; } + +private: + std::vector m_buffer; +}; + +} // anonymous + +int shaderc_compile_from_memory(const char* source, size_t source_len, const char* profile, + uint8_t** out_data, size_t* out_size, char** out_error) { + if (!source || source_len == 0) { + if (out_error) *out_error = strdup("empty source"); + return -1; + } + + try { + bgfx::Options opts; + // Map profile string (e.g., "vertex"/"fragment") into shader type + if (profile && std::strcmp(profile, "vertex") == 0) { + opts.shaderType = 'v'; + } else if (profile && std::strcmp(profile, "fragment") == 0) { + opts.shaderType = 'f'; + } else if (profile && std::strcmp(profile, "compute") == 0) { + opts.shaderType = 'c'; + } else { + // default to fragment + opts.shaderType = 'f'; + } + + // Use SPIR-V target by default (matches CLI's typical default for desktop) + const uint32_t spirvVersion = 1010; + + MemoryWriter writer; + bx::ErrorAssert err; + bx::WriterI* messageWriter = &writer; // reuse writer for messages as well + + const std::string code(source, source_len); + + bool ok = bgfx::compileSPIRVShader(opts, spirvVersion, code, &writer, messageWriter); + if (!ok) { + // capture any message buffer + const auto& msg = writer.data(); + std::string s(msg.begin(), msg.end()); + if (out_error) *out_error = strdup(s.c_str()); + return -1; + } + + const auto& out = writer.data(); + if (out.empty()) { + if (out_error) *out_error = strdup("shaderc: empty output"); + return -1; + } + + uint8_t* buf = (uint8_t*)malloc(out.size()); + if (!buf) { + if (out_error) *out_error = strdup("out of memory"); + return -1; + } + memcpy(buf, out.data(), out.size()); + *out_data = buf; + *out_size = out.size(); + if (out_error) *out_error = nullptr; + return 0; + } catch (const std::exception& e) { + if (out_error) *out_error = strdup(e.what()); + return -1; + } +} + +void shaderc_free_buffer(uint8_t* data) { free(data); } +void shaderc_free_error(char* err) { free(err); } + +int shaderc_compile_from_memory_with_target(const char* source, size_t source_len, const char* profile, + const char* target, uint8_t** out_data, size_t* out_size, char** out_error) { + if (!target) { + // delegate to legacy function + return shaderc_compile_from_memory(source, source_len, profile, out_data, out_size, out_error); + } + + if (!source || source_len == 0) { + if (out_error) *out_error = strdup("empty source"); + return -1; + } + + try { + bgfx::Options opts; + if (profile && std::strcmp(profile, "vertex") == 0) { + opts.shaderType = 'v'; + } else if (profile && std::strcmp(profile, "fragment") == 0) { + opts.shaderType = 'f'; + } else if (profile && std::strcmp(profile, "compute") == 0) { + opts.shaderType = 'c'; + } else { + opts.shaderType = 'f'; + } + + MemoryWriter writer; + bx::ErrorAssert err; + bx::WriterI* messageWriter = &writer; + const std::string code(source, source_len); + + bool ok = false; + uint32_t version = 1010; + // Dispatch based on target language + if (0 == std::strcmp(target, "spirv")) { + ok = bgfx::compileSPIRVShader(opts, version, code, &writer, messageWriter); + } else if (0 == std::strcmp(target, "glsl")) { + ok = bgfx::compileGLSLShader(opts, version, code, &writer, messageWriter); + } else if (0 == std::strcmp(target, "msl") || 0 == std::strcmp(target, "metal")) { + ok = bgfx::compileMetalShader(opts, version, code, &writer, messageWriter); + } else if (0 == std::strcmp(target, "hlsl")) { + ok = bgfx::compileHLSLShader(opts, version, code, &writer, messageWriter); + } else { + // Unknown target: fallback to SPIR-V + ok = bgfx::compileSPIRVShader(opts, version, code, &writer, messageWriter); + } + + if (!ok) { + const auto& msg = writer.data(); + std::string s(msg.begin(), msg.end()); + if (out_error) *out_error = strdup(s.c_str()); + return -1; + } + + const auto& out = writer.data(); + if (out.empty()) { + if (out_error) *out_error = strdup("shaderc: empty output"); + return -1; + } + + uint8_t* buf = (uint8_t*)malloc(out.size()); + if (!buf) { + if (out_error) *out_error = strdup("out of memory"); + return -1; + } + memcpy(buf, out.data(), out.size()); + *out_data = buf; + *out_size = out.size(); + if (out_error) *out_error = nullptr; + return 0; + } catch (const std::exception& e) { + if (out_error) *out_error = strdup(e.what()); + return -1; + } +} diff --git a/src/bgfx_tools/shaderc/shaderc_mem.h b/src/bgfx_tools/shaderc/shaderc_mem.h new file mode 100644 index 0000000..e8db06e --- /dev/null +++ b/src/bgfx_tools/shaderc/shaderc_mem.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#ifdef __cplusplus +extern "C" { +#endif + +// Compile GLSL source provided in-memory to SPIR-V-like bgfx binary +// Returns 0 on success, non-zero on failure. +int shaderc_compile_from_memory(const char* source, size_t source_len, const char* profile, + uint8_t** out_data, size_t* out_size, char** out_error); + +// Variant that accepts an explicit output target language. Valid targets include: +// "spirv" (SPIR-V), "glsl" (GLSL), "msl" (Metal), "hlsl" (HLSL). +int shaderc_compile_from_memory_with_target(const char* source, size_t source_len, const char* profile, + const char* target, uint8_t** out_data, size_t* out_size, char** out_error); + +// Free buffers returned by shaderc_compile_from_memory +void shaderc_free_buffer(uint8_t* data); +void shaderc_free_error(char* err); + +#ifdef __cplusplus +} +#endif diff --git a/src/services/impl/bgfx_graphics_backend.cpp b/src/services/impl/bgfx_graphics_backend.cpp index a017d9f..7108af9 100644 --- a/src/services/impl/bgfx_graphics_backend.cpp +++ b/src/services/impl/bgfx_graphics_backend.cpp @@ -1,11 +1,11 @@ #include "bgfx_graphics_backend.hpp" +#include "../interfaces/i_pipeline_compiler_service.hpp" #include #include #include #include #include -#include #include #include @@ -272,10 +272,12 @@ std::optional RecommendFallbackRenderer( BgfxGraphicsBackend::BgfxGraphicsBackend(std::shared_ptr configService, std::shared_ptr platformService, - std::shared_ptr logger) + std::shared_ptr logger, + std::shared_ptr pipelineCompiler) : configService_(std::move(configService)), platformService_(std::move(platformService)), - logger_(std::move(logger)) { + logger_(std::move(logger)), + pipelineCompiler_(std::move(pipelineCompiler)) { if (logger_) { logger_->Trace("BgfxGraphicsBackend", "BgfxGraphicsBackend", "configService=" + std::string(configService_ ? "set" : "null") + @@ -696,55 +698,46 @@ bgfx::ShaderHandle BgfxGraphicsBackend::CreateShader(const std::string& label, return handle; } - // For Vulkan/Metal/DX: Compile to SPIRV and wrap in bgfx binary format - shaderc::Compiler compiler; - shaderc::CompileOptions options; - options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_1); - options.SetAutoBindUniforms(true); - // Do NOT use SetAutoMapLocations - it overrides explicit layout(location=N) declarations - // and assigns locations alphabetically by variable name, breaking the vertex layout. - // MaterialX and other shaders already specify explicit locations matching our VertexLayout. - // options.SetAutoMapLocations(true); - shaderc_shader_kind kind = isVertex ? shaderc_vertex_shader : shaderc_fragment_shader; - auto result = compiler.CompileGlslToSpv(source, kind, label.c_str(), options); - - if (result.GetCompilationStatus() != shaderc_compilation_status_success) { - std::string error = result.GetErrorMessage(); - if (logger_) { - logger_->Error("Shader GLSL->SPIRV compilation failed: " + label + "\n" + error); - } - throw std::runtime_error("Shader GLSL->SPIRV compilation failed: " + label + "\n" + error); + // Use PipelineCompilerService to compile shader via bgfx_tools + std::string tempInputPath = "/tmp/" + label + (isVertex ? ".vert.glsl" : ".frag.glsl"); + std::string tempOutputPath = "/tmp/" + label + (isVertex ? ".vert.bin" : ".frag.bin"); + // Write source to tempInputPath + { + std::ofstream ofs(tempInputPath); + ofs << source; } - - std::vector spirv(result.cbegin(), result.cend()); - - // Wrap SPIRV with bgfx binary format: magic + hashes + uniformCount + spirvSize + spirv + nul - constexpr uint8_t kBgfxShaderVersion = 11; - constexpr uint32_t kMagicVSH = ('V') | ('S' << 8) | ('H' << 16) | (kBgfxShaderVersion << 24); - constexpr uint32_t kMagicFSH = ('F') | ('S' << 8) | ('H' << 16) | (kBgfxShaderVersion << 24); - const uint32_t magic = isVertex ? kMagicVSH : kMagicFSH; - const uint32_t inputHash = static_cast(std::hash{}(source)); - const uint32_t spirvSize = static_cast(spirv.size() * sizeof(uint32_t)); - const uint16_t uniformCount = 0; - const uint32_t totalSize = 4 + 4 + 4 + 2 + 4 + spirvSize + 1; - - const bgfx::Memory* mem = bgfx::alloc(totalSize); - uint8_t* data = mem->data; - uint32_t offset = 0; - - std::memcpy(data + offset, &magic, 4); offset += 4; - std::memcpy(data + offset, &inputHash, 4); offset += 4; - std::memcpy(data + offset, &inputHash, 4); offset += 4; - std::memcpy(data + offset, &uniformCount, 2); offset += 2; - std::memcpy(data + offset, &spirvSize, 4); offset += 4; - std::memcpy(data + offset, spirv.data(), spirvSize); offset += spirvSize; - data[offset] = 0; - + std::vector args; + // Add any required args for bgfx_tools/shaderc here (e.g., profile, macros) + bool success = pipelineCompiler_ && pipelineCompiler_->Compile(tempInputPath, tempOutputPath, args); + if (!success) { + std::string error = pipelineCompiler_ ? pipelineCompiler_->GetLastError().value_or("") : "No compiler service"; + if (logger_) { + logger_->Error("PipelineCompilerService failed: " + label + "\n" + error); + } + throw std::runtime_error("PipelineCompilerService failed: " + label + "\n" + error); + } + // Read compiled binary + std::ifstream ifs(tempOutputPath, std::ios::binary | std::ios::ate); + if (!ifs) { + if (logger_) { + logger_->Error("Failed to read compiled shader: " + tempOutputPath); + } + throw std::runtime_error("Failed to read compiled shader: " + tempOutputPath); + } + std::streamsize size = ifs.tellg(); + ifs.seekg(0, std::ios::beg); + std::vector buffer(size); + if (!ifs.read(buffer.data(), size)) { + if (logger_) { + logger_->Error("Failed to read compiled shader data: " + tempOutputPath); + } + throw std::runtime_error("Failed to read compiled shader data: " + tempOutputPath); + } + const bgfx::Memory* mem = bgfx::copy(buffer.data(), static_cast(size)); bgfx::ShaderHandle handle = bgfx::createShader(mem); if (!bgfx::isValid(handle) && logger_) { - logger_->Error("bgfx::createShader failed for " + label + - " (renderer=" + RendererTypeName(rendererType) + ", spirvSize=" + std::to_string(spirv.size()) + " words)"); + logger_->Error("bgfx::createShader failed for " + label + " (renderer=" + RendererTypeName(rendererType) + ", binSize=" + std::to_string(size) + ")"); } return handle; } diff --git a/src/services/impl/bgfx_graphics_backend.hpp b/src/services/impl/bgfx_graphics_backend.hpp index 0980120..c3ea58d 100644 --- a/src/services/impl/bgfx_graphics_backend.hpp +++ b/src/services/impl/bgfx_graphics_backend.hpp @@ -17,7 +17,8 @@ class BgfxGraphicsBackend : public IGraphicsBackend { public: BgfxGraphicsBackend(std::shared_ptr configService, std::shared_ptr platformService, - std::shared_ptr logger); + std::shared_ptr logger, + std::shared_ptr pipelineCompiler); ~BgfxGraphicsBackend() override; void Initialize(void* window, const GraphicsConfig& config) override; @@ -111,6 +112,7 @@ private: std::shared_ptr configService_; std::shared_ptr platformService_; std::shared_ptr logger_; + std::shared_ptr pipelineCompiler_; bgfx::VertexLayout vertexLayout_; std::unordered_map> pipelines_; std::unordered_map> vertexBuffers_; diff --git a/src/services/impl/bgfx_shader_compiler.cpp b/src/services/impl/bgfx_shader_compiler.cpp index ae92b1b..9d3df56 100644 --- a/src/services/impl/bgfx_shader_compiler.cpp +++ b/src/services/impl/bgfx_shader_compiler.cpp @@ -1,18 +1,21 @@ #include "bgfx_shader_compiler.hpp" -#include #include #include -#include +#include #include +#include +#include +#include +// For runtime symbol lookup +#if defined(__linux__) || defined(__APPLE__) +#include +#endif namespace sdl3cpp::services::impl { namespace { -constexpr uint8_t kUniformFragmentBit = 0x10; -constexpr uint8_t kUniformMask = 0x10 | 0x20 | 0x40 | 0x80; - const char* RendererTypeName(bgfx::RendererType::Enum type) { switch (type) { case bgfx::RendererType::Vulkan: return "Vulkan"; @@ -26,62 +29,11 @@ const char* RendererTypeName(bgfx::RendererType::Enum type) { } } -uint16_t WriteUniformArray(uint8_t* data, - uint32_t& offset, - const std::vector& uniforms, - bool isFragmentShader) { - uint16_t size = 0; - const uint16_t count = static_cast(uniforms.size()); - std::memcpy(data + offset, &count, sizeof(count)); - offset += sizeof(count); - - const uint8_t fragmentBit = isFragmentShader ? kUniformFragmentBit : 0; - for (const auto& un : uniforms) { - if ((static_cast(un.type) & ~kUniformMask) > bgfx::UniformType::End) { - size = std::max(size, static_cast(un.regIndex + un.regCount * 16)); - } - - const uint8_t nameSize = static_cast(un.name.size()); - std::memcpy(data + offset, &nameSize, sizeof(nameSize)); - offset += sizeof(nameSize); - std::memcpy(data + offset, un.name.data(), nameSize); - offset += nameSize; - - const uint8_t typeByte = static_cast(un.type) | fragmentBit; - std::memcpy(data + offset, &typeByte, sizeof(typeByte)); - offset += sizeof(typeByte); - std::memcpy(data + offset, &un.num, sizeof(un.num)); - offset += sizeof(un.num); - std::memcpy(data + offset, &un.regIndex, sizeof(un.regIndex)); - offset += sizeof(un.regIndex); - std::memcpy(data + offset, &un.regCount, sizeof(un.regCount)); - offset += sizeof(un.regCount); - std::memcpy(data + offset, &un.texComponent, sizeof(un.texComponent)); - offset += sizeof(un.texComponent); - std::memcpy(data + offset, &un.texDimension, sizeof(un.texDimension)); - offset += sizeof(un.texDimension); - std::memcpy(data + offset, &un.texFormat, sizeof(un.texFormat)); - offset += sizeof(un.texFormat); - } - return size; -} - -uint16_t AttributeToId(bgfx::Attrib::Enum attr) { - switch (attr) { - case bgfx::Attrib::Position: return 0x0001; - case bgfx::Attrib::Color0: return 0x0005; - case bgfx::Attrib::TexCoord0: return 0x0010; - case bgfx::Attrib::Normal: return 0x0002; - case bgfx::Attrib::Tangent: return 0x0003; - case bgfx::Attrib::Bitangent: return 0x0004; - default: return 0xFFFF; - } -} - } // namespace -BgfxShaderCompiler::BgfxShaderCompiler(std::shared_ptr logger) - : logger_(std::move(logger)) { +BgfxShaderCompiler::BgfxShaderCompiler(std::shared_ptr logger, + std::shared_ptr pipelineCompiler) + : logger_(std::move(logger)), pipelineCompiler_(std::move(pipelineCompiler)) { } bgfx::ShaderHandle BgfxShaderCompiler::CompileShader( @@ -90,9 +42,9 @@ bgfx::ShaderHandle BgfxShaderCompiler::CompileShader( bool isVertex, const std::vector& uniforms, const std::vector& attributes) const { - + const bgfx::RendererType::Enum rendererType = bgfx::getRendererType(); - + if (logger_) { logger_->Trace("BgfxShaderCompiler", "CompileShader", "label=" + label + @@ -115,63 +67,133 @@ bgfx::ShaderHandle BgfxShaderCompiler::CompileShader( } return handle; } - - // For Vulkan/Metal/DX: Compile GLSL to SPIRV - shaderc::Compiler compiler; - shaderc::CompileOptions options; - options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_1); - options.SetAutoBindUniforms(false); // We use explicit binding=N in shaders - // Do NOT use SetAutoMapLocations - it overrides explicit layout(location=N) declarations - - shaderc_shader_kind kind = isVertex ? shaderc_vertex_shader : shaderc_fragment_shader; - auto result = compiler.CompileGlslToSpv(source, kind, label.c_str(), options); - - if (result.GetCompilationStatus() != shaderc_compilation_status_success) { - if (logger_) { - logger_->Error("BgfxShaderCompiler: GLSL->SPIRV compilation failed for " + label + "\n" + result.GetErrorMessage()); + + // Try in-memory in-process compilation first (if bgfx_tools provides C API) + std::vector buffer; + bool compiledInMemory = false; + +#if defined(__linux__) || defined(__APPLE__) + using shaderc_fn_t = int (*)(const char*, size_t, const char*, uint8_t**, size_t*, char**); + using shaderc_with_target_fn_t = int (*)(const char*, size_t, const char*, const char*, uint8_t**, size_t*, char**); + void* sym_with_target = dlsym(RTLD_DEFAULT, "shaderc_compile_from_memory_with_target"); + shaderc_with_target_fn_t shaderc_with_target_fn = reinterpret_cast(sym_with_target); + + void* sym = dlsym(RTLD_DEFAULT, "shaderc_compile_from_memory"); + shaderc_fn_t shaderc_fn = reinterpret_cast(sym); + + if (shaderc_with_target_fn || shaderc_fn) { + uint8_t* out_data = nullptr; + size_t out_size = 0; + char* out_err = nullptr; + // profile: choose based on vertex/fragment and renderer; simple heuristic + const char* profile = isVertex ? "vertex" : "fragment"; + int r = -1; + if (shaderc_with_target_fn) { + // choose target based on renderer + const char* target = "spirv"; + switch (rendererType) { + case bgfx::RendererType::Vulkan: target = "spirv"; break; + case bgfx::RendererType::Metal: target = "msl"; break; + case bgfx::RendererType::Direct3D11: + case bgfx::RendererType::Direct3D12: target = "hlsl"; break; + case bgfx::RendererType::OpenGL: + case bgfx::RendererType::OpenGLES: target = "glsl"; break; + default: target = "spirv"; break; + } + r = shaderc_with_target_fn(source.c_str(), source.size(), profile, target, &out_data, &out_size, &out_err); + } else if (shaderc_fn) { + r = shaderc_fn(source.c_str(), source.size(), profile, &out_data, &out_size, &out_err); } - return BGFX_INVALID_HANDLE; + if (r == 0 && out_data && out_size > 0) { + buffer.resize(out_size); + memcpy(buffer.data(), out_data, out_size); + // free using provided free if available + // try to find free function + void* free_sym = dlsym(RTLD_DEFAULT, "shaderc_free_buffer"); + if (free_sym) { + using free_fn_t = void (*)(uint8_t*); + reinterpret_cast(free_sym)(out_data); + } else { + free(out_data); + } + if (out_err) { + void* free_err_sym = dlsym(RTLD_DEFAULT, "shaderc_free_error"); + if (free_err_sym) { + using free_err_fn_t = void (*)(char*); + reinterpret_cast(free_err_sym)(out_err); + } else { + free(out_err); + } + } + compiledInMemory = true; + if (logger_) logger_->Trace("BgfxShaderCompiler", "CompileShader", "in-memory compile succeeded for " + label); + } else { + if (out_err && logger_) { + logger_->Error(std::string("BgfxShaderCompiler: in-memory shaderc error: ") + out_err); + void* free_err_sym = dlsym(RTLD_DEFAULT, "shaderc_free_error"); + if (free_err_sym) { + using free_err_fn_t = void (*)(char*); + reinterpret_cast(free_err_sym)(out_err); + } else { + free(out_err); + } + } + } + } +#endif + + if (!compiledInMemory) { + // Fallback to temp-file + pipelineCompiler_/executable flow + std::string tempInputPath = "/tmp/" + label + (isVertex ? ".vert.glsl" : ".frag.glsl"); + std::string tempOutputPath = "/tmp/" + label + (isVertex ? ".vert.bin" : ".frag.bin"); + { + std::ofstream ofs(tempInputPath); + ofs << source; + } + + bool ok = false; + if (pipelineCompiler_) { + ok = pipelineCompiler_->Compile(tempInputPath, tempOutputPath, {}); + } else { + std::string cmd = "./src/bgfx_tools/shaderc/shaderc -f " + tempInputPath + " -o " + tempOutputPath; + if (logger_) logger_->Trace("BgfxShaderCompiler", "CompileShaderCmd", cmd); + int rc = std::system(cmd.c_str()); + ok = (rc == 0); + } + + if (!ok) { + if (logger_) logger_->Error("BgfxShaderCompiler: shader compilation failed for " + label); + return BGFX_INVALID_HANDLE; + } + + std::ifstream ifs(tempOutputPath, std::ios::binary | std::ios::ate); + if (!ifs) { + if (logger_) logger_->Error("BgfxShaderCompiler: Failed to read compiled shader: " + tempOutputPath); + return BGFX_INVALID_HANDLE; + } + std::streamsize size = ifs.tellg(); + ifs.seekg(0, std::ios::beg); + buffer.resize(size); + if (!ifs.read(buffer.data(), size)) { + if (logger_) logger_->Error("BgfxShaderCompiler: Failed to read compiled shader data"); + return BGFX_INVALID_HANDLE; + } + // cleanup temp files + remove(tempInputPath.c_str()); + remove(tempOutputPath.c_str()); } - std::vector spirv(result.cbegin(), result.cend()); - - if (logger_) { - logger_->Trace("BgfxShaderCompiler", "CompileShader", - "label=" + label + " SPIRV compiled, " + std::to_string(spirv.size()) + " words"); - } - - // Wrap SPIRV with bgfx binary format (simple version without uniform metadata embedding) - // bgfx can extract uniform info from SPIRV reflection, so we don't need to embed it - constexpr uint8_t kBgfxShaderVersion = 11; - constexpr uint32_t kMagicVSH = ('V') | ('S' << 8) | ('H' << 16) | (kBgfxShaderVersion << 24); - constexpr uint32_t kMagicFSH = ('F') | ('S' << 8) | ('H' << 16) | (kBgfxShaderVersion << 24); - const uint32_t magic = isVertex ? kMagicVSH : kMagicFSH; - const uint32_t inputHash = static_cast(std::hash{}(source)); - const uint32_t spirvSize = static_cast(spirv.size() * sizeof(uint32_t)); - const uint16_t uniformCount = 0; // Let bgfx extract uniforms from SPIRV - const uint32_t totalSize = 4 + 4 + 4 + 2 + 4 + spirvSize + 1; - - const bgfx::Memory* mem = bgfx::alloc(totalSize); - uint8_t* data = mem->data; - uint32_t offset = 0; - - std::memcpy(data + offset, &magic, 4); offset += 4; - std::memcpy(data + offset, &inputHash, 4); offset += 4; - std::memcpy(data + offset, &inputHash, 4); offset += 4; // outputHash = inputHash - std::memcpy(data + offset, &uniformCount, 2); offset += 2; - std::memcpy(data + offset, &spirvSize, 4); offset += 4; - std::memcpy(data + offset, spirv.data(), spirvSize); offset += spirvSize; - data[offset] = 0; // null terminator - + uint32_t binSize = static_cast(buffer.size()); + const bgfx::Memory* mem = bgfx::copy(buffer.data(), binSize); bgfx::ShaderHandle handle = bgfx::createShader(mem); if (!bgfx::isValid(handle) && logger_) { logger_->Error("BgfxShaderCompiler: bgfx::createShader failed for " + label + - " (spirvSize=" + std::to_string(spirv.size()) + " words)"); + " (binSize=" + std::to_string(binSize) + ")"); } else if (logger_) { logger_->Trace("BgfxShaderCompiler", "CompileShader", "label=" + label + " shader created successfully, handle=" + std::to_string(handle.idx)); } - + return handle; } diff --git a/src/services/impl/bgfx_shader_compiler.hpp b/src/services/impl/bgfx_shader_compiler.hpp index f144f4f..944f7b4 100644 --- a/src/services/impl/bgfx_shader_compiler.hpp +++ b/src/services/impl/bgfx_shader_compiler.hpp @@ -5,6 +5,7 @@ #include #include #include +#include "../interfaces/i_pipeline_compiler_service.hpp" namespace sdl3cpp::services::impl { @@ -30,7 +31,8 @@ struct BgfxShaderUniform { */ class BgfxShaderCompiler { public: - explicit BgfxShaderCompiler(std::shared_ptr logger); + explicit BgfxShaderCompiler(std::shared_ptr logger, + std::shared_ptr pipelineCompiler = nullptr); /** * @brief Compile GLSL source to bgfx shader handle. @@ -53,6 +55,7 @@ public: private: std::shared_ptr logger_; + std::shared_ptr pipelineCompiler_; }; } // namespace sdl3cpp::services::impl diff --git a/src/services/impl/pipeline_compiler_service.cpp b/src/services/impl/pipeline_compiler_service.cpp new file mode 100644 index 0000000..4b722ef --- /dev/null +++ b/src/services/impl/pipeline_compiler_service.cpp @@ -0,0 +1,42 @@ +#include "../interfaces/i_pipeline_compiler_service.hpp" +#include "../interfaces/i_logger.hpp" +#include +#include +#include +#include +#include + +namespace sdl3cpp::services::impl { +class PipelineCompilerService : public IPipelineCompilerService { +public: + PipelineCompilerService(std::shared_ptr logger) + : logger_(std::move(logger)) {} + + bool Compile(const std::string& inputPath, + const std::string& outputPath, + const std::vector& args = {}) override { + std::ostringstream cmd; + cmd << "./src/bgfx_tools/shaderc/shaderc"; + cmd << " -f " << inputPath << " -o " << outputPath; + for (const auto& arg : args) { + cmd << " " << arg; + } + logger_->Trace("PipelineCompilerService", "Compile", cmd.str()); + int result = std::system(cmd.str().c_str()); + if (result != 0) { + lastError_ = "bgfx_tools shaderc failed with code " + std::to_string(result); + logger_->Error(lastError_.value()); + return false; + } + lastError_.reset(); + return true; + } + + std::optional GetLastError() const override { + return lastError_; + } +private: + std::shared_ptr logger_; + std::optional lastError_; +}; +} // namespace sdl3cpp::services::impl diff --git a/src/services/interfaces/i_pipeline_compiler_service.hpp b/src/services/interfaces/i_pipeline_compiler_service.hpp new file mode 100644 index 0000000..995cbd7 --- /dev/null +++ b/src/services/interfaces/i_pipeline_compiler_service.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include + +namespace sdl3cpp::services { +/** + * @brief Interface for pipeline/shader compilation using bgfx_tools. + */ +class IPipelineCompilerService { +public: + virtual ~IPipelineCompilerService() = default; + /** + * Compile a shader or pipeline using bgfx_tools. + * @param inputPath Path to input shader source file. + * @param outputPath Path to output compiled binary. + * @param args Additional arguments for bgfx_tools (e.g., profile, macros). + * @return true if compilation succeeded, false otherwise. + */ + virtual bool Compile(const std::string& inputPath, + const std::string& outputPath, + const std::vector& args = {}) = 0; + /** + * Get last error message (if any). + */ + virtual std::optional GetLastError() const = 0; +}; +} // namespace sdl3cpp::services