mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 13:44:58 +00:00
feat(shader): Integrate bgfx_tools for shader compilation and add PipelineCompilerService
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
171
src/bgfx_tools/shaderc/shaderc_mem.cpp
Normal file
171
src/bgfx_tools/shaderc/shaderc_mem.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "shaderc_mem.h"
|
||||
#include "shaderc.h"
|
||||
#include <bx/file.h>
|
||||
#include <bx/allocator.h>
|
||||
#include <bx/error.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace {
|
||||
|
||||
// Writer that captures binary output into a std::vector<uint8_t>
|
||||
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<const uint8_t*>(_data);
|
||||
m_buffer.insert(m_buffer.end(), d, d + _size);
|
||||
return _size;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& data() const { return m_buffer; }
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> 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;
|
||||
}
|
||||
}
|
||||
24
src/bgfx_tools/shaderc/shaderc_mem.h
Normal file
24
src/bgfx_tools/shaderc/shaderc_mem.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#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
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "bgfx_graphics_backend.hpp"
|
||||
#include "../interfaces/i_pipeline_compiler_service.hpp"
|
||||
#include <stb_image.h>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_properties.h>
|
||||
#include <SDL3/SDL_video.h>
|
||||
#include <bgfx/platform.h>
|
||||
#include <shaderc/shaderc.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
@@ -272,10 +272,12 @@ std::optional<bgfx::RendererType::Enum> RecommendFallbackRenderer(
|
||||
|
||||
BgfxGraphicsBackend::BgfxGraphicsBackend(std::shared_ptr<IConfigService> configService,
|
||||
std::shared_ptr<IPlatformService> platformService,
|
||||
std::shared_ptr<ILogger> logger)
|
||||
std::shared_ptr<ILogger> logger,
|
||||
std::shared_ptr<IPipelineCompilerService> 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<uint32_t> 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<uint32_t>(std::hash<std::string>{}(source));
|
||||
const uint32_t spirvSize = static_cast<uint32_t>(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<std::string> 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<char> 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<uint32_t>(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;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ class BgfxGraphicsBackend : public IGraphicsBackend {
|
||||
public:
|
||||
BgfxGraphicsBackend(std::shared_ptr<IConfigService> configService,
|
||||
std::shared_ptr<IPlatformService> platformService,
|
||||
std::shared_ptr<ILogger> logger);
|
||||
std::shared_ptr<ILogger> logger,
|
||||
std::shared_ptr<IPipelineCompilerService> pipelineCompiler);
|
||||
~BgfxGraphicsBackend() override;
|
||||
|
||||
void Initialize(void* window, const GraphicsConfig& config) override;
|
||||
@@ -111,6 +112,7 @@ private:
|
||||
std::shared_ptr<IConfigService> configService_;
|
||||
std::shared_ptr<IPlatformService> platformService_;
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
std::shared_ptr<IPipelineCompilerService> pipelineCompiler_;
|
||||
bgfx::VertexLayout vertexLayout_;
|
||||
std::unordered_map<GraphicsPipelineHandle, std::unique_ptr<PipelineEntry>> pipelines_;
|
||||
std::unordered_map<GraphicsBufferHandle, std::unique_ptr<VertexBufferEntry>> vertexBuffers_;
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
#include "bgfx_shader_compiler.hpp"
|
||||
|
||||
#include <shaderc/shaderc.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <numeric>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
// For runtime symbol lookup
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
#include <dlfcn.h>
|
||||
#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<BgfxShaderUniform>& uniforms,
|
||||
bool isFragmentShader) {
|
||||
uint16_t size = 0;
|
||||
const uint16_t count = static_cast<uint16_t>(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<uint8_t>(un.type) & ~kUniformMask) > bgfx::UniformType::End) {
|
||||
size = std::max<uint16_t>(size, static_cast<uint16_t>(un.regIndex + un.regCount * 16));
|
||||
}
|
||||
|
||||
const uint8_t nameSize = static_cast<uint8_t>(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<uint8_t>(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<ILogger> logger)
|
||||
: logger_(std::move(logger)) {
|
||||
BgfxShaderCompiler::BgfxShaderCompiler(std::shared_ptr<ILogger> logger,
|
||||
std::shared_ptr<sdl3cpp::services::IPipelineCompilerService> 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<BgfxShaderUniform>& uniforms,
|
||||
const std::vector<bgfx::Attrib::Enum>& 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<char> 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<shaderc_with_target_fn_t>(sym_with_target);
|
||||
|
||||
void* sym = dlsym(RTLD_DEFAULT, "shaderc_compile_from_memory");
|
||||
shaderc_fn_t shaderc_fn = reinterpret_cast<shaderc_fn_t>(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_fn_t>(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_fn_t>(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_fn_t>(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<uint32_t> 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<uint32_t>(std::hash<std::string>{}(source));
|
||||
const uint32_t spirvSize = static_cast<uint32_t>(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<uint32_t>(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#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<ILogger> logger);
|
||||
explicit BgfxShaderCompiler(std::shared_ptr<ILogger> logger,
|
||||
std::shared_ptr<sdl3cpp::services::IPipelineCompilerService> pipelineCompiler = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Compile GLSL source to bgfx shader handle.
|
||||
@@ -53,6 +55,7 @@ public:
|
||||
|
||||
private:
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
std::shared_ptr<sdl3cpp::services::IPipelineCompilerService> pipelineCompiler_;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
|
||||
42
src/services/impl/pipeline_compiler_service.cpp
Normal file
42
src/services/impl/pipeline_compiler_service.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include "../interfaces/i_pipeline_compiler_service.hpp"
|
||||
#include "../interfaces/i_logger.hpp"
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
class PipelineCompilerService : public IPipelineCompilerService {
|
||||
public:
|
||||
PipelineCompilerService(std::shared_ptr<ILogger> logger)
|
||||
: logger_(std::move(logger)) {}
|
||||
|
||||
bool Compile(const std::string& inputPath,
|
||||
const std::string& outputPath,
|
||||
const std::vector<std::string>& 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<std::string> GetLastError() const override {
|
||||
return lastError_;
|
||||
}
|
||||
private:
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
std::optional<std::string> lastError_;
|
||||
};
|
||||
} // namespace sdl3cpp::services::impl
|
||||
28
src/services/interfaces/i_pipeline_compiler_service.hpp
Normal file
28
src/services/interfaces/i_pipeline_compiler_service.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
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<std::string>& args = {}) = 0;
|
||||
/**
|
||||
* Get last error message (if any).
|
||||
*/
|
||||
virtual std::optional<std::string> GetLastError() const = 0;
|
||||
};
|
||||
} // namespace sdl3cpp::services
|
||||
Reference in New Issue
Block a user