feat(shader): Integrate local shader compilation with bgfx_tools and add PipelineCompilerService

This commit is contained in:
2026-01-07 18:38:01 +00:00
parent cb0a420242
commit 2cbd4c232f
12 changed files with 245 additions and 60 deletions

View File

@@ -11,14 +11,8 @@ if(ENABLE_VITA)
set(BUILD_SDL3_APP OFF CACHE BOOL "Build the SDL3 bgfx demo" FORCE)
set(BUILD_SCRIPT_TESTS OFF CACHE BOOL "Build script engine tests" FORCE)
endif()
if(CMAKE_GENERATOR MATCHES "Ninja")
if(DEFINED CMAKE_GENERATOR_PLATFORM)
set(CMAKE_GENERATOR_PLATFORM "" CACHE STRING "" FORCE)
endif()
if(DEFINED CMAKE_GENERATOR_TOOLSET)
set(CMAKE_GENERATOR_TOOLSET "" CACHE STRING "" FORCE)
endif()
endif()
project(SDL3App LANGUAGES CXX)
list(APPEND CMAKE_PROGRAM_PATH "C:/ProgramData/chocolatey/bin")
if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)
@@ -149,8 +143,8 @@ set(SDL3CPP_RENDER_STACK_LIBS EnTT::EnTT)
if(TARGET bgfx::bgfx)
list(APPEND SDL3CPP_RENDER_STACK_LIBS bgfx::bgfx)
endif()
if(TARGET bx::bx)
list(APPEND SDL3CPP_RENDER_STACK_LIBS bx::bx)
if(TARGET bgfx::bx)
list(APPEND SDL3CPP_RENDER_STACK_LIBS bgfx::bx)
endif()
if(TARGET bimg::bimg)
list(APPEND SDL3CPP_RENDER_STACK_LIBS bimg::bimg)
@@ -200,6 +194,48 @@ elseif(TARGET stb)
endif()
endif()
## Local build of bgfx_tools shaderc sources as a library so we can link in-process.
add_library(shaderc_local STATIC
src/bgfx_tools/shaderc/shaderc.cpp
src/bgfx_tools/shaderc/shaderc_glsl.cpp
src/bgfx_tools/shaderc/shaderc_hlsl.cpp
src/bgfx_tools/shaderc/shaderc_metal.cpp
src/bgfx_tools/shaderc/shaderc_pssl.cpp
src/bgfx_tools/shaderc/shaderc_spirv.cpp
src/bgfx_tools/shaderc/shaderc_mem.cpp
)
target_include_directories(shaderc_local PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc" "${CMAKE_CURRENT_SOURCE_DIR}/src")
if(TARGET bgfx::bx)
target_link_libraries(shaderc_local PUBLIC bgfx::bx)
endif()
if(TARGET bgfx::bgfx)
target_link_libraries(shaderc_local PUBLIC bgfx::bgfx)
endif()
# Link shaderc dependencies
if(TARGET glslang::glslang)
target_link_libraries(shaderc_local PUBLIC glslang::glslang)
endif()
if(TARGET spirv-tools::spirv-tools)
target_link_libraries(shaderc_local PUBLIC spirv-tools::spirv-tools)
endif()
if(TARGET shaderc::shaderc)
target_link_libraries(shaderc_local PUBLIC shaderc::shaderc)
endif()
## Build geometryc tool
# add_executable(geometryc
# src/bgfx_tools/geometryc/geometryc.cpp
# )
# target_include_directories(geometryc PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
# target_link_libraries(geometryc PRIVATE ${SDL3CPP_RENDER_STACK_LIBS} meshoptimizer::meshoptimizer cgltf::cgltf)
## Build texturev tool
# add_executable(texturev
# src/bgfx_tools/texturev/texturev.cpp
# )
# target_include_directories(texturev PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
# target_link_libraries(texturev PRIVATE ${SDL3CPP_RENDER_STACK_LIBS})
if(BUILD_SDL3_APP)
add_executable(sdl3_app
src/main.cpp
@@ -240,8 +276,10 @@ if(BUILD_SDL3_APP)
$<$<NOT:$<BOOL:${ENABLE_VITA}>>:src/services/impl/bgfx_graphics_backend.cpp>
$<$<BOOL:${ENABLE_VITA}>:src/services/impl/gxm_graphics_backend.cpp>
src/app/service_based_app.cpp
src/bgfx_tools/shaderc/shaderc_mem.cpp
)
target_include_directories(sdl3_app PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_include_directories(sdl3_app PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc")
target_link_libraries(sdl3_app PRIVATE
${SDL_TARGET}
lua::lua
@@ -266,7 +304,11 @@ if(BUILD_SDL3_APP)
elseif(TARGET shaderc::shaderc_combined)
target_link_libraries(sdl3_app PRIVATE shaderc::shaderc_combined)
else()
message(WARNING "shaderc CMake target not found; skipping link. Using local bgfx_tools for shader compilation at runtime.")
if(TARGET shaderc_local)
target_link_libraries(sdl3_app PRIVATE shaderc_local)
else()
message(WARNING "shaderc CMake target not found; skipping link. Using local bgfx_tools executable for shader compilation at runtime.")
endif()
endif()
endif()
target_compile_definitions(sdl3_app PRIVATE SDL_MAIN_HANDLED
@@ -327,8 +369,10 @@ add_executable(script_engine_tests
src/events/event_bus.cpp
src/stb_image.cpp
src/services/impl/pipeline_compiler_service.cpp
src/bgfx_tools/shaderc/shaderc_mem.cpp
)
target_include_directories(script_engine_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_include_directories(script_engine_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc")
target_link_libraries(script_engine_tests PRIVATE
${SDL_TARGET}
lua::lua
@@ -341,12 +385,17 @@ target_link_libraries(script_engine_tests PRIVATE
zip::zip
libzip::zip
)
if(TARGET shaderc::shaderc)
target_link_libraries(script_engine_tests PRIVATE shaderc::shaderc)
if(TARGET shaderc_local)
target_link_libraries(script_engine_tests PRIVATE shaderc_local)
elseif(TARGET shaderc::shaderc)
elseif(TARGET shaderc::shaderc_combined)
target_link_libraries(script_engine_tests PRIVATE shaderc::shaderc_combined)
else()
message(WARNING "shaderc CMake target not found; skipping link for script_engine_tests.")
if(TARGET shaderc_local)
target_link_libraries(script_engine_tests PRIVATE shaderc_local)
else()
message(WARNING "shaderc CMake target not found; skipping link for script_engine_tests.")
endif()
endif()
add_test(NAME script_engine_tests COMMAND script_engine_tests)
@@ -356,20 +405,27 @@ add_executable(bgfx_gui_service_tests
src/services/impl/bgfx_shader_compiler.cpp
src/services/impl/materialx_shader_generator.cpp
src/services/impl/pipeline_compiler_service.cpp
src/bgfx_tools/shaderc/shaderc_mem.cpp
)
target_include_directories(bgfx_gui_service_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_include_directories(bgfx_gui_service_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc")
target_link_libraries(bgfx_gui_service_tests PRIVATE
${SDL_TARGET}
${SDL3CPP_RENDER_STACK_LIBS}
${SDL3CPP_FREETYPE_LIBS}
lunasvg::lunasvg
)
if(TARGET shaderc::shaderc)
target_link_libraries(bgfx_gui_service_tests PRIVATE shaderc::shaderc)
if(TARGET shaderc_local)
target_link_libraries(bgfx_gui_service_tests PRIVATE shaderc_local)
elseif(TARGET shaderc::shaderc)
elseif(TARGET shaderc::shaderc_combined)
target_link_libraries(bgfx_gui_service_tests PRIVATE shaderc::shaderc_combined)
else()
message(WARNING "shaderc CMake target not found; skipping link for bgfx_gui_service_tests.")
if(TARGET shaderc_local)
target_link_libraries(bgfx_gui_service_tests PRIVATE shaderc_local)
else()
message(WARNING "shaderc CMake target not found; skipping link for bgfx_gui_service_tests.")
endif()
endif()
add_test(NAME bgfx_gui_service_tests COMMAND bgfx_gui_service_tests)
@@ -388,6 +444,7 @@ add_executable(vulkan_shader_linking_test
src/services/impl/pipeline_compiler_service.cpp
)
target_include_directories(vulkan_shader_linking_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_include_directories(vulkan_shader_linking_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc")
target_link_libraries(vulkan_shader_linking_test PRIVATE
${SDL_TARGET}
${SDL3CPP_RENDER_STACK_LIBS}
@@ -397,7 +454,9 @@ target_link_libraries(vulkan_shader_linking_test PRIVATE
rapidjson
glm::glm
)
if(TARGET shaderc::shaderc)
if(TARGET shaderc_local)
target_link_libraries(vulkan_shader_linking_test PRIVATE shaderc_local)
elseif(TARGET shaderc::shaderc)
target_link_libraries(vulkan_shader_linking_test PRIVATE shaderc::shaderc)
elseif(TARGET shaderc::shaderc_combined)
target_link_libraries(vulkan_shader_linking_test PRIVATE shaderc::shaderc_combined)

View File

@@ -7,7 +7,7 @@
#include <bx/string.h>
#include <bgfx/bgfx.h>
#include "../../src/vertexlayout.h"
#include "../vertexlayout.h"
#include <tinystl/allocator.h>
#include <tinystl/string.h>

View File

@@ -23,7 +23,7 @@ namespace bgfx
#include <bx/string.h>
#include <bx/hash.h>
#include <bx/file.h>
#include "../../src/vertexlayout.h"
#include "../vertexlayout.h"
#include <string.h>
#include <algorithm>

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2011-2025 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
*/
#ifndef BGFX_VERTEXDECL_H_HEADER_GUARD
#define BGFX_VERTEXDECL_H_HEADER_GUARD
#include <bgfx/bgfx.h>
#include <bx/readerwriter.h>
namespace bgfx
{
///
void initAttribTypeSizeTable(RendererType::Enum _type);
///
bool isFloat(AttribType::Enum _type);
/// Returns attribute name.
const char* getAttribName(Attrib::Enum _attr);
///
const char* getAttribNameShort(Attrib::Enum _attr);
///
Attrib::Enum idToAttrib(uint16_t id);
///
uint16_t attribToId(Attrib::Enum _attr);
///
AttribType::Enum idToAttribType(uint16_t id);
///
int32_t write(bx::WriterI* _writer, const bgfx::VertexLayout& _layout, bx::Error* _err = NULL);
///
int32_t read(bx::ReaderI* _reader, bgfx::VertexLayout& _layout, bx::Error* _err = NULL);
///
uint32_t weldVertices(void* _output, const VertexLayout& _layout, const void* _data, uint32_t _num, bool _index32, float _epsilon, bx::AllocatorI* _allocator);
} // namespace bgfx
#endif // BGFX_VERTEXDECL_H_HEADER_GUARD

View File

@@ -4,6 +4,7 @@
#include "../interfaces/i_graphics_backend.hpp"
#include "../interfaces/i_logger.hpp"
#include "../interfaces/i_platform_service.hpp"
#include "../interfaces/i_pipeline_compiler_service.hpp"
#include "../../core/vertex.hpp"
#include <bgfx/bgfx.h>
#include <array>

View File

@@ -62,10 +62,7 @@ out VertexData
layout (location = 1) vec2 texcoord;
} vd;
layout (binding=0) uniform GuiUniforms
{
mat4 u_modelViewProj;
};
uniform mat4 u_modelViewProj;
void main()
{
@@ -86,7 +83,7 @@ in VertexData
layout (location = 0) out vec4 out_color;
layout (binding=0) uniform sampler2D s_tex;
uniform sampler2D s_tex;
void main()
{
@@ -114,14 +111,17 @@ struct BgfxGuiService::FreeTypeState {
};
BgfxGuiService::BgfxGuiService(std::shared_ptr<IConfigService> configService,
std::shared_ptr<ILogger> logger)
std::shared_ptr<ILogger> logger,
std::shared_ptr<sdl3cpp::services::IPipelineCompilerService> pipelineCompiler)
: configService_(std::move(configService)),
logger_(std::move(logger)),
pipelineCompiler_(std::move(pipelineCompiler)),
materialxGenerator_(logger_),
freeType_(std::make_unique<FreeTypeState>()) {
if (logger_) {
logger_->Trace("BgfxGuiService", "BgfxGuiService",
"configService=" + std::string(configService_ ? "set" : "null"));
"configService=" + std::string(configService_ ? "set" : "null") +
", pipelineCompiler=" + std::string(pipelineCompiler_ ? "set" : "null"));
}
}
@@ -1003,7 +1003,7 @@ bgfx::ProgramHandle BgfxGuiService::CreateProgram(const char* vertexSource,
bgfx::ShaderHandle BgfxGuiService::CreateShader(const std::string& label,
const std::string& source,
bool isVertex) const {
BgfxShaderCompiler compiler(logger_);
BgfxShaderCompiler compiler(logger_, pipelineCompiler_);
std::vector<BgfxShaderUniform> uniforms;
std::vector<bgfx::Attrib::Enum> attributes;

View File

@@ -3,6 +3,7 @@
#include "../interfaces/i_config_service.hpp"
#include "../interfaces/i_gui_service.hpp"
#include "../interfaces/i_logger.hpp"
#include "../interfaces/i_pipeline_compiler_service.hpp"
#include "../../di/lifecycle.hpp"
#include <bgfx/bgfx.h>
@@ -25,7 +26,8 @@ class BgfxGuiService final : public IGuiService,
public di::IShutdownable {
public:
BgfxGuiService(std::shared_ptr<IConfigService> configService,
std::shared_ptr<ILogger> logger);
std::shared_ptr<ILogger> logger,
std::shared_ptr<sdl3cpp::services::IPipelineCompilerService> pipelineCompiler = nullptr);
~BgfxGuiService() override;
void PrepareFrame(const std::vector<GuiCommand>& commands,
@@ -146,6 +148,7 @@ private:
std::shared_ptr<IConfigService> configService_;
std::shared_ptr<ILogger> logger_;
std::shared_ptr<sdl3cpp::services::IPipelineCompilerService> pipelineCompiler_;
MaterialXShaderGenerator materialxGenerator_;
std::unique_ptr<FreeTypeState> freeType_;

View File

@@ -153,7 +153,8 @@ bgfx::ShaderHandle BgfxShaderCompiler::CompileShader(
bool ok = false;
if (pipelineCompiler_) {
ok = pipelineCompiler_->Compile(tempInputPath, tempOutputPath, {});
std::vector<std::string> args = {"--type", isVertex ? "vertex" : "fragment", "--profile", "spirv"};
ok = pipelineCompiler_->Compile(tempInputPath, tempOutputPath, args);
} else {
std::string cmd = "./src/bgfx_tools/shaderc/shaderc -f " + tempInputPath + " -o " + tempOutputPath;
if (logger_) logger_->Trace("BgfxShaderCompiler", "CompileShaderCmd", cmd);

View File

@@ -1,42 +1,87 @@
#include "../interfaces/i_pipeline_compiler_service.hpp"
#include "../interfaces/i_logger.hpp"
#include "pipeline_compiler_service.hpp"
#include <cstdlib>
#include <sstream>
#include <vector>
#include <optional>
#include <string>
#include <memory>
#include <utility>
#include <fstream>
#include <iterator>
#include <shaderc_mem.h>
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;
PipelineCompilerService::PipelineCompilerService(std::shared_ptr<sdl3cpp::services::ILogger> logger)
: logger_(std::move(logger)) {}
bool PipelineCompilerService::Compile(const std::string& inputPath,
const std::string& outputPath,
const std::vector<std::string>& args) {
// Parse args to determine shader type and profile
bool isVertex = false;
std::string profile = "spirv"; // default
for (size_t i = 0; i < args.size(); ++i) {
if (args[i] == "--type" && i + 1 < args.size()) {
isVertex = (args[i + 1] == "vertex");
} else if (args[i] == "--profile" && i + 1 < args.size()) {
profile = args[i + 1];
}
logger_->Trace("PipelineCompilerService", "Compile", cmd.str());
int result = std::system(cmd.str().c_str());
}
// Read input file
std::ifstream inputFile(inputPath);
if (!inputFile) {
lastError_ = "Failed to open input file: " + inputPath;
logger_->Error(lastError_.value());
return false;
}
std::string source((std::istreambuf_iterator<char>(inputFile)), std::istreambuf_iterator<char>());
// Write output
std::ofstream outputFile(outputPath, std::ios::binary);
if (!outputFile) {
lastError_ = "Failed to open output file: " + outputPath;
logger_->Error(lastError_.value());
return false;
}
if (profile == "glsl") {
// For GLSL, just copy the source
outputFile.write(source.c_str(), source.size());
} else {
// For SPIR-V, compile using bgfx_tools shaderc_mem
const char* shaderType = isVertex ? "vertex" : "fragment";
uint8_t* compiledData = nullptr;
size_t compiledSize = 0;
char* errorMsg = nullptr;
int result = shaderc_compile_from_memory(source.c_str(), source.size(), shaderType,
&compiledData, &compiledSize, &errorMsg);
if (result != 0) {
lastError_ = "bgfx_tools shaderc failed with code " + std::to_string(result);
std::string error = errorMsg ? errorMsg : "Unknown shader compilation error";
lastError_ = "Shader compilation failed: " + error;
logger_->Error(lastError_.value());
if (errorMsg) shaderc_free_error(errorMsg);
return false;
}
lastError_.reset();
return true;
// Write the compiled bgfx binary data directly
outputFile.write(reinterpret_cast<const char*>(compiledData), compiledSize);
// Clean up
shaderc_free_buffer(compiledData);
if (errorMsg) shaderc_free_error(errorMsg);
}
std::optional<std::string> GetLastError() const override {
return lastError_;
}
private:
std::shared_ptr<ILogger> logger_;
std::optional<std::string> lastError_;
};
logger_->Trace("PipelineCompilerService", "Compile", "Successfully compiled " + inputPath + " to " + outputPath);
lastError_.reset();
return true;
}
std::optional<std::string> PipelineCompilerService::GetLastError() const {
return lastError_;
}
} // namespace sdl3cpp::services::impl

View File

@@ -0,0 +1,28 @@
#pragma once
#include "../interfaces/i_pipeline_compiler_service.hpp"
#include "../interfaces/i_logger.hpp"
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace sdl3cpp::services::impl {
class PipelineCompilerService : public IPipelineCompilerService {
public:
PipelineCompilerService(std::shared_ptr<sdl3cpp::services::ILogger> logger);
~PipelineCompilerService() override = default;
bool Compile(const std::string& inputPath,
const std::string& outputPath,
const std::vector<std::string>& args = {}) override;
std::optional<std::string> GetLastError() const override;
private:
std::shared_ptr<sdl3cpp::services::ILogger> logger_;
std::optional<std::string> lastError_;
};
} // namespace sdl3cpp::services::impl

View File

@@ -206,7 +206,7 @@ int main() {
configService->DisableFreeType();
configService->EnableMaterialXGuiShader();
sdl3cpp::services::impl::BgfxGuiService service(configService, logger);
sdl3cpp::services::impl::BgfxGuiService service(configService, logger, nullptr);
service.PrepareFrame({}, 1, 1);
// Noop renderer won't actually create valid shader programs
@@ -246,7 +246,7 @@ int main() {
configService->DisableFreeType();
// Don't enable MaterialX for GUI - use built-in shaders
sdl3cpp::services::impl::BgfxGuiService service(configService, logger);
sdl3cpp::services::impl::BgfxGuiService service(configService, logger, nullptr);
service.PrepareFrame({}, 1, 1);
// Noop renderer won't actually create valid shader programs, so we skip program/texture validation

View File

@@ -3,6 +3,7 @@
#include "services/impl/json_config_service.hpp"
#include "services/impl/platform_service.hpp"
#include "services/impl/sdl_window_service.hpp"
#include "services/impl/pipeline_compiler_service.hpp"
#include "services/interfaces/i_logger.hpp"
#include "services/interfaces/gui_types.hpp"
#include "events/event_bus.hpp"
@@ -119,6 +120,7 @@ int main() {
std::cout << "Loading config from: " << configPath << '\n';
auto configService = std::make_shared<sdl3cpp::services::impl::JsonConfigService>(logger, configPath, false);
auto platformService = std::make_shared<sdl3cpp::services::impl::PlatformService>(logger);
auto pipelineCompilerService = std::make_shared<sdl3cpp::services::impl::PipelineCompilerService>(logger);
// Setup window service
auto eventBus = std::make_shared<sdl3cpp::events::EventBus>();
@@ -162,7 +164,7 @@ int main() {
}
// Initialize graphics backend (this will init bgfx with production renderer settings)
sdl3cpp::services::impl::BgfxGraphicsBackend backend(configService, platformService, logger);
sdl3cpp::services::impl::BgfxGraphicsBackend backend(configService, platformService, logger, pipelineCompilerService);
std::cout << "Attempting to initialize bgfx...\n";
try {
@@ -187,7 +189,7 @@ int main() {
}
// Now test GUI service shader compilation and linking (same as production)
sdl3cpp::services::impl::BgfxGuiService guiService(configService, logger);
sdl3cpp::services::impl::BgfxGuiService guiService(configService, logger, pipelineCompilerService);
// This triggers shader compilation and program linking
const uint32_t width = configService->GetWindowWidth();