mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 13:44:58 +00:00
feat: Enhance GUI rendering and configuration management
- Added support for FreeType font rendering in GuiRenderer, including glyph loading and text rendering capabilities. - Introduced GuiFontConfig structure to manage font settings through configuration. - Updated JsonConfigService to parse and provide GUI font settings from JSON configuration. - Implemented MaterialX shader generation capabilities with a new MaterialXShaderGenerator class. - Enhanced ShaderScriptService to utilize MaterialX for shader generation based on configuration. - Added NullGuiService as a placeholder implementation for GUI service. - Extended IConfigService interface to include methods for retrieving graphics backend, MaterialX, and GUI font configurations. - Updated RuntimeConfig structure to include graphics backend and MaterialX configurations. - Added tests to ensure proper integration of new configuration settings and shader generation functionality.
This commit is contained in:
@@ -136,8 +136,9 @@ find_package(lua CONFIG REQUIRED)
|
||||
find_package(CLI11 CONFIG REQUIRED)
|
||||
find_package(RapidJSON CONFIG REQUIRED)
|
||||
find_package(EnTT CONFIG REQUIRED)
|
||||
find_package(bgfx CONFIG QUIET)
|
||||
find_package(MaterialX CONFIG QUIET)
|
||||
find_package(bgfx CONFIG REQUIRED)
|
||||
find_package(MaterialX CONFIG REQUIRED)
|
||||
find_package(Freetype CONFIG REQUIRED)
|
||||
find_package(assimp CONFIG REQUIRED)
|
||||
find_package(Bullet CONFIG REQUIRED)
|
||||
find_package(Vorbis CONFIG REQUIRED)
|
||||
@@ -159,10 +160,12 @@ set(SDL3CPP_MATERIALX_LIBS)
|
||||
if(TARGET MaterialX::MaterialX)
|
||||
list(APPEND SDL3CPP_MATERIALX_LIBS MaterialX::MaterialX)
|
||||
else()
|
||||
foreach(candidate IN ITEMS
|
||||
MaterialX::MaterialXCore
|
||||
MaterialX::MaterialXFormat
|
||||
MaterialX::MaterialXGenShader)
|
||||
foreach(candidate IN ITEMS
|
||||
MaterialX::MaterialXCore
|
||||
MaterialX::MaterialXFormat
|
||||
MaterialX::MaterialXGenShader
|
||||
MaterialX::MaterialXGenGlsl
|
||||
MaterialX::MaterialXRender)
|
||||
if(TARGET ${candidate})
|
||||
list(APPEND SDL3CPP_MATERIALX_LIBS ${candidate})
|
||||
endif()
|
||||
@@ -171,6 +174,13 @@ endif()
|
||||
if(SDL3CPP_MATERIALX_LIBS)
|
||||
list(APPEND SDL3CPP_RENDER_STACK_LIBS ${SDL3CPP_MATERIALX_LIBS})
|
||||
endif()
|
||||
|
||||
set(SDL3CPP_FREETYPE_LIBS)
|
||||
if(TARGET Freetype::Freetype)
|
||||
list(APPEND SDL3CPP_FREETYPE_LIBS Freetype::Freetype)
|
||||
elseif(TARGET freetype::freetype)
|
||||
list(APPEND SDL3CPP_FREETYPE_LIBS freetype::freetype)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_SDL3_APP)
|
||||
@@ -188,6 +198,7 @@ if(BUILD_SDL3_APP)
|
||||
src/services/impl/lua_helpers.cpp
|
||||
src/services/impl/scene_script_service.cpp
|
||||
src/services/impl/shader_script_service.cpp
|
||||
src/services/impl/materialx_shader_generator.cpp
|
||||
src/services/impl/render_graph_script_service.cpp
|
||||
src/services/impl/gui_script_service.cpp
|
||||
src/services/impl/audio_command_service.cpp
|
||||
@@ -207,10 +218,12 @@ if(BUILD_SDL3_APP)
|
||||
src/services/impl/render_coordinator_service.cpp
|
||||
src/services/impl/gui_renderer_service.cpp
|
||||
src/services/impl/vulkan_gui_service.cpp
|
||||
src/services/impl/null_gui_service.cpp
|
||||
src/services/impl/bullet_physics_service.cpp
|
||||
src/services/impl/scene_service.cpp
|
||||
src/services/impl/graphics_service.cpp
|
||||
$<$<NOT:$<BOOL:${ENABLE_VITA}>>:src/services/impl/vulkan_graphics_backend.cpp>
|
||||
$<$<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/services/impl/gui_renderer.cpp
|
||||
@@ -223,6 +236,7 @@ if(BUILD_SDL3_APP)
|
||||
CLI11::CLI11
|
||||
rapidjson
|
||||
${SDL3CPP_RENDER_STACK_LIBS}
|
||||
${SDL3CPP_FREETYPE_LIBS}
|
||||
assimp::assimp
|
||||
Bullet::Bullet
|
||||
glm::glm
|
||||
@@ -285,11 +299,13 @@ add_executable(script_engine_tests
|
||||
src/services/impl/lua_helpers.cpp
|
||||
src/services/impl/scene_script_service.cpp
|
||||
src/services/impl/shader_script_service.cpp
|
||||
src/services/impl/materialx_shader_generator.cpp
|
||||
)
|
||||
target_include_directories(script_engine_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(script_engine_tests PRIVATE
|
||||
${SDL_TARGET}
|
||||
lua::lua
|
||||
${SDL3CPP_RENDER_STACK_LIBS}
|
||||
assimp::assimp
|
||||
Bullet::Bullet
|
||||
glm::glm
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "services/impl/render_command_service.hpp"
|
||||
#include "services/impl/graphics_service.hpp"
|
||||
#include "services/impl/vulkan_graphics_backend.hpp"
|
||||
#include "services/impl/bgfx_graphics_backend.hpp"
|
||||
#include "services/impl/script_engine_service.hpp"
|
||||
#include "services/impl/scene_script_service.hpp"
|
||||
#include "services/impl/shader_script_service.hpp"
|
||||
@@ -32,6 +33,7 @@
|
||||
#include "services/impl/gui_renderer_service.hpp"
|
||||
#include "services/impl/sdl_audio_service.hpp"
|
||||
#include "services/impl/vulkan_gui_service.hpp"
|
||||
#include "services/impl/null_gui_service.hpp"
|
||||
#include "services/impl/bullet_physics_service.hpp"
|
||||
#include "services/impl/crash_recovery_service.hpp"
|
||||
#include "services/impl/logger_service.hpp"
|
||||
@@ -141,14 +143,22 @@ void ServiceBasedApp::Run() {
|
||||
|
||||
// Initialize GUI service after graphics
|
||||
auto guiService = registry_.GetService<services::IGuiService>();
|
||||
auto vulkanDeviceService = registry_.GetService<services::IVulkanDeviceService>();
|
||||
auto swapchainService = registry_.GetService<services::ISwapchainService>();
|
||||
if (guiService && vulkanDeviceService && swapchainService) {
|
||||
guiService->Initialize(vulkanDeviceService->GetDevice(),
|
||||
vulkanDeviceService->GetPhysicalDevice(),
|
||||
swapchainService->GetSwapchainImageFormat(),
|
||||
swapchainService->GetRenderPass(),
|
||||
runtimeConfig_.scriptPath.parent_path());
|
||||
bool useBgfx = false;
|
||||
if (configService) {
|
||||
useBgfx = configService->GetGraphicsBackendConfig().backend == services::GraphicsBackendType::Bgfx;
|
||||
}
|
||||
if (!useBgfx &&
|
||||
registry_.HasService<services::IVulkanDeviceService>() &&
|
||||
registry_.HasService<services::ISwapchainService>()) {
|
||||
auto vulkanDeviceService = registry_.GetService<services::IVulkanDeviceService>();
|
||||
auto swapchainService = registry_.GetService<services::ISwapchainService>();
|
||||
if (guiService && vulkanDeviceService && swapchainService) {
|
||||
guiService->Initialize(vulkanDeviceService->GetDevice(),
|
||||
vulkanDeviceService->GetPhysicalDevice(),
|
||||
swapchainService->GetSwapchainImageFormat(),
|
||||
swapchainService->GetRenderPass(),
|
||||
runtimeConfig_.scriptPath.parent_path());
|
||||
}
|
||||
}
|
||||
|
||||
// Run the main application loop with crash recovery
|
||||
@@ -231,6 +241,11 @@ void ServiceBasedApp::RegisterServices() {
|
||||
// Configuration service
|
||||
registry_.RegisterService<services::IConfigService, services::impl::JsonConfigService>(
|
||||
registry_.GetService<services::ILogger>(), runtimeConfig_);
|
||||
auto configService = registry_.GetService<services::IConfigService>();
|
||||
bool useBgfx = false;
|
||||
if (configService) {
|
||||
useBgfx = configService->GetGraphicsBackendConfig().backend == services::GraphicsBackendType::Bgfx;
|
||||
}
|
||||
|
||||
// ECS service (entt registry)
|
||||
registry_.RegisterService<services::IEcsService, services::impl::EcsService>(
|
||||
@@ -281,6 +296,7 @@ void ServiceBasedApp::RegisterServices() {
|
||||
registry_.GetService<services::ILogger>());
|
||||
registry_.RegisterService<services::IShaderScriptService, services::impl::ShaderScriptService>(
|
||||
registry_.GetService<services::IScriptEngineService>(),
|
||||
registry_.GetService<services::IConfigService>(),
|
||||
registry_.GetService<services::ILogger>());
|
||||
registry_.RegisterService<services::IRenderGraphScriptService, services::impl::RenderGraphScriptService>(
|
||||
registry_.GetService<services::IScriptEngineService>(),
|
||||
@@ -299,53 +315,63 @@ void ServiceBasedApp::RegisterServices() {
|
||||
inputService->SetGuiScriptService(guiScriptService.get());
|
||||
}
|
||||
|
||||
// Vulkan device service
|
||||
registry_.RegisterService<services::IVulkanDeviceService, services::impl::VulkanDeviceService>(
|
||||
registry_.GetService<services::ILogger>());
|
||||
std::shared_ptr<services::IGraphicsBackend> graphicsBackend;
|
||||
if (!useBgfx) {
|
||||
// Vulkan device service
|
||||
registry_.RegisterService<services::IVulkanDeviceService, services::impl::VulkanDeviceService>(
|
||||
registry_.GetService<services::ILogger>());
|
||||
|
||||
// Swapchain service
|
||||
registry_.RegisterService<services::ISwapchainService, services::impl::SwapchainService>(
|
||||
registry_.GetService<services::IVulkanDeviceService>(),
|
||||
registry_.GetService<events::IEventBus>(),
|
||||
registry_.GetService<services::ILogger>());
|
||||
// Swapchain service
|
||||
registry_.RegisterService<services::ISwapchainService, services::impl::SwapchainService>(
|
||||
registry_.GetService<services::IVulkanDeviceService>(),
|
||||
registry_.GetService<events::IEventBus>(),
|
||||
registry_.GetService<services::ILogger>());
|
||||
|
||||
// Pipeline service
|
||||
registry_.RegisterService<services::IPipelineService, services::impl::PipelineService>(
|
||||
registry_.GetService<services::IVulkanDeviceService>(),
|
||||
registry_.GetService<services::ILogger>());
|
||||
// Pipeline service
|
||||
registry_.RegisterService<services::IPipelineService, services::impl::PipelineService>(
|
||||
registry_.GetService<services::IVulkanDeviceService>(),
|
||||
registry_.GetService<services::ILogger>());
|
||||
|
||||
// Buffer service
|
||||
registry_.RegisterService<services::IBufferService, services::impl::BufferService>(
|
||||
registry_.GetService<services::IVulkanDeviceService>(),
|
||||
registry_.GetService<services::ILogger>());
|
||||
// Buffer service
|
||||
registry_.RegisterService<services::IBufferService, services::impl::BufferService>(
|
||||
registry_.GetService<services::IVulkanDeviceService>(),
|
||||
registry_.GetService<services::ILogger>());
|
||||
|
||||
// GUI renderer service (needed by render command service and GUI service)
|
||||
registry_.RegisterService<services::IGuiRendererService, services::impl::GuiRendererService>(
|
||||
registry_.GetService<services::ILogger>(),
|
||||
registry_.GetService<services::IBufferService>());
|
||||
logger_->Trace("ServiceBasedApp", "RegisterServices",
|
||||
"Registered GUI renderer service before render command service");
|
||||
// GUI renderer service (needed by render command service and GUI service)
|
||||
registry_.RegisterService<services::IGuiRendererService, services::impl::GuiRendererService>(
|
||||
registry_.GetService<services::ILogger>(),
|
||||
registry_.GetService<services::IBufferService>(),
|
||||
registry_.GetService<services::IConfigService>());
|
||||
logger_->Trace("ServiceBasedApp", "RegisterServices",
|
||||
"Registered GUI renderer service before render command service");
|
||||
|
||||
// Render command service
|
||||
registry_.RegisterService<services::IRenderCommandService, services::impl::RenderCommandService>(
|
||||
registry_.GetService<services::IVulkanDeviceService>(),
|
||||
registry_.GetService<services::ISwapchainService>(),
|
||||
registry_.GetService<services::IPipelineService>(),
|
||||
registry_.GetService<services::IBufferService>(),
|
||||
registry_.GetService<services::IGuiRendererService>(),
|
||||
std::static_pointer_cast<services::impl::JsonConfigService>(registry_.GetService<services::IConfigService>()),
|
||||
registry_.GetService<services::ILogger>());
|
||||
// Render command service
|
||||
registry_.RegisterService<services::IRenderCommandService, services::impl::RenderCommandService>(
|
||||
registry_.GetService<services::IVulkanDeviceService>(),
|
||||
registry_.GetService<services::ISwapchainService>(),
|
||||
registry_.GetService<services::IPipelineService>(),
|
||||
registry_.GetService<services::IBufferService>(),
|
||||
registry_.GetService<services::IGuiRendererService>(),
|
||||
std::static_pointer_cast<services::impl::JsonConfigService>(registry_.GetService<services::IConfigService>()),
|
||||
registry_.GetService<services::ILogger>());
|
||||
|
||||
// Graphics service (facade)
|
||||
registry_.RegisterService<services::IGraphicsService, services::impl::GraphicsService>(
|
||||
registry_.GetService<services::ILogger>(),
|
||||
std::make_shared<services::impl::VulkanGraphicsBackend>(
|
||||
graphicsBackend = std::make_shared<services::impl::VulkanGraphicsBackend>(
|
||||
registry_.GetService<services::IVulkanDeviceService>(),
|
||||
registry_.GetService<services::ISwapchainService>(),
|
||||
registry_.GetService<services::IRenderCommandService>(),
|
||||
registry_.GetService<services::IPipelineService>(),
|
||||
registry_.GetService<services::IBufferService>(),
|
||||
registry_.GetService<services::ILogger>()),
|
||||
registry_.GetService<services::ILogger>());
|
||||
} else {
|
||||
graphicsBackend = std::make_shared<services::impl::BgfxGraphicsBackend>(
|
||||
registry_.GetService<services::IConfigService>(),
|
||||
registry_.GetService<services::ILogger>());
|
||||
}
|
||||
|
||||
// Graphics service (facade)
|
||||
registry_.RegisterService<services::IGraphicsService, services::impl::GraphicsService>(
|
||||
registry_.GetService<services::ILogger>(),
|
||||
graphicsBackend,
|
||||
registry_.GetService<services::IWindowService>());
|
||||
|
||||
// Scene service
|
||||
@@ -355,9 +381,14 @@ void ServiceBasedApp::RegisterServices() {
|
||||
registry_.GetService<services::ILogger>());
|
||||
|
||||
// GUI service
|
||||
registry_.RegisterService<services::IGuiService, services::impl::VulkanGuiService>(
|
||||
registry_.GetService<services::ILogger>(),
|
||||
registry_.GetService<services::IGuiRendererService>());
|
||||
if (!useBgfx) {
|
||||
registry_.RegisterService<services::IGuiService, services::impl::VulkanGuiService>(
|
||||
registry_.GetService<services::ILogger>(),
|
||||
registry_.GetService<services::IGuiRendererService>());
|
||||
} else {
|
||||
registry_.RegisterService<services::IGuiService, services::impl::NullGuiService>(
|
||||
registry_.GetService<services::ILogger>());
|
||||
}
|
||||
|
||||
// Physics service
|
||||
registry_.RegisterService<services::IPhysicsService, services::impl::BulletPhysicsService>(
|
||||
|
||||
461
src/services/impl/bgfx_graphics_backend.cpp
Normal file
461
src/services/impl/bgfx_graphics_backend.cpp
Normal file
@@ -0,0 +1,461 @@
|
||||
#include "bgfx_graphics_backend.hpp"
|
||||
|
||||
#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>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
namespace {
|
||||
|
||||
std::string ToLower(std::string value) {
|
||||
std::transform(value.begin(), value.end(), value.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return value;
|
||||
}
|
||||
|
||||
bgfx::RendererType::Enum RendererFromString(const std::string& value) {
|
||||
const std::string lower = ToLower(value);
|
||||
if (lower == "vulkan") {
|
||||
return bgfx::RendererType::Vulkan;
|
||||
}
|
||||
if (lower == "auto") {
|
||||
return bgfx::RendererType::Count;
|
||||
}
|
||||
return bgfx::RendererType::Vulkan;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BgfxGraphicsBackend::BgfxGraphicsBackend(std::shared_ptr<IConfigService> configService,
|
||||
std::shared_ptr<ILogger> logger)
|
||||
: configService_(std::move(configService)),
|
||||
logger_(std::move(logger)) {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "BgfxGraphicsBackend",
|
||||
"configService=" + std::string(configService_ ? "set" : "null"));
|
||||
}
|
||||
vertexLayout_.begin()
|
||||
.add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float)
|
||||
.add(bgfx::Attrib::Color0, 3, bgfx::AttribType::Float)
|
||||
.end();
|
||||
}
|
||||
|
||||
BgfxGraphicsBackend::~BgfxGraphicsBackend() {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "~BgfxGraphicsBackend");
|
||||
}
|
||||
if (initialized_) {
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void BgfxGraphicsBackend::SetupPlatformData(void* window) {
|
||||
bgfx::PlatformData pd{};
|
||||
SDL_Window* sdlWindow = static_cast<SDL_Window*>(window);
|
||||
if (!sdlWindow) {
|
||||
bgfx::setPlatformData(pd);
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_PropertiesID props = SDL_GetWindowProperties(sdlWindow);
|
||||
#if defined(_WIN32)
|
||||
pd.nwh = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
|
||||
#elif defined(__APPLE__)
|
||||
pd.nwh = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr);
|
||||
#elif defined(__linux__)
|
||||
void* wlDisplay = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr);
|
||||
void* wlSurface = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr);
|
||||
if (wlDisplay && wlSurface) {
|
||||
pd.ndt = wlDisplay;
|
||||
pd.nwh = wlSurface;
|
||||
} else {
|
||||
void* x11Display = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr);
|
||||
Sint64 x11Window = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
|
||||
if (x11Display && x11Window != 0) {
|
||||
pd.ndt = x11Display;
|
||||
pd.nwh = reinterpret_cast<void*>(static_cast<uintptr_t>(x11Window));
|
||||
}
|
||||
}
|
||||
#else
|
||||
pd.nwh = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
|
||||
#endif
|
||||
bgfx::setPlatformData(pd);
|
||||
}
|
||||
|
||||
bgfx::RendererType::Enum BgfxGraphicsBackend::ResolveRendererType() const {
|
||||
if (!configService_) {
|
||||
return bgfx::RendererType::Vulkan;
|
||||
}
|
||||
const auto& config = configService_->GetGraphicsBackendConfig();
|
||||
bgfx::RendererType::Enum renderer = RendererFromString(config.bgfxRenderer);
|
||||
if (renderer != bgfx::RendererType::Vulkan) {
|
||||
if (logger_) {
|
||||
logger_->Warn("BgfxGraphicsBackend: Forcing bgfx renderer to Vulkan");
|
||||
}
|
||||
renderer = bgfx::RendererType::Vulkan;
|
||||
}
|
||||
return renderer;
|
||||
}
|
||||
|
||||
void BgfxGraphicsBackend::Initialize(void* window, const GraphicsConfig& config) {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "Initialize");
|
||||
}
|
||||
if (initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Window* sdlWindow = static_cast<SDL_Window*>(window);
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
if (sdlWindow) {
|
||||
SDL_GetWindowSizeInPixels(sdlWindow, &width, &height);
|
||||
}
|
||||
viewportWidth_ = static_cast<uint32_t>(std::max(1, width));
|
||||
viewportHeight_ = static_cast<uint32_t>(std::max(1, height));
|
||||
|
||||
SetupPlatformData(window);
|
||||
|
||||
bgfx::Init init{};
|
||||
init.type = ResolveRendererType();
|
||||
init.resolution.width = viewportWidth_;
|
||||
init.resolution.height = viewportHeight_;
|
||||
init.resolution.reset = BGFX_RESET_VSYNC;
|
||||
|
||||
if (!bgfx::init(init)) {
|
||||
throw std::runtime_error("Failed to initialize bgfx");
|
||||
}
|
||||
|
||||
bgfx::setViewClear(viewId_, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x1f1f1fff, 1.0f, 0);
|
||||
bgfx::setDebug(BGFX_DEBUG_TEXT);
|
||||
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
void BgfxGraphicsBackend::Shutdown() {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "Shutdown");
|
||||
}
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
DestroyPipelines();
|
||||
DestroyBuffers();
|
||||
bgfx::shutdown();
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
void BgfxGraphicsBackend::RecreateSwapchain(uint32_t width, uint32_t height) {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "RecreateSwapchain",
|
||||
"width=" + std::to_string(width) +
|
||||
", height=" + std::to_string(height));
|
||||
}
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
if (width == 0 || height == 0) {
|
||||
return;
|
||||
}
|
||||
viewportWidth_ = width;
|
||||
viewportHeight_ = height;
|
||||
bgfx::reset(viewportWidth_, viewportHeight_, BGFX_RESET_VSYNC);
|
||||
}
|
||||
|
||||
void BgfxGraphicsBackend::WaitIdle() {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "WaitIdle");
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsDeviceHandle BgfxGraphicsBackend::CreateDevice() {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "CreateDevice");
|
||||
}
|
||||
return reinterpret_cast<GraphicsDeviceHandle>(1);
|
||||
}
|
||||
|
||||
void BgfxGraphicsBackend::DestroyDevice(GraphicsDeviceHandle device) {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "DestroyDevice");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BgfxGraphicsBackend::ReadShaderSource(const std::string& path,
|
||||
const std::string& source) const {
|
||||
if (!source.empty()) {
|
||||
return std::vector<uint8_t>(source.begin(), source.end());
|
||||
}
|
||||
if (path.empty()) {
|
||||
throw std::runtime_error("Shader path and source are empty");
|
||||
}
|
||||
std::filesystem::path shaderPath(path);
|
||||
if (shaderPath.extension() == ".spv") {
|
||||
shaderPath.replace_extension();
|
||||
}
|
||||
if (!std::filesystem::exists(shaderPath)) {
|
||||
throw std::runtime_error("Shader file not found: " + shaderPath.string());
|
||||
}
|
||||
std::ifstream sourceFile(shaderPath);
|
||||
if (!sourceFile) {
|
||||
throw std::runtime_error("Failed to open shader source: " + shaderPath.string());
|
||||
}
|
||||
return std::vector<uint8_t>((std::istreambuf_iterator<char>(sourceFile)),
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
bgfx::ShaderHandle BgfxGraphicsBackend::CreateShader(const std::string& label,
|
||||
const std::string& source,
|
||||
bool isVertex) const {
|
||||
shaderc::Compiler compiler;
|
||||
shaderc::CompileOptions options;
|
||||
options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2);
|
||||
|
||||
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("Bgfx shader compilation failed: " + label + "\n" + error);
|
||||
}
|
||||
throw std::runtime_error("Bgfx shader compilation failed: " + label + "\n" + error);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> spirv(result.cbegin(), result.cend());
|
||||
const bgfx::Memory* mem = bgfx::copy(spirv.data(),
|
||||
static_cast<uint32_t>(spirv.size() * sizeof(uint32_t)));
|
||||
return bgfx::createShader(mem);
|
||||
}
|
||||
|
||||
GraphicsPipelineHandle BgfxGraphicsBackend::CreatePipeline(GraphicsDeviceHandle device,
|
||||
const std::string& shaderKey,
|
||||
const ShaderPaths& shaderPaths) {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "CreatePipeline", "shaderKey=" + shaderKey);
|
||||
}
|
||||
std::vector<uint8_t> vertexBytes = ReadShaderSource(shaderPaths.vertex, shaderPaths.vertexSource);
|
||||
std::vector<uint8_t> fragmentBytes = ReadShaderSource(shaderPaths.fragment, shaderPaths.fragmentSource);
|
||||
|
||||
std::string vertexSource(vertexBytes.begin(), vertexBytes.end());
|
||||
std::string fragmentSource(fragmentBytes.begin(), fragmentBytes.end());
|
||||
|
||||
bgfx::ShaderHandle vs = CreateShader(shaderKey + ":vertex", vertexSource, true);
|
||||
bgfx::ShaderHandle fs = CreateShader(shaderKey + ":fragment", fragmentSource, false);
|
||||
bgfx::ProgramHandle program = bgfx::createProgram(vs, fs, true);
|
||||
|
||||
auto entry = std::make_unique<PipelineEntry>();
|
||||
entry->program = program;
|
||||
GraphicsPipelineHandle handle = reinterpret_cast<GraphicsPipelineHandle>(entry.get());
|
||||
pipelines_.emplace(handle, std::move(entry));
|
||||
return handle;
|
||||
}
|
||||
|
||||
void BgfxGraphicsBackend::DestroyPipeline(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline) {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "DestroyPipeline");
|
||||
}
|
||||
auto it = pipelines_.find(pipeline);
|
||||
if (it == pipelines_.end()) {
|
||||
return;
|
||||
}
|
||||
if (bgfx::isValid(it->second->program)) {
|
||||
bgfx::destroy(it->second->program);
|
||||
}
|
||||
pipelines_.erase(it);
|
||||
}
|
||||
|
||||
GraphicsBufferHandle BgfxGraphicsBackend::CreateVertexBuffer(GraphicsDeviceHandle device,
|
||||
const std::vector<uint8_t>& data) {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "CreateVertexBuffer",
|
||||
"data.size=" + std::to_string(data.size()));
|
||||
}
|
||||
if (data.empty() || data.size() % sizeof(core::Vertex) != 0) {
|
||||
throw std::runtime_error("Vertex data invalid for bgfx");
|
||||
}
|
||||
uint32_t vertexCount = static_cast<uint32_t>(data.size() / sizeof(core::Vertex));
|
||||
const bgfx::Memory* mem = bgfx::copy(data.data(), static_cast<uint32_t>(data.size()));
|
||||
bgfx::VertexBufferHandle buffer = bgfx::createVertexBuffer(mem, vertexLayout_);
|
||||
|
||||
auto entry = std::make_unique<VertexBufferEntry>();
|
||||
entry->handle = buffer;
|
||||
entry->vertexCount = vertexCount;
|
||||
GraphicsBufferHandle handle = reinterpret_cast<GraphicsBufferHandle>(entry.get());
|
||||
vertexBuffers_.emplace(handle, std::move(entry));
|
||||
return handle;
|
||||
}
|
||||
|
||||
GraphicsBufferHandle BgfxGraphicsBackend::CreateIndexBuffer(GraphicsDeviceHandle device,
|
||||
const std::vector<uint8_t>& data) {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "CreateIndexBuffer",
|
||||
"data.size=" + std::to_string(data.size()));
|
||||
}
|
||||
if (data.empty() || data.size() % sizeof(uint16_t) != 0) {
|
||||
throw std::runtime_error("Index data invalid for bgfx");
|
||||
}
|
||||
uint32_t indexCount = static_cast<uint32_t>(data.size() / sizeof(uint16_t));
|
||||
const bgfx::Memory* mem = bgfx::copy(data.data(), static_cast<uint32_t>(data.size()));
|
||||
bgfx::IndexBufferHandle buffer = bgfx::createIndexBuffer(mem);
|
||||
|
||||
auto entry = std::make_unique<IndexBufferEntry>();
|
||||
entry->handle = buffer;
|
||||
entry->indexCount = indexCount;
|
||||
GraphicsBufferHandle handle = reinterpret_cast<GraphicsBufferHandle>(entry.get());
|
||||
indexBuffers_.emplace(handle, std::move(entry));
|
||||
return handle;
|
||||
}
|
||||
|
||||
void BgfxGraphicsBackend::DestroyBuffer(GraphicsDeviceHandle device, GraphicsBufferHandle buffer) {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "DestroyBuffer");
|
||||
}
|
||||
auto vIt = vertexBuffers_.find(buffer);
|
||||
if (vIt != vertexBuffers_.end()) {
|
||||
if (bgfx::isValid(vIt->second->handle)) {
|
||||
bgfx::destroy(vIt->second->handle);
|
||||
}
|
||||
vertexBuffers_.erase(vIt);
|
||||
return;
|
||||
}
|
||||
auto iIt = indexBuffers_.find(buffer);
|
||||
if (iIt != indexBuffers_.end()) {
|
||||
if (bgfx::isValid(iIt->second->handle)) {
|
||||
bgfx::destroy(iIt->second->handle);
|
||||
}
|
||||
indexBuffers_.erase(iIt);
|
||||
}
|
||||
}
|
||||
|
||||
bool BgfxGraphicsBackend::BeginFrame(GraphicsDeviceHandle device) {
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
const float identity[16] = {
|
||||
1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 1.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f
|
||||
};
|
||||
bgfx::setViewRect(viewId_, 0, 0, viewportWidth_, viewportHeight_);
|
||||
bgfx::setViewTransform(viewId_, viewProj_.data(), identity);
|
||||
bgfx::touch(viewId_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BgfxGraphicsBackend::EndFrame(GraphicsDeviceHandle device) {
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
bgfx::frame();
|
||||
return true;
|
||||
}
|
||||
|
||||
void BgfxGraphicsBackend::SetViewProjection(const std::array<float, 16>& viewProj) {
|
||||
viewProj_ = viewProj;
|
||||
}
|
||||
|
||||
void BgfxGraphicsBackend::SetRenderGraphDefinition(const RenderGraphDefinition& definition) {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "SetRenderGraphDefinition",
|
||||
"passes=" + std::to_string(definition.passes.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void BgfxGraphicsBackend::Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline,
|
||||
GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer,
|
||||
uint32_t indexOffset, uint32_t indexCount, int32_t vertexOffset,
|
||||
const std::array<float, 16>& modelMatrix) {
|
||||
auto pipelineIt = pipelines_.find(pipeline);
|
||||
if (pipelineIt == pipelines_.end()) {
|
||||
if (logger_) {
|
||||
logger_->Error("BgfxGraphicsBackend::Draw: Pipeline not found");
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto vertexIt = vertexBuffers_.find(vertexBuffer);
|
||||
auto indexIt = indexBuffers_.find(indexBuffer);
|
||||
if (vertexIt == vertexBuffers_.end() || indexIt == indexBuffers_.end()) {
|
||||
if (logger_) {
|
||||
logger_->Error("BgfxGraphicsBackend::Draw: Buffer handles not found");
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto& vb = vertexIt->second;
|
||||
const auto& ib = indexIt->second;
|
||||
|
||||
uint32_t startVertex = static_cast<uint32_t>(std::max(0, vertexOffset));
|
||||
uint32_t availableVertices = vb->vertexCount > startVertex
|
||||
? vb->vertexCount - startVertex
|
||||
: 0;
|
||||
if (availableVertices == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bgfx::setTransform(modelMatrix.data());
|
||||
bgfx::setVertexBuffer(0, vb->handle, startVertex, availableVertices);
|
||||
bgfx::setIndexBuffer(ib->handle, indexOffset, indexCount);
|
||||
bgfx::setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_WRITE_Z |
|
||||
BGFX_STATE_DEPTH_TEST_LESS | BGFX_STATE_CULL_CW | BGFX_STATE_MSAA);
|
||||
bgfx::submit(viewId_, pipelineIt->second->program);
|
||||
}
|
||||
|
||||
GraphicsDeviceHandle BgfxGraphicsBackend::GetPhysicalDevice() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::pair<uint32_t, uint32_t> BgfxGraphicsBackend::GetSwapchainExtent() const {
|
||||
return {viewportWidth_, viewportHeight_};
|
||||
}
|
||||
|
||||
uint32_t BgfxGraphicsBackend::GetSwapchainFormat() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* BgfxGraphicsBackend::GetCurrentCommandBuffer() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* BgfxGraphicsBackend::GetGraphicsQueue() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void BgfxGraphicsBackend::DestroyPipelines() {
|
||||
for (auto& [handle, entry] : pipelines_) {
|
||||
if (bgfx::isValid(entry->program)) {
|
||||
bgfx::destroy(entry->program);
|
||||
}
|
||||
}
|
||||
pipelines_.clear();
|
||||
}
|
||||
|
||||
void BgfxGraphicsBackend::DestroyBuffers() {
|
||||
for (auto& [handle, entry] : vertexBuffers_) {
|
||||
if (bgfx::isValid(entry->handle)) {
|
||||
bgfx::destroy(entry->handle);
|
||||
}
|
||||
}
|
||||
vertexBuffers_.clear();
|
||||
for (auto& [handle, entry] : indexBuffers_) {
|
||||
if (bgfx::isValid(entry->handle)) {
|
||||
bgfx::destroy(entry->handle);
|
||||
}
|
||||
}
|
||||
indexBuffers_.clear();
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
88
src/services/impl/bgfx_graphics_backend.hpp
Normal file
88
src/services/impl/bgfx_graphics_backend.hpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include "../interfaces/i_config_service.hpp"
|
||||
#include "../interfaces/i_graphics_backend.hpp"
|
||||
#include "../interfaces/i_logger.hpp"
|
||||
#include "../../core/vertex.hpp"
|
||||
#include <bgfx/bgfx.h>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
class BgfxGraphicsBackend : public IGraphicsBackend {
|
||||
public:
|
||||
BgfxGraphicsBackend(std::shared_ptr<IConfigService> configService,
|
||||
std::shared_ptr<ILogger> logger);
|
||||
~BgfxGraphicsBackend() override;
|
||||
|
||||
void Initialize(void* window, const GraphicsConfig& config) override;
|
||||
void Shutdown() override;
|
||||
void RecreateSwapchain(uint32_t width, uint32_t height) override;
|
||||
void WaitIdle() override;
|
||||
GraphicsDeviceHandle CreateDevice() override;
|
||||
void DestroyDevice(GraphicsDeviceHandle device) override;
|
||||
GraphicsPipelineHandle CreatePipeline(GraphicsDeviceHandle device,
|
||||
const std::string& shaderKey,
|
||||
const ShaderPaths& shaderPaths) override;
|
||||
void DestroyPipeline(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline) override;
|
||||
GraphicsBufferHandle CreateVertexBuffer(GraphicsDeviceHandle device,
|
||||
const std::vector<uint8_t>& data) override;
|
||||
GraphicsBufferHandle CreateIndexBuffer(GraphicsDeviceHandle device,
|
||||
const std::vector<uint8_t>& data) override;
|
||||
void DestroyBuffer(GraphicsDeviceHandle device, GraphicsBufferHandle buffer) override;
|
||||
bool BeginFrame(GraphicsDeviceHandle device) override;
|
||||
bool EndFrame(GraphicsDeviceHandle device) override;
|
||||
void SetViewProjection(const std::array<float, 16>& viewProj) override;
|
||||
void SetRenderGraphDefinition(const RenderGraphDefinition& definition) override;
|
||||
void Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline,
|
||||
GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer,
|
||||
uint32_t indexOffset, uint32_t indexCount, int32_t vertexOffset,
|
||||
const std::array<float, 16>& modelMatrix) override;
|
||||
GraphicsDeviceHandle GetPhysicalDevice() const override;
|
||||
std::pair<uint32_t, uint32_t> GetSwapchainExtent() const override;
|
||||
uint32_t GetSwapchainFormat() const override;
|
||||
void* GetCurrentCommandBuffer() const override;
|
||||
void* GetGraphicsQueue() const override;
|
||||
|
||||
private:
|
||||
struct PipelineEntry {
|
||||
bgfx::ProgramHandle program = BGFX_INVALID_HANDLE;
|
||||
};
|
||||
|
||||
struct VertexBufferEntry {
|
||||
bgfx::VertexBufferHandle handle = BGFX_INVALID_HANDLE;
|
||||
uint32_t vertexCount = 0;
|
||||
};
|
||||
|
||||
struct IndexBufferEntry {
|
||||
bgfx::IndexBufferHandle handle = BGFX_INVALID_HANDLE;
|
||||
uint32_t indexCount = 0;
|
||||
};
|
||||
|
||||
void SetupPlatformData(void* window);
|
||||
bgfx::RendererType::Enum ResolveRendererType() const;
|
||||
std::vector<uint8_t> ReadShaderSource(const std::string& path,
|
||||
const std::string& source) const;
|
||||
bgfx::ShaderHandle CreateShader(const std::string& label,
|
||||
const std::string& source,
|
||||
bool isVertex) const;
|
||||
void DestroyPipelines();
|
||||
void DestroyBuffers();
|
||||
|
||||
std::shared_ptr<IConfigService> configService_;
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
bgfx::VertexLayout vertexLayout_;
|
||||
std::unordered_map<GraphicsPipelineHandle, std::unique_ptr<PipelineEntry>> pipelines_;
|
||||
std::unordered_map<GraphicsBufferHandle, std::unique_ptr<VertexBufferEntry>> vertexBuffers_;
|
||||
std::unordered_map<GraphicsBufferHandle, std::unique_ptr<IndexBufferEntry>> indexBuffers_;
|
||||
std::array<float, 16> viewProj_{};
|
||||
uint32_t viewportWidth_ = 0;
|
||||
uint32_t viewportHeight_ = 0;
|
||||
bool initialized_ = false;
|
||||
bgfx::ViewId viewId_ = 0;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "gui_renderer.hpp"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
@@ -407,6 +410,7 @@ void main() {
|
||||
|
||||
GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFormat swapchainFormat,
|
||||
VkRenderPass renderPass, const std::filesystem::path& scriptDirectory,
|
||||
const GuiFontConfig& fontConfig,
|
||||
std::shared_ptr<IBufferService> bufferService,
|
||||
std::shared_ptr<ILogger> logger)
|
||||
: device_(device),
|
||||
@@ -414,6 +418,7 @@ GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFor
|
||||
swapchainFormat_(swapchainFormat),
|
||||
renderPass_(renderPass),
|
||||
scriptDirectory_(scriptDirectory),
|
||||
fontConfig_(fontConfig),
|
||||
bufferService_(std::move(bufferService)),
|
||||
logger_(std::move(logger)) {
|
||||
}
|
||||
@@ -421,6 +426,16 @@ GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFor
|
||||
GuiRenderer::~GuiRenderer() {
|
||||
CleanupBuffers();
|
||||
CleanupPipeline();
|
||||
if (freetypeReady_) {
|
||||
FT_Face face = reinterpret_cast<FT_Face>(ftFace_);
|
||||
FT_Library library = reinterpret_cast<FT_Library>(ftLibrary_);
|
||||
if (face) {
|
||||
FT_Done_Face(face);
|
||||
}
|
||||
if (library) {
|
||||
FT_Done_FreeType(library);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GuiRenderer::IsReady() const {
|
||||
@@ -504,6 +519,218 @@ void GuiRenderer::Resize(uint32_t width, uint32_t height, VkFormat format, VkRen
|
||||
}
|
||||
}
|
||||
|
||||
bool GuiRenderer::EnsureFreeTypeReady() {
|
||||
if (freetypeReady_ || !fontConfig_.useFreeType) {
|
||||
return freetypeReady_;
|
||||
}
|
||||
|
||||
FT_Library library = nullptr;
|
||||
if (FT_Init_FreeType(&library) != 0) {
|
||||
if (logger_) {
|
||||
logger_->Warn("GuiRenderer: FreeType initialization failed");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::filesystem::path fontPath = ResolveFontPath();
|
||||
if (fontPath.empty()) {
|
||||
if (logger_) {
|
||||
logger_->Warn("GuiRenderer: FreeType font path not set or not found");
|
||||
}
|
||||
FT_Done_FreeType(library);
|
||||
return false;
|
||||
}
|
||||
|
||||
FT_Face face = nullptr;
|
||||
if (FT_New_Face(library, fontPath.string().c_str(), 0, &face) != 0) {
|
||||
if (logger_) {
|
||||
logger_->Warn("GuiRenderer: Failed to load FreeType font at " + fontPath.string());
|
||||
}
|
||||
FT_Done_FreeType(library);
|
||||
return false;
|
||||
}
|
||||
|
||||
ftLibrary_ = library;
|
||||
ftFace_ = face;
|
||||
freetypeReady_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::filesystem::path GuiRenderer::ResolveFontPath() const {
|
||||
if (!fontConfig_.fontPath.empty()) {
|
||||
std::filesystem::path candidate = fontConfig_.fontPath;
|
||||
if (candidate.is_absolute()) {
|
||||
return candidate;
|
||||
}
|
||||
if (!scriptDirectory_.empty()) {
|
||||
auto projectRoot = scriptDirectory_.parent_path();
|
||||
if (!projectRoot.empty()) {
|
||||
return std::filesystem::weakly_canonical(projectRoot / candidate);
|
||||
}
|
||||
return std::filesystem::weakly_canonical(scriptDirectory_ / candidate);
|
||||
}
|
||||
}
|
||||
|
||||
if (!scriptDirectory_.empty()) {
|
||||
auto fallback = scriptDirectory_ / "assets" / "fonts" / "Roboto-Regular.ttf";
|
||||
if (std::filesystem::exists(fallback)) {
|
||||
return std::filesystem::weakly_canonical(fallback);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
const GuiRenderer::GlyphBitmap* GuiRenderer::LoadGlyph(char c, int pixelSize) {
|
||||
if (!EnsureFreeTypeReady()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (pixelSize <= 0) {
|
||||
pixelSize = 1;
|
||||
}
|
||||
uint64_t key = (static_cast<uint64_t>(static_cast<uint32_t>(pixelSize)) << 32) |
|
||||
static_cast<uint8_t>(c);
|
||||
auto it = glyphCache_.find(key);
|
||||
if (it != glyphCache_.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
FT_Face face = reinterpret_cast<FT_Face>(ftFace_);
|
||||
if (!face) {
|
||||
return nullptr;
|
||||
}
|
||||
if (currentFontSize_ != pixelSize) {
|
||||
FT_Set_Pixel_Sizes(face, 0, static_cast<FT_UInt>(pixelSize));
|
||||
currentFontSize_ = pixelSize;
|
||||
}
|
||||
|
||||
if (FT_Load_Char(face, static_cast<FT_ULong>(static_cast<unsigned char>(c)), FT_LOAD_RENDER) != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const FT_GlyphSlot slot = face->glyph;
|
||||
GlyphBitmap glyph;
|
||||
glyph.width = static_cast<int>(slot->bitmap.width);
|
||||
glyph.height = static_cast<int>(slot->bitmap.rows);
|
||||
glyph.pitch = static_cast<int>(slot->bitmap.pitch);
|
||||
glyph.bearingX = slot->bitmap_left;
|
||||
glyph.bearingY = slot->bitmap_top;
|
||||
glyph.advance = static_cast<int>(slot->advance.x >> 6);
|
||||
int pitch = glyph.pitch;
|
||||
if (pitch < 0) {
|
||||
pitch = -pitch;
|
||||
}
|
||||
glyph.pixels.assign(slot->bitmap.buffer, slot->bitmap.buffer + pitch * glyph.height);
|
||||
|
||||
auto inserted = glyphCache_.emplace(key, std::move(glyph));
|
||||
return &inserted.first->second;
|
||||
}
|
||||
|
||||
void GuiRenderer::RenderFreeTypeText(const GuiCommand& cmd,
|
||||
const GuiCommand::RectData& activeClip,
|
||||
const GuiCommand::RectData& bounds) {
|
||||
FT_Face face = reinterpret_cast<FT_Face>(ftFace_);
|
||||
if (!face) {
|
||||
return;
|
||||
}
|
||||
int pixelSize = static_cast<int>(std::max(1.0f, cmd.fontSize));
|
||||
if (currentFontSize_ != pixelSize) {
|
||||
FT_Set_Pixel_Sizes(face, 0, static_cast<FT_UInt>(pixelSize));
|
||||
currentFontSize_ = pixelSize;
|
||||
}
|
||||
|
||||
float ascender = face->size ? static_cast<float>(face->size->metrics.ascender) / 64.0f : cmd.fontSize;
|
||||
float lineHeight = face->size ? static_cast<float>(face->size->metrics.height) / 64.0f : cmd.fontSize;
|
||||
|
||||
float textWidth = 0.0f;
|
||||
for (char c : cmd.text) {
|
||||
const GlyphBitmap* glyph = LoadGlyph(c, pixelSize);
|
||||
if (glyph) {
|
||||
textWidth += static_cast<float>(glyph->advance);
|
||||
}
|
||||
}
|
||||
|
||||
float startX = bounds.x;
|
||||
float startY = bounds.y;
|
||||
if (cmd.alignX == "center") {
|
||||
startX += (bounds.width - textWidth) * 0.5f;
|
||||
} else if (cmd.alignX == "right") {
|
||||
startX += bounds.width - textWidth;
|
||||
}
|
||||
if (cmd.alignY == "center") {
|
||||
startY += (bounds.height - lineHeight) * 0.5f;
|
||||
} else if (cmd.alignY == "bottom") {
|
||||
startY += bounds.height - lineHeight;
|
||||
}
|
||||
|
||||
float penX = startX;
|
||||
float baseline = startY + ascender;
|
||||
|
||||
for (char c : cmd.text) {
|
||||
const GlyphBitmap* glyph = LoadGlyph(c, pixelSize);
|
||||
if (!glyph || glyph->pixels.empty()) {
|
||||
penX += static_cast<float>(glyph ? glyph->advance : pixelSize / 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
float glyphX = penX + static_cast<float>(glyph->bearingX);
|
||||
float glyphY = baseline - static_cast<float>(glyph->bearingY);
|
||||
|
||||
int pitch = glyph->pitch < 0 ? -glyph->pitch : glyph->pitch;
|
||||
for (int row = 0; row < glyph->height; ++row) {
|
||||
for (int col = 0; col < glyph->width; ++col) {
|
||||
uint8_t alpha = glyph->pixels[row * pitch + col];
|
||||
if (alpha == 0) {
|
||||
continue;
|
||||
}
|
||||
float a = cmd.color.a * (static_cast<float>(alpha) / 255.0f);
|
||||
GuiColor color{cmd.color.r, cmd.color.g, cmd.color.b, a};
|
||||
GuiCommand::RectData pixelRect{
|
||||
glyphX + static_cast<float>(col),
|
||||
glyphY + static_cast<float>(row),
|
||||
1.0f,
|
||||
1.0f
|
||||
};
|
||||
AddQuad(pixelRect, color, activeClip);
|
||||
}
|
||||
}
|
||||
penX += static_cast<float>(glyph->advance);
|
||||
}
|
||||
}
|
||||
|
||||
void GuiRenderer::AddQuad(const GuiCommand::RectData& rect,
|
||||
const GuiColor& color,
|
||||
const GuiCommand::RectData& clipRect) {
|
||||
GuiCommand::RectData clipped = IntersectRect(rect, clipRect);
|
||||
if (!RectHasArea(clipped)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto toNDC = [this](float x, float y) -> std::pair<float, float> {
|
||||
float width = viewportWidth_ == 0 ? 1.0f : static_cast<float>(viewportWidth_);
|
||||
float height = viewportHeight_ == 0 ? 1.0f : static_cast<float>(viewportHeight_);
|
||||
return {
|
||||
(x / width) * 2.0f - 1.0f,
|
||||
(y / height) * 2.0f - 1.0f
|
||||
};
|
||||
};
|
||||
|
||||
auto [x1, y1] = toNDC(clipped.x, clipped.y);
|
||||
auto [x2, y2] = toNDC(clipped.x + clipped.width, clipped.y + clipped.height);
|
||||
|
||||
size_t baseIndex = vertices_.size();
|
||||
vertices_.push_back({x1, y1, 0.0f, color.r, color.g, color.b, color.a});
|
||||
vertices_.push_back({x2, y1, 0.0f, color.r, color.g, color.b, color.a});
|
||||
vertices_.push_back({x2, y2, 0.0f, color.r, color.g, color.b, color.a});
|
||||
vertices_.push_back({x1, y2, 0.0f, color.r, color.g, color.b, color.a});
|
||||
|
||||
indices_.push_back(static_cast<uint32_t>(baseIndex + 0));
|
||||
indices_.push_back(static_cast<uint32_t>(baseIndex + 1));
|
||||
indices_.push_back(static_cast<uint32_t>(baseIndex + 2));
|
||||
indices_.push_back(static_cast<uint32_t>(baseIndex + 0));
|
||||
indices_.push_back(static_cast<uint32_t>(baseIndex + 2));
|
||||
indices_.push_back(static_cast<uint32_t>(baseIndex + 3));
|
||||
}
|
||||
|
||||
void GuiRenderer::GenerateGuiGeometry(const std::vector<GuiCommand>& commands, uint32_t width, uint32_t height) {
|
||||
vertices_.clear();
|
||||
indices_.clear();
|
||||
@@ -519,29 +746,6 @@ void GuiRenderer::GenerateGuiGeometry(const std::vector<GuiCommand>& commands, u
|
||||
std::vector<GuiCommand::RectData> clipStack;
|
||||
clipStack.push_back({0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height)});
|
||||
|
||||
auto addQuad = [&](const GuiCommand::RectData& rect, const GuiColor& color,
|
||||
const GuiCommand::RectData& clipRect) {
|
||||
GuiCommand::RectData clipped = IntersectRect(rect, clipRect);
|
||||
if (!RectHasArea(clipped)) {
|
||||
return;
|
||||
}
|
||||
auto [x0, y0] = toNDC(clipped.x, clipped.y);
|
||||
auto [x1, y1] = toNDC(clipped.x + clipped.width, clipped.y + clipped.height);
|
||||
|
||||
uint32_t baseIndex = static_cast<uint32_t>(vertices_.size());
|
||||
vertices_.push_back({x0, y0, 0.0f, color.r, color.g, color.b, color.a});
|
||||
vertices_.push_back({x1, y0, 0.0f, color.r, color.g, color.b, color.a});
|
||||
vertices_.push_back({x1, y1, 0.0f, color.r, color.g, color.b, color.a});
|
||||
vertices_.push_back({x0, y1, 0.0f, color.r, color.g, color.b, color.a});
|
||||
|
||||
indices_.push_back(baseIndex + 0);
|
||||
indices_.push_back(baseIndex + 1);
|
||||
indices_.push_back(baseIndex + 2);
|
||||
indices_.push_back(baseIndex + 0);
|
||||
indices_.push_back(baseIndex + 2);
|
||||
indices_.push_back(baseIndex + 3);
|
||||
};
|
||||
|
||||
auto addClippedPolygon = [&](const std::vector<ClipPoint>& polygon,
|
||||
const GuiColor& color,
|
||||
const GuiCommand::RectData& clipRect) {
|
||||
@@ -580,7 +784,7 @@ void GuiRenderer::GenerateGuiGeometry(const std::vector<GuiCommand>& commands, u
|
||||
}
|
||||
|
||||
if (cmd.type == GuiCommand::Type::Rect) {
|
||||
addQuad(cmd.rect, cmd.color, activeClip);
|
||||
AddQuad(cmd.rect, cmd.color, activeClip);
|
||||
if (cmd.borderWidth > 0.0f && cmd.borderColor.a > 0.0f) {
|
||||
float border = std::min(cmd.borderWidth, std::min(cmd.rect.width, cmd.rect.height));
|
||||
if (border > 0.0f) {
|
||||
@@ -591,14 +795,13 @@ void GuiRenderer::GenerateGuiGeometry(const std::vector<GuiCommand>& commands, u
|
||||
GuiCommand::RectData left{cmd.rect.x, cmd.rect.y + border, border, innerHeight};
|
||||
GuiCommand::RectData right{cmd.rect.x + cmd.rect.width - border,
|
||||
cmd.rect.y + border, border, innerHeight};
|
||||
addQuad(top, cmd.borderColor, activeClip);
|
||||
addQuad(bottom, cmd.borderColor, activeClip);
|
||||
addQuad(left, cmd.borderColor, activeClip);
|
||||
addQuad(right, cmd.borderColor, activeClip);
|
||||
AddQuad(top, cmd.borderColor, activeClip);
|
||||
AddQuad(bottom, cmd.borderColor, activeClip);
|
||||
AddQuad(left, cmd.borderColor, activeClip);
|
||||
AddQuad(right, cmd.borderColor, activeClip);
|
||||
}
|
||||
}
|
||||
} else if (cmd.type == GuiCommand::Type::Text) {
|
||||
// Render text using 8x8 bitmap font
|
||||
if (cmd.text.empty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -646,36 +849,31 @@ void GuiRenderer::GenerateGuiGeometry(const std::vector<GuiCommand>& commands, u
|
||||
continue;
|
||||
}
|
||||
|
||||
// Render each character as a small quad
|
||||
float x = startX;
|
||||
for (char c : cmd.text) {
|
||||
// Only render printable ASCII characters
|
||||
if (c >= 32 && c < 127) {
|
||||
// Get character bitmap from font8x8_basic
|
||||
const uint8_t* glyph = font8x8_basic[static_cast<unsigned char>(c)];
|
||||
|
||||
// Render each pixel of the 8x8 glyph
|
||||
for (int row = 0; row < 8; ++row) {
|
||||
uint8_t rowData = glyph[row];
|
||||
for (int col = 0; col < 8; ++col) {
|
||||
if (rowData & (1 << col)) {
|
||||
// This pixel is on, render a small quad
|
||||
// Add slight overlap (1.15x) for smoother appearance
|
||||
const float pixelScale = 1.15f;
|
||||
float pixelWidth = (charWidth / 8.0f) * pixelScale;
|
||||
float pixelHeight = (charHeight / 8.0f) * pixelScale;
|
||||
float px = x + col * (charWidth / 8.0f) - (pixelWidth - charWidth / 8.0f) * 0.5f;
|
||||
float py = startY + row * (charHeight / 8.0f) - (pixelHeight - charHeight / 8.0f) * 0.5f;
|
||||
float pw = pixelWidth;
|
||||
float ph = pixelHeight;
|
||||
|
||||
GuiCommand::RectData pixelRect{px, py, pw, ph};
|
||||
addQuad(pixelRect, cmd.color, textClip);
|
||||
if (fontConfig_.useFreeType && EnsureFreeTypeReady()) {
|
||||
RenderFreeTypeText(cmd, textClip, textBounds);
|
||||
} else {
|
||||
// Render text using 8x8 bitmap font
|
||||
float x = startX;
|
||||
for (char c : cmd.text) {
|
||||
if (c >= 32 && c < 127) {
|
||||
const uint8_t* glyph = font8x8_basic[static_cast<unsigned char>(c)];
|
||||
for (int row = 0; row < 8; ++row) {
|
||||
uint8_t rowData = glyph[row];
|
||||
for (int col = 0; col < 8; ++col) {
|
||||
if (rowData & (1 << col)) {
|
||||
const float pixelScale = 1.15f;
|
||||
float pixelWidth = (charWidth / 8.0f) * pixelScale;
|
||||
float pixelHeight = (charHeight / 8.0f) * pixelScale;
|
||||
float px = x + col * (charWidth / 8.0f) - (pixelWidth - charWidth / 8.0f) * 0.5f;
|
||||
float py = startY + row * (charHeight / 8.0f) - (pixelHeight - charHeight / 8.0f) * 0.5f;
|
||||
GuiCommand::RectData pixelRect{px, py, pixelWidth, pixelHeight};
|
||||
AddQuad(pixelRect, cmd.color, textClip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
x += charWidth + charSpacing;
|
||||
}
|
||||
x += charWidth + charSpacing;
|
||||
}
|
||||
} else if (cmd.type == GuiCommand::Type::Svg) {
|
||||
if (cmd.svgPath.empty()) {
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include "services/interfaces/config_types.hpp"
|
||||
#include "services/interfaces/gui_types.hpp"
|
||||
#include "services/interfaces/i_buffer_service.hpp"
|
||||
#include "services/interfaces/i_logger.hpp"
|
||||
@@ -36,6 +38,7 @@ class GuiRenderer {
|
||||
public:
|
||||
GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFormat swapchainFormat,
|
||||
VkRenderPass renderPass, const std::filesystem::path& scriptDirectory,
|
||||
const GuiFontConfig& fontConfig,
|
||||
std::shared_ptr<IBufferService> bufferService, std::shared_ptr<ILogger> logger);
|
||||
~GuiRenderer();
|
||||
|
||||
@@ -49,7 +52,26 @@ public:
|
||||
bool IsReady() const;
|
||||
|
||||
private:
|
||||
struct GlyphBitmap {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int pitch = 0;
|
||||
int bearingX = 0;
|
||||
int bearingY = 0;
|
||||
int advance = 0;
|
||||
std::vector<uint8_t> pixels;
|
||||
};
|
||||
|
||||
const ParsedSvg* LoadSvg(const std::string& relativePath);
|
||||
bool EnsureFreeTypeReady();
|
||||
std::filesystem::path ResolveFontPath() const;
|
||||
const GlyphBitmap* LoadGlyph(char c, int pixelSize);
|
||||
void RenderFreeTypeText(const GuiCommand& cmd,
|
||||
const GuiCommand::RectData& activeClip,
|
||||
const GuiCommand::RectData& bounds);
|
||||
void AddQuad(const GuiCommand::RectData& rect,
|
||||
const GuiColor& color,
|
||||
const GuiCommand::RectData& clipRect);
|
||||
|
||||
void CreatePipeline(VkRenderPass renderPass, VkExtent2D extent);
|
||||
void CreateVertexAndIndexBuffers(size_t vertexCount, size_t indexCount);
|
||||
@@ -68,6 +90,7 @@ private:
|
||||
VkFormat swapchainFormat_;
|
||||
VkRenderPass renderPass_;
|
||||
std::filesystem::path scriptDirectory_;
|
||||
GuiFontConfig fontConfig_;
|
||||
|
||||
// Pipeline resources
|
||||
VkPipeline pipeline_ = VK_NULL_HANDLE;
|
||||
@@ -89,6 +112,11 @@ private:
|
||||
uint32_t viewportHeight_ = 0;
|
||||
std::unordered_map<std::string, ParsedSvg> svgCache_;
|
||||
std::unordered_map<std::string, std::vector<uint8_t>> shaderSpirvCache_;
|
||||
std::unordered_map<uint64_t, GlyphBitmap> glyphCache_;
|
||||
void* ftLibrary_ = nullptr;
|
||||
void* ftFace_ = nullptr;
|
||||
int currentFontSize_ = 0;
|
||||
bool freetypeReady_ = false;
|
||||
std::shared_ptr<IBufferService> bufferService_;
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
};
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
GuiRendererService::GuiRendererService(std::shared_ptr<ILogger> logger,
|
||||
std::shared_ptr<IBufferService> bufferService)
|
||||
std::shared_ptr<IBufferService> bufferService,
|
||||
std::shared_ptr<IConfigService> configService)
|
||||
: logger_(std::move(logger)),
|
||||
bufferService_(std::move(bufferService)) {
|
||||
bufferService_(std::move(bufferService)),
|
||||
configService_(std::move(configService)) {
|
||||
if (logger_) {
|
||||
logger_->Trace("GuiRendererService", "GuiRendererService",
|
||||
"bufferService=" + std::string(bufferService_ ? "set" : "null"));
|
||||
@@ -28,8 +30,13 @@ void GuiRendererService::Initialize(VkDevice device,
|
||||
", renderPassIsNull=" + std::string(renderPass == VK_NULL_HANDLE ? "true" : "false") +
|
||||
", resourcePath=" + resourcePath.string());
|
||||
}
|
||||
GuiFontConfig fontConfig;
|
||||
if (configService_) {
|
||||
fontConfig = configService_->GetGuiFontConfig();
|
||||
}
|
||||
renderer_ = std::make_unique<GuiRenderer>(
|
||||
device, physicalDevice, format, renderPass, resourcePath, bufferService_, logger_);
|
||||
device, physicalDevice, format, renderPass, resourcePath, fontConfig,
|
||||
bufferService_, logger_);
|
||||
}
|
||||
|
||||
void GuiRendererService::PrepareFrame(const std::vector<GuiCommand>& commands,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../interfaces/i_buffer_service.hpp"
|
||||
#include "../interfaces/i_config_service.hpp"
|
||||
#include "../interfaces/i_gui_renderer_service.hpp"
|
||||
#include "../interfaces/i_logger.hpp"
|
||||
#include "../../di/lifecycle.hpp"
|
||||
@@ -12,7 +13,8 @@ namespace sdl3cpp::services::impl {
|
||||
class GuiRendererService : public IGuiRendererService, public di::IShutdownable {
|
||||
public:
|
||||
GuiRendererService(std::shared_ptr<ILogger> logger,
|
||||
std::shared_ptr<IBufferService> bufferService);
|
||||
std::shared_ptr<IBufferService> bufferService,
|
||||
std::shared_ptr<IConfigService> configService);
|
||||
~GuiRendererService() override = default;
|
||||
|
||||
void Initialize(VkDevice device,
|
||||
@@ -36,6 +38,7 @@ public:
|
||||
private:
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
std::shared_ptr<IBufferService> bufferService_;
|
||||
std::shared_ptr<IConfigService> configService_;
|
||||
std::unique_ptr<GuiRenderer> renderer_;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <rapidjson/prettywriter.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
@@ -19,6 +21,29 @@ static const std::vector<const char*> kDeviceExtensions = {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
|
||||
};
|
||||
|
||||
GraphicsBackendType ParseBackendType(const std::string& value) {
|
||||
std::string lower = value;
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
if (lower == "vulkan") {
|
||||
return GraphicsBackendType::Vulkan;
|
||||
}
|
||||
if (lower == "bgfx") {
|
||||
return GraphicsBackendType::Bgfx;
|
||||
}
|
||||
throw std::runtime_error("graphics_backend.type must be 'vulkan' or 'bgfx'");
|
||||
}
|
||||
|
||||
std::string BackendTypeToString(GraphicsBackendType type) {
|
||||
switch (type) {
|
||||
case GraphicsBackendType::Vulkan:
|
||||
return "vulkan";
|
||||
case GraphicsBackendType::Bgfx:
|
||||
return "bgfx";
|
||||
}
|
||||
return "vulkan";
|
||||
}
|
||||
|
||||
JsonConfigService::JsonConfigService(std::shared_ptr<ILogger> logger, const char* argv0)
|
||||
: logger_(std::move(logger)), configJson_(), config_(RuntimeConfig{}) {
|
||||
if (logger_) {
|
||||
@@ -386,6 +411,129 @@ RuntimeConfig JsonConfigService::LoadFromJson(std::shared_ptr<ILogger> logger,
|
||||
}
|
||||
}
|
||||
|
||||
if (document.HasMember("graphics_backend")) {
|
||||
const auto& backendValue = document["graphics_backend"];
|
||||
if (!backendValue.IsObject()) {
|
||||
throw std::runtime_error("JSON member 'graphics_backend' must be an object");
|
||||
}
|
||||
if (backendValue.HasMember("type")) {
|
||||
const auto& value = backendValue["type"];
|
||||
if (!value.IsString()) {
|
||||
throw std::runtime_error("JSON member 'graphics_backend.type' must be a string");
|
||||
}
|
||||
config.graphicsBackend.backend = ParseBackendType(value.GetString());
|
||||
}
|
||||
if (backendValue.HasMember("bgfx_renderer")) {
|
||||
const auto& value = backendValue["bgfx_renderer"];
|
||||
if (!value.IsString()) {
|
||||
throw std::runtime_error("JSON member 'graphics_backend.bgfx_renderer' must be a string");
|
||||
}
|
||||
config.graphicsBackend.bgfxRenderer = value.GetString();
|
||||
}
|
||||
}
|
||||
|
||||
if (document.HasMember("materialx")) {
|
||||
const auto& materialValue = document["materialx"];
|
||||
if (!materialValue.IsObject()) {
|
||||
throw std::runtime_error("JSON member 'materialx' must be an object");
|
||||
}
|
||||
if (materialValue.HasMember("enabled")) {
|
||||
const auto& value = materialValue["enabled"];
|
||||
if (!value.IsBool()) {
|
||||
throw std::runtime_error("JSON member 'materialx.enabled' must be a boolean");
|
||||
}
|
||||
config.materialX.enabled = value.GetBool();
|
||||
}
|
||||
if (materialValue.HasMember("document")) {
|
||||
const auto& value = materialValue["document"];
|
||||
if (!value.IsString()) {
|
||||
throw std::runtime_error("JSON member 'materialx.document' must be a string");
|
||||
}
|
||||
config.materialX.documentPath = value.GetString();
|
||||
}
|
||||
if (materialValue.HasMember("shader_key")) {
|
||||
const auto& value = materialValue["shader_key"];
|
||||
if (!value.IsString()) {
|
||||
throw std::runtime_error("JSON member 'materialx.shader_key' must be a string");
|
||||
}
|
||||
config.materialX.shaderKey = value.GetString();
|
||||
}
|
||||
if (materialValue.HasMember("material")) {
|
||||
const auto& value = materialValue["material"];
|
||||
if (!value.IsString()) {
|
||||
throw std::runtime_error("JSON member 'materialx.material' must be a string");
|
||||
}
|
||||
config.materialX.materialName = value.GetString();
|
||||
}
|
||||
if (materialValue.HasMember("library_path")) {
|
||||
const auto& value = materialValue["library_path"];
|
||||
if (!value.IsString()) {
|
||||
throw std::runtime_error("JSON member 'materialx.library_path' must be a string");
|
||||
}
|
||||
config.materialX.libraryPath = value.GetString();
|
||||
}
|
||||
if (materialValue.HasMember("library_folders")) {
|
||||
const auto& value = materialValue["library_folders"];
|
||||
if (!value.IsArray()) {
|
||||
throw std::runtime_error("JSON member 'materialx.library_folders' must be an array");
|
||||
}
|
||||
config.materialX.libraryFolders.clear();
|
||||
for (rapidjson::SizeType i = 0; i < value.Size(); ++i) {
|
||||
if (!value[i].IsString()) {
|
||||
throw std::runtime_error("JSON member 'materialx.library_folders[" + std::to_string(i) + "]' must be a string");
|
||||
}
|
||||
config.materialX.libraryFolders.emplace_back(value[i].GetString());
|
||||
}
|
||||
}
|
||||
if (materialValue.HasMember("use_constant_color")) {
|
||||
const auto& value = materialValue["use_constant_color"];
|
||||
if (!value.IsBool()) {
|
||||
throw std::runtime_error("JSON member 'materialx.use_constant_color' must be a boolean");
|
||||
}
|
||||
config.materialX.useConstantColor = value.GetBool();
|
||||
}
|
||||
if (materialValue.HasMember("constant_color")) {
|
||||
const auto& value = materialValue["constant_color"];
|
||||
if (!value.IsArray() || value.Size() != 3) {
|
||||
throw std::runtime_error("JSON member 'materialx.constant_color' must be an array of 3 numbers");
|
||||
}
|
||||
for (rapidjson::SizeType i = 0; i < 3; ++i) {
|
||||
if (!value[i].IsNumber()) {
|
||||
throw std::runtime_error("JSON member 'materialx.constant_color[" + std::to_string(i) + "]' must be a number");
|
||||
}
|
||||
config.materialX.constantColor[i] = static_cast<float>(value[i].GetDouble());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (document.HasMember("gui_font")) {
|
||||
const auto& fontValue = document["gui_font"];
|
||||
if (!fontValue.IsObject()) {
|
||||
throw std::runtime_error("JSON member 'gui_font' must be an object");
|
||||
}
|
||||
if (fontValue.HasMember("use_freetype")) {
|
||||
const auto& value = fontValue["use_freetype"];
|
||||
if (!value.IsBool()) {
|
||||
throw std::runtime_error("JSON member 'gui_font.use_freetype' must be a boolean");
|
||||
}
|
||||
config.guiFont.useFreeType = value.GetBool();
|
||||
}
|
||||
if (fontValue.HasMember("font_path")) {
|
||||
const auto& value = fontValue["font_path"];
|
||||
if (!value.IsString()) {
|
||||
throw std::runtime_error("JSON member 'gui_font.font_path' must be a string");
|
||||
}
|
||||
config.guiFont.fontPath = value.GetString();
|
||||
}
|
||||
if (fontValue.HasMember("font_size")) {
|
||||
const auto& value = fontValue["font_size"];
|
||||
if (!value.IsNumber()) {
|
||||
throw std::runtime_error("JSON member 'gui_font.font_size' must be a number");
|
||||
}
|
||||
config.guiFont.fontSize = static_cast<float>(value.GetDouble());
|
||||
}
|
||||
}
|
||||
|
||||
if (document.HasMember("gui_opacity")) {
|
||||
const auto& value = document["gui_opacity"];
|
||||
if (!value.IsNumber()) {
|
||||
@@ -442,6 +590,50 @@ std::string JsonConfigService::BuildConfigJson(const RuntimeConfig& config,
|
||||
allocator);
|
||||
document.AddMember("render_graph", renderGraphObject, allocator);
|
||||
|
||||
rapidjson::Value backendObject(rapidjson::kObjectType);
|
||||
backendObject.AddMember("type",
|
||||
rapidjson::Value(BackendTypeToString(config.graphicsBackend.backend).c_str(), allocator),
|
||||
allocator);
|
||||
backendObject.AddMember("bgfx_renderer",
|
||||
rapidjson::Value(config.graphicsBackend.bgfxRenderer.c_str(), allocator),
|
||||
allocator);
|
||||
document.AddMember("graphics_backend", backendObject, allocator);
|
||||
|
||||
rapidjson::Value materialObject(rapidjson::kObjectType);
|
||||
materialObject.AddMember("enabled", config.materialX.enabled, allocator);
|
||||
materialObject.AddMember("document",
|
||||
rapidjson::Value(config.materialX.documentPath.string().c_str(), allocator),
|
||||
allocator);
|
||||
materialObject.AddMember("shader_key",
|
||||
rapidjson::Value(config.materialX.shaderKey.c_str(), allocator),
|
||||
allocator);
|
||||
materialObject.AddMember("material",
|
||||
rapidjson::Value(config.materialX.materialName.c_str(), allocator),
|
||||
allocator);
|
||||
materialObject.AddMember("library_path",
|
||||
rapidjson::Value(config.materialX.libraryPath.string().c_str(), allocator),
|
||||
allocator);
|
||||
rapidjson::Value libraryFolders(rapidjson::kArrayType);
|
||||
for (const auto& folder : config.materialX.libraryFolders) {
|
||||
libraryFolders.PushBack(rapidjson::Value(folder.c_str(), allocator), allocator);
|
||||
}
|
||||
materialObject.AddMember("library_folders", libraryFolders, allocator);
|
||||
materialObject.AddMember("use_constant_color", config.materialX.useConstantColor, allocator);
|
||||
rapidjson::Value constantColor(rapidjson::kArrayType);
|
||||
constantColor.PushBack(config.materialX.constantColor[0], allocator);
|
||||
constantColor.PushBack(config.materialX.constantColor[1], allocator);
|
||||
constantColor.PushBack(config.materialX.constantColor[2], allocator);
|
||||
materialObject.AddMember("constant_color", constantColor, allocator);
|
||||
document.AddMember("materialx", materialObject, allocator);
|
||||
|
||||
rapidjson::Value fontObject(rapidjson::kObjectType);
|
||||
fontObject.AddMember("use_freetype", config.guiFont.useFreeType, allocator);
|
||||
fontObject.AddMember("font_path",
|
||||
rapidjson::Value(config.guiFont.fontPath.string().c_str(), allocator),
|
||||
allocator);
|
||||
fontObject.AddMember("font_size", config.guiFont.fontSize, allocator);
|
||||
document.AddMember("gui_font", fontObject, allocator);
|
||||
|
||||
rapidjson::Value bindingsObject(rapidjson::kObjectType);
|
||||
auto addBindingMember = [&](const char* name, const std::string& value) {
|
||||
rapidjson::Value nameValue(name, allocator);
|
||||
|
||||
@@ -94,6 +94,24 @@ public:
|
||||
}
|
||||
return config_.renderGraph;
|
||||
}
|
||||
const GraphicsBackendConfig& GetGraphicsBackendConfig() const override {
|
||||
if (logger_) {
|
||||
logger_->Trace("JsonConfigService", "GetGraphicsBackendConfig");
|
||||
}
|
||||
return config_.graphicsBackend;
|
||||
}
|
||||
const MaterialXConfig& GetMaterialXConfig() const override {
|
||||
if (logger_) {
|
||||
logger_->Trace("JsonConfigService", "GetMaterialXConfig");
|
||||
}
|
||||
return config_.materialX;
|
||||
}
|
||||
const GuiFontConfig& GetGuiFontConfig() const override {
|
||||
if (logger_) {
|
||||
logger_->Trace("JsonConfigService", "GetGuiFontConfig");
|
||||
}
|
||||
return config_.guiFont;
|
||||
}
|
||||
const std::string& GetConfigJson() const override {
|
||||
if (logger_) {
|
||||
logger_->Trace("JsonConfigService", "GetConfigJson");
|
||||
|
||||
113
src/services/impl/materialx_shader_generator.cpp
Normal file
113
src/services/impl/materialx_shader_generator.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#include "materialx_shader_generator.hpp"
|
||||
|
||||
#include <MaterialXCore/Document.h>
|
||||
#include <MaterialXFormat/File.h>
|
||||
#include <MaterialXGenGlsl/VkShaderGenerator.h>
|
||||
#include <MaterialXGenShader/GenContext.h>
|
||||
#include <MaterialXGenShader/Util.h>
|
||||
#include <MaterialXRender/Util.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
namespace mx = MaterialX;
|
||||
|
||||
MaterialXShaderGenerator::MaterialXShaderGenerator(std::shared_ptr<ILogger> logger)
|
||||
: logger_(std::move(logger)) {}
|
||||
|
||||
std::filesystem::path MaterialXShaderGenerator::ResolvePath(
|
||||
const std::filesystem::path& path,
|
||||
const std::filesystem::path& scriptDirectory) const {
|
||||
if (path.empty()) {
|
||||
return {};
|
||||
}
|
||||
if (path.is_absolute()) {
|
||||
return path;
|
||||
}
|
||||
std::filesystem::path base = scriptDirectory;
|
||||
if (!base.empty()) {
|
||||
auto projectRoot = base.parent_path();
|
||||
if (!projectRoot.empty()) {
|
||||
return std::filesystem::weakly_canonical(projectRoot / path);
|
||||
}
|
||||
}
|
||||
return std::filesystem::weakly_canonical(path);
|
||||
}
|
||||
|
||||
ShaderPaths MaterialXShaderGenerator::Generate(const MaterialXConfig& config,
|
||||
const std::filesystem::path& scriptDirectory) const {
|
||||
if (!config.enabled) {
|
||||
return {};
|
||||
}
|
||||
|
||||
mx::FileSearchPath searchPath;
|
||||
std::filesystem::path libraryPath = ResolvePath(config.libraryPath, scriptDirectory);
|
||||
if (libraryPath.empty() && !scriptDirectory.empty()) {
|
||||
auto fallback = scriptDirectory.parent_path() / "MaterialX" / "libraries";
|
||||
if (std::filesystem::exists(fallback)) {
|
||||
libraryPath = fallback;
|
||||
}
|
||||
}
|
||||
if (!libraryPath.empty()) {
|
||||
searchPath.append(mx::FilePath(libraryPath.string()));
|
||||
}
|
||||
|
||||
mx::DocumentPtr stdLib = mx::createDocument();
|
||||
if (!config.libraryFolders.empty()) {
|
||||
mx::FilePathVec folders;
|
||||
for (const auto& folder : config.libraryFolders) {
|
||||
folders.emplace_back(folder);
|
||||
}
|
||||
mx::loadLibraries(folders, searchPath, stdLib);
|
||||
}
|
||||
|
||||
mx::ShaderGeneratorPtr generator = mx::VkShaderGenerator::create();
|
||||
mx::GenContext context(generator);
|
||||
context.registerSourceCodeSearchPath(searchPath);
|
||||
|
||||
mx::ShaderPtr shader;
|
||||
if (config.useConstantColor) {
|
||||
mx::Color3 color(config.constantColor[0], config.constantColor[1], config.constantColor[2]);
|
||||
shader = mx::createConstantShader(context, stdLib, config.shaderKey, color);
|
||||
} else {
|
||||
if (config.documentPath.empty()) {
|
||||
throw std::runtime_error("MaterialX document path is required when use_constant_color is false");
|
||||
}
|
||||
|
||||
std::filesystem::path documentPath = ResolvePath(config.documentPath, scriptDirectory);
|
||||
if (documentPath.empty()) {
|
||||
throw std::runtime_error("MaterialX document path could not be resolved");
|
||||
}
|
||||
|
||||
mx::DocumentPtr document = mx::createDocument();
|
||||
mx::readFromXmlFile(document, mx::FilePath(documentPath.string()), &searchPath);
|
||||
document->importLibrary(stdLib);
|
||||
|
||||
mx::TypedElementPtr element;
|
||||
if (!config.materialName.empty()) {
|
||||
element = document->getMaterial(config.materialName);
|
||||
}
|
||||
if (!element) {
|
||||
auto renderables = mx::findRenderableElements(document);
|
||||
if (!renderables.empty()) {
|
||||
element = renderables.front();
|
||||
}
|
||||
}
|
||||
if (!element) {
|
||||
throw std::runtime_error("MaterialX document has no renderable elements");
|
||||
}
|
||||
|
||||
shader = mx::createShader(config.shaderKey, context, element);
|
||||
}
|
||||
|
||||
if (!shader) {
|
||||
throw std::runtime_error("MaterialX shader generation failed");
|
||||
}
|
||||
|
||||
ShaderPaths paths;
|
||||
paths.vertexSource = shader->getSourceCode(mx::Stage::VERTEX);
|
||||
paths.fragmentSource = shader->getSourceCode(mx::Stage::PIXEL);
|
||||
return paths;
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
24
src/services/impl/materialx_shader_generator.hpp
Normal file
24
src/services/impl/materialx_shader_generator.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "../interfaces/config_types.hpp"
|
||||
#include "../interfaces/graphics_types.hpp"
|
||||
#include "../interfaces/i_logger.hpp"
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
class MaterialXShaderGenerator {
|
||||
public:
|
||||
MaterialXShaderGenerator(std::shared_ptr<ILogger> logger);
|
||||
|
||||
ShaderPaths Generate(const MaterialXConfig& config,
|
||||
const std::filesystem::path& scriptDirectory) const;
|
||||
|
||||
private:
|
||||
std::filesystem::path ResolvePath(const std::filesystem::path& path,
|
||||
const std::filesystem::path& scriptDirectory) const;
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
53
src/services/impl/null_gui_service.cpp
Normal file
53
src/services/impl/null_gui_service.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "null_gui_service.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
NullGuiService::NullGuiService(std::shared_ptr<ILogger> logger)
|
||||
: logger_(std::move(logger)) {
|
||||
if (logger_) {
|
||||
logger_->Trace("NullGuiService", "NullGuiService");
|
||||
}
|
||||
}
|
||||
|
||||
void NullGuiService::Initialize(VkDevice device,
|
||||
VkPhysicalDevice physicalDevice,
|
||||
VkFormat format,
|
||||
VkRenderPass renderPass,
|
||||
const std::filesystem::path& resourcePath) {
|
||||
if (logger_) {
|
||||
logger_->Trace("NullGuiService", "Initialize");
|
||||
}
|
||||
}
|
||||
|
||||
void NullGuiService::PrepareFrame(const std::vector<GuiCommand>& commands,
|
||||
uint32_t width,
|
||||
uint32_t height) {
|
||||
if (logger_) {
|
||||
logger_->Trace("NullGuiService", "PrepareFrame",
|
||||
"commands.size=" + std::to_string(commands.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void NullGuiService::RenderToSwapchain(VkCommandBuffer commandBuffer, VkImage image) {
|
||||
if (logger_) {
|
||||
logger_->Trace("NullGuiService", "RenderToSwapchain");
|
||||
}
|
||||
}
|
||||
|
||||
void NullGuiService::Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) {
|
||||
if (logger_) {
|
||||
logger_->Trace("NullGuiService", "Resize",
|
||||
"width=" + std::to_string(width) +
|
||||
", height=" + std::to_string(height));
|
||||
}
|
||||
}
|
||||
|
||||
void NullGuiService::Shutdown() {
|
||||
if (logger_) {
|
||||
logger_->Trace("NullGuiService", "Shutdown");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
29
src/services/impl/null_gui_service.hpp
Normal file
29
src/services/impl/null_gui_service.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "../interfaces/i_gui_service.hpp"
|
||||
#include "../interfaces/i_logger.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
class NullGuiService : public IGuiService {
|
||||
public:
|
||||
explicit NullGuiService(std::shared_ptr<ILogger> logger);
|
||||
|
||||
void Initialize(VkDevice device,
|
||||
VkPhysicalDevice physicalDevice,
|
||||
VkFormat format,
|
||||
VkRenderPass renderPass,
|
||||
const std::filesystem::path& resourcePath) override;
|
||||
void PrepareFrame(const std::vector<GuiCommand>& commands,
|
||||
uint32_t width,
|
||||
uint32_t height) override;
|
||||
void RenderToSwapchain(VkCommandBuffer commandBuffer, VkImage image) override;
|
||||
void Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) override;
|
||||
void Shutdown() override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
@@ -15,12 +15,16 @@
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
ShaderScriptService::ShaderScriptService(std::shared_ptr<IScriptEngineService> engineService,
|
||||
std::shared_ptr<IConfigService> configService,
|
||||
std::shared_ptr<ILogger> logger)
|
||||
: engineService_(std::move(engineService)),
|
||||
logger_(std::move(logger)) {
|
||||
configService_(std::move(configService)),
|
||||
logger_(std::move(logger)),
|
||||
materialxGenerator_(logger_) {
|
||||
if (logger_) {
|
||||
logger_->Trace("ShaderScriptService", "ShaderScriptService",
|
||||
"engineService=" + std::string(engineService_ ? "set" : "null"));
|
||||
"engineService=" + std::string(engineService_ ? "set" : "null") +
|
||||
", configService=" + std::string(configService_ ? "set" : "null"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,12 +69,32 @@ std::unordered_map<std::string, ShaderPaths> ShaderScriptService::LoadShaderPath
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (configService_) {
|
||||
const auto& materialConfig = configService_->GetMaterialXConfig();
|
||||
if (materialConfig.enabled) {
|
||||
try {
|
||||
ShaderPaths materialShader = materialxGenerator_.Generate(
|
||||
materialConfig,
|
||||
engineService_ ? engineService_->GetScriptDirectory() : std::filesystem::path{});
|
||||
if (!materialConfig.shaderKey.empty()) {
|
||||
shaderMap[materialConfig.shaderKey] = std::move(materialShader);
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
if (logger_) {
|
||||
logger_->Error("MaterialX shader generation failed: " + std::string(ex.what()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shaderMap.empty()) {
|
||||
if (logger_) {
|
||||
logger_->Error("'get_shader_paths' did not return any shader variants");
|
||||
}
|
||||
throw std::runtime_error("'get_shader_paths' did not return any shader variants");
|
||||
}
|
||||
|
||||
return shaderMap;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
#include "../interfaces/i_shader_script_service.hpp"
|
||||
#include "../interfaces/i_script_engine_service.hpp"
|
||||
#include "../interfaces/i_config_service.hpp"
|
||||
#include "../interfaces/i_logger.hpp"
|
||||
#include "materialx_shader_generator.hpp"
|
||||
#include <memory>
|
||||
|
||||
struct lua_State;
|
||||
@@ -15,6 +17,7 @@ namespace sdl3cpp::services::impl {
|
||||
class ShaderScriptService : public IShaderScriptService {
|
||||
public:
|
||||
ShaderScriptService(std::shared_ptr<IScriptEngineService> engineService,
|
||||
std::shared_ptr<IConfigService> configService,
|
||||
std::shared_ptr<ILogger> logger);
|
||||
|
||||
std::unordered_map<std::string, ShaderPaths> LoadShaderPathsMap() override;
|
||||
@@ -25,7 +28,9 @@ private:
|
||||
std::string ResolveShaderPath(const std::string& path) const;
|
||||
|
||||
std::shared_ptr<IScriptEngineService> engineService_;
|
||||
std::shared_ptr<IConfigService> configService_;
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
MaterialXShaderGenerator materialxGenerator_;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace sdl3cpp::services {
|
||||
|
||||
@@ -89,6 +90,41 @@ struct RenderGraphConfig {
|
||||
std::string functionName = "get_render_graph";
|
||||
};
|
||||
|
||||
enum class GraphicsBackendType {
|
||||
Vulkan,
|
||||
Bgfx
|
||||
};
|
||||
|
||||
struct GraphicsBackendConfig {
|
||||
GraphicsBackendType backend = GraphicsBackendType::Vulkan;
|
||||
std::string bgfxRenderer = "vulkan";
|
||||
};
|
||||
|
||||
struct MaterialXConfig {
|
||||
bool enabled = false;
|
||||
std::filesystem::path documentPath;
|
||||
std::string shaderKey = "materialx";
|
||||
std::string materialName;
|
||||
std::filesystem::path libraryPath;
|
||||
std::vector<std::string> libraryFolders = {
|
||||
"stdlib",
|
||||
"pbrlib",
|
||||
"lights",
|
||||
"bxdf",
|
||||
"cmlib",
|
||||
"nprlib",
|
||||
"targets"
|
||||
};
|
||||
bool useConstantColor = false;
|
||||
std::array<float, 3> constantColor = {1.0f, 1.0f, 1.0f};
|
||||
};
|
||||
|
||||
struct GuiFontConfig {
|
||||
bool useFreeType = false;
|
||||
std::filesystem::path fontPath;
|
||||
float fontSize = 18.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Runtime configuration values used across services.
|
||||
*/
|
||||
@@ -102,6 +138,9 @@ struct RuntimeConfig {
|
||||
InputBindings inputBindings{};
|
||||
AtmosphericsConfig atmospherics{};
|
||||
RenderGraphConfig renderGraph{};
|
||||
GraphicsBackendConfig graphicsBackend{};
|
||||
MaterialXConfig materialX{};
|
||||
GuiFontConfig guiFont{};
|
||||
float guiOpacity = 1.0f;
|
||||
};
|
||||
|
||||
|
||||
@@ -72,6 +72,24 @@ public:
|
||||
*/
|
||||
virtual const RenderGraphConfig& GetRenderGraphConfig() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get graphics backend settings.
|
||||
* @return Graphics backend configuration
|
||||
*/
|
||||
virtual const GraphicsBackendConfig& GetGraphicsBackendConfig() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get MaterialX settings.
|
||||
* @return MaterialX configuration
|
||||
*/
|
||||
virtual const MaterialXConfig& GetMaterialXConfig() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get GUI font settings.
|
||||
* @return GUI font configuration
|
||||
*/
|
||||
virtual const GuiFontConfig& GetGuiFontConfig() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the full JSON configuration as a string.
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "services/impl/script_engine_service.hpp"
|
||||
#include "services/impl/scene_script_service.hpp"
|
||||
#include "services/impl/shader_script_service.hpp"
|
||||
#include "services/interfaces/i_config_service.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
@@ -68,7 +69,8 @@ int main() {
|
||||
engineService->Initialize();
|
||||
|
||||
sdl3cpp::services::impl::SceneScriptService sceneService(engineService, logger);
|
||||
sdl3cpp::services::impl::ShaderScriptService shaderService(engineService, logger);
|
||||
std::shared_ptr<sdl3cpp::services::IConfigService> configService;
|
||||
sdl3cpp::services::impl::ShaderScriptService shaderService(engineService, configService, logger);
|
||||
|
||||
auto objects = sceneService.LoadSceneObjects();
|
||||
Assert(objects.size() == 1, "expected exactly one scene object", failures);
|
||||
|
||||
Reference in New Issue
Block a user