diff --git a/CMakeLists.txt b/CMakeLists.txt index e16979b..18670d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -196,8 +196,11 @@ 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_hlsl.cpp + src/bgfx_tools/shaderc/shaderc_metal.cpp src/bgfx_tools/shaderc/shaderc_mem.cpp + src/bgfx_tools/shaderc/shaderc_spirv.cpp + src/bgfx_tools/shaderc/shaderc_utils.cpp ) target_include_directories(shaderc_local PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc" "${CMAKE_CURRENT_SOURCE_DIR}/src" "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_deps") if(TARGET bgfx::bx) @@ -271,7 +274,6 @@ if(BUILD_SDL3_APP) $<$>:src/services/impl/bgfx_graphics_backend.cpp> $<$: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") @@ -294,16 +296,17 @@ if(BUILD_SDL3_APP) cpptrace::cpptrace ) if(NOT ENABLE_VITA) + if(TARGET shaderc_local) + target_link_libraries(sdl3_app PRIVATE shaderc_local) + else() + message(WARNING "shaderc_local target not found; in-process shader compilation will be unavailable.") + endif() if(TARGET shaderc::shaderc) target_link_libraries(sdl3_app PRIVATE shaderc::shaderc) elseif(TARGET shaderc::shaderc_combined) target_link_libraries(sdl3_app PRIVATE shaderc::shaderc_combined) else() - 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() + message(WARNING "shaderc CMake target not found; PipelineCompilerService will be unavailable.") endif() endif() target_compile_definitions(sdl3_app PRIVATE SDL_MAIN_HANDLED @@ -364,7 +367,6 @@ 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") @@ -400,7 +402,6 @@ 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") diff --git a/conanfile.py b/conanfile.py index 16e813c..ae71d9d 100644 --- a/conanfile.py +++ b/conanfile.py @@ -40,6 +40,7 @@ class SDL3CppConan(ConanFile): "bgfx/1.129.8930-495", "entt/3.16.0", "materialx/1.39.1", + "spirv-cross/1.4.321.0" ) def configure(self): diff --git a/src/app/service_based_app.cpp b/src/app/service_based_app.cpp index 921cefb..12592e9 100644 --- a/src/app/service_based_app.cpp +++ b/src/app/service_based_app.cpp @@ -31,6 +31,7 @@ #include "services/impl/bullet_physics_service.hpp" #include "services/impl/crash_recovery_service.hpp" #include "services/impl/logger_service.hpp" +#include "services/impl/pipeline_compiler_service.hpp" #include "services/interfaces/i_platform_service.hpp" #include #include @@ -214,6 +215,10 @@ void ServiceBasedApp::RegisterServices() { registry_.RegisterService( registry_.GetService()); + // Pipeline compiler service (bgfx shader compilation) + registry_.RegisterService( + registry_.GetService()); + // Window service registry_.RegisterService( registry_.GetService(), @@ -275,7 +280,8 @@ void ServiceBasedApp::RegisterServices() { auto graphicsBackend = std::make_shared( registry_.GetService(), registry_.GetService(), - registry_.GetService()); + registry_.GetService(), + registry_.GetService()); // Graphics service (facade) registry_.RegisterService( @@ -296,7 +302,8 @@ void ServiceBasedApp::RegisterServices() { #else registry_.RegisterService( registry_.GetService(), - registry_.GetService()); + registry_.GetService(), + registry_.GetService()); #endif // Physics service diff --git a/src/bgfx_deps/spirv_cfg.hpp b/src/bgfx_deps/spirv_cfg.hpp new file mode 100644 index 0000000..1d85fe0 --- /dev/null +++ b/src/bgfx_deps/spirv_cfg.hpp @@ -0,0 +1,168 @@ +/* + * Copyright 2016-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * At your option, you may choose to accept this material under either: + * 1. The Apache License, Version 2.0, found at , or + * 2. The MIT License, found at . + */ + +#ifndef SPIRV_CROSS_CFG_HPP +#define SPIRV_CROSS_CFG_HPP + +#include "spirv_common.hpp" +#include + +namespace SPIRV_CROSS_NAMESPACE +{ +class Compiler; +class CFG +{ +public: + CFG(Compiler &compiler, const SPIRFunction &function); + + Compiler &get_compiler() + { + return compiler; + } + + const Compiler &get_compiler() const + { + return compiler; + } + + const SPIRFunction &get_function() const + { + return func; + } + + uint32_t get_immediate_dominator(uint32_t block) const + { + auto itr = immediate_dominators.find(block); + if (itr != std::end(immediate_dominators)) + return itr->second; + else + return 0; + } + + bool is_reachable(uint32_t block) const + { + return visit_order.count(block) != 0; + } + + uint32_t get_visit_order(uint32_t block) const + { + auto itr = visit_order.find(block); + assert(itr != std::end(visit_order)); + int v = itr->second.get(); + assert(v > 0); + return uint32_t(v); + } + + uint32_t find_common_dominator(uint32_t a, uint32_t b) const; + + const SmallVector &get_preceding_edges(uint32_t block) const + { + auto itr = preceding_edges.find(block); + if (itr != std::end(preceding_edges)) + return itr->second; + else + return empty_vector; + } + + const SmallVector &get_succeeding_edges(uint32_t block) const + { + auto itr = succeeding_edges.find(block); + if (itr != std::end(succeeding_edges)) + return itr->second; + else + return empty_vector; + } + + template + void walk_from(std::unordered_set &seen_blocks, uint32_t block, const Op &op) const + { + if (seen_blocks.count(block)) + return; + seen_blocks.insert(block); + + if (op(block)) + { + for (auto b : get_succeeding_edges(block)) + walk_from(seen_blocks, b, op); + } + } + + uint32_t find_loop_dominator(uint32_t block) const; + + bool node_terminates_control_flow_in_sub_graph(BlockID from, BlockID to) const; + +private: + struct VisitOrder + { + int &get() + { + return v; + } + + const int &get() const + { + return v; + } + + int v = -1; + }; + + Compiler &compiler; + const SPIRFunction &func; + std::unordered_map> preceding_edges; + std::unordered_map> succeeding_edges; + std::unordered_map immediate_dominators; + std::unordered_map visit_order; + SmallVector post_order; + SmallVector empty_vector; + + void add_branch(uint32_t from, uint32_t to); + void build_post_order_visit_order(); + void build_immediate_dominators(); + bool post_order_visit(uint32_t block); + uint32_t visit_count = 0; + + bool is_back_edge(uint32_t to) const; + bool has_visited_forward_edge(uint32_t to) const; +}; + +class DominatorBuilder +{ +public: + DominatorBuilder(const CFG &cfg); + + void add_block(uint32_t block); + uint32_t get_dominator() const + { + return dominator; + } + + void lift_continue_block_dominator(); + +private: + const CFG &cfg; + uint32_t dominator = 0; +}; +} // namespace SPIRV_CROSS_NAMESPACE + +#endif diff --git a/src/bgfx_tools/shaderc/shaderc_mem.cpp b/src/bgfx_tools/shaderc/shaderc_mem.cpp index 59a2156..dff3eb0 100644 --- a/src/bgfx_tools/shaderc/shaderc_mem.cpp +++ b/src/bgfx_tools/shaderc/shaderc_mem.cpp @@ -127,18 +127,18 @@ int shaderc_compile_from_memory_with_target(const char* source, size_t source_le bool ok = false; uint32_t version = 1010; - // Dispatch based on target language + // Dispatch based on target language (in-process supports SPIR-V/MSL/HLSL only). 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 (out_error) { + *out_error = strdup("shaderc: target not supported by in-process compiler"); + } + return -1; } if (!ok) { diff --git a/src/bgfx_tools/shaderc/shaderc_utils.cpp b/src/bgfx_tools/shaderc/shaderc_utils.cpp new file mode 100644 index 0000000..5632192 --- /dev/null +++ b/src/bgfx_tools/shaderc/shaderc_utils.cpp @@ -0,0 +1,89 @@ +#include "shaderc.h" + +#include +#include + +#include + +namespace bgfx { + +bool g_verbose = false; + +int32_t writef(bx::WriterI* _writer, const char* _format, ...) { + va_list argList; + va_start(argList, _format); + + char temp[2048]; + + char* out = temp; + int32_t max = sizeof(temp); + int32_t len = bx::vsnprintf(out, max, _format, argList); + if (len > max) { + out = static_cast(BX_STACK_ALLOC(len)); + len = bx::vsnprintf(out, len, _format, argList); + } + + len = bx::write(_writer, out, len, bx::ErrorAssert{}); + + va_end(argList); + + return len; +} + +void strReplace(char* _str, const char* _find, const char* _replace) { + const int32_t len = bx::strLen(_find); + + char* replace = static_cast(BX_STACK_ALLOC(len + 1)); + bx::strCopy(replace, len + 1, _replace); + for (int32_t ii = bx::strLen(replace); ii < len; ++ii) { + replace[ii] = ' '; + } + replace[len] = '\0'; + + BX_ASSERT(len >= bx::strLen(_replace), ""); + for (bx::StringView ptr = bx::strFind(_str, _find); + !ptr.isEmpty(); + ptr = bx::strFind(ptr.getPtr() + len, _find)) { + bx::memCopy(const_cast(ptr.getPtr()), replace, len); + } +} + +void printCode(const char* _code, int32_t _line, int32_t _start, int32_t _end, int32_t _column) { + bx::printf("Code:\n---\n"); + + bx::LineReader reader(_code); + for (int32_t line = 1; !reader.isDone() && line < _end; ++line) { + bx::StringView strLine = reader.next(); + + if (line >= _start) { + if (_line == line) { + bx::printf("\n"); + bx::printf(">>> %3d: %.*s\n", line, strLine.getLength(), strLine.getPtr()); + if (-1 != _column) { + bx::printf(">>> %3d: %*s\n", _column, _column, "^"); + } + bx::printf("\n"); + } else { + bx::printf(" %3d: %.*s\n", line, strLine.getLength(), strLine.getPtr()); + } + } + } + + bx::printf("---\n"); +} + +void writeFile(const char* _filePath, const void* _data, int32_t _size) { + bx::FileWriter out; + if (bx::open(&out, _filePath)) { + bx::write(&out, _data, _size, bx::ErrorAssert{}); + bx::close(&out); + } +} + +bx::StringView nextWord(bx::StringView& _parse) { + bx::StringView word = bx::strWord(bx::strLTrimSpace(_parse)); + _parse = bx::strLTrimSpace(bx::StringView(word.getTerm(), _parse.getTerm())); + return word; +} + +} // namespace bgfx diff --git a/src/services/impl/bgfx_shader_compiler.cpp b/src/services/impl/bgfx_shader_compiler.cpp index db4e3f1..6ce313e 100644 --- a/src/services/impl/bgfx_shader_compiler.cpp +++ b/src/services/impl/bgfx_shader_compiler.cpp @@ -7,10 +7,8 @@ #include #include #include -// For runtime symbol lookup -#if defined(__linux__) || defined(__APPLE__) -#include -#endif + +#include "shaderc_mem.h" namespace sdl3cpp::services::impl { @@ -29,6 +27,18 @@ const char* RendererTypeName(bgfx::RendererType::Enum type) { } } +const char* ShadercTargetName(bgfx::RendererType::Enum type) { + switch (type) { + case bgfx::RendererType::Vulkan: return "spirv"; + case bgfx::RendererType::Metal: return "msl"; + case bgfx::RendererType::Direct3D11: + case bgfx::RendererType::Direct3D12: return "hlsl"; + case bgfx::RendererType::OpenGL: + case bgfx::RendererType::OpenGLES: return "glsl"; + default: return "spirv"; + } +} + } // namespace BgfxShaderCompiler::BgfxShaderCompiler(std::shared_ptr logger, @@ -72,77 +82,54 @@ bgfx::ShaderHandle BgfxShaderCompiler::CompileShader( 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); + const char* profile = isVertex ? "vertex" : "fragment"; + const char* target = ShadercTargetName(rendererType); + uint8_t* outData = nullptr; + size_t outSize = 0; + char* outError = nullptr; - void* sym = dlsym(RTLD_DEFAULT, "shaderc_compile_from_memory"); - shaderc_fn_t shaderc_fn = reinterpret_cast(sym); + int result = shaderc_compile_from_memory_with_target( + source.c_str(), + source.size(), + profile, + target, + &outData, + &outSize, + &outError); - 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); + if (result == 0 && outData && outSize > 0) { + buffer.resize(outSize); + memcpy(buffer.data(), outData, outSize); + compiledInMemory = true; + if (logger_) { + logger_->Trace("BgfxShaderCompiler", "CompileShader", + "in-memory compile succeeded for " + label + + ", target=" + std::string(target) + + ", size=" + std::to_string(outSize)); } - 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); - } - } + } else if (logger_) { + logger_->Trace("BgfxShaderCompiler", "CompileShader", + "in-memory compile failed for " + label + + ", target=" + std::string(target) + + ", result=" + std::to_string(result) + + ", size=" + std::to_string(outSize)); + if (outError) { + logger_->Error(std::string("BgfxShaderCompiler: in-memory shaderc error: ") + outError); } } -#endif + + if (outData) { + shaderc_free_buffer(outData); + } + if (outError) { + shaderc_free_error(outError); + } if (!compiledInMemory) { + if (logger_) { + logger_->Trace("BgfxShaderCompiler", "CompileShader", + "falling back to temp-file compilation for " + label); + } // 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"); diff --git a/tests/test_cube_script.cpp b/tests/test_cube_script.cpp index 518fe16..0187094 100644 --- a/tests/test_cube_script.cpp +++ b/tests/test_cube_script.cpp @@ -1,6 +1,7 @@ #include "services/impl/bgfx_graphics_backend.hpp" #include "services/impl/logger_service.hpp" #include "services/impl/mesh_service.hpp" +#include "services/impl/pipeline_compiler_service.hpp" #include "services/impl/physics_bridge_service.hpp" #include "services/impl/platform_service.hpp" #include "services/impl/script_engine_service.hpp" @@ -340,7 +341,8 @@ bool RunGpuRenderTest(int& failures, std::cout << "SDL video driver selected for GPU test: default\n"; } - sdl3cpp::services::impl::BgfxGraphicsBackend backend(configService, platformService, logger); + auto pipelineCompiler = std::make_shared(logger); + sdl3cpp::services::impl::BgfxGraphicsBackend backend(configService, platformService, logger, pipelineCompiler); bool success = true; try {