diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a96508..dfe4954 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ if(ENABLE_VITA) include_directories("$ENV{VITASDK}/arm-vita-eabi/include") link_directories("$ENV{VITASDK}/arm-vita-eabi/lib") # Disable SDL3 app when building for Vita - set(BUILD_SDL3_APP OFF CACHE BOOL "Build the SDL3 Vulkan demo" FORCE) + set(BUILD_SDL3_APP OFF CACHE BOOL "Build the SDL3 bgfx demo" FORCE) set(BUILD_SCRIPT_TESTS OFF CACHE BOOL "Build script engine tests" FORCE) endif() if(CMAKE_GENERATOR MATCHES "Ninja") @@ -67,13 +67,13 @@ if(ENABLE_VITA) set(VITA_APP_NAME "SDL3App") set(VITA_TITLEID "ABCD00001") endif() -option(BUILD_SDL3_APP "Build the SDL3 Vulkan demo" ON) +option(BUILD_SDL3_APP "Build the SDL3 bgfx demo" ON) set(SDL_VERSION "SDL3" CACHE STRING "SDL version to use: SDL3 or sdl") set_property(CACHE SDL_VERSION PROPERTY STRINGS "SDL3" "sdl") if(BUILD_SDL3_APP AND NOT SDL_VERSION STREQUAL "SDL3") message(STATUS "Disabling BUILD_SDL3_APP because SDL_VERSION is set to '${SDL_VERSION}' instead of SDL3") - set(BUILD_SDL3_APP OFF CACHE BOOL "Build the SDL3 Vulkan demo" FORCE) + set(BUILD_SDL3_APP OFF CACHE BOOL "Build the SDL3 bgfx demo" FORCE) endif() if(ENABLE_VITA) @@ -113,11 +113,8 @@ if(CMAKE_GENERATOR MATCHES "Ninja" OR CMAKE_GENERATOR MATCHES "Ninja Multi-Confi endif() endif() -if(BUILD_SDL3_APP) - find_package(Vulkan REQUIRED) - if(NOT ENABLE_VITA) - find_package(shaderc CONFIG REQUIRED) - endif() +if(BUILD_SDL3_APP AND NOT ENABLE_VITA) + find_package(shaderc CONFIG REQUIRED) endif() # SDL is required for both the demo app and cube_script_tests (used by audio_player) @@ -199,39 +196,28 @@ if(BUILD_SDL3_APP) 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 src/services/impl/physics_bridge_service.cpp src/services/impl/mesh_service.cpp src/services/impl/sdl_window_service.cpp src/services/impl/sdl_input_service.cpp - src/services/impl/vulkan_device_service.cpp - src/services/impl/swapchain_service.cpp - src/services/impl/pipeline_service.cpp - src/services/impl/buffer_service.cpp - src/services/impl/render_command_service.cpp src/services/impl/sdl_audio_service.cpp src/services/impl/crash_recovery_service.cpp src/services/impl/lifecycle_service.cpp src/services/impl/application_loop_service.cpp 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 - $<$>:src/services/impl/vulkan_graphics_backend.cpp> $<$>:src/services/impl/bgfx_graphics_backend.cpp> $<$:src/services/impl/gxm_graphics_backend.cpp> src/app/service_based_app.cpp - src/services/impl/gui_renderer.cpp ) target_include_directories(sdl3_app PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") target_link_libraries(sdl3_app PRIVATE ${SDL_TARGET} - Vulkan::Vulkan lua::lua CLI11::CLI11 rapidjson diff --git a/README.md b/README.md index 02bc2e0..1d9ad67 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # SDL3CPlusPlus -SDL3 + Vulkan demo app with Lua-driven runtime configuration, audio playback, and a small GUI sample. +SDL3 + bgfx demo app with Lua-driven runtime configuration, audio playback, and a small GUI sample. ## Overview -- Renders a spinning cube with Vulkan via SDL3. +- Renders a spinning cube with bgfx (Vulkan renderer by default) via SDL3. - Lua scripts control behavior under `scripts/`. - JSON runtime configs live in `config/`. - Optional helpers for audio asset generation and workflow diagnostics. @@ -12,7 +12,7 @@ SDL3 + Vulkan demo app with Lua-driven runtime configuration, audio playback, an - Conan for dependency resolution. - CMake and a C++ compiler. - Ninja (default generator) or Visual Studio 2022 on Windows. -- Vulkan runtime/driver to run the demo. +- Vulkan runtime/driver for the default bgfx renderer (or set `bgfx.renderer` in config). ## Quick start 1. `python scripts/dev_commands.py dependencies` @@ -88,7 +88,7 @@ Example: ``` ## GUI demo -`scripts/gui_demo.lua` paints the Lua GUI framework on top of the Vulkan scene. Launch it as `python scripts/dev_commands.py run -- --json-file-in config/gui_runtime.json` or register that config via `sdl3_app --set-default-json`. +`scripts/gui_demo.lua` exercises the Lua GUI framework; rendering hooks are currently stubbed while bgfx GUI integration is wired up. Launch it as `python scripts/dev_commands.py run -- --json-file-in config/gui_runtime.json` or register that config via `sdl3_app --set-default-json`. ## Audio assets Install the dependencies that power `scripts/generate_audio_assets.py`: diff --git a/conanfile.py b/conanfile.py index 63b0145..5208452 100644 --- a/conanfile.py +++ b/conanfile.py @@ -17,11 +17,6 @@ class SDL3CppConan(ConanFile): BASE_REQUIRES = ( "lua/5.4.8", "sdl/3.2.20", - "vulkan-loader/1.4.313.0", - "vulkan-headers/1.4.313.0", - "vulkan-memory-allocator/3.3.0", - "spirv-tools/1.4.313.0", - "spirv-headers/1.4.313.0", "shaderc/2025.3", "cpptrace/1.0.4", "ogg/1.3.5", diff --git a/config/gui_runtime.json b/config/gui_runtime.json index 1840020..adfd8a7 100644 --- a/config/gui_runtime.json +++ b/config/gui_runtime.json @@ -10,9 +10,9 @@ "scripts_directory": "scripts", "project_root": "../", "shaders_directory": "shaders", - "device_extensions": [ - "VK_KHR_swapchain" - ], + "bgfx": { + "renderer": "vulkan" + }, "mouse_grab": { "enabled": true, "grab_on_click": false, diff --git a/config/seed_runtime.json b/config/seed_runtime.json index 78e8a5f..c7dd606 100644 --- a/config/seed_runtime.json +++ b/config/seed_runtime.json @@ -57,9 +57,9 @@ }, "project_root": "../", "shaders_directory": "shaders", - "device_extensions": [ - "VK_KHR_swapchain" - ], + "bgfx": { + "renderer": "vulkan" + }, "atmospherics": { "ambient_strength": 0.01, "fog_density": 0.003, @@ -74,10 +74,6 @@ "pbr_roughness": 0.3, "pbr_metallic": 0.1 }, - "render_graph": { - "enabled": true, - "function": "get_render_graph" - }, "gui_opacity": 1.0, "config_file": "config/seed_runtime.json" } diff --git a/config/soundboard_runtime.json b/config/soundboard_runtime.json index 4070ffd..8be8276 100644 --- a/config/soundboard_runtime.json +++ b/config/soundboard_runtime.json @@ -10,9 +10,9 @@ "scripts_directory": "scripts", "project_root": "../", "shaders_directory": "shaders", - "device_extensions": [ - "VK_KHR_swapchain" - ], + "bgfx": { + "renderer": "vulkan" + }, "config_file": "config/soundboard_runtime.json", "input_bindings": { "move_forward": "W", diff --git a/config/vita_cube_runtime.json b/config/vita_cube_runtime.json index 22039bb..cc14bdc 100644 --- a/config/vita_cube_runtime.json +++ b/config/vita_cube_runtime.json @@ -10,10 +10,6 @@ "scripts_directory": "scripts", "project_root": "../", "shaders_directory": "shaders", - "render_graph": { - "enabled": false, - "function": "get_render_graph" - }, "vita_specific": { "analog_stick_sensitivity": 1.2, "gyroscope_enabled": true, diff --git a/docs/test_error_handling.md b/docs/test_error_handling.md deleted file mode 100644 index 6e5143c..0000000 --- a/docs/test_error_handling.md +++ /dev/null @@ -1,229 +0,0 @@ -# Error Handling Improvements - Summary - -## Problem Statement -The application was crashing with a "see-through window" and no user feedback about what went wrong. The process would be killed (exit code 137 - SIGKILL) without any diagnostic information. - -## Changes Made - -### 1. SDL Initialization Error Dialogs ([sdl3_app_core.cpp](src/app/sdl3_app_core.cpp)) -- Added `ShowErrorDialog()` helper function to display visual error messages via `SDL_ShowSimpleMessageBox` -- Wrapped SDL initialization calls with try-catch blocks that show error dialogs: - - **SDL_Init**: Shows which subsystem failed to initialize - - **SDL_Vulkan_LoadLibrary**: Warns user to check Vulkan driver installation - - **SDL_CreateWindow**: Provides window creation error details - -**Example error message**: -``` -Title: "Vulkan Library Load Failed" -Message: "Failed to load Vulkan library. Make sure Vulkan drivers are installed. - -Error: SDL_Vulkan_LoadLibrary failed: ..." -``` - -### 2. Vulkan Validation with Detailed Feedback ([sdl3_app_device.cpp](src/app/sdl3_app_device.cpp)) - -#### Instance Creation -- Lists all required Vulkan extensions when creation fails -- Provides hints about common causes (missing drivers, incompatible GPU) - -#### Physical Device Selection -- Enumerates all detected GPUs with their names -- Marks each as [SELECTED] or [UNSUITABLE] -- Explains required features: - - Graphics queue support - - Present queue support - - Swapchain extension (VK_KHR_swapchain) - - Adequate swapchain formats and present modes - -**Example error message**: -``` -Failed to find a suitable GPU. - -Found 2 GPU(s), but none meet the requirements. -GPU 0: Intel HD Graphics 620 [UNSUITABLE] -GPU 1: NVIDIA GeForce GTX 1050 [UNSUITABLE] - -Required features: -- Graphics queue support -- Present queue support -- Swapchain extension support (VK_KHR_swapchain) -- Adequate swapchain formats and present modes -``` - -#### Device Extension Support -- Logs all missing required extensions to stderr -- Helps identify driver or GPU capability issues - -### 3. Resource Existence Validation ([sdl3_app_core.cpp](src/app/sdl3_app_core.cpp), [sdl3_app_pipeline.cpp](src/app/sdl3_app_pipeline.cpp)) - -#### Enhanced ReadFile() Function -Now validates before attempting to read: -- ✅ File exists at the specified path -- ✅ Path points to a regular file (not a directory) -- ✅ File is not empty -- ✅ File has read permissions - -**Example error messages**: -``` -File not found: /path/to/shader.vert - -Please ensure the file exists at this location. -``` - -``` -Failed to open file: /path/to/file.txt - -The file exists but cannot be opened. Check file permissions. -``` - -#### Shader File Validation -- Checks both vertex and fragment shader files exist before loading -- Provides the shader key to help identify which pipeline failed -- Suggests checking that shader sources are present in the correct directory - -**Example error message**: -``` -Vertex shader not found: shaders/missing.vert - -Shader key: default - -Please ensure shader source files are present in the shaders directory. -``` - -### 4. Structured Vulkan Initialization ([sdl3_app_core.cpp](src/app/sdl3_app_core.cpp)) - -The `InitVulkan()` function now wraps each stage in try-catch blocks with specific error dialogs: - -1. **Instance Creation** → "Vulkan Instance Creation Failed" -2. **Surface Creation** → "Vulkan Surface Creation Failed" -3. **GPU Selection** → "GPU Selection Failed" -4. **Logical Device** → "Logical Device Creation Failed" -5. **Resource Setup** → "Vulkan Resource Setup Failed" - - Swapchain, GUI renderer, image views, render pass - - Scene data, graphics pipeline, framebuffers - - Command pool, vertex/index buffers, command buffers, sync objects - -Each failure shows a message box with the error details before rethrowing. - -### 5. Main Function Error Handling ([main.cpp](src/main.cpp)) - -- Catches all exceptions at the top level -- Displays error via `SDL_ShowSimpleMessageBox` for visual feedback -- Also logs to stderr for console/log file capture -- Returns EXIT_FAILURE with clear error indication - -## Benefits - -### 1. Visual Feedback ✨ -**Before**: Silent crash with transparent window -**After**: Clear error dialog explaining what went wrong - -### 2. Actionable Messages 🔧 -Errors now include: -- What failed (e.g., "GPU Selection Failed") -- Why it might have failed (e.g., "Missing Vulkan drivers") -- What to check (e.g., "Ensure shader files are compiled") - -### 3. Debugging Aid 🐛 -Detailed information helps diagnose issues: -- List of detected GPUs and their suitability -- Required vs available extensions -- Full file paths for missing resources - -### 4. Early Validation ⚡ -Resources are validated before use: -- No more cryptic segfaults from missing files -- Fails fast with clear error messages -- Reduces time to identify and fix issues - -### 5. No Silent Failures 📢 -All error paths now provide feedback: -- Console output (stderr) -- Visual message boxes (SDL) -- Proper exit codes - -## Testing the Improvements - -### Test Case 1: Missing Lua Script -```bash -cd build/Release -sed -i 's/cube_logic.lua/nonexistent_file.lua/' config/test_bad_config.json -./sdl3_app --json-file-in ./config/test_bad_config.json -``` - -**Expected Result**: -``` -ERROR: Lua script not found at /path/to/nonexistent_file.lua -``` -Plus a message box with the same error. - -### Test Case 2: Missing Shader File -```bash -cd build/Release -sed -i 's/return {default = variant}/return {default = {vertex = "shaders\/missing.vert", fragment = "shaders\/missing.frag"}}/' ../scripts/gui_demo.lua -./sdl3_app --json-file-in ./config/gui_runtime.json -``` - -**Expected Result**: -Message box showing: -``` -Vertex shader not found: shaders/missing.vert - -Shader key: default - -Please ensure shader files are compiled and present in the shaders directory. -``` -Restore `scripts/gui_demo.lua` after the test run. - -### Test Case 3: Normal Operation -```bash -cd build/Release -./sdl3_app --json-file-in ./config/seed_runtime.json -``` - -**Expected Result**: -Application runs normally. If any errors occur during initialization, they will be caught and displayed with helpful context instead of causing a silent crash. - -## Files Modified - -1. **[src/app/sdl3_app_core.cpp](src/app/sdl3_app_core.cpp)** - - Added `ShowErrorDialog()` function - - Enhanced `ReadFile()` with validation - - Added error handling to `InitSDL()` and `InitVulkan()` - -2. **[src/app/sdl3_app_device.cpp](src/app/sdl3_app_device.cpp)** - - Enhanced error messages in `CreateInstance()` - - Improved GPU enumeration in `PickPhysicalDevice()` - - Added missing extension logging in `CheckDeviceExtensionSupport()` - -3. **[src/app/sdl3_app_pipeline.cpp](src/app/sdl3_app_pipeline.cpp)** - - Added shader file existence validation in `CreateGraphicsPipeline()` - -4. **[src/main.cpp](src/main.cpp)** - - Added SDL message box display for all caught exceptions - -## Recommendations - -1. **Enable Tracing**: Use the `--trace` flag to get detailed execution logs: - ```bash - ./sdl3_app --json-file-in config/seed_runtime.json --trace - ``` - -2. **Check Logs**: If the app crashes, check: - - Console output (stderr) - - System logs: `journalctl -xe` or `dmesg` (on Linux) - - Application return code: `echo $?` - -3. **Verify Environment**: - - Vulkan drivers installed: `vulkaninfo` - - GPU compatibility: `vkcube` (if available) - - Required extensions available - -4. **Config Validation**: The app now catches config errors early. Use `--dump-json` to verify your configuration: - ```bash - ./sdl3_app --json-file-in config/seed_runtime.json --dump-json - ``` - -## Conclusion - -The application now provides comprehensive error feedback instead of failing silently with a "see-through window". Every potential failure point has been wrapped with validation and clear error messages, making it much easier to diagnose and fix issues. diff --git a/scripts/add_traces.sh b/scripts/add_traces.sh deleted file mode 100755 index f182f5d..0000000 --- a/scripts/add_traces.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -# Script to add logging includes to files that need them - -FILES=( - "src/script/lua_helpers.cpp" - "src/script/mesh_loader.cpp" - "src/app/vulkan_api.cpp" - "src/script/physics_bridge.cpp" - "src/script/scene_manager.cpp" - "src/script/shader_manager.cpp" - "src/script/audio_manager.cpp" - "src/script/gui_manager.cpp" - "src/gui/gui_renderer.cpp" - "src/script/lua_bindings.cpp" -) - -for file in "${FILES[@]}"; do - # Check if file already includes logger.hpp - if ! grep -q "#include.*logging/logger.hpp" "$file"; then - echo "Adding logger include to $file" - # Add include after the first #include line - sed -i '0,/#include/s|#include.*|&\n#include "logging/logger.hpp"|' "$file" - else - echo "Skipping $file - already includes logger" - fi -done - -echo "Done adding logger includes" diff --git a/scripts/check_macros.sh b/scripts/check_macros.sh index acc231c..775341c 100755 --- a/scripts/check_macros.sh +++ b/scripts/check_macros.sh @@ -7,21 +7,7 @@ set -euo pipefail # Whitelisted headers that are allowed to contain macros # These are primarily include guards and necessary SDK configuration WHITELIST=( - "src/logging/logger.hpp" - "src/logging/string_utils.hpp" - "src/core/platform.hpp" "src/core/vertex.hpp" - "src/app/sdl3_app.hpp" - "src/app/sdl_macros.hpp" - "src/app/vulkan_api.hpp" - "src/app/audio_player.hpp" - "src/script/script_engine.hpp" - "src/script/mesh_loader.hpp" - "src/script/lua_helpers.hpp" - "src/script/lua_bindings.hpp" - "src/script/physics_bridge.hpp" - "src/script/gui_types.hpp" - "src/gui/gui_renderer.hpp" ) # Function to check if a file is whitelisted diff --git a/scripts/cube_logic.lua b/scripts/cube_logic.lua index fddb47f..078493e 100644 --- a/scripts/cube_logic.lua +++ b/scripts/cube_logic.lua @@ -764,426 +764,6 @@ function get_shader_paths() end -local function resolve_number(value, fallback) - if type(value) == "number" then - return value - end - return fallback -end - -local function resolve_string(value, fallback) - if type(value) == "string" then - return value - end - return fallback -end - -local function resolve_boolean(value, fallback) - if type(value) == "boolean" then - return value - end - return fallback -end - -local function build_compat_render_graph(enable_tonemap, exposure, gamma) - local resources = {} - local passes = {} - - if enable_tonemap then - resources.scene_hdr = {type = "color", format = "rgba16f", size = "swapchain"} - table.insert(passes, { - name = "scene", - kind = "forward_plus", - output = "scene_hdr", - }) - table.insert(passes, { - name = "tonemap", - kind = "fullscreen", - shader = "tonemap_fallback", - input = "scene_hdr", - output = "swapchain", - settings = { - exposure = exposure, - gamma = gamma, - curve = "aces", - highlight_rolloff = 0.85, - }, - }) - else - table.insert(passes, { - name = "scene", - kind = "forward_plus", - output = "swapchain", - }) - end - - return { - resources = resources, - passes = passes, - } -end - -function get_render_graph() - local atmospherics = {} - local rendering = {} - if type(config) == "table" then - if type(config.atmospherics) == "table" then - atmospherics = config.atmospherics - end - if type(config.rendering) == "table" then - rendering = config.rendering - end - end - - local exposure = resolve_number(atmospherics.exposure, 1.0) - local gamma = resolve_number(atmospherics.gamma, 2.2) - local fog_density = resolve_number(atmospherics.fog_density, 0.003) - local enable_shadows = resolve_boolean(atmospherics.enable_shadows, true) - local enable_volumetrics = resolve_boolean(atmospherics.enable_volumetric_lighting, true) - local enable_tonemap = resolve_boolean(atmospherics.enable_tone_mapping, true) - local enable_sdf = resolve_boolean(rendering.enable_sdf, true) - local enable_ssgi = resolve_boolean(rendering.enable_ssgi, true) - local enable_ddgi = resolve_boolean(rendering.enable_ddgi, true) - local enable_vxgi = resolve_boolean(rendering.enable_vxgi, false) - local enable_depth_of_field = resolve_boolean(rendering.enable_depth_of_field, true) - local enable_motion_blur = resolve_boolean(rendering.enable_motion_blur, true) - local render_graph_profile = resolve_string(rendering.render_graph_profile, "compat") - local pipeline_mode = resolve_string(rendering.pipeline, "deferred") - local use_forward_plus = pipeline_mode == "forward_plus" - or pipeline_mode == "forward+" - or pipeline_mode == "forward" - - if render_graph_profile ~= "full" then - log_debug("Render graph profile '%s' using compatibility graph", render_graph_profile) - return build_compat_render_graph(enable_tonemap, exposure, gamma) - end - - local passes = { - { - name = "shadow_csm", - kind = "shadow_csm", - output = "shadow_atlas", - settings = { - enabled = enable_shadows, - cascades = 4, - bias = 0.002, - normal_bias = 0.02, - pcf = 7, - filter = "pcf", - softness = 0.6, - }, - }, - { - name = "shadow_spot", - kind = "shadow_spot", - output = "shadow_atlas", - settings = { - enabled = enable_shadows, - lights = 4, - atlas_slice = 1, - bias = 0.0015, - pcf = 5, - filter = "pcf", - softness = 0.5, - }, - }, - { - name = "shadow_point", - kind = "shadow_point", - output = "shadow_atlas", - settings = { - enabled = enable_shadows, - lights = 2, - atlas_slice = 2, - bias = 0.002, - pcf = 5, - filter = "pcf", - softness = 0.5, - }, - }, - { - name = "sdf_build", - kind = "sdf_build", - shader = "sdf_build", - output = "sdf_atlas", - settings = {enabled = enable_sdf, voxel_size = 0.15, max_distance = 8.0}, - }, - } - - if use_forward_plus then - table.insert(passes, { - name = "depth_normals", - kind = "depth_prepass", - shader = "depth_normals", - outputs = { - depth = "depth", - normal = "normal_rough", - motion = "motion", - }, - }) - table.insert(passes, { - name = "ssao", - kind = "fullscreen", - shader = "ssao", - inputs = {depth = "depth", normal = "normal_rough"}, - output = "ao_ssao", - settings = {radius = 0.5, power = 1.3}, - }) - table.insert(passes, { - name = "sdf_ao", - kind = "fullscreen", - shader = "sdf_ao", - inputs = {depth = "depth", normal = "normal_rough", sdf = "sdf_atlas"}, - output = "ao_sdf", - settings = {enabled = enable_sdf, radius = 0.6, power = 1.1}, - }) - table.insert(passes, { - name = "ao_combine", - kind = "fullscreen", - shader = "ao_combine", - inputs = {ssao = "ao_ssao", sdf = "ao_sdf"}, - output = "ao", - settings = {sdf_enabled = enable_sdf, ssao_weight = 0.7, sdf_weight = 0.3}, - }) - table.insert(passes, { - name = "sdf_soft_shadows", - kind = "fullscreen", - shader = "sdf_soft_shadow", - inputs = {depth = "depth", normal = "normal_rough", sdf = "sdf_atlas"}, - output = "shadow_soft", - settings = {enabled = enable_sdf, softness = 0.6, max_distance = 6.0}, - }) - table.insert(passes, { - name = "forward_plus", - kind = "forward_plus", - shader = "forward_plus", - inputs = { - depth = "depth", - normal = "normal_rough", - ao = "ao", - shadow = "shadow_atlas", - shadow_soft = "shadow_soft", - }, - output = "scene_hdr", - settings = { - clustered = true, - key_light_color = {1.0, 0.94, 0.85}, - sky_color = {0.55, 0.68, 0.92}, - }, - }) - else - table.insert(passes, { - name = "gbuffer", - kind = "gbuffer", - shader = "pbr", - outputs = { - albedo = "gbuffer_albedo", - depth = "depth", - normal = "normal_rough", - motion = "motion", - }, - }) - table.insert(passes, { - name = "ssao", - kind = "fullscreen", - shader = "ssao", - inputs = {depth = "depth", normal = "normal_rough"}, - output = "ao_ssao", - settings = {radius = 0.5, power = 1.3}, - }) - table.insert(passes, { - name = "sdf_ao", - kind = "fullscreen", - shader = "sdf_ao", - inputs = {depth = "depth", normal = "normal_rough", sdf = "sdf_atlas"}, - output = "ao_sdf", - settings = {enabled = enable_sdf, radius = 0.6, power = 1.1}, - }) - table.insert(passes, { - name = "ao_combine", - kind = "fullscreen", - shader = "ao_combine", - inputs = {ssao = "ao_ssao", sdf = "ao_sdf"}, - output = "ao", - settings = {sdf_enabled = enable_sdf, ssao_weight = 0.7, sdf_weight = 0.3}, - }) - table.insert(passes, { - name = "sdf_soft_shadows", - kind = "fullscreen", - shader = "sdf_soft_shadow", - inputs = {depth = "depth", normal = "normal_rough", sdf = "sdf_atlas"}, - output = "shadow_soft", - settings = {enabled = enable_sdf, softness = 0.6, max_distance = 6.0}, - }) - table.insert(passes, { - name = "lighting", - kind = "lighting", - shader = "deferred_lighting", - inputs = { - albedo = "gbuffer_albedo", - depth = "depth", - normal = "normal_rough", - ao = "ao", - shadow = "shadow_atlas", - shadow_soft = "shadow_soft", - }, - output = "scene_hdr", - settings = { - key_light_color = {1.0, 0.94, 0.85}, - sky_color = {0.55, 0.68, 0.92}, - }, - }) - end - - table.insert(passes, { - name = "ssgi", - kind = "fullscreen", - shader = "ssgi", - inputs = {scene = "scene_hdr", depth = "depth", normal = "normal_rough"}, - output = "gi_ssgi", - settings = {enabled = enable_ssgi, intensity = 0.35}, - }) - table.insert(passes, { - name = "ddgi_update", - kind = "ddgi_update", - shader = "ddgi_update", - inputs = {depth = "depth", normal = "normal_rough"}, - output = "ddgi_volume", - settings = {enabled = enable_ddgi, probes = {8, 4, 8}, rays = 96}, - }) - table.insert(passes, { - name = "vxgi_update", - kind = "vxgi_update", - shader = "vxgi_update", - inputs = {depth = "depth", normal = "normal_rough"}, - output = "vxgi_volume", - settings = {enabled = enable_vxgi, voxels = 96, steps = 32}, - }) - table.insert(passes, { - name = "gi_composite", - kind = "fullscreen", - shader = "gi_composite", - inputs = { - scene = "scene_hdr", - ssgi = "gi_ssgi", - ddgi = "ddgi_volume", - vxgi = "vxgi_volume", - }, - output = "scene_hdr", - settings = {ssgi_weight = 0.6, ddgi_weight = 0.25, vxgi_weight = 0.15}, - }) - table.insert(passes, { - name = "ssr", - kind = "fullscreen", - shader = "ssr", - inputs = {scene = "scene_hdr", depth = "depth", normal = "normal_rough"}, - output = "scene_hdr", - settings = {max_steps = 64, thickness = 0.1, roughness_fallback = 0.7}, - }) - table.insert(passes, { - name = "volumetric_lighting", - kind = "fullscreen", - shader = "volumetric", - inputs = {scene = "scene_hdr", depth = "depth", shadow = "shadow_atlas", shadow_soft = "shadow_soft"}, - output = "scene_hdr", - settings = { - enabled = enable_volumetrics, - density = fog_density * 4.0, - height_start = 0.0, - height_falloff = 0.12, - }, - }) - table.insert(passes, { - name = "transparent", - kind = "transparent", - shader = "transparent", - inputs = {scene = "scene_hdr", depth = "depth", shadow = "shadow_atlas", shadow_soft = "shadow_soft"}, - output = "scene_hdr", - }) - table.insert(passes, { - name = "taa", - kind = "taa", - inputs = {scene = "scene_hdr", history = "taa_history", motion = "motion"}, - output = "scene_hdr", - settings = {feedback = 0.9, sharpen = 0.2}, - }) - table.insert(passes, { - name = "motion_blur", - kind = "fullscreen", - shader = "motion_blur", - inputs = {scene = "scene_hdr", motion = "motion", depth = "depth"}, - output = "post_motion", - settings = {enabled = enable_motion_blur, strength = 0.15, max_blur = 0.02}, - }) - table.insert(passes, { - name = "depth_of_field", - kind = "fullscreen", - shader = "depth_of_field", - inputs = {scene = "post_motion", depth = "depth"}, - output = "scene_hdr", - settings = {enabled = enable_depth_of_field, focus_distance = 6.0, focus_range = 3.5, max_blur = 0.015}, - }) - table.insert(passes, { - name = "bloom", - kind = "bloom", - input = "scene_hdr", - output = "bloom", - settings = {threshold = 1.2, soft_knee = 0.6, intensity = 0.35}, - }) - table.insert(passes, { - name = "color_grade", - kind = "fullscreen", - shader = "color_grade", - inputs = {scene = "scene_hdr", bloom = "bloom"}, - output = "scene_hdr", - settings = {grade = "warm", exposure = exposure}, - }) - table.insert(passes, { - name = "tonemap", - kind = "fullscreen", - shader = "tonemap", - input = "scene_hdr", - output = "swapchain", - settings = { - enabled = enable_tonemap, - exposure = exposure, - gamma = gamma, - curve = "aces", - highlight_rolloff = 0.85, - }, - }) - table.insert(passes, { - name = "ui_composite", - kind = "ui_composite", - inputs = {scene = "swapchain"}, - output = "swapchain", - }) - - return { - resources = { - scene_hdr = {type = "color", format = "rgba16f", size = "swapchain"}, - depth = {type = "depth", format = "d32", size = "swapchain"}, - normal_rough = {type = "color", format = "a2b10g10r10", size = "swapchain"}, - motion = {type = "color", format = "rg16f", size = "swapchain"}, - gbuffer_albedo = {type = "color", format = "rgba8", size = "swapchain"}, - shadow_atlas = {type = "depth_array", format = "d32", size = {4096, 4096}, layers = 8}, - sdf_atlas = {type = "color", format = "r16f", size = "half", layers = 16}, - shadow_soft = {type = "color", format = "r8", size = "half"}, - ao_ssao = {type = "color", format = "r8", size = "half"}, - ao_sdf = {type = "color", format = "r8", size = "half"}, - ao = {type = "color", format = "r8", size = "half"}, - gi_ssgi = {type = "color", format = "rgba16f", size = "half"}, - ddgi_volume = {type = "color", format = "rgba16f", size = {64, 64}, layers = 16}, - vxgi_volume = {type = "color", format = "rgba16f", size = {96, 96}, layers = 16}, - taa_history = {type = "color", format = "rgba16f", size = "swapchain"}, - post_motion = {type = "color", format = "rgba16f", size = "swapchain"}, - bloom = {type = "color", format = "rgba16f", size = "half", mips = 5}, - }, - passes = passes, - } -end - - function get_view_projection(aspect) local now = os.clock() local dt = 0.0 diff --git a/scripts/dev_commands.py b/scripts/dev_commands.py index eed17cb..8cbc245 100644 --- a/scripts/dev_commands.py +++ b/scripts/dev_commands.py @@ -1268,7 +1268,9 @@ return {{ "scripts_directory": "scripts", "project_root": "../", "shaders_directory": "shaders", - "device_extensions": ["VK_KHR_swapchain"], + "bgfx": { + "renderer": "vulkan" + }, "mouse_grab": { "enabled": False } @@ -1329,7 +1331,9 @@ return {{ "scripts_directory": "scripts", "project_root": "../", "shaders_directory": "shaders", - "device_extensions": ["VK_KHR_swapchain"], + "bgfx": { + "renderer": "vulkan" + }, "config_file": f"config/{project_id}_runtime.json", "mouse_grab": { "enabled": False diff --git a/src/app/service_based_app.cpp b/src/app/service_based_app.cpp index 29a5b04..50453b0 100644 --- a/src/app/service_based_app.cpp +++ b/src/app/service_based_app.cpp @@ -13,26 +13,17 @@ #include "services/impl/sdl_window_service.hpp" #include "services/impl/sdl_input_service.hpp" #include "services/impl/ecs_service.hpp" -#include "services/impl/vulkan_device_service.hpp" -#include "services/impl/swapchain_service.hpp" -#include "services/impl/pipeline_service.hpp" -#include "services/impl/buffer_service.hpp" -#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" -#include "services/impl/render_graph_script_service.hpp" #include "services/impl/gui_script_service.hpp" #include "services/impl/audio_command_service.hpp" #include "services/impl/physics_bridge_service.hpp" #include "services/impl/mesh_service.hpp" #include "services/impl/scene_service.hpp" -#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" @@ -131,36 +122,10 @@ void ServiceBasedApp::Run() { auto graphicsService = registry_.GetService(); if (graphicsService && windowService) { services::GraphicsConfig graphicsConfig; - if (configService) { - graphicsConfig.deviceExtensions = configService->GetDeviceExtensions(); - } else { - graphicsConfig.deviceExtensions = {"VK_KHR_swapchain"}; - } - graphicsConfig.enableValidationLayers = false; graphicsService->InitializeDevice(windowService->GetNativeHandle(), graphicsConfig); graphicsService->InitializeSwapchain(); } - // Initialize GUI service after graphics - auto guiService = registry_.GetService(); - bool useBgfx = false; - if (configService) { - useBgfx = configService->GetGraphicsBackendConfig().backend == services::GraphicsBackendType::Bgfx; - } - if (!useBgfx && - registry_.HasService() && - registry_.HasService()) { - auto vulkanDeviceService = registry_.GetService(); - auto swapchainService = registry_.GetService(); - 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 if (crashRecoveryService_) { bool success = crashRecoveryService_->ExecuteWithTimeout( @@ -242,11 +207,6 @@ void ServiceBasedApp::RegisterServices() { registry_.RegisterService( registry_.GetService(), runtimeConfig_); auto configService = registry_.GetService(); - bool useBgfx = false; - if (configService) { - useBgfx = configService->GetGraphicsBackendConfig().backend == services::GraphicsBackendType::Bgfx; - } - // ECS service (entt registry) registry_.RegisterService( registry_.GetService()); @@ -298,12 +258,6 @@ void ServiceBasedApp::RegisterServices() { registry_.GetService(), registry_.GetService(), registry_.GetService()); - registry_.RegisterService( - registry_.GetService(), - registry_.GetService(), - registry_.GetService()); - logger_->Trace("ServiceBasedApp", "RegisterServices", - "Registered render graph script service"); registry_.RegisterService( registry_.GetService(), registry_.GetService()); @@ -315,58 +269,9 @@ void ServiceBasedApp::RegisterServices() { inputService->SetGuiScriptService(guiScriptService.get()); } - std::shared_ptr graphicsBackend; - if (!useBgfx) { - // Vulkan device service - registry_.RegisterService( - registry_.GetService()); - - // Swapchain service - registry_.RegisterService( - registry_.GetService(), - registry_.GetService(), - registry_.GetService()); - - // Pipeline service - registry_.RegisterService( - registry_.GetService(), - registry_.GetService()); - - // Buffer service - registry_.RegisterService( - registry_.GetService(), - registry_.GetService()); - - // GUI renderer service (needed by render command service and GUI service) - registry_.RegisterService( - registry_.GetService(), - registry_.GetService(), - registry_.GetService()); - logger_->Trace("ServiceBasedApp", "RegisterServices", - "Registered GUI renderer service before render command service"); - - // Render command service - registry_.RegisterService( - registry_.GetService(), - registry_.GetService(), - registry_.GetService(), - registry_.GetService(), - registry_.GetService(), - std::static_pointer_cast(registry_.GetService()), - registry_.GetService()); - - graphicsBackend = std::make_shared( - registry_.GetService(), - registry_.GetService(), - registry_.GetService(), - registry_.GetService(), - registry_.GetService(), - registry_.GetService()); - } else { - graphicsBackend = std::make_shared( - registry_.GetService(), - registry_.GetService()); - } + auto graphicsBackend = std::make_shared( + registry_.GetService(), + registry_.GetService()); // Graphics service (facade) registry_.RegisterService( @@ -381,14 +286,8 @@ void ServiceBasedApp::RegisterServices() { registry_.GetService()); // GUI service - if (!useBgfx) { - registry_.RegisterService( - registry_.GetService(), - registry_.GetService()); - } else { - registry_.RegisterService( - registry_.GetService()); - } + registry_.RegisterService( + registry_.GetService()); // Physics service registry_.RegisterService( @@ -400,7 +299,6 @@ void ServiceBasedApp::RegisterServices() { registry_.GetService(), registry_.GetService(), registry_.GetService(), - registry_.GetService(), registry_.GetService(), registry_.GetService(), registry_.GetService()); diff --git a/src/font8x8_basic.h b/src/font8x8_basic.h deleted file mode 100644 index f5f715d..0000000 --- a/src/font8x8_basic.h +++ /dev/null @@ -1,153 +0,0 @@ -/** - * 8x8 monochrome bitmap fonts for rendering - * Author: Daniel Hepper - * - * License: Public Domain - * - * Based on: - * // Summary: font8x8.h - * // 8x8 monochrome bitmap fonts for rendering - * // - * // Author: - * // Marcel Sondaar - * // International Business Machines (public domain VGA fonts) - * // - * // License: - * // Public Domain - * - * Fetched from: http://dimensionalrift.homelinux.net/combuster/mos3/?p=viewsource&file=/modules/gfx/font8_8.asm - **/ - -// Constant: font8x8_basic -// Contains an 8x8 font map for unicode points U+0000 - U+007F (basic latin) -#include -const uint8_t font8x8_basic[128][8] = { - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul) - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019 - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space) - { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!) - { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (") - { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#) - { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($) - { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%) - { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&) - { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (') - { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (() - { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ()) - { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*) - { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+) - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,) - { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-) - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.) - { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/) - { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0) - { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1) - { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2) - { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3) - { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4) - { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5) - { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6) - { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7) - { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8) - { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9) - { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:) - { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;) - { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<) - { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=) - { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>) - { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?) - { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@) - { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A) - { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B) - { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C) - { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D) - { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E) - { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F) - { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G) - { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H) - { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I) - { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J) - { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K) - { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L) - { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M) - { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N) - { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O) - { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P) - { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q) - { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R) - { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S) - { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T) - { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U) - { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V) - { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W) - { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X) - { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y) - { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z) - { 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([) - { 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\) - { 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (]) - { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^) - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_) - { 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`) - { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a) - { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b) - { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c) - { 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d) - { 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e) - { 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f) - { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g) - { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h) - { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i) - { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j) - { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k) - { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l) - { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m) - { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n) - { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o) - { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p) - { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q) - { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r) - { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s) - { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t) - { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u) - { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v) - { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w) - { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x) - { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y) - { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z) - { 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({) - { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|) - { 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (}) - { 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~) - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // U+007F -}; diff --git a/src/services/impl/bgfx_graphics_backend.cpp b/src/services/impl/bgfx_graphics_backend.cpp index 1e56a65..60c899d 100644 --- a/src/services/impl/bgfx_graphics_backend.cpp +++ b/src/services/impl/bgfx_graphics_backend.cpp @@ -98,15 +98,8 @@ 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; + const auto& config = configService_->GetBgfxConfig(); + return RendererFromString(config.renderer); } void BgfxGraphicsBackend::Initialize(void* window, const GraphicsConfig& config) { @@ -116,6 +109,7 @@ void BgfxGraphicsBackend::Initialize(void* window, const GraphicsConfig& config) if (initialized_) { return; } + (void)config; SDL_Window* sdlWindow = static_cast(window); int width = 0; @@ -369,13 +363,6 @@ void BgfxGraphicsBackend::SetViewProjection(const std::array& viewPro 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, diff --git a/src/services/impl/bgfx_graphics_backend.hpp b/src/services/impl/bgfx_graphics_backend.hpp index 4e848bc..04fd294 100644 --- a/src/services/impl/bgfx_graphics_backend.hpp +++ b/src/services/impl/bgfx_graphics_backend.hpp @@ -36,7 +36,6 @@ public: bool BeginFrame(GraphicsDeviceHandle device) override; bool EndFrame(GraphicsDeviceHandle device) override; void SetViewProjection(const std::array& 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, diff --git a/src/services/impl/buffer_service.cpp b/src/services/impl/buffer_service.cpp deleted file mode 100644 index 5163974..0000000 --- a/src/services/impl/buffer_service.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include "buffer_service.hpp" -#include -#include -#include - -namespace sdl3cpp::services::impl { - -BufferService::BufferService(std::shared_ptr deviceService, std::shared_ptr logger) - : deviceService_(std::move(deviceService)), logger_(logger) { - if (logger_) { - logger_->Trace("BufferService", "BufferService", - "deviceService=" + std::string(deviceService_ ? "set" : "null")); - } -} - -BufferService::~BufferService() { - if (logger_) { - logger_->Trace("BufferService", "~BufferService"); - } - if (vertexBuffer_ != VK_NULL_HANDLE || indexBuffer_ != VK_NULL_HANDLE) { - Shutdown(); - } -} - -void BufferService::UploadVertexData(const std::vector& vertices) { - logger_->Trace("BufferService", "UploadVertexData", - "vertices.size=" + std::to_string(vertices.size())); - - if (vertices.empty()) { - throw std::runtime_error("Cannot upload vertex data: empty vertex array"); - } - - // Cleanup old buffer if exists - if (vertexBuffer_ != VK_NULL_HANDLE) { - auto device = deviceService_->GetDevice(); - vkDestroyBuffer(device, vertexBuffer_, nullptr); - vkFreeMemory(device, vertexBufferMemory_, nullptr); - vertexBuffer_ = VK_NULL_HANDLE; - vertexBufferMemory_ = VK_NULL_HANDLE; - } - - VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - logger_->Info("Uploading vertex buffer: " + std::to_string(vertices.size()) + - " vertices (" + std::to_string(bufferSize / 1024) + " KB)"); - - CreateBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - vertexBuffer_, vertexBufferMemory_); - - auto device = deviceService_->GetDevice(); - void* data; - vkMapMemory(device, vertexBufferMemory_, 0, bufferSize, 0, &data); - std::memcpy(data, vertices.data(), static_cast(bufferSize)); - vkUnmapMemory(device, vertexBufferMemory_); - - vertexCount_ = vertices.size(); -} - -void BufferService::UploadIndexData(const std::vector& indices) { - logger_->Trace("BufferService", "UploadIndexData", - "indices.size=" + std::to_string(indices.size())); - - if (indices.empty()) { - throw std::runtime_error("Cannot upload index data: empty index array"); - } - - // Cleanup old buffer if exists - if (indexBuffer_ != VK_NULL_HANDLE) { - auto device = deviceService_->GetDevice(); - vkDestroyBuffer(device, indexBuffer_, nullptr); - vkFreeMemory(device, indexBufferMemory_, nullptr); - indexBuffer_ = VK_NULL_HANDLE; - indexBufferMemory_ = VK_NULL_HANDLE; - } - - VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - logger_->Info("Uploading index buffer: " + std::to_string(indices.size()) + - " indices (" + std::to_string(bufferSize / 1024) + " KB)"); - - CreateBuffer(bufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - indexBuffer_, indexBufferMemory_); - - auto device = deviceService_->GetDevice(); - void* data; - vkMapMemory(device, indexBufferMemory_, 0, bufferSize, 0, &data); - std::memcpy(data, indices.data(), static_cast(bufferSize)); - vkUnmapMemory(device, indexBufferMemory_); - - indexCount_ = indices.size(); -} - -void BufferService::Cleanup() { - logger_->Trace("BufferService", "Cleanup"); - CleanupBuffers(); -} - -void BufferService::Shutdown() noexcept { - logger_->Trace("BufferService", "Shutdown"); - CleanupBuffers(); -} - -void BufferService::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, - VkMemoryPropertyFlags properties, - VkBuffer& buffer, VkDeviceMemory& bufferMemory) { - logger_->Trace("BufferService", "CreateBuffer", - "size=" + std::to_string(size) + - ", usage=" + std::to_string(static_cast(usage)) + - ", properties=" + std::to_string(static_cast(properties)) + - ", bufferIsNull=" + std::string(buffer == VK_NULL_HANDLE ? "true" : "false") + - ", bufferMemoryIsNull=" + std::string(bufferMemory == VK_NULL_HANDLE ? "true" : "false")); - - auto device = deviceService_->GetDevice(); - auto physicalDevice = deviceService_->GetPhysicalDevice(); - - if (logger_) { - logger_->Debug("Creating buffer with size " + std::to_string(size) + " bytes"); - } - - if (size == 0) { - if (logger_) { - logger_->Error("Cannot create buffer with size 0"); - } - throw std::runtime_error("Cannot create buffer with size 0"); - } - - VkPhysicalDeviceMemoryProperties memProps; - vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps); - - uint64_t totalAvailable = 0; - for (uint32_t i = 0; i < memProps.memoryHeapCount; ++i) { - if (memProps.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) { - totalAvailable += memProps.memoryHeaps[i].size; - } - } - - if (size > totalAvailable) { - throw std::runtime_error("Requested buffer size (" + - std::to_string(size / (1024 * 1024)) + " MB) exceeds available GPU memory (" + - std::to_string(totalAvailable / (1024 * 1024)) + " MB)"); - } - - VkBufferCreateInfo bufferInfo{}; - bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = size; - bufferInfo.usage = usage; - bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - - VkResult createResult = vkCreateBuffer(device, &bufferInfo, nullptr, &buffer); - if (createResult != VK_SUCCESS) { - throw std::runtime_error("Failed to create buffer (error code: " + - std::to_string(createResult) + ", size: " + - std::to_string(size / 1024) + " KB)"); - } - - VkMemoryRequirements memRequirements; - vkGetBufferMemoryRequirements(device, buffer, &memRequirements); - - VkMemoryAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - allocInfo.allocationSize = memRequirements.size; - - bool foundType = false; - for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { - if ((memRequirements.memoryTypeBits & (1 << i)) && - (memProps.memoryTypes[i].propertyFlags & properties) == properties) { - allocInfo.memoryTypeIndex = i; - foundType = true; - if (logger_) { - logger_->Debug("Found suitable memory type: " + std::to_string(i)); - } - break; - } - } - - if (!foundType) { - vkDestroyBuffer(device, buffer, nullptr); - if (logger_) { - logger_->Error("Failed to find suitable memory type"); - } - throw std::runtime_error("Failed to find suitable memory type"); - } - - VkResult allocResult = vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory); - if (allocResult != VK_SUCCESS) { - vkDestroyBuffer(device, buffer, nullptr); - std::string errorMsg = "Failed to allocate buffer memory.\n"; - errorMsg += "Requested: " + std::to_string(memRequirements.size / (1024 * 1024)) + " MB\n"; - errorMsg += "Error code: " + std::to_string(allocResult) + "\n"; - if (allocResult == VK_ERROR_OUT_OF_DEVICE_MEMORY) { - errorMsg += "\nOut of GPU memory. Try:\n"; - errorMsg += "- Closing other GPU-intensive applications\n"; - errorMsg += "- Reducing window resolution\n"; - errorMsg += "- Upgrading GPU or system memory"; - } else if (allocResult == VK_ERROR_OUT_OF_HOST_MEMORY) { - errorMsg += "\nOut of system memory. Try:\n"; - errorMsg += "- Closing other applications\n"; - errorMsg += "- Adding more RAM to your system"; - } - throw std::runtime_error(errorMsg); - } - - vkBindBufferMemory(device, buffer, bufferMemory, 0); -} - -void BufferService::CleanupBuffers() { - logger_->Trace("BufferService", "CleanupBuffers"); - - auto device = deviceService_->GetDevice(); - - if (vertexBuffer_ != VK_NULL_HANDLE) { - vkDestroyBuffer(device, vertexBuffer_, nullptr); - vertexBuffer_ = VK_NULL_HANDLE; - } - - if (vertexBufferMemory_ != VK_NULL_HANDLE) { - vkFreeMemory(device, vertexBufferMemory_, nullptr); - vertexBufferMemory_ = VK_NULL_HANDLE; - } - - if (indexBuffer_ != VK_NULL_HANDLE) { - vkDestroyBuffer(device, indexBuffer_, nullptr); - indexBuffer_ = VK_NULL_HANDLE; - } - - if (indexBufferMemory_ != VK_NULL_HANDLE) { - vkFreeMemory(device, indexBufferMemory_, nullptr); - indexBufferMemory_ = VK_NULL_HANDLE; - } - - vertexCount_ = 0; - indexCount_ = 0; -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/buffer_service.hpp b/src/services/impl/buffer_service.hpp deleted file mode 100644 index f487f11..0000000 --- a/src/services/impl/buffer_service.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include "../interfaces/i_buffer_service.hpp" -#include "../interfaces/i_vulkan_device_service.hpp" -#include "../interfaces/i_logger.hpp" -#include "../../di/lifecycle.hpp" -#include - -namespace sdl3cpp::services::impl { - -/** - * @brief Buffer service implementation. - * - * Small, focused service (~150 lines) for Vulkan buffer management. - * Handles vertex and index buffer creation and memory management. - */ -class BufferService : public IBufferService, - public di::IShutdownable { -public: - explicit BufferService(std::shared_ptr deviceService, std::shared_ptr logger); - ~BufferService() override; - - // IBufferService interface - void UploadVertexData(const std::vector& vertices) override; - void UploadIndexData(const std::vector& indices) override; - void Cleanup() override; - - VkBuffer GetVertexBuffer() const override { - logger_->Trace("BufferService", "GetVertexBuffer"); - return vertexBuffer_; - } - VkBuffer GetIndexBuffer() const override { - logger_->Trace("BufferService", "GetIndexBuffer"); - return indexBuffer_; - } - size_t GetVertexCount() const override { - logger_->Trace("BufferService", "GetVertexCount"); - return vertexCount_; - } - size_t GetIndexCount() const override { - logger_->Trace("BufferService", "GetIndexCount"); - return indexCount_; - } - - // Public buffer creation utility - void CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, - VkBuffer& buffer, VkDeviceMemory& bufferMemory) override; - - // IShutdownable interface - void Shutdown() noexcept override; - -private: - std::shared_ptr deviceService_; - std::shared_ptr logger_; - - VkBuffer vertexBuffer_ = VK_NULL_HANDLE; - VkDeviceMemory vertexBufferMemory_ = VK_NULL_HANDLE; - VkBuffer indexBuffer_ = VK_NULL_HANDLE; - VkDeviceMemory indexBufferMemory_ = VK_NULL_HANDLE; - - size_t vertexCount_ = 0; - size_t indexCount_ = 0; - - // Helper methods - void CleanupBuffers(); -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/command_line_service.cpp b/src/services/impl/command_line_service.cpp index 072d60e..a9f8a1e 100644 --- a/src/services/impl/command_line_service.cpp +++ b/src/services/impl/command_line_service.cpp @@ -50,7 +50,7 @@ CommandLineOptions CommandLineService::Parse(int argc, char** argv) { bool dumpRuntimeJson = false; bool traceRuntime = false; - CLI::App app("SDL3 + Vulkan runtime helper"); + CLI::App app("SDL3 + bgfx runtime helper"); app.add_option("-j,--json-file-in", jsonInputText, "Path to a runtime JSON config") ->check(CLI::ExistingFile); app.add_option("-s,--create-seed-json", seedOutputText, diff --git a/src/services/impl/graphics_service.cpp b/src/services/impl/graphics_service.cpp index 7bb9557..7c474d6 100644 --- a/src/services/impl/graphics_service.cpp +++ b/src/services/impl/graphics_service.cpp @@ -50,8 +50,7 @@ void GraphicsService::Shutdown() noexcept { void GraphicsService::InitializeDevice(SDL_Window* window, const GraphicsConfig& config) { logger_->Trace("GraphicsService", "InitializeDevice", "windowIsNull=" + std::string(window ? "false" : "true") + - ", deviceExtensions.size=" + std::to_string(config.deviceExtensions.size()) + - ", enableValidationLayers=" + std::string(config.enableValidationLayers ? "true" : "false")); + ", preferredFormat=" + std::to_string(config.preferredFormat)); if (!initialized_) { throw std::runtime_error("Graphics service not initialized"); @@ -106,22 +105,6 @@ void GraphicsService::LoadShaders(const std::unordered_mapTrace("GraphicsService", "SetRenderGraphDefinition", - "resources=" + std::to_string(definition.resources.size()) + - ", passes=" + std::to_string(definition.passes.size())); - - renderGraphDefinition_ = definition; - if (backend_) { - backend_->SetRenderGraphDefinition(definition); - } -} - -const RenderGraphDefinition& GraphicsService::GetRenderGraphDefinition() const { - logger_->Trace("GraphicsService", "GetRenderGraphDefinition"); - return renderGraphDefinition_; -} - void GraphicsService::UploadVertexData(const std::vector& vertices) { logger_->Trace("GraphicsService", "UploadVertexData", "vertices.size=" + std::to_string(vertices.size())); diff --git a/src/services/impl/graphics_service.hpp b/src/services/impl/graphics_service.hpp index 83f91dc..35e67fd 100644 --- a/src/services/impl/graphics_service.hpp +++ b/src/services/impl/graphics_service.hpp @@ -34,8 +34,6 @@ public: void InitializeSwapchain() override; void RecreateSwapchain() override; void LoadShaders(const std::unordered_map& shaders) override; - void SetRenderGraphDefinition(const RenderGraphDefinition& definition) override; - const RenderGraphDefinition& GetRenderGraphDefinition() const override; void UploadVertexData(const std::vector& vertices) override; void UploadIndexData(const std::vector& indices) override; bool BeginFrame() override; @@ -58,7 +56,6 @@ private: std::unordered_map pipelines_; GraphicsBufferHandle vertexBuffer_; GraphicsBufferHandle indexBuffer_; - RenderGraphDefinition renderGraphDefinition_{}; // Other state bool initialized_ = false; }; diff --git a/src/services/impl/gui_renderer.cpp b/src/services/impl/gui_renderer.cpp deleted file mode 100644 index e4236a6..0000000 --- a/src/services/impl/gui_renderer.cpp +++ /dev/null @@ -1,1257 +0,0 @@ -#include "gui_renderer.hpp" - -#include -#include FT_FREETYPE_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "font8x8_basic.h" - -namespace sdl3cpp::services::impl { -namespace { - -bool ExtractAttribute(const std::string& source, const char* name, std::string& outValue) { - std::string key = name; - size_t pos = source.find(key); - while (pos != std::string::npos) { - size_t eq = source.find('=', pos + key.size()); - if (eq == std::string::npos) { - break; - } - size_t valueStart = eq + 1; - while (valueStart < source.size() && - std::isspace(static_cast(source[valueStart]))) { - valueStart++; - } - if (valueStart >= source.size()) { - break; - } - char quote = source[valueStart]; - if (quote != '\"' && quote != '\'') { - break; - } - size_t valueEnd = source.find(quote, valueStart + 1); - if (valueEnd == std::string::npos) { - break; - } - outValue = source.substr(valueStart + 1, valueEnd - valueStart - 1); - return true; - } - return false; -} - -float ParseFloatValue(const std::string& text) { - try { - size_t idx = 0; - return std::stof(text, &idx); - } catch (...) { - return 0.0f; - } -} - -GuiColor ParseColorString(const std::string& text, const GuiColor& fallback) { - if (text.empty() || text[0] != '#') { - return fallback; - } - try { - if (text.size() == 7) { - unsigned int rgb = std::stoul(text.substr(1), nullptr, 16); - return {((rgb >> 16) & 0xFF) / 255.0f, ((rgb >> 8) & 0xFF) / 255.0f, - (rgb & 0xFF) / 255.0f, 1.0f}; - } - if (text.size() == 9) { - unsigned int rgba = std::stoul(text.substr(1), nullptr, 16); - return {((rgba >> 24) & 0xFF) / 255.0f, ((rgba >> 16) & 0xFF) / 255.0f, - ((rgba >> 8) & 0xFF) / 255.0f, (rgba & 0xFF) / 255.0f}; - } - } catch (...) { - } - return fallback; -} - -ParsedSvg ParseSvgFile(const std::filesystem::path& path) { - std::ifstream file(path); - if (!file) { - throw std::runtime_error("Failed to open SVG file: " + path.string()); - } - std::string data((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - ParsedSvg result; - std::string value; - if (ExtractAttribute(data, "viewBox", value)) { - float x = 0.0f, y = 0.0f, w = 0.0f, h = 0.0f; - std::sscanf(value.c_str(), "%f %f %f %f", &x, &y, &w, &h); - if (w > 0.0f && h > 0.0f) { - result.viewWidth = w; - result.viewHeight = h; - } - } - if (ExtractAttribute(data, "width", value)) { - result.viewWidth = ParseFloatValue(value); - } - if (ExtractAttribute(data, "height", value)) { - result.viewHeight = ParseFloatValue(value); - } - if (result.viewWidth <= 0.0f) { - result.viewWidth = 128.0f; - } - if (result.viewHeight <= 0.0f) { - result.viewHeight = 128.0f; - } - - size_t search = 0; - while (true) { - size_t tagStart = data.find("', tagStart); - if (tagEnd == std::string::npos) { - break; - } - std::string tag = data.substr(tagStart, tagEnd - tagStart); - SvgCircle circle; - std::string attr; - if (ExtractAttribute(tag, "cx", attr)) { - circle.cx = ParseFloatValue(attr); - } - if (ExtractAttribute(tag, "cy", attr)) { - circle.cy = ParseFloatValue(attr); - } - if (ExtractAttribute(tag, "r", attr)) { - circle.r = ParseFloatValue(attr); - } - if (ExtractAttribute(tag, "fill", attr)) { - circle.color = ParseColorString(attr, circle.color); - } - result.circles.push_back(circle); - search = tagEnd + 1; - } - return result; -} - -GuiCommand::RectData IntersectRect(const GuiCommand::RectData& a, const GuiCommand::RectData& b) { - GuiCommand::RectData result; - result.x = std::max(a.x, b.x); - result.y = std::max(a.y, b.y); - float right = std::min(a.x + a.width, b.x + b.width); - float bottom = std::min(a.y + a.height, b.y + b.height); - result.width = std::max(0.0f, right - result.x); - result.height = std::max(0.0f, bottom - result.y); - return result; -} - -bool RectHasArea(const GuiCommand::RectData& rect) { - return rect.width > 0.0f && rect.height > 0.0f; -} - -struct ClipPoint { - float x = 0.0f; - float y = 0.0f; -}; - -enum class ClipEdge { - Left, - Right, - Top, - Bottom -}; - -bool IsInsideClip(const ClipPoint& point, const GuiCommand::RectData& rect, ClipEdge edge) { - switch (edge) { - case ClipEdge::Left: - return point.x >= rect.x; - case ClipEdge::Right: - return point.x <= rect.x + rect.width; - case ClipEdge::Top: - return point.y >= rect.y; - case ClipEdge::Bottom: - return point.y <= rect.y + rect.height; - } - return false; -} - -ClipPoint IntersectClipEdge(const ClipPoint& a, const ClipPoint& b, - const GuiCommand::RectData& rect, ClipEdge edge) { - ClipPoint result = a; - float dx = b.x - a.x; - float dy = b.y - a.y; - - if (edge == ClipEdge::Left || edge == ClipEdge::Right) { - float clipX = (edge == ClipEdge::Left) ? rect.x : (rect.x + rect.width); - float t = (dx != 0.0f) ? (clipX - a.x) / dx : 0.0f; - result.x = clipX; - result.y = a.y + t * dy; - } else { - float clipY = (edge == ClipEdge::Top) ? rect.y : (rect.y + rect.height); - float t = (dy != 0.0f) ? (clipY - a.y) / dy : 0.0f; - result.x = a.x + t * dx; - result.y = clipY; - } - return result; -} - -std::vector ClipPolygonToRect(const std::vector& polygon, - const GuiCommand::RectData& rect) { - std::vector output = polygon; - for (ClipEdge edge : {ClipEdge::Left, ClipEdge::Right, ClipEdge::Top, ClipEdge::Bottom}) { - if (output.empty()) { - break; - } - std::vector input = output; - output.clear(); - - for (size_t i = 0; i < input.size(); ++i) { - const ClipPoint& current = input[i]; - const ClipPoint& previous = input[(i + input.size() - 1) % input.size()]; - bool currentInside = IsInsideClip(current, rect, edge); - bool previousInside = IsInsideClip(previous, rect, edge); - - if (currentInside) { - if (!previousInside) { - output.push_back(IntersectClipEdge(previous, current, rect, edge)); - } - output.push_back(current); - } else if (previousInside) { - output.push_back(IntersectClipEdge(previous, current, rect, edge)); - } - } - } - return output; -} - -shaderc_shader_kind ShadercKindFromStage(VkShaderStageFlagBits stage) { - switch (stage) { - case VK_SHADER_STAGE_VERTEX_BIT: - return shaderc_vertex_shader; - case VK_SHADER_STAGE_FRAGMENT_BIT: - return shaderc_fragment_shader; - case VK_SHADER_STAGE_GEOMETRY_BIT: - return shaderc_geometry_shader; - case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT: - return shaderc_tess_control_shader; - case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT: - return shaderc_tess_evaluation_shader; - case VK_SHADER_STAGE_COMPUTE_BIT: - return shaderc_compute_shader; - default: - return shaderc_glsl_infer_from_source; - } -} - -std::vector ReadShaderFile(const std::filesystem::path& path, - VkShaderStageFlagBits stage, - ILogger* logger) { - if (logger) { - logger->Trace("GuiRenderer", "ReadShaderFile", - "path=" + path.string() + ", stage=" + - std::to_string(static_cast(stage))); - } - - if (path.empty()) { - throw std::runtime_error("Shader path is empty"); - } - - std::filesystem::path shaderPath = path; - if (shaderPath.extension() == ".spv") { - std::filesystem::path sourcePath = shaderPath; - sourcePath.replace_extension(); - if (logger) { - logger->Trace("GuiRenderer", "ReadShaderFile", - "usingSource=" + sourcePath.string()); - } - shaderPath = sourcePath; - } - - if (!std::filesystem::exists(shaderPath)) { - throw std::runtime_error("Shader file not found: " + shaderPath.string() + - "\n\nPlease ensure the shader source (.vert/.frag/etc.) exists."); - } - - if (!std::filesystem::is_regular_file(shaderPath)) { - throw std::runtime_error("Path is not a regular file: " + shaderPath.string()); - } - - std::ifstream sourceFile(shaderPath); - if (!sourceFile) { - throw std::runtime_error("Failed to open shader source: " + shaderPath.string()); - } - std::string source((std::istreambuf_iterator(sourceFile)), - std::istreambuf_iterator()); - sourceFile.close(); - - shaderc::Compiler compiler; - shaderc::CompileOptions options; - options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2); - - shaderc_shader_kind kind = ShadercKindFromStage(stage); - auto result = compiler.CompileGlslToSpv(source, kind, shaderPath.string().c_str(), options); - if (result.GetCompilationStatus() != shaderc_compilation_status_success) { - std::string error = result.GetErrorMessage(); - if (logger) { - logger->Error("GuiRenderer shader compilation failed: " + shaderPath.string() + - "\n" + error); - } - throw std::runtime_error("Shader compilation failed: " + shaderPath.string() + - "\n" + error); - } - - std::vector spirv(result.cbegin(), result.cend()); - std::vector buffer(spirv.size() * sizeof(uint32_t)); - if (!buffer.empty()) { - std::memcpy(buffer.data(), spirv.data(), buffer.size()); - } - if (logger) { - logger->Trace("GuiRenderer", "ReadShaderFile", - "compiledBytes=" + std::to_string(buffer.size())); - } - return buffer; -} - -std::vector ReadShaderSource(const std::string& source, - VkShaderStageFlagBits stage, - const std::string& label, - ILogger* logger) { - if (logger) { - logger->Trace("GuiRenderer", "ReadShaderSource", - "label=" + label + ", stage=" + - std::to_string(static_cast(stage))); - } - - if (source.empty()) { - throw std::runtime_error("Shader source is empty"); - } - - shaderc::Compiler compiler; - shaderc::CompileOptions options; - options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2); - - shaderc_shader_kind kind = ShadercKindFromStage(stage); - 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("GuiRenderer shader compilation failed: " + label + "\n" + error); - } - throw std::runtime_error("Shader compilation failed: " + label + "\n" + error); - } - - std::vector spirv(result.cbegin(), result.cend()); - std::vector buffer(spirv.size() * sizeof(uint32_t)); - if (!buffer.empty()) { - std::memcpy(buffer.data(), spirv.data(), buffer.size()); - } - if (logger) { - logger->Trace("GuiRenderer", "ReadShaderSource", - "compiledBytes=" + std::to_string(buffer.size())); - } - return buffer; -} - -const char* kGuiVertexSource = R"( -#version 450 - -layout(location = 0) in vec3 inPos; -layout(location = 1) in vec4 inColor; - -layout(location = 0) out vec4 fragColor; - -layout(push_constant) uniform PushConstants { - mat4 model; - mat4 viewProj; - // Extended fields for PBR/atmospherics (ignored by basic shaders) - mat4 view; - mat4 proj; - mat4 lightViewProj; - vec3 cameraPos; - float time; - // Atmospherics parameters - float ambientStrength; - float fogDensity; - float fogStart; - float fogEnd; - vec3 fogColor; - float gamma; - float exposure; - int enableShadows; - int enableFog; -} pushConstants; - -void main() { - fragColor = inColor; - vec4 worldPos = pushConstants.model * vec4(inPos, 1.0); - gl_Position = pushConstants.viewProj * worldPos; -} -)"; - -const char* kGuiFragmentSource = R"( -#version 450 - -layout(location = 0) in vec4 fragColor; -layout(location = 0) out vec4 outColor; - -void main() { - outColor = fragColor; -} -)"; - -} // namespace - -GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFormat swapchainFormat, - VkRenderPass renderPass, const std::filesystem::path& scriptDirectory, - const GuiFontConfig& fontConfig, - std::shared_ptr bufferService, - std::shared_ptr logger) - : device_(device), - physicalDevice_(physicalDevice), - swapchainFormat_(swapchainFormat), - renderPass_(renderPass), - scriptDirectory_(scriptDirectory), - fontConfig_(fontConfig), - bufferService_(std::move(bufferService)), - logger_(std::move(logger)) { -} - -GuiRenderer::~GuiRenderer() { - CleanupBuffers(); - CleanupPipeline(); - if (freetypeReady_) { - FT_Face face = reinterpret_cast(ftFace_); - FT_Library library = reinterpret_cast(ftLibrary_); - if (face) { - FT_Done_Face(face); - } - if (library) { - FT_Done_FreeType(library); - } - } -} - -bool GuiRenderer::IsReady() const { - return pipeline_ != VK_NULL_HANDLE && !vertices_.empty(); -} - -void GuiRenderer::Prepare(const std::vector& commands, uint32_t width, uint32_t height) { - if (width == 0 || height == 0) { - return; - } - - viewportWidth_ = width; - viewportHeight_ = height; - - // Create pipeline if needed - if (pipeline_ == VK_NULL_HANDLE) { - CreatePipeline(renderPass_, {width, height}); - } - - // Generate geometry from GUI commands - GenerateGuiGeometry(commands, width, height); - - // Create/update vertex and index buffers - if (!vertices_.empty() && !indices_.empty()) { - CreateVertexAndIndexBuffers(vertices_.size(), indices_.size()); - } -} - -void GuiRenderer::RenderToSwapchain(VkCommandBuffer commandBuffer, VkRenderPass renderPass) { - if (!IsReady() || vertices_.empty() || indices_.empty()) { - return; - } - - // Bind pipeline - vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_); - - // Bind vertex and index buffers - VkDeviceSize offset = 0; - vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer_, &offset); - vkCmdBindIndexBuffer(commandBuffer, indexBuffer_, 0, VK_INDEX_TYPE_UINT32); - - // Push identity matrices for 2D GUI rendering (no 3D transformation) - // The GUI coordinates are already in NDC space, so we use identity matrices - struct GuiPushConstants { - float model[16]; // Identity matrix - float viewProj[16]; // Identity matrix - } pushConstants{}; - - // Initialize as identity matrices - pushConstants.model[0] = 1.0f; - pushConstants.model[5] = 1.0f; - pushConstants.model[10] = 1.0f; - pushConstants.model[15] = 1.0f; - - pushConstants.viewProj[0] = 1.0f; - pushConstants.viewProj[5] = 1.0f; - pushConstants.viewProj[10] = 1.0f; - pushConstants.viewProj[15] = 1.0f; - - vkCmdPushConstants(commandBuffer, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT, - 0, sizeof(GuiPushConstants), &pushConstants); - - // Draw - vkCmdDrawIndexed(commandBuffer, static_cast(indices_.size()), 1, 0, 0, 0); -} - -void GuiRenderer::Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) { - if (width == viewportWidth_ && height == viewportHeight_ && - format == swapchainFormat_ && renderPass == renderPass_) { - return; - } - UpdateFormat(format); - viewportWidth_ = width; - viewportHeight_ = height; - renderPass_ = renderPass; - - // Recreate pipeline for new viewport size - if (pipeline_ != VK_NULL_HANDLE) { - CleanupPipeline(); - CreatePipeline(renderPass_, {width, height}); - } -} - -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(static_cast(pixelSize)) << 32) | - static_cast(c); - auto it = glyphCache_.find(key); - if (it != glyphCache_.end()) { - return &it->second; - } - - FT_Face face = reinterpret_cast(ftFace_); - if (!face) { - return nullptr; - } - if (currentFontSize_ != pixelSize) { - FT_Set_Pixel_Sizes(face, 0, static_cast(pixelSize)); - currentFontSize_ = pixelSize; - } - - if (FT_Load_Char(face, static_cast(static_cast(c)), FT_LOAD_RENDER) != 0) { - return nullptr; - } - - const FT_GlyphSlot slot = face->glyph; - GlyphBitmap glyph; - glyph.width = static_cast(slot->bitmap.width); - glyph.height = static_cast(slot->bitmap.rows); - glyph.pitch = static_cast(slot->bitmap.pitch); - glyph.bearingX = slot->bitmap_left; - glyph.bearingY = slot->bitmap_top; - glyph.advance = static_cast(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(ftFace_); - if (!face) { - return; - } - int pixelSize = static_cast(std::max(1.0f, cmd.fontSize)); - if (currentFontSize_ != pixelSize) { - FT_Set_Pixel_Sizes(face, 0, static_cast(pixelSize)); - currentFontSize_ = pixelSize; - } - - float ascender = face->size ? static_cast(face->size->metrics.ascender) / 64.0f : cmd.fontSize; - float lineHeight = face->size ? static_cast(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(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(glyph ? glyph->advance : pixelSize / 2); - continue; - } - - float glyphX = penX + static_cast(glyph->bearingX); - float glyphY = baseline - static_cast(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(alpha) / 255.0f); - GuiColor color{cmd.color.r, cmd.color.g, cmd.color.b, a}; - GuiCommand::RectData pixelRect{ - glyphX + static_cast(col), - glyphY + static_cast(row), - 1.0f, - 1.0f - }; - AddQuad(pixelRect, color, activeClip); - } - } - penX += static_cast(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 width = viewportWidth_ == 0 ? 1.0f : static_cast(viewportWidth_); - float height = viewportHeight_ == 0 ? 1.0f : static_cast(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(baseIndex + 0)); - indices_.push_back(static_cast(baseIndex + 1)); - indices_.push_back(static_cast(baseIndex + 2)); - indices_.push_back(static_cast(baseIndex + 0)); - indices_.push_back(static_cast(baseIndex + 2)); - indices_.push_back(static_cast(baseIndex + 3)); -} - -void GuiRenderer::GenerateGuiGeometry(const std::vector& commands, uint32_t width, uint32_t height) { - vertices_.clear(); - indices_.clear(); - - // Convert screen coordinates to NDC (-1 to 1) - auto toNDC = [width, height](float x, float y) -> std::pair { - return { - (x / static_cast(width)) * 2.0f - 1.0f, - (y / static_cast(height)) * 2.0f - 1.0f - }; - }; - - std::vector clipStack; - clipStack.push_back({0.0f, 0.0f, static_cast(width), static_cast(height)}); - - auto addClippedPolygon = [&](const std::vector& polygon, - const GuiColor& color, - const GuiCommand::RectData& clipRect) { - std::vector clipped = ClipPolygonToRect(polygon, clipRect); - if (clipped.size() < 3) { - return; - } - uint32_t baseIndex = static_cast(vertices_.size()); - for (const auto& point : clipped) { - auto [x, y] = toNDC(point.x, point.y); - vertices_.push_back({x, y, 0.0f, color.r, color.g, color.b, color.a}); - } - for (size_t i = 1; i + 1 < clipped.size(); ++i) { - indices_.push_back(baseIndex); - indices_.push_back(baseIndex + static_cast(i)); - indices_.push_back(baseIndex + static_cast(i + 1)); - } - }; - - for (const auto& cmd : commands) { - if (cmd.type == GuiCommand::Type::ClipPush) { - GuiCommand::RectData nextClip = IntersectRect(clipStack.back(), cmd.rect); - clipStack.push_back(nextClip); - continue; - } - if (cmd.type == GuiCommand::Type::ClipPop) { - if (clipStack.size() > 1) { - clipStack.pop_back(); - } - continue; - } - - const GuiCommand::RectData& activeClip = clipStack.back(); - if (!RectHasArea(activeClip)) { - continue; - } - - if (cmd.type == GuiCommand::Type::Rect) { - 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) { - float innerHeight = std::max(0.0f, cmd.rect.height - border * 2.0f); - GuiCommand::RectData top{cmd.rect.x, cmd.rect.y, cmd.rect.width, border}; - GuiCommand::RectData bottom{cmd.rect.x, cmd.rect.y + cmd.rect.height - border, - cmd.rect.width, border}; - 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); - } - } - } else if (cmd.type == GuiCommand::Type::Text) { - if (cmd.text.empty()) { - continue; - } - - // Font metrics (8x8 bitmap font) - const float charWidth = cmd.fontSize * 0.6f; // Slightly wider for better appearance - const float charHeight = cmd.fontSize; - const float charSpacing = charWidth * 0.05f; // Tighter spacing - - // Calculate text width for alignment - float textWidth = cmd.text.size() * (charWidth + charSpacing); - - // Calculate text position based on alignment - // Text commands use bounds field, not rect field - GuiCommand::RectData textBounds = cmd.bounds; - if (!cmd.hasBounds) { - textBounds = { - cmd.rect.x, - cmd.rect.y, - cmd.fontSize * static_cast(std::max(1, cmd.text.size())), - cmd.fontSize - }; - } - - float startX = textBounds.x; - float startY = textBounds.y; - - if (cmd.alignX == "center") { - startX += (textBounds.width - textWidth) * 0.5f; - } else if (cmd.alignX == "right") { - startX += textBounds.width - textWidth; - } - - if (cmd.alignY == "center") { - startY += (textBounds.height - charHeight) * 0.5f; - } else if (cmd.alignY == "bottom") { - startY += textBounds.height - charHeight; - } - - GuiCommand::RectData textClip = activeClip; - if (cmd.hasClipRect) { - textClip = IntersectRect(textClip, cmd.clipRect); - } - if (!RectHasArea(textClip)) { - continue; - } - - 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(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; - } - } - } else if (cmd.type == GuiCommand::Type::Svg) { - if (cmd.svgPath.empty()) { - continue; - } - const ParsedSvg* svg = LoadSvg(cmd.svgPath); - if (!svg || svg->circles.empty() || svg->viewWidth <= 0.0f || svg->viewHeight <= 0.0f) { - continue; - } - - GuiCommand::RectData clippedTarget = IntersectRect(cmd.rect, activeClip); - if (!RectHasArea(clippedTarget)) { - continue; - } - - float scaleX = clippedTarget.width / svg->viewWidth; - float scaleY = clippedTarget.height / svg->viewHeight; - float scale = std::min(scaleX, scaleY); - - for (const auto& circle : svg->circles) { - float cx = clippedTarget.x + circle.cx * scaleX; - float cy = clippedTarget.y + circle.cy * scaleY; - float radius = circle.r * scale; - if (radius <= 0.0f) { - continue; - } - - GuiColor color = circle.color; - if (cmd.svgTint.a > 0.0f) { - color.r *= cmd.svgTint.r; - color.g *= cmd.svgTint.g; - color.b *= cmd.svgTint.b; - color.a *= cmd.svgTint.a; - } - - int segments = std::max(12, static_cast(radius * 0.25f)); - const float twoPi = 6.283185307179586f; - ClipPoint center{cx, cy}; - - for (int i = 0; i < segments; ++i) { - float angle0 = twoPi * static_cast(i) / static_cast(segments); - float angle1 = twoPi * static_cast(i + 1) / static_cast(segments); - ClipPoint p0{cx + std::cos(angle0) * radius, cy + std::sin(angle0) * radius}; - ClipPoint p1{cx + std::cos(angle1) * radius, cy + std::sin(angle1) * radius}; - addClippedPolygon({center, p0, p1}, color, activeClip); - } - } - } - } -} - -const std::vector& GuiRenderer::LoadShaderBytes(const std::filesystem::path& path, - VkShaderStageFlagBits stage) { - const std::string key = path.string(); - auto cached = shaderSpirvCache_.find(key); - if (cached != shaderSpirvCache_.end()) { - if (logger_) { - logger_->Trace("GuiRenderer", "LoadShaderBytes", - "cacheHit=true, path=" + key + - ", bytes=" + std::to_string(cached->second.size())); - } - return cached->second; - } - - std::vector shaderBytes = ReadShaderFile(path, stage, logger_.get()); - auto inserted = shaderSpirvCache_.emplace(key, std::move(shaderBytes)); - if (logger_) { - logger_->Trace("GuiRenderer", "LoadShaderBytes", - "cacheHit=false, path=" + key + - ", bytes=" + std::to_string(inserted.first->second.size())); - } - return inserted.first->second; -} - -const std::vector& GuiRenderer::LoadShaderBytes(const std::string& cacheKey, - const std::string& source, - VkShaderStageFlagBits stage) { - auto cached = shaderSpirvCache_.find(cacheKey); - if (cached != shaderSpirvCache_.end()) { - if (logger_) { - logger_->Trace("GuiRenderer", "LoadShaderBytes", - "cacheHit=true, key=" + cacheKey + - ", bytes=" + std::to_string(cached->second.size())); - } - return cached->second; - } - - std::vector shaderBytes = ReadShaderSource(source, stage, cacheKey, logger_.get()); - auto inserted = shaderSpirvCache_.emplace(cacheKey, std::move(shaderBytes)); - if (logger_) { - logger_->Trace("GuiRenderer", "LoadShaderBytes", - "cacheHit=false, key=" + cacheKey + - ", bytes=" + std::to_string(inserted.first->second.size())); - } - return inserted.first->second; -} - -void GuiRenderer::CreatePipeline(VkRenderPass renderPass, VkExtent2D extent) { - // Load shader modules - const std::string vertexLabel = "inline:gui_2d.vert"; - const std::string fragmentLabel = "inline:gui_2d.frag"; - if (logger_) { - logger_->Trace("GuiRenderer", "CreatePipeline", - "renderPassIsNull=" + std::string(renderPass == VK_NULL_HANDLE ? "true" : "false") + - ", extent=" + std::to_string(extent.width) + "x" + std::to_string(extent.height) + - ", vertexShader=" + vertexLabel + - ", fragmentShader=" + fragmentLabel); - } - - const auto& vertShaderCode = LoadShaderBytes(vertexLabel, kGuiVertexSource, - VK_SHADER_STAGE_VERTEX_BIT); - const auto& fragShaderCode = LoadShaderBytes(fragmentLabel, kGuiFragmentSource, - VK_SHADER_STAGE_FRAGMENT_BIT); - - VkShaderModuleCreateInfo vertModuleInfo{}; - vertModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - vertModuleInfo.codeSize = vertShaderCode.size(); - vertModuleInfo.pCode = reinterpret_cast(vertShaderCode.data()); - - if (vkCreateShaderModule(device_, &vertModuleInfo, nullptr, &vertShaderModule_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create vertex shader module for GUI"); - } - - VkShaderModuleCreateInfo fragModuleInfo{}; - fragModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - fragModuleInfo.codeSize = fragShaderCode.size(); - fragModuleInfo.pCode = reinterpret_cast(fragShaderCode.data()); - - if (vkCreateShaderModule(device_, &fragModuleInfo, nullptr, &fragShaderModule_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create fragment shader module for GUI"); - } - - // Shader stages - VkPipelineShaderStageCreateInfo vertStageInfo{}; - vertStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - vertStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; - vertStageInfo.module = vertShaderModule_; - vertStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo fragStageInfo{}; - fragStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - fragStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; - fragStageInfo.module = fragShaderModule_; - fragStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo shaderStages[] = {vertStageInfo, fragStageInfo}; - - // Vertex input - GuiVertex format - VkVertexInputBindingDescription bindingDesc{}; - bindingDesc.binding = 0; - bindingDesc.stride = sizeof(GuiVertex); - bindingDesc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - - std::array attributeDescs{}; - // Position (vec3) - attributeDescs[0].binding = 0; - attributeDescs[0].location = 0; - attributeDescs[0].format = VK_FORMAT_R32G32B32_SFLOAT; - attributeDescs[0].offset = offsetof(GuiVertex, x); - - // Color (vec4) - attributeDescs[1].binding = 0; - attributeDescs[1].location = 1; - attributeDescs[1].format = VK_FORMAT_R32G32B32A32_SFLOAT; - attributeDescs[1].offset = offsetof(GuiVertex, r); - - VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; - vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.pVertexBindingDescriptions = &bindingDesc; - vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescs.size()); - vertexInputInfo.pVertexAttributeDescriptions = attributeDescs.data(); - - // Input assembly - VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; - inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - inputAssembly.primitiveRestartEnable = VK_FALSE; - - // Viewport and scissor - VkViewport viewport{}; - viewport.x = 0.0f; - viewport.y = 0.0f; - viewport.width = static_cast(extent.width); - viewport.height = static_cast(extent.height); - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - - VkRect2D scissor{}; - scissor.offset = {0, 0}; - scissor.extent = extent; - - VkPipelineViewportStateCreateInfo viewportState{}; - viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportState.viewportCount = 1; - viewportState.pViewports = &viewport; - viewportState.scissorCount = 1; - viewportState.pScissors = &scissor; - - // Rasterization - VkPipelineRasterizationStateCreateInfo rasterizer{}; - rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizer.depthClampEnable = VK_FALSE; - rasterizer.rasterizerDiscardEnable = VK_FALSE; - rasterizer.polygonMode = VK_POLYGON_MODE_FILL; - rasterizer.lineWidth = 1.0f; - rasterizer.cullMode = VK_CULL_MODE_NONE; // No culling for 2D GUI - rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; - rasterizer.depthBiasEnable = VK_FALSE; - - // Multisampling - enable sample shading for smoother text - VkPipelineMultisampleStateCreateInfo multisampling{}; - multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampling.sampleShadingEnable = VK_TRUE; // Enable for smoother rendering - multisampling.minSampleShading = 0.5f; // Shade at least 50% of samples - multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - - // **CRITICAL: Alpha blending for transparency** - VkPipelineColorBlendAttachmentState colorBlendAttachment{}; - colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | - VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - colorBlendAttachment.blendEnable = VK_TRUE; // Enable blending! - colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; - colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; - colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; - colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; - colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; - - VkPipelineColorBlendStateCreateInfo colorBlending{}; - colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlending.logicOpEnable = VK_FALSE; - colorBlending.attachmentCount = 1; - colorBlending.pAttachments = &colorBlendAttachment; - - // Depth stencil - disable depth test for 2D GUI overlay - VkPipelineDepthStencilStateCreateInfo depthStencil{}; - depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; - depthStencil.depthTestEnable = VK_FALSE; - depthStencil.depthWriteEnable = VK_FALSE; - depthStencil.stencilTestEnable = VK_FALSE; - - // Push constants for transformation matrices - VkPushConstantRange pushConstantRange{}; - pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - pushConstantRange.offset = 0; - pushConstantRange.size = sizeof(float) * 32; // 2 mat4s - - // Pipeline layout - VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; - pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutInfo.pushConstantRangeCount = 1; - pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange; - - if (vkCreatePipelineLayout(device_, &pipelineLayoutInfo, nullptr, &pipelineLayout_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create GUI pipeline layout"); - } - - // Create graphics pipeline - VkGraphicsPipelineCreateInfo pipelineInfo{}; - pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineInfo.stageCount = 2; - pipelineInfo.pStages = shaderStages; - pipelineInfo.pVertexInputState = &vertexInputInfo; - pipelineInfo.pInputAssemblyState = &inputAssembly; - pipelineInfo.pViewportState = &viewportState; - pipelineInfo.pRasterizationState = &rasterizer; - pipelineInfo.pMultisampleState = &multisampling; - pipelineInfo.pDepthStencilState = &depthStencil; - pipelineInfo.pColorBlendState = &colorBlending; - pipelineInfo.layout = pipelineLayout_; - pipelineInfo.renderPass = renderPass; - pipelineInfo.subpass = 0; - - if (vkCreateGraphicsPipelines(device_, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create GUI graphics pipeline"); - } -} - -void GuiRenderer::CreateVertexAndIndexBuffers(size_t vertexCount, size_t indexCount) { - // Clean up old buffers - CleanupBuffers(); - - if (vertexCount == 0 || indexCount == 0) { - return; - } - - // Create vertex buffer - VkDeviceSize vertexBufferSize = sizeof(GuiVertex) * vertexCount; - bufferService_->CreateBuffer( - vertexBufferSize, - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - vertexBuffer_, - vertexMemory_ - ); - - // Upload vertex data - void* vertexData; - vkMapMemory(device_, vertexMemory_, 0, vertexBufferSize, 0, &vertexData); - std::memcpy(vertexData, vertices_.data(), vertexBufferSize); - vkUnmapMemory(device_, vertexMemory_); - - // Create index buffer - VkDeviceSize indexBufferSize = sizeof(uint32_t) * indexCount; - bufferService_->CreateBuffer( - indexBufferSize, - VK_BUFFER_USAGE_INDEX_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - indexBuffer_, - indexMemory_ - ); - - // Upload index data - void* indexData; - vkMapMemory(device_, indexMemory_, 0, indexBufferSize, 0, &indexData); - std::memcpy(indexData, indices_.data(), indexBufferSize); - vkUnmapMemory(device_, indexMemory_); -} - -void GuiRenderer::CleanupPipeline() { - if (pipeline_ != VK_NULL_HANDLE) { - vkDestroyPipeline(device_, pipeline_, nullptr); - pipeline_ = VK_NULL_HANDLE; - } - if (pipelineLayout_ != VK_NULL_HANDLE) { - vkDestroyPipelineLayout(device_, pipelineLayout_, nullptr); - pipelineLayout_ = VK_NULL_HANDLE; - } - if (vertShaderModule_ != VK_NULL_HANDLE) { - vkDestroyShaderModule(device_, vertShaderModule_, nullptr); - vertShaderModule_ = VK_NULL_HANDLE; - } - if (fragShaderModule_ != VK_NULL_HANDLE) { - vkDestroyShaderModule(device_, fragShaderModule_, nullptr); - fragShaderModule_ = VK_NULL_HANDLE; - } -} - -void GuiRenderer::CleanupBuffers() { - if (vertexBuffer_ != VK_NULL_HANDLE) { - vkDestroyBuffer(device_, vertexBuffer_, nullptr); - vertexBuffer_ = VK_NULL_HANDLE; - } - if (vertexMemory_ != VK_NULL_HANDLE) { - vkFreeMemory(device_, vertexMemory_, nullptr); - vertexMemory_ = VK_NULL_HANDLE; - } - if (indexBuffer_ != VK_NULL_HANDLE) { - vkDestroyBuffer(device_, indexBuffer_, nullptr); - indexBuffer_ = VK_NULL_HANDLE; - } - if (indexMemory_ != VK_NULL_HANDLE) { - vkFreeMemory(device_, indexMemory_, nullptr); - indexMemory_ = VK_NULL_HANDLE; - } -} - -void GuiRenderer::UpdateFormat(VkFormat format) { - if (swapchainFormat_ == format) { - return; - } - swapchainFormat_ = format; -} - -const ParsedSvg* GuiRenderer::LoadSvg(const std::string& relativePath) { - auto it = svgCache_.find(relativePath); - if (it != svgCache_.end()) { - return &it->second; - } - std::filesystem::path path = scriptDirectory_ / relativePath; - try { - ParsedSvg parsed = ParseSvgFile(path); - auto inserted = svgCache_.emplace(relativePath, std::move(parsed)); - return &inserted.first->second; - } catch (...) { - return nullptr; - } -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/gui_renderer.hpp b/src/services/impl/gui_renderer.hpp deleted file mode 100644 index d91a5f0..0000000 --- a/src/services/impl/gui_renderer.hpp +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef SDL3CPP_SERVICES_GUI_RENDERER_HPP -#define SDL3CPP_SERVICES_GUI_RENDERER_HPP - -#include -#include -#include -#include -#include - -#include - -#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" - -namespace sdl3cpp::services::impl { - -struct GuiVertex { - float x, y, z; - float r, g, b, a; -}; - -struct SvgCircle { - float cx = 0.0f; - float cy = 0.0f; - float r = 0.0f; - GuiColor color{1.0f, 1.0f, 1.0f, 1.0f}; -}; - -struct ParsedSvg { - float viewWidth = 1.0f; - float viewHeight = 1.0f; - std::vector circles; -}; - -class GuiRenderer { -public: - GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFormat swapchainFormat, - VkRenderPass renderPass, const std::filesystem::path& scriptDirectory, - const GuiFontConfig& fontConfig, - std::shared_ptr bufferService, std::shared_ptr logger); - ~GuiRenderer(); - - GuiRenderer(const GuiRenderer&) = delete; - GuiRenderer& operator=(const GuiRenderer&) = delete; - - void Prepare(const std::vector& commands, uint32_t width, - uint32_t height); - void RenderToSwapchain(VkCommandBuffer commandBuffer, VkRenderPass renderPass); - void Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass); - 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 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); - void CleanupPipeline(); - void CleanupBuffers(); - void UpdateFormat(VkFormat format); - void GenerateGuiGeometry(const std::vector& commands, uint32_t width, uint32_t height); - const std::vector& LoadShaderBytes(const std::filesystem::path& path, - VkShaderStageFlagBits stage); - const std::vector& LoadShaderBytes(const std::string& cacheKey, - const std::string& source, - VkShaderStageFlagBits stage); - - VkDevice device_; - VkPhysicalDevice physicalDevice_; - VkFormat swapchainFormat_; - VkRenderPass renderPass_; - std::filesystem::path scriptDirectory_; - GuiFontConfig fontConfig_; - - // Pipeline resources - VkPipeline pipeline_ = VK_NULL_HANDLE; - VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE; - VkShaderModule vertShaderModule_ = VK_NULL_HANDLE; - VkShaderModule fragShaderModule_ = VK_NULL_HANDLE; - - // Vertex/index buffers - VkBuffer vertexBuffer_ = VK_NULL_HANDLE; - VkDeviceMemory vertexMemory_ = VK_NULL_HANDLE; - VkBuffer indexBuffer_ = VK_NULL_HANDLE; - VkDeviceMemory indexMemory_ = VK_NULL_HANDLE; - - // Geometry data - std::vector vertices_; - std::vector indices_; - - uint32_t viewportWidth_ = 0; - uint32_t viewportHeight_ = 0; - std::unordered_map svgCache_; - std::unordered_map> shaderSpirvCache_; - std::unordered_map glyphCache_; - void* ftLibrary_ = nullptr; - void* ftFace_ = nullptr; - int currentFontSize_ = 0; - bool freetypeReady_ = false; - std::shared_ptr bufferService_; - std::shared_ptr logger_; -}; - -} // namespace sdl3cpp::services::impl - -#endif // SDL3CPP_SERVICES_GUI_RENDERER_HPP diff --git a/src/services/impl/gui_renderer_service.cpp b/src/services/impl/gui_renderer_service.cpp deleted file mode 100644 index f89945c..0000000 --- a/src/services/impl/gui_renderer_service.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "gui_renderer_service.hpp" - -#include -#include - -namespace sdl3cpp::services::impl { - -GuiRendererService::GuiRendererService(std::shared_ptr logger, - std::shared_ptr bufferService, - std::shared_ptr configService) - : logger_(std::move(logger)), - bufferService_(std::move(bufferService)), - configService_(std::move(configService)) { - if (logger_) { - logger_->Trace("GuiRendererService", "GuiRendererService", - "bufferService=" + std::string(bufferService_ ? "set" : "null")); - } -} - -void GuiRendererService::Initialize(VkDevice device, - VkPhysicalDevice physicalDevice, - VkFormat format, - VkRenderPass renderPass, - const std::filesystem::path& resourcePath) { - if (logger_) { - logger_->Trace("GuiRendererService", "Initialize", - "deviceIsNull=" + std::string(device == VK_NULL_HANDLE ? "true" : "false") + - ", physicalDeviceIsNull=" + std::string(physicalDevice == VK_NULL_HANDLE ? "true" : "false") + - ", format=" + std::to_string(static_cast(format)) + - ", renderPassIsNull=" + std::string(renderPass == VK_NULL_HANDLE ? "true" : "false") + - ", resourcePath=" + resourcePath.string()); - } - GuiFontConfig fontConfig; - if (configService_) { - fontConfig = configService_->GetGuiFontConfig(); - } - renderer_ = std::make_unique( - device, physicalDevice, format, renderPass, resourcePath, fontConfig, - bufferService_, logger_); -} - -void GuiRendererService::PrepareFrame(const std::vector& commands, - uint32_t width, - uint32_t height) { - if (logger_) { - logger_->Trace("GuiRendererService", "PrepareFrame", - "commands.size=" + std::to_string(commands.size()) + - ", width=" + std::to_string(width) + - ", height=" + std::to_string(height)); - } - if (!renderer_) { - throw std::runtime_error("GuiRenderer service not initialized"); - } - renderer_->Prepare(commands, width, height); -} - -void GuiRendererService::RenderToSwapchain(VkCommandBuffer commandBuffer, VkImage image) { - if (logger_) { - logger_->Trace("GuiRendererService", "RenderToSwapchain", - "commandBufferIsNull=" + std::string(commandBuffer == VK_NULL_HANDLE ? "true" : "false") + - ", imageIsNull=" + std::string(image == VK_NULL_HANDLE ? "true" : "false")); - } - if (!renderer_) { - throw std::runtime_error("GuiRenderer service not initialized"); - } - // Note: image parameter is ignored - GUI renders into active render pass, not a specific image - renderer_->RenderToSwapchain(commandBuffer, VK_NULL_HANDLE); -} - -void GuiRendererService::Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) { - if (logger_) { - logger_->Trace("GuiRendererService", "Resize", - "width=" + std::to_string(width) + - ", height=" + std::to_string(height) + - ", format=" + std::to_string(static_cast(format)) + - ", renderPassIsNull=" + std::string(renderPass == VK_NULL_HANDLE ? "true" : "false")); - } - if (!renderer_) { - return; - } - renderer_->Resize(width, height, format, renderPass); -} - -void GuiRendererService::Shutdown() noexcept { - if (logger_) { - logger_->Trace("GuiRendererService", "Shutdown"); - } - renderer_.reset(); -} - -bool GuiRendererService::IsReady() const { - if (logger_) { - logger_->Trace("GuiRendererService", "IsReady"); - } - return renderer_ && renderer_->IsReady(); -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/gui_renderer_service.hpp b/src/services/impl/gui_renderer_service.hpp deleted file mode 100644 index 302d19c..0000000 --- a/src/services/impl/gui_renderer_service.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#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" -#include "gui_renderer.hpp" -#include - -namespace sdl3cpp::services::impl { - -class GuiRendererService : public IGuiRendererService, public di::IShutdownable { -public: - GuiRendererService(std::shared_ptr logger, - std::shared_ptr bufferService, - std::shared_ptr configService); - ~GuiRendererService() override = default; - - void Initialize(VkDevice device, - VkPhysicalDevice physicalDevice, - VkFormat format, - VkRenderPass renderPass, - const std::filesystem::path& resourcePath) override; - - void PrepareFrame(const std::vector& 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() noexcept override; - - bool IsReady() const override; - -private: - std::shared_ptr logger_; - std::shared_ptr bufferService_; - std::shared_ptr configService_; - std::unique_ptr renderer_; -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/gxm_graphics_backend.cpp b/src/services/impl/gxm_graphics_backend.cpp index fc5dd36..28d7225 100644 --- a/src/services/impl/gxm_graphics_backend.cpp +++ b/src/services/impl/gxm_graphics_backend.cpp @@ -47,6 +47,8 @@ void GxmGraphicsBackend::Initialize(void* window, const GraphicsConfig& config) std::cout << "GXM: Initializing GXM graphics backend" << std::endl; int err; + (void)window; + (void)config; // Initialize GXM SceGxmInitializeParams gxmInitParams; @@ -464,10 +466,6 @@ void GxmGraphicsBackend::SetViewProjection(const std::array& viewProj // Matrix will be set when drawing with specific pipeline } -void GxmGraphicsBackend::SetRenderGraphDefinition(const RenderGraphDefinition& definition) { - (void)definition; -} - void GxmGraphicsBackend::Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline, GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer, uint32_t indexOffset, uint32_t indexCount, int32_t vertexOffset, diff --git a/src/services/impl/gxm_graphics_backend.hpp b/src/services/impl/gxm_graphics_backend.hpp index 6d79d47..9c95f35 100644 --- a/src/services/impl/gxm_graphics_backend.hpp +++ b/src/services/impl/gxm_graphics_backend.hpp @@ -37,7 +37,6 @@ public: bool EndFrame(GraphicsDeviceHandle device) override; void SetViewProjection(const std::array& viewProj) override; - void SetRenderGraphDefinition(const RenderGraphDefinition& definition) override; void Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline, GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer, diff --git a/src/services/impl/json_config_service.cpp b/src/services/impl/json_config_service.cpp index 738bbe2..805c1b9 100644 --- a/src/services/impl/json_config_service.cpp +++ b/src/services/impl/json_config_service.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -16,34 +15,6 @@ namespace sdl3cpp::services::impl { -// Default Vulkan device extensions -static const std::vector 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(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 logger, const char* argv0) : logger_(std::move(logger)), configJson_(), config_(RuntimeConfig{}) { if (logger_) { @@ -80,13 +51,6 @@ JsonConfigService::JsonConfigService(std::shared_ptr logger, const Runt logger_->Info("JsonConfigService initialized with explicit configuration"); } -std::vector JsonConfigService::GetDeviceExtensions() const { - if (logger_) { - logger_->Trace("JsonConfigService", "GetDeviceExtensions"); - } - return kDeviceExtensions; -} - std::filesystem::path JsonConfigService::FindScriptPath(const char* argv0) { if (logger_) { logger_->Trace("JsonConfigService", "FindScriptPath", @@ -388,47 +352,17 @@ RuntimeConfig JsonConfigService::LoadFromJson(std::shared_ptr logger, readFloat("pbr_metallic", config.atmospherics.pbrMetallic); } - if (document.HasMember("render_graph")) { - const auto& renderGraphValue = document["render_graph"]; - if (!renderGraphValue.IsObject()) { - throw std::runtime_error("JSON member 'render_graph' must be an object"); + if (document.HasMember("bgfx")) { + const auto& bgfxValue = document["bgfx"]; + if (!bgfxValue.IsObject()) { + throw std::runtime_error("JSON member 'bgfx' must be an object"); } - - if (renderGraphValue.HasMember("enabled")) { - const auto& value = renderGraphValue["enabled"]; - if (!value.IsBool()) { - throw std::runtime_error("JSON member 'render_graph.enabled' must be a boolean"); - } - config.renderGraph.enabled = value.GetBool(); - } - - if (renderGraphValue.HasMember("function")) { - const auto& value = renderGraphValue["function"]; + if (bgfxValue.HasMember("renderer")) { + const auto& value = bgfxValue["renderer"]; if (!value.IsString()) { - throw std::runtime_error("JSON member 'render_graph.function' must be a string"); + throw std::runtime_error("JSON member 'bgfx.renderer' must be a string"); } - config.renderGraph.functionName = value.GetString(); - } - } - - 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(); + config.bgfx.renderer = value.GetString(); } } @@ -583,21 +517,11 @@ std::string JsonConfigService::BuildConfigJson(const RuntimeConfig& config, allocator); document.AddMember("mouse_grab", mouseGrabObject, allocator); - rapidjson::Value renderGraphObject(rapidjson::kObjectType); - renderGraphObject.AddMember("enabled", config.renderGraph.enabled, allocator); - renderGraphObject.AddMember("function", - rapidjson::Value(config.renderGraph.functionName.c_str(), allocator), - 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 bgfxObject(rapidjson::kObjectType); + bgfxObject.AddMember("renderer", + rapidjson::Value(config.bgfx.renderer.c_str(), allocator), + allocator); + document.AddMember("bgfx", bgfxObject, allocator); rapidjson::Value materialObject(rapidjson::kObjectType); materialObject.AddMember("enabled", config.materialX.enabled, allocator); @@ -696,12 +620,6 @@ std::string JsonConfigService::BuildConfigJson(const RuntimeConfig& config, document.AddMember("gui_opacity", config.guiOpacity, allocator); - rapidjson::Value extensionArray(rapidjson::kArrayType); - for (const char* extension : kDeviceExtensions) { - rapidjson::Value extensionValue(extension, allocator); - extensionArray.PushBack(extensionValue, allocator); - } - document.AddMember("device_extensions", extensionArray, allocator); if (!configPath.empty()) { addStringMember("config_file", configPath.string()); } diff --git a/src/services/impl/json_config_service.hpp b/src/services/impl/json_config_service.hpp index 9e4841e..54bc406 100644 --- a/src/services/impl/json_config_service.hpp +++ b/src/services/impl/json_config_service.hpp @@ -75,7 +75,6 @@ public: } return config_.windowTitle; } - std::vector GetDeviceExtensions() const override; const InputBindings& GetInputBindings() const override { if (logger_) { logger_->Trace("JsonConfigService", "GetInputBindings"); @@ -88,17 +87,11 @@ public: } return config_.mouseGrab; } - const RenderGraphConfig& GetRenderGraphConfig() const override { + const BgfxConfig& GetBgfxConfig() const override { if (logger_) { - logger_->Trace("JsonConfigService", "GetRenderGraphConfig"); + logger_->Trace("JsonConfigService", "GetBgfxConfig"); } - return config_.renderGraph; - } - const GraphicsBackendConfig& GetGraphicsBackendConfig() const override { - if (logger_) { - logger_->Trace("JsonConfigService", "GetGraphicsBackendConfig"); - } - return config_.graphicsBackend; + return config_.bgfx; } const MaterialXConfig& GetMaterialXConfig() const override { if (logger_) { diff --git a/src/services/impl/json_config_writer_service.cpp b/src/services/impl/json_config_writer_service.cpp index 1524013..d40c1e9 100644 --- a/src/services/impl/json_config_writer_service.cpp +++ b/src/services/impl/json_config_writer_service.cpp @@ -3,14 +3,12 @@ #include #include #include -#include #include #include #include #include #include -#include namespace sdl3cpp::services::impl { @@ -125,13 +123,11 @@ void JsonConfigWriterService::WriteConfig(const RuntimeConfig& config, const std addStringMember("shaders_directory", "shaders"); } - std::vector deviceExtensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; - rapidjson::Value extensionArray(rapidjson::kArrayType); - for (const char* extension : deviceExtensions) { - rapidjson::Value extensionValue(extension, allocator); - extensionArray.PushBack(extensionValue, allocator); - } - document.AddMember("device_extensions", extensionArray, allocator); + rapidjson::Value bgfxObject(rapidjson::kObjectType); + bgfxObject.AddMember("renderer", + rapidjson::Value(config.bgfx.renderer.c_str(), allocator), + allocator); + document.AddMember("bgfx", bgfxObject, allocator); addStringMember("config_file", configPath.string()); rapidjson::StringBuffer buffer; diff --git a/src/services/impl/null_gui_service.cpp b/src/services/impl/null_gui_service.cpp index ea0d48b..bcedd3f 100644 --- a/src/services/impl/null_gui_service.cpp +++ b/src/services/impl/null_gui_service.cpp @@ -11,43 +11,15 @@ NullGuiService::NullGuiService(std::shared_ptr logger) } } -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& 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) + + "commands.size=" + std::to_string(commands.size()) + + ", width=" + std::to_string(width) + ", height=" + std::to_string(height)); } } -void NullGuiService::Shutdown() { - if (logger_) { - logger_->Trace("NullGuiService", "Shutdown"); - } -} - } // namespace sdl3cpp::services::impl diff --git a/src/services/impl/null_gui_service.hpp b/src/services/impl/null_gui_service.hpp index b4affa1..f407ed2 100644 --- a/src/services/impl/null_gui_service.hpp +++ b/src/services/impl/null_gui_service.hpp @@ -10,17 +10,9 @@ class NullGuiService : public IGuiService { public: explicit NullGuiService(std::shared_ptr logger); - void Initialize(VkDevice device, - VkPhysicalDevice physicalDevice, - VkFormat format, - VkRenderPass renderPass, - const std::filesystem::path& resourcePath) override; void PrepareFrame(const std::vector& 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 logger_; diff --git a/src/services/impl/pipeline_service.cpp b/src/services/impl/pipeline_service.cpp deleted file mode 100644 index f695642..0000000 --- a/src/services/impl/pipeline_service.cpp +++ /dev/null @@ -1,565 +0,0 @@ -#include "pipeline_service.hpp" -#include "../../core/vertex.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { -shaderc_shader_kind ShadercKindFromStage(VkShaderStageFlagBits stage) { - switch (stage) { - case VK_SHADER_STAGE_VERTEX_BIT: - return shaderc_vertex_shader; - case VK_SHADER_STAGE_FRAGMENT_BIT: - return shaderc_fragment_shader; - case VK_SHADER_STAGE_GEOMETRY_BIT: - return shaderc_geometry_shader; - case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT: - return shaderc_tess_control_shader; - case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT: - return shaderc_tess_evaluation_shader; - case VK_SHADER_STAGE_COMPUTE_BIT: - return shaderc_compute_shader; - default: - return shaderc_glsl_infer_from_source; - } -} -} // namespace - -namespace sdl3cpp::services::impl { - -PipelineService::PipelineService(std::shared_ptr deviceService, std::shared_ptr logger) - : deviceService_(std::move(deviceService)), logger_(logger) { - if (logger_) { - logger_->Trace("PipelineService", "PipelineService", - "deviceService=" + std::string(deviceService_ ? "set" : "null")); - } -} - -PipelineService::~PipelineService() { - if (logger_) { - logger_->Trace("PipelineService", "~PipelineService"); - } - if (pipelineLayout_ != VK_NULL_HANDLE || !pipelines_.empty()) { - Shutdown(); - } -} - -void PipelineService::RegisterShader(const std::string& key, const ShaderPaths& paths) { - std::string vertexLabel = paths.vertex.empty() - ? (paths.vertexSource.empty() ? "" : "") - : paths.vertex; - std::string fragmentLabel = paths.fragment.empty() - ? (paths.fragmentSource.empty() ? "" : "") - : paths.fragment; - logger_->Trace("PipelineService", "RegisterShader", - "key=" + key + - ", vertex=" + vertexLabel + - ", fragment=" + fragmentLabel); - shaderPathMap_[key] = paths; - logger_->Debug("Registered shader: " + key); -} - -void PipelineService::CompileAll(VkRenderPass renderPass, VkExtent2D extent) { - logger_->Trace("PipelineService", "CompileAll", - "renderPassIsNull=" + std::string(renderPass == VK_NULL_HANDLE ? "true" : "false") + - ", extent.width=" + std::to_string(extent.width) + - ", extent.height=" + std::to_string(extent.height)); - - if (shaderPathMap_.empty()) { - throw std::runtime_error("No shader paths were registered before pipeline creation"); - } - - CreatePipelineLayout(); - CreatePipelinesInternal(renderPass, extent); - - logger_->Info("Compiled " + std::to_string(pipelines_.size()) + " pipeline(s)"); -} - -void PipelineService::RecreatePipelines(VkRenderPass renderPass, VkExtent2D extent) { - logger_->Trace("PipelineService", "RecreatePipelines", - "renderPassIsNull=" + std::string(renderPass == VK_NULL_HANDLE ? "true" : "false") + - ", extent.width=" + std::to_string(extent.width) + - ", extent.height=" + std::to_string(extent.height)); - - CleanupPipelines(); - CreatePipelineLayout(); - CreatePipelinesInternal(renderPass, extent); - - logger_->Info("Recreated " + std::to_string(pipelines_.size()) + " pipeline(s)"); -} - -void PipelineService::Cleanup() { - logger_->Trace("PipelineService", "Cleanup"); - CleanupPipelines(); - shaderSpirvCache_.clear(); - - auto device = deviceService_->GetDevice(); - - if (pipelineLayout_ != VK_NULL_HANDLE) { - vkDestroyPipelineLayout(device, pipelineLayout_, nullptr); - pipelineLayout_ = VK_NULL_HANDLE; - } - if (descriptorSetLayout_ != VK_NULL_HANDLE) { - vkDestroyDescriptorSetLayout(device, descriptorSetLayout_, nullptr); - descriptorSetLayout_ = VK_NULL_HANDLE; - } -} - -void PipelineService::Shutdown() noexcept { - logger_->Trace("PipelineService", "Shutdown"); - Cleanup(); -} - -VkPipeline PipelineService::GetPipeline(const std::string& key) const { - logger_->Trace("PipelineService", "GetPipeline", "key=" + key); - auto it = pipelines_.find(key); - if (it == pipelines_.end()) { - throw std::out_of_range("Pipeline not found: " + key); - } - return it->second; -} - -bool PipelineService::HasShader(const std::string& key) const { - logger_->Trace("PipelineService", "HasShader", "key=" + key); - return shaderPathMap_.find(key) != shaderPathMap_.end(); -} - -void PipelineService::CreatePipelineLayout() { - logger_->Trace("PipelineService", "CreatePipelineLayout"); - - auto device = deviceService_->GetDevice(); - - if (descriptorSetLayout_ != VK_NULL_HANDLE) { - vkDestroyDescriptorSetLayout(device, descriptorSetLayout_, nullptr); - descriptorSetLayout_ = VK_NULL_HANDLE; - } - if (pipelineLayout_ != VK_NULL_HANDLE) { - vkDestroyPipelineLayout(device, pipelineLayout_, nullptr); - pipelineLayout_ = VK_NULL_HANDLE; - } - - VkDescriptorSetLayoutBinding samplerBinding{}; - samplerBinding.binding = 0; - samplerBinding.descriptorCount = 1; - samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - - VkDescriptorSetLayoutCreateInfo setLayoutInfo{}; - setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - setLayoutInfo.bindingCount = 1; - setLayoutInfo.pBindings = &samplerBinding; - - if (vkCreateDescriptorSetLayout(device, &setLayoutInfo, nullptr, &descriptorSetLayout_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create descriptor set layout"); - } - - VkPushConstantRange pushRange{}; - pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; - pushRange.offset = 0; - pushRange.size = sizeof(core::PushConstants); - - VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; - pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutInfo.setLayoutCount = 1; - pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout_; - pipelineLayoutInfo.pushConstantRangeCount = 1; - pipelineLayoutInfo.pPushConstantRanges = &pushRange; - - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create pipeline layout"); - } -} - -void PipelineService::CreatePipelinesInternal(VkRenderPass renderPass, VkExtent2D extent) { - logger_->Trace("PipelineService", "CreatePipelinesInternal", - "renderPassIsNull=" + std::string(renderPass == VK_NULL_HANDLE ? "true" : "false") + - ", extent.width=" + std::to_string(extent.width) + - ", extent.height=" + std::to_string(extent.height)); - - auto device = deviceService_->GetDevice(); - - // Vertex input configuration - VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; - VkVertexInputBindingDescription bindingDescription{}; - bindingDescription.binding = 0; - bindingDescription.stride = sizeof(core::Vertex); - bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - - std::array attributeDescriptions{}; - attributeDescriptions[0].binding = 0; - attributeDescriptions[0].location = 0; - attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; - attributeDescriptions[0].offset = offsetof(core::Vertex, position); - - attributeDescriptions[1].binding = 0; - attributeDescriptions[1].location = 1; - attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; - attributeDescriptions[1].offset = offsetof(core::Vertex, color); - - vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; - vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); - vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); - - // Input assembly - VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; - inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - inputAssembly.primitiveRestartEnable = VK_FALSE; - - // Viewport and scissor - VkViewport viewport{}; - viewport.x = 0.0f; - viewport.y = 0.0f; - viewport.width = static_cast(extent.width); - viewport.height = static_cast(extent.height); - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - - VkRect2D scissor{}; - scissor.offset = {0, 0}; - scissor.extent = extent; - - VkPipelineViewportStateCreateInfo viewportState{}; - viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportState.viewportCount = 1; - viewportState.pViewports = &viewport; - viewportState.scissorCount = 1; - viewportState.pScissors = &scissor; - - std::array dynamicStates = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - VkPipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); - dynamicState.pDynamicStates = dynamicStates.data(); - - // Rasterization - VkPipelineRasterizationStateCreateInfo rasterizer{}; - rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizer.depthClampEnable = VK_FALSE; - rasterizer.rasterizerDiscardEnable = VK_FALSE; - rasterizer.polygonMode = VK_POLYGON_MODE_FILL; - rasterizer.lineWidth = 1.0f; - rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; - rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; - rasterizer.depthBiasEnable = VK_FALSE; - - // Multisampling - VkPipelineMultisampleStateCreateInfo multisampling{}; - multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampling.sampleShadingEnable = VK_FALSE; - multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - - // Color blending - VkPipelineColorBlendAttachmentState colorBlendAttachment{}; - colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | - VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - colorBlendAttachment.blendEnable = VK_FALSE; - - VkPipelineColorBlendStateCreateInfo colorBlending{}; - colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlending.logicOpEnable = VK_FALSE; - colorBlending.attachmentCount = 1; - colorBlending.pAttachments = &colorBlendAttachment; - - // Depth stencil - VkPipelineDepthStencilStateCreateInfo depthStencil{}; - depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; - depthStencil.depthTestEnable = VK_TRUE; - depthStencil.depthWriteEnable = VK_TRUE; - depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; - depthStencil.depthBoundsTestEnable = VK_FALSE; - depthStencil.minDepthBounds = 0.0f; - depthStencil.maxDepthBounds = 1.0f; - depthStencil.stencilTestEnable = VK_FALSE; - depthStencil.front = {}; - depthStencil.back = {}; - - // Base pipeline info - VkGraphicsPipelineCreateInfo pipelineInfo{}; - pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineInfo.pVertexInputState = &vertexInputInfo; - pipelineInfo.pInputAssemblyState = &inputAssembly; - pipelineInfo.pViewportState = &viewportState; - pipelineInfo.pRasterizationState = &rasterizer; - pipelineInfo.pMultisampleState = &multisampling; - pipelineInfo.pDepthStencilState = &depthStencil; - pipelineInfo.pColorBlendState = &colorBlending; - pipelineInfo.pDynamicState = &dynamicState; - pipelineInfo.layout = pipelineLayout_; - pipelineInfo.renderPass = renderPass; - pipelineInfo.subpass = 0; - - // Create pipeline for each registered shader - for (const auto& [key, paths] : shaderPathMap_) { - auto requireShader = [&](const std::string& label, - const std::string& path, - const std::string& source) { - if (!HasShaderSource(path, source)) { - std::string labelPath = path.empty() ? "" : path; - throw std::runtime_error( - label + " shader not found: " + labelPath + - "\n\nShader key: " + key + - "\n\nPlease ensure the shader source (.vert/.frag/etc.) exists."); - } - }; - - // Validate shader files exist - requireShader("Vertex", paths.vertex, paths.vertexSource); - requireShader("Fragment", paths.fragment, paths.fragmentSource); - - bool hasGeometry = !paths.geometry.empty() || !paths.geometrySource.empty(); - bool hasTessControl = !paths.tessControl.empty() || !paths.tessControlSource.empty(); - bool hasTessEval = !paths.tessEval.empty() || !paths.tessEvalSource.empty(); - - if (hasGeometry) { - requireShader("Geometry", paths.geometry, paths.geometrySource); - } - if (hasTessControl != hasTessEval) { - throw std::runtime_error( - "Tessellation shaders require both 'tesc' and 'tese' paths. Shader key: " + key); - } - if (hasTessControl) { - requireShader("Tessellation control", paths.tessControl, paths.tessControlSource); - } - if (hasTessEval) { - requireShader("Tessellation evaluation", paths.tessEval, paths.tessEvalSource); - } - - VkPipelineRasterizationStateCreateInfo rasterizerState = rasterizer; - VkPipelineDepthStencilStateCreateInfo depthStencilState = depthStencil; - if (paths.disableCulling) { - rasterizerState.cullMode = VK_CULL_MODE_NONE; - } - if (paths.disableDepthTest) { - depthStencilState.depthTestEnable = VK_FALSE; - depthStencilState.depthWriteEnable = VK_FALSE; - } - if (logger_ && (paths.disableCulling || paths.disableDepthTest)) { - logger_->Trace("PipelineService", "CreatePipelinesInternal", - "shaderKey=" + key + - ", cullMode=" + std::string(paths.disableCulling ? "none" : "back") + - ", depthTest=" + std::string(paths.disableDepthTest ? "off" : "on")); - } - - std::vector shaderModules; - std::vector shaderStages; - shaderStages.reserve(2 + (hasGeometry ? 1 : 0) + (hasTessControl ? 2 : 0)); - - auto destroyShaderModules = [&]() { - for (VkShaderModule module : shaderModules) { - vkDestroyShaderModule(device, module, nullptr); - } - shaderModules.clear(); - }; - - auto addStage = [&](VkShaderStageFlagBits stage, - const std::string& path, - const std::string& source) { - const auto& shaderCode = ReadShaderSource(path, source, stage); - VkShaderModule shaderModule = CreateShaderModule(shaderCode); - shaderModules.push_back(shaderModule); - - VkPipelineShaderStageCreateInfo stageInfo{}; - stageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - stageInfo.stage = stage; - stageInfo.module = shaderModule; - stageInfo.pName = "main"; - shaderStages.push_back(stageInfo); - }; - - try { - addStage(VK_SHADER_STAGE_VERTEX_BIT, paths.vertex, paths.vertexSource); - if (hasTessControl) { - addStage(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, paths.tessControl, - paths.tessControlSource); - addStage(VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, paths.tessEval, - paths.tessEvalSource); - } - if (hasGeometry) { - addStage(VK_SHADER_STAGE_GEOMETRY_BIT, paths.geometry, paths.geometrySource); - } - addStage(VK_SHADER_STAGE_FRAGMENT_BIT, paths.fragment, paths.fragmentSource); - - VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = inputAssembly; - VkPipelineTessellationStateCreateInfo tessellationState{}; - bool useTessellation = hasTessControl && hasTessEval; - if (useTessellation) { - inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; - tessellationState.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO; - tessellationState.patchControlPoints = 3; - } - - VkGraphicsPipelineCreateInfo pipelineCreateInfo = pipelineInfo; - pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; - pipelineCreateInfo.pTessellationState = useTessellation ? &tessellationState : nullptr; - pipelineCreateInfo.pRasterizationState = &rasterizerState; - pipelineCreateInfo.pDepthStencilState = &depthStencilState; - pipelineCreateInfo.stageCount = static_cast(shaderStages.size()); - pipelineCreateInfo.pStages = shaderStages.data(); - - VkPipeline pipeline; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, - &pipeline) != VK_SUCCESS) { - destroyShaderModules(); - throw std::runtime_error("Failed to create graphics pipeline for shader: " + key); - } - - pipelines_[key] = pipeline; - destroyShaderModules(); - } catch (...) { - destroyShaderModules(); - throw; - } - - logger_->Debug("Created pipeline: " + key); - } -} - -void PipelineService::CleanupPipelines() { - logger_->Trace("PipelineService", "CleanupPipelines"); - - auto device = deviceService_->GetDevice(); - - for (auto& [key, pipeline] : pipelines_) { - vkDestroyPipeline(device, pipeline, nullptr); - } - pipelines_.clear(); -} - -VkShaderModule PipelineService::CreateShaderModule(const std::vector& code) { - logger_->Trace("PipelineService", "CreateShaderModule", - "code.size=" + std::to_string(code.size())); - - auto device = deviceService_->GetDevice(); - - VkShaderModuleCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - createInfo.codeSize = code.size(); - createInfo.pCode = reinterpret_cast(code.data()); - - VkShaderModule shaderModule; - if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { - throw std::runtime_error("Failed to create shader module"); - } - return shaderModule; -} - -std::string PipelineService::BuildShaderCacheKey(const std::string& path, - const std::string& source, - VkShaderStageFlagBits stage) const { - if (!source.empty()) { - size_t hash = std::hash{}(source); - return "inline:" + std::to_string(hash) + "|" + - std::to_string(static_cast(stage)); - } - std::filesystem::path shaderPath(path); - if (shaderPath.extension() == ".spv") { - shaderPath.replace_extension(); - } - return shaderPath.string() + "|" + - std::to_string(static_cast(stage)); -} - -bool PipelineService::HasShaderSource(const std::string& path, const std::string& source) const { - if (!source.empty()) { - return true; - } - if (path.empty()) { - return false; - } - std::filesystem::path shaderPath(path); - if (shaderPath.extension() == ".spv") { - shaderPath.replace_extension(); - } - return std::filesystem::exists(shaderPath); -} - -const std::vector& PipelineService::ReadShaderSource(const std::string& path, - const std::string& source, - VkShaderStageFlagBits stage) { - logger_->Trace("PipelineService", "ReadShaderSource", - "path=" + path + ", stage=" + std::to_string(static_cast(stage)) + - ", source=" + std::string(source.empty() ? "path" : "inline")); - - if (path.empty() && source.empty()) { - throw std::runtime_error("Shader path and source are empty"); - } - - const std::string cacheKey = BuildShaderCacheKey(path, source, stage); - auto cached = shaderSpirvCache_.find(cacheKey); - if (cached != shaderSpirvCache_.end()) { - logger_->Trace("PipelineService", "ReadShaderSource", - "cacheHit=true, bytes=" + std::to_string(cached->second.size())); - return cached->second; - } - - std::string shaderLabel = path.empty() ? "" : path; - std::string shaderSource = source; - if (shaderSource.empty()) { - std::filesystem::path shaderPath(path); - if (shaderPath.extension() == ".spv") { - std::filesystem::path sourcePath = shaderPath; - sourcePath.replace_extension(); - logger_->Trace("PipelineService", "ReadShaderSource", - "usingSource=" + sourcePath.string()); - shaderPath = sourcePath; - } - - if (!std::filesystem::exists(shaderPath)) { - throw std::runtime_error("Shader file not found: " + shaderPath.string() + - "\n\nPlease ensure the shader source (.vert/.frag/etc.) exists."); - } - - if (!std::filesystem::is_regular_file(shaderPath)) { - throw std::runtime_error("Path is not a regular file: " + shaderPath.string()); - } - - std::ifstream sourceFile(shaderPath); - if (!sourceFile) { - throw std::runtime_error("Failed to open shader source: " + shaderPath.string()); - } - shaderSource.assign((std::istreambuf_iterator(sourceFile)), - std::istreambuf_iterator()); - sourceFile.close(); - shaderLabel = shaderPath.string(); - } - - shaderc::Compiler compiler; - shaderc::CompileOptions options; - options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2); - - shaderc_shader_kind kind = ShadercKindFromStage(stage); - auto result = compiler.CompileGlslToSpv(shaderSource, kind, shaderLabel.c_str(), options); - if (result.GetCompilationStatus() != shaderc_compilation_status_success) { - std::string error = result.GetErrorMessage(); - logger_->Error("Shader compilation failed: " + shaderLabel + "\n" + error); - throw std::runtime_error("Shader compilation failed: " + shaderLabel + "\n" + error); - } - - std::vector spirv(result.cbegin(), result.cend()); - std::vector buffer(spirv.size() * sizeof(uint32_t)); - if (!buffer.empty()) { - std::memcpy(buffer.data(), spirv.data(), buffer.size()); - } - - logger_->Debug("Compiled shader: " + shaderLabel + - " (" + std::to_string(buffer.size()) + " bytes)"); - - auto inserted = shaderSpirvCache_.emplace(cacheKey, std::move(buffer)); - logger_->Trace("PipelineService", "ReadShaderSource", - "cacheHit=false, bytes=" + std::to_string(inserted.first->second.size())); - return inserted.first->second; -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/pipeline_service.hpp b/src/services/impl/pipeline_service.hpp deleted file mode 100644 index 4a03307..0000000 --- a/src/services/impl/pipeline_service.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -#include "../interfaces/i_pipeline_service.hpp" -#include "../interfaces/i_vulkan_device_service.hpp" -#include "../interfaces/i_logger.hpp" -#include "../../di/lifecycle.hpp" -#include -#include -#include - -namespace sdl3cpp::services::impl { - -/** - * @brief Pipeline service implementation. - * - * Small, focused service (~200 lines) for Vulkan graphics pipeline management. - * Handles shader compilation, pipeline creation, and pipeline layout. - */ -class PipelineService : public IPipelineService, - public di::IShutdownable { -public: - explicit PipelineService(std::shared_ptr deviceService, std::shared_ptr logger); - ~PipelineService() override; - - // IPipelineService interface - void RegisterShader(const std::string& key, const ShaderPaths& paths) override; - void CompileAll(VkRenderPass renderPass, VkExtent2D extent) override; - void RecreatePipelines(VkRenderPass renderPass, VkExtent2D extent) override; - void Cleanup() override; - - VkPipeline GetPipeline(const std::string& key) const override; - VkPipelineLayout GetPipelineLayout() const override { - logger_->Trace("PipelineService", "GetPipelineLayout"); - return pipelineLayout_; - } - VkDescriptorSetLayout GetDescriptorSetLayout() const override { - logger_->Trace("PipelineService", "GetDescriptorSetLayout"); - return descriptorSetLayout_; - } - bool HasShader(const std::string& key) const override; - size_t GetShaderCount() const override { - logger_->Trace("PipelineService", "GetShaderCount"); - return shaderPathMap_.size(); - } - - // IShutdownable interface - void Shutdown() noexcept override; - -private: - std::shared_ptr deviceService_; - std::shared_ptr logger_; - - VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE; - VkDescriptorSetLayout descriptorSetLayout_ = VK_NULL_HANDLE; - std::unordered_map shaderPathMap_; - std::unordered_map pipelines_; - - // Helper methods - VkShaderModule CreateShaderModule(const std::vector& code); - const std::vector& ReadShaderSource(const std::string& path, - const std::string& source, - VkShaderStageFlagBits stage); - bool HasShaderSource(const std::string& path, const std::string& source) const; - std::string BuildShaderCacheKey(const std::string& path, - const std::string& source, - VkShaderStageFlagBits stage) const; - void CreatePipelineLayout(); - void CreatePipelinesInternal(VkRenderPass renderPass, VkExtent2D extent); - void CleanupPipelines(); - - std::unordered_map> shaderSpirvCache_; -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/render_command_service.cpp b/src/services/impl/render_command_service.cpp deleted file mode 100644 index a512c41..0000000 --- a/src/services/impl/render_command_service.cpp +++ /dev/null @@ -1,1553 +0,0 @@ -#include "render_command_service.hpp" -#include "../../core/vertex.hpp" -#include -#include -#include - -namespace sdl3cpp::services::impl { - -RenderCommandService::RenderCommandService(std::shared_ptr deviceService, - std::shared_ptr swapchainService, - std::shared_ptr pipelineService, - std::shared_ptr bufferService, - std::shared_ptr guiRendererService, - std::shared_ptr configService, - std::shared_ptr logger) - : deviceService_(std::move(deviceService)), - swapchainService_(std::move(swapchainService)), - pipelineService_(std::move(pipelineService)), - bufferService_(std::move(bufferService)), - guiRendererService_(std::move(guiRendererService)), - configService_(std::move(configService)), - logger_(logger) { - if (logger_) { - logger_->Trace("RenderCommandService", "RenderCommandService", - "deviceService=" + std::string(deviceService_ ? "set" : "null") + - ", swapchainService=" + std::string(swapchainService_ ? "set" : "null") + - ", pipelineService=" + std::string(pipelineService_ ? "set" : "null") + - ", bufferService=" + std::string(bufferService_ ? "set" : "null") + - ", guiRendererService=" + std::string(guiRendererService_ ? "set" : "null")); - } - - if (!deviceService_ || !swapchainService_ || !pipelineService_ || !bufferService_) { - throw std::invalid_argument("All render command dependencies must be provided"); - } -} - -RenderCommandService::~RenderCommandService() { - if (logger_) { - logger_->Trace("RenderCommandService", "~RenderCommandService"); - } - if (commandPool_ != VK_NULL_HANDLE || imageAvailableSemaphore_ != VK_NULL_HANDLE) { - Shutdown(); - } -} - -void RenderCommandService::Cleanup() { - logger_->Trace("RenderCommandService", "Cleanup"); - CleanupCommandResources(); - CleanupSyncObjects(); - CleanupRenderGraphResources(); - CleanupDescriptorResources(); -} - -void RenderCommandService::Shutdown() noexcept { - logger_->Trace("RenderCommandService", "Shutdown"); - Cleanup(); -} - -bool RenderCommandService::BeginFrame(uint32_t& imageIndex) { - logger_->Trace("RenderCommandService", "BeginFrame", - "imageIndex=" + std::to_string(imageIndex)); - - // Lazy initialization - if (commandPool_ == VK_NULL_HANDLE) { - CreateCommandPool(); - CreateCommandBuffers(); - CreateSyncObjects(); - logger_->Info("RenderCommandService initialized lazily"); - } - - auto device = deviceService_->GetDevice(); - - // Wait for previous frame (with timeout) - constexpr uint64_t kFenceTimeout = 5000000000ULL; // 5 seconds - VkResult fenceResult = vkWaitForFences(device, 1, &inFlightFence_, VK_TRUE, kFenceTimeout); - - if (fenceResult == VK_TIMEOUT) { - logger_->Error("Fence wait timeout: GPU appears to be hung"); - throw std::runtime_error("Fence wait timeout: GPU appears to be hung"); - } else if (fenceResult != VK_SUCCESS) { - logger_->Error("Fence wait failed"); - throw std::runtime_error("Fence wait failed"); - } - - vkResetFences(device, 1, &inFlightFence_); - - // Acquire next image - VkResult result = swapchainService_->AcquireNextImage(imageAvailableSemaphore_, imageIndex); - - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { - return false; // Need swapchain recreation - } else if (result == VK_TIMEOUT) { - logger_->Error("Image acquisition timeout"); - throw std::runtime_error("Image acquisition timeout: GPU appears to be hung"); - } else if (result != VK_SUCCESS) { - throw std::runtime_error("Failed to acquire swap chain image"); - } - - return true; -} - -void RenderCommandService::RecordCommands(uint32_t imageIndex, - const std::vector& commands, - const std::array& viewProj) { - logger_->Trace("RenderCommandService", "RecordCommands", - "imageIndex=" + std::to_string(imageIndex) + - ", commands.size=" + std::to_string(commands.size()) + - ", viewProj.size=" + std::to_string(viewProj.size())); - - VkCommandBuffer commandBuffer = commandBuffers_[imageIndex]; - - // Reset command buffer - vkResetCommandBuffer(commandBuffer, 0); - - // Begin command buffer - VkCommandBufferBeginInfo beginInfo{}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffer, &beginInfo); - EnsureDummyImageLayout(commandBuffer); - - EnsureDescriptorResources(); - EnsureDummyImageLayout(commandBuffer); - - // Begin render pass - auto framebuffers = swapchainService_->GetSwapchainFramebuffers(); - auto extent = swapchainService_->GetSwapchainExtent(); - auto renderPass = swapchainService_->GetRenderPass(); - - VkRenderPassBeginInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - renderPassInfo.renderPass = renderPass; - renderPassInfo.framebuffer = framebuffers[imageIndex]; - renderPassInfo.renderArea.offset = {0, 0}; - renderPassInfo.renderArea.extent = extent; - - const auto& config = configService_->GetConfig(); - std::array clearValues{}; - const auto& skyColor = config.atmospherics.skyColor; - clearValues[0].color = {{skyColor[0], skyColor[1], skyColor[2], 1.0f}}; // Skybox clear - clearValues[1].depthStencil = {1.0f, 0}; // Depth clear - - if (logger_) { - logger_->Trace("RenderCommandService", "RecordCommands", - "clearColor=" + std::to_string(skyColor[0]) + "," + - std::to_string(skyColor[1]) + "," + - std::to_string(skyColor[2])); - } - - renderPassInfo.clearValueCount = static_cast(clearValues.size()); - renderPassInfo.pClearValues = clearValues.data(); - - vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - VkViewport viewport{}; - viewport.x = 0.0f; - viewport.y = 0.0f; - viewport.width = static_cast(extent.width); - viewport.height = static_cast(extent.height); - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - - VkRect2D scissor{}; - scissor.offset = {0, 0}; - scissor.extent = extent; - vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - - if (commands.empty()) { - logger_->Trace("RenderCommandService", "RecordCommands", - "No render commands to draw; skipping buffer bind"); - } else { - VkBuffer vertexBuffer = bufferService_->GetVertexBuffer(); - VkBuffer indexBuffer = bufferService_->GetIndexBuffer(); - if (vertexBuffer == VK_NULL_HANDLE || indexBuffer == VK_NULL_HANDLE) { - logger_->Error("RenderCommandService: Vertex or index buffer not initialized"); - } else { - VkDeviceSize offsets[] = {0}; - vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer, offsets); - vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); - - VkPipelineLayout pipelineLayout = pipelineService_->GetPipelineLayout(); - if (pipelineLayout == VK_NULL_HANDLE) { - logger_->Error("RenderCommandService: Pipeline layout is not initialized"); - } else { - if (defaultDescriptorSet_ == VK_NULL_HANDLE) { - logger_->Error("RenderCommandService: Default descriptor set not available"); - vkCmdEndRenderPass(commandBuffer); - vkEndCommandBuffer(commandBuffer); - return; - } - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - pipelineLayout, 0, 1, &defaultDescriptorSet_, 0, nullptr); - if (logger_) { - logger_->Trace("RenderCommandService", "RecordCommands", - "drawing commands=" + std::to_string(commands.size())); - } - size_t drawIndex = 0; - for (const auto& command : commands) { - if (!pipelineService_->HasShader(command.shaderKey)) { - logger_->Error("RenderCommandService: Missing pipeline for shader key: " + command.shaderKey); - continue; - } - - if (logger_) { - logger_->Trace("RenderCommandService", "RecordCommands", - "draw=" + std::to_string(drawIndex) + - ", shaderKey=" + command.shaderKey + - ", indexOffset=" + std::to_string(command.indexOffset) + - ", indexCount=" + std::to_string(command.indexCount) + - ", vertexOffset=" + std::to_string(command.vertexOffset)); - } - - VkPipeline pipeline = pipelineService_->GetPipeline(command.shaderKey); - vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - - core::PushConstants pushConstants{}; - pushConstants.model = command.modelMatrix; - pushConstants.viewProj = viewProj; - - // For PBR shaders, populate extended push constants - if (command.shaderKey.find("pbr") != std::string::npos) { - // For now, use identity for view and proj (since viewProj is already combined) - // In a full implementation, we'd need separate view/proj matrices - pushConstants.view = {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}; - pushConstants.proj = pushConstants.view; // Identity for now - pushConstants.lightViewProj = pushConstants.view; // Identity for now - - // Camera position (0,0,0) for now - would need to be passed from scene - pushConstants.cameraPos = {0.0f, 0.0f, 0.0f}; - pushConstants.time = 0.0f; // Would need actual time - - // Atmospherics - pushConstants.ambientStrength = config.atmospherics.ambientStrength; - pushConstants.fogDensity = config.atmospherics.fogDensity; - pushConstants.fogStart = 0.0f; - pushConstants.fogEnd = 100.0f; - pushConstants.fogColor = config.atmospherics.fogColor; - pushConstants.gamma = config.atmospherics.gamma; - pushConstants.exposure = config.atmospherics.exposure; - pushConstants.enableShadows = config.atmospherics.enableShadows ? 1 : 0; - pushConstants.enableFog = 1; // Enable fog for PBR - } - - vkCmdPushConstants(commandBuffer, pipelineLayout, - VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(core::PushConstants), &pushConstants); - - vkCmdDrawIndexed(commandBuffer, command.indexCount, 1, command.indexOffset, command.vertexOffset, 0); - ++drawIndex; - } - } - } - } - - // Render GUI overlay BEFORE ending render pass so we can use alpha blending - if (guiRendererService_) { - bool guiReady = guiRendererService_->IsReady(); - const auto& images = swapchainService_->GetSwapchainImages(); - if (!guiReady) { - if (logger_) { - logger_->Trace("RenderCommandService", "RecordCommands", - "GUI overlay skipped: renderer not ready"); - } - } else if (imageIndex >= images.size() || images[imageIndex] == VK_NULL_HANDLE) { - if (logger_) { - logger_->Error("RenderCommandService: GUI overlay skipped due to invalid swapchain image"); - } - } else { - if (logger_) { - logger_->Trace("RenderCommandService", "RecordCommands", - "Rendering GUI overlay imageIndex=" + std::to_string(imageIndex)); - } - guiRendererService_->RenderToSwapchain(commandBuffer, images[imageIndex]); - } - } else if (logger_) { - logger_->Trace("RenderCommandService", "RecordCommands", - "GUI overlay skipped: renderer service not available"); - } - - vkCmdEndRenderPass(commandBuffer); - vkEndCommandBuffer(commandBuffer); -} - -void RenderCommandService::RecordRenderGraph(uint32_t imageIndex, - const RenderGraphDefinition& graph, - const std::vector& commands, - const std::array& viewProj) { - logger_->Trace("RenderCommandService", "RecordRenderGraph", - "imageIndex=" + std::to_string(imageIndex) + - ", commands.size=" + std::to_string(commands.size()) + - ", resources=" + std::to_string(graph.resources.size()) + - ", passes=" + std::to_string(graph.passes.size())); - - EnsureRenderGraphResources(graph); - EnsureDescriptorResources(); - - VkCommandBuffer commandBuffer = commandBuffers_[imageIndex]; - vkResetCommandBuffer(commandBuffer, 0); - - VkCommandBufferBeginInfo beginInfo{}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffer, &beginInfo); - EnsureDummyImageLayout(commandBuffer); - - VkPipelineLayout pipelineLayout = pipelineService_->GetPipelineLayout(); - if (pipelineLayout == VK_NULL_HANDLE) { - logger_->Error("RenderCommandService: Pipeline layout is not initialized"); - vkEndCommandBuffer(commandBuffer); - return; - } - - VkBuffer vertexBuffer = bufferService_->GetVertexBuffer(); - VkBuffer indexBuffer = bufferService_->GetIndexBuffer(); - bool sceneBuffersReady = vertexBuffer != VK_NULL_HANDLE && indexBuffer != VK_NULL_HANDLE; - if (sceneBuffersReady) { - VkDeviceSize offsets[] = {0}; - vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer, offsets); - vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); - } else { - logger_->Warn("RenderCommandService: Vertex or index buffer not initialized for render graph"); - } - - const auto& config = configService_->GetConfig(); - const auto& skyColor = config.atmospherics.skyColor; - - std::array sceneClear{}; - sceneClear[0].color = {{skyColor[0], skyColor[1], skyColor[2], 1.0f}}; - sceneClear[1].depthStencil = {1.0f, 0}; - - std::array postClear{}; - postClear[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}}; - postClear[1].depthStencil = {1.0f, 0}; - - auto framebuffers = swapchainService_->GetSwapchainFramebuffers(); - auto swapchainRenderPass = swapchainService_->GetRenderPass(); - auto extent = swapchainService_->GetSwapchainExtent(); - - bool swapchainUsed = false; - - for (const auto& pass : graph.passes) { - if (!IsPassEnabled(pass)) { - if (logger_) { - logger_->Trace("RenderCommandService", "RecordRenderGraph", - "Skipping disabled pass=" + pass.name); - } - continue; - } - bool scenePass = IsScenePass(pass); - std::string shaderKey = pass.shader; - if (shaderKey.empty() && !scenePass) { - shaderKey = pass.kind; - } - if (shaderKey.empty() && !scenePass) { - if (logger_ && warnedPassesWithoutShader_.insert(pass.name).second) { - logger_->Warn("RenderCommandService: Render graph pass '" + pass.name + "' has no shader"); - } - continue; - } - - std::string outputName = ResolvePassOutput(pass); - if (logger_) { - logger_->Trace("RenderCommandService", "RecordRenderGraph", - "pass=" + pass.name + - ", kind=" + pass.kind + - ", scenePass=" + std::string(scenePass ? "true" : "false") + - ", shaderKey=" + (shaderKey.empty() ? "" : shaderKey) + - ", output=" + (outputName.empty() ? "" : outputName)); - } - if (outputName.empty()) { - if (logger_) { - logger_->Warn("RenderCommandService: Render graph pass '" + pass.name + "' has no output"); - } - continue; - } - - bool outputSwapchain = outputName == "swapchain"; - VkRenderPass renderPass = outputSwapchain ? swapchainRenderPass : offscreenRenderPass_; - VkFramebuffer framebuffer = VK_NULL_HANDLE; - VkExtent2D targetExtent = extent; - RenderGraphImage* target = nullptr; - - if (!outputSwapchain && renderPass == VK_NULL_HANDLE) { - if (logger_) { - logger_->Warn("RenderCommandService: Offscreen render pass is not initialized"); - } - continue; - } - - if (outputSwapchain) { - if (imageIndex >= framebuffers.size()) { - logger_->Error("RenderCommandService: Swapchain framebuffer index out of range"); - continue; - } - framebuffer = framebuffers[imageIndex]; - targetExtent = extent; - swapchainUsed = true; - } else { - target = FindRenderTarget(outputName); - if (!target || target->framebuffer == VK_NULL_HANDLE) { - if (logger_ && warnedMissingTargets_.insert("output:" + outputName).second) { - logger_->Warn("RenderCommandService: Render target not found for '" + outputName + "'"); - } - continue; - } - framebuffer = target->framebuffer; - targetExtent = target->extent; - TransitionImageLayout(commandBuffer, *target, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_IMAGE_ASPECT_COLOR_BIT); - RenderGraphImage* depthTarget = FindDepthTarget(outputName); - if (depthTarget) { - TransitionImageLayout(commandBuffer, *depthTarget, - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - VK_IMAGE_ASPECT_DEPTH_BIT); - } else if (logger_ && warnedMissingTargets_.insert("depth:" + outputName).second) { - logger_->Warn("RenderCommandService: Depth target not found for '" + outputName + "'"); - } - } - - VkRenderPassBeginInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - renderPassInfo.renderPass = renderPass; - renderPassInfo.framebuffer = framebuffer; - renderPassInfo.renderArea.offset = {0, 0}; - renderPassInfo.renderArea.extent = targetExtent; - - const auto& clearValues = scenePass ? sceneClear : postClear; - renderPassInfo.clearValueCount = static_cast(clearValues.size()); - renderPassInfo.pClearValues = clearValues.data(); - - vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - - VkViewport viewport{}; - viewport.x = 0.0f; - viewport.y = 0.0f; - viewport.width = static_cast(targetExtent.width); - viewport.height = static_cast(targetExtent.height); - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - - VkRect2D scissor{}; - scissor.offset = {0, 0}; - scissor.extent = targetExtent; - vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - - if (!scenePass) { - if (!pipelineService_->HasShader(shaderKey)) { - if (logger_ && warnedMissingPipelines_.insert(shaderKey).second) { - logger_->Warn("RenderCommandService: Missing pipeline for shader key: " + shaderKey); - } - vkCmdEndRenderPass(commandBuffer); - continue; - } - - VkPipeline pipeline = pipelineService_->GetPipeline(shaderKey); - vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - } - - if (scenePass) { - if (defaultDescriptorSet_ == VK_NULL_HANDLE) { - if (logger_) { - logger_->Warn("RenderCommandService: Default descriptor set not available"); - } - vkCmdEndRenderPass(commandBuffer); - continue; - } - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - pipelineLayout, 0, 1, &defaultDescriptorSet_, 0, nullptr); - - if (!sceneBuffersReady) { - if (logger_) { - logger_->Warn("RenderCommandService: Skipping scene pass due to missing buffers"); - } - } else if (commands.empty()) { - if (logger_) { - logger_->Trace("RenderCommandService", "RecordRenderGraph", - "No scene commands for pass=" + pass.name); - } - } else { - size_t drawIndex = 0; - for (const auto& command : commands) { - if (!pipelineService_->HasShader(command.shaderKey)) { - logger_->Error("RenderCommandService: Missing pipeline for shader key: " + command.shaderKey); - continue; - } - - if (logger_) { - logger_->Trace("RenderCommandService", "RecordRenderGraph", - "pass=" + pass.name + - ", draw=" + std::to_string(drawIndex) + - ", shaderKey=" + command.shaderKey); - } - - VkPipeline scenePipeline = pipelineService_->GetPipeline(command.shaderKey); - vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, scenePipeline); - - core::PushConstants pushConstants{}; - pushConstants.model = command.modelMatrix; - pushConstants.viewProj = viewProj; - - if (command.shaderKey.find("pbr") != std::string::npos) { - pushConstants.view = {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}; - pushConstants.proj = pushConstants.view; - pushConstants.lightViewProj = pushConstants.view; - pushConstants.cameraPos = {0.0f, 0.0f, 0.0f}; - pushConstants.time = 0.0f; - pushConstants.ambientStrength = config.atmospherics.ambientStrength; - pushConstants.fogDensity = config.atmospherics.fogDensity; - pushConstants.fogStart = 0.0f; - pushConstants.fogEnd = 100.0f; - pushConstants.fogColor = config.atmospherics.fogColor; - pushConstants.gamma = config.atmospherics.gamma; - pushConstants.exposure = config.atmospherics.exposure; - pushConstants.enableShadows = config.atmospherics.enableShadows ? 1 : 0; - pushConstants.enableFog = 1; - } - - vkCmdPushConstants(commandBuffer, pipelineLayout, - VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(core::PushConstants), &pushConstants); - vkCmdDrawIndexed(commandBuffer, command.indexCount, 1, - command.indexOffset, command.vertexOffset, 0); - ++drawIndex; - } - } - } else { - std::string inputName = ResolvePassInput(pass); - if (logger_) { - logger_->Trace("RenderCommandService", "RecordRenderGraph", - "pass=" + pass.name + - ", input=" + (inputName.empty() ? "" : inputName)); - } - if (inputName == "swapchain" && outputSwapchain) { - if (logger_) { - logger_->Trace("RenderCommandService", "RecordRenderGraph", - "Skipping feedback pass=" + pass.name); - } - vkCmdEndRenderPass(commandBuffer); - continue; - } - - bool hasInput = true; - VkImageView inputView = VK_NULL_HANDLE; - if (inputName.empty()) { - hasInput = false; - if (logger_ && warnedMissingTargets_.insert("input:" + pass.name).second) { - logger_->Warn("RenderCommandService: Pass '" + pass.name + "' missing input"); - } - } else if (inputName == "swapchain") { - hasInput = false; - if (logger_ && warnedMissingTargets_.insert("input:" + pass.name + ":swapchain").second) { - logger_->Warn("RenderCommandService: Pass '" + pass.name + - "' cannot sample from swapchain output"); - } - } else { - RenderGraphImage* inputTarget = FindRenderTarget(inputName); - if (inputTarget) { - TransitionImageLayout(commandBuffer, *inputTarget, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - VK_IMAGE_ASPECT_COLOR_BIT); - inputView = inputTarget->view; - } else { - hasInput = false; - if (logger_ && warnedMissingTargets_.insert("input:" + inputName).second) { - logger_->Warn("RenderCommandService: Input target not found for '" + inputName + "'"); - } - } - } - - if (!hasInput || inputView == VK_NULL_HANDLE) { - vkCmdEndRenderPass(commandBuffer); - continue; - } - - if (postProcessDescriptorSet_ == VK_NULL_HANDLE) { - if (logger_) { - logger_->Warn("RenderCommandService: Post-process descriptor set not available"); - } - vkCmdEndRenderPass(commandBuffer); - continue; - } - - UpdateDescriptorSet(postProcessDescriptorSet_, inputView); - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - pipelineLayout, 0, 1, &postProcessDescriptorSet_, 0, nullptr); - - core::PushConstants pushConstants = BuildFullscreenConstants(pass); - vkCmdPushConstants(commandBuffer, pipelineLayout, - VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(core::PushConstants), &pushConstants); - vkCmdDraw(commandBuffer, 3, 1, 0, 0); - } - - if (outputSwapchain && guiRendererService_) { - const auto& images = swapchainService_->GetSwapchainImages(); - if (imageIndex < images.size() && images[imageIndex] != VK_NULL_HANDLE) { - guiRendererService_->RenderToSwapchain(commandBuffer, images[imageIndex]); - } - } - - vkCmdEndRenderPass(commandBuffer); - - if (!outputSwapchain && target) { - TransitionImageLayout(commandBuffer, *target, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - VK_IMAGE_ASPECT_COLOR_BIT); - } - } - - if (!swapchainUsed && logger_) { - logger_->Warn("RenderCommandService: Render graph did not output to swapchain"); - } - - vkEndCommandBuffer(commandBuffer); -} - -bool RenderCommandService::EndFrame(uint32_t imageIndex) { - logger_->Trace("RenderCommandService", "EndFrame", - "imageIndex=" + std::to_string(imageIndex)); - - auto device = deviceService_->GetDevice(); - auto graphicsQueue = deviceService_->GetGraphicsQueue(); - - VkCommandBuffer commandBuffer = commandBuffers_[imageIndex]; - - // Submit command buffer - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore_}; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore_}; - VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; - - VkSubmitInfo submitInfo{}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pWaitSemaphores = waitSemaphores; - submitInfo.pWaitDstStageMask = waitStages; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffer; - submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = signalSemaphores; - - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence_) != VK_SUCCESS) { - throw std::runtime_error("Failed to submit draw command buffer"); - } - - // Present - std::vector presentWaitSemaphores = {renderFinishedSemaphore_}; - VkResult result = swapchainService_->Present(presentWaitSemaphores, imageIndex); - - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { - return false; // Need swapchain recreation - } else if (result != VK_SUCCESS) { - throw std::runtime_error("Failed to present swap chain image"); - } - - return true; -} - -VkCommandBuffer RenderCommandService::GetCurrentCommandBuffer() const { - logger_->Trace("RenderCommandService", "GetCurrentCommandBuffer"); - if (commandBuffers_.empty()) { - return VK_NULL_HANDLE; - } - return commandBuffers_[currentFrame_]; -} - -void RenderCommandService::OnSwapchainRecreated() { - logger_->Trace("RenderCommandService", "OnSwapchainRecreated"); - - Cleanup(); - currentFrame_ = 0; - - if (guiRendererService_) { - VkExtent2D extent = swapchainService_->GetSwapchainExtent(); - VkFormat format = swapchainService_->GetSwapchainImageFormat(); - VkRenderPass renderPass = swapchainService_->GetRenderPass(); - guiRendererService_->Resize(extent.width, extent.height, format, renderPass); - } -} - -void RenderCommandService::CreateCommandPool() { - logger_->Trace("RenderCommandService", "CreateCommandPool"); - - auto device = deviceService_->GetDevice(); - auto queueFamilies = deviceService_->GetQueueFamilies(); - - VkCommandPoolCreateInfo poolInfo{}; - poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilies.graphicsFamily; - poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - - if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create command pool"); - } -} - -void RenderCommandService::CreateCommandBuffers() { - logger_->Trace("RenderCommandService", "CreateCommandBuffers"); - - auto device = deviceService_->GetDevice(); - auto framebuffers = swapchainService_->GetSwapchainFramebuffers(); - - commandBuffers_.resize(framebuffers.size()); - - VkCommandBufferAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.commandPool = commandPool_; - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandBufferCount = static_cast(commandBuffers_.size()); - - if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers_.data()) != VK_SUCCESS) { - throw std::runtime_error("Failed to allocate command buffers"); - } - - logger_->Debug("Created " + std::to_string(commandBuffers_.size()) + " command buffers"); -} - -void RenderCommandService::CreateSyncObjects() { - logger_->Trace("RenderCommandService", "CreateSyncObjects"); - - auto device = deviceService_->GetDevice(); - - VkSemaphoreCreateInfo semaphoreInfo{}; - semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - - VkFenceCreateInfo fenceInfo{}; - fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; // Start signaled - - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore_) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore_) != VK_SUCCESS || - vkCreateFence(device, &fenceInfo, nullptr, &inFlightFence_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create synchronization objects"); - } - - logger_->Debug("Created synchronization objects"); -} - -void RenderCommandService::CleanupCommandResources() { - logger_->Trace("RenderCommandService", "CleanupCommandResources"); - - auto device = deviceService_->GetDevice(); - - if (!commandBuffers_.empty() && commandPool_ != VK_NULL_HANDLE) { - vkFreeCommandBuffers(device, commandPool_, - static_cast(commandBuffers_.size()), - commandBuffers_.data()); - commandBuffers_.clear(); - } - - if (commandPool_ != VK_NULL_HANDLE) { - vkDestroyCommandPool(device, commandPool_, nullptr); - commandPool_ = VK_NULL_HANDLE; - } -} - -void RenderCommandService::CleanupSyncObjects() { - logger_->Trace("RenderCommandService", "CleanupSyncObjects"); - - auto device = deviceService_->GetDevice(); - - if (imageAvailableSemaphore_ != VK_NULL_HANDLE) { - vkDestroySemaphore(device, imageAvailableSemaphore_, nullptr); - imageAvailableSemaphore_ = VK_NULL_HANDLE; - } - - if (renderFinishedSemaphore_ != VK_NULL_HANDLE) { - vkDestroySemaphore(device, renderFinishedSemaphore_, nullptr); - renderFinishedSemaphore_ = VK_NULL_HANDLE; - } - - if (inFlightFence_ != VK_NULL_HANDLE) { - vkDestroyFence(device, inFlightFence_, nullptr); - inFlightFence_ = VK_NULL_HANDLE; - } -} - -void RenderCommandService::EnsureDescriptorResources() { - if (descriptorPool_ != VK_NULL_HANDLE) { - return; - } - if (logger_) { - logger_->Trace("RenderCommandService", "EnsureDescriptorResources"); - } - CreateDescriptorPool(); - CreateSampler(); - CreateDummyImage(); - AllocateDescriptorSets(); - UpdateDescriptorSet(defaultDescriptorSet_, dummyImageView_); - UpdateDescriptorSet(postProcessDescriptorSet_, dummyImageView_); -} - -void RenderCommandService::CleanupDescriptorResources() { - logger_->Trace("RenderCommandService", "CleanupDescriptorResources"); - - auto device = deviceService_->GetDevice(); - - if (dummyImageView_ != VK_NULL_HANDLE) { - vkDestroyImageView(device, dummyImageView_, nullptr); - dummyImageView_ = VK_NULL_HANDLE; - } - if (dummyImage_ != VK_NULL_HANDLE) { - vkDestroyImage(device, dummyImage_, nullptr); - dummyImage_ = VK_NULL_HANDLE; - } - if (dummyImageMemory_ != VK_NULL_HANDLE) { - vkFreeMemory(device, dummyImageMemory_, nullptr); - dummyImageMemory_ = VK_NULL_HANDLE; - } - if (sampler_ != VK_NULL_HANDLE) { - vkDestroySampler(device, sampler_, nullptr); - sampler_ = VK_NULL_HANDLE; - } - if (descriptorPool_ != VK_NULL_HANDLE) { - vkDestroyDescriptorPool(device, descriptorPool_, nullptr); - descriptorPool_ = VK_NULL_HANDLE; - } - - defaultDescriptorSet_ = VK_NULL_HANDLE; - postProcessDescriptorSet_ = VK_NULL_HANDLE; - dummyImageLayout_ = VK_IMAGE_LAYOUT_UNDEFINED; -} - -void RenderCommandService::CreateDescriptorPool() { - logger_->Trace("RenderCommandService", "CreateDescriptorPool"); - - auto device = deviceService_->GetDevice(); - - VkDescriptorPoolSize poolSize{}; - poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - poolSize.descriptorCount = 8; - - VkDescriptorPoolCreateInfo poolInfo{}; - poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - poolInfo.poolSizeCount = 1; - poolInfo.pPoolSizes = &poolSize; - poolInfo.maxSets = 4; - - if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create descriptor pool"); - } -} - -void RenderCommandService::CreateSampler() { - logger_->Trace("RenderCommandService", "CreateSampler"); - - auto device = deviceService_->GetDevice(); - - VkSamplerCreateInfo samplerInfo{}; - samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - samplerInfo.magFilter = VK_FILTER_LINEAR; - samplerInfo.minFilter = VK_FILTER_LINEAR; - samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - samplerInfo.maxLod = 1.0f; - - if (vkCreateSampler(device, &samplerInfo, nullptr, &sampler_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create sampler"); - } -} - -void RenderCommandService::CreateDummyImage() { - logger_->Trace("RenderCommandService", "CreateDummyImage"); - - auto device = deviceService_->GetDevice(); - - VkImageCreateInfo imageInfo{}; - imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.extent.width = 1; - imageInfo.extent.height = 1; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - - if (vkCreateImage(device, &imageInfo, nullptr, &dummyImage_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create dummy image"); - } - - VkMemoryRequirements memRequirements; - vkGetImageMemoryRequirements(device, dummyImage_, &memRequirements); - - VkMemoryAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = deviceService_->FindMemoryType( - memRequirements.memoryTypeBits, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - - if (vkAllocateMemory(device, &allocInfo, nullptr, &dummyImageMemory_) != VK_SUCCESS) { - throw std::runtime_error("Failed to allocate dummy image memory"); - } - - vkBindImageMemory(device, dummyImage_, dummyImageMemory_, 0); - - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = dummyImage_; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - if (vkCreateImageView(device, &viewInfo, nullptr, &dummyImageView_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create dummy image view"); - } - - dummyImageLayout_ = VK_IMAGE_LAYOUT_UNDEFINED; -} - -void RenderCommandService::AllocateDescriptorSets() { - logger_->Trace("RenderCommandService", "AllocateDescriptorSets"); - - VkDescriptorSetLayout layout = pipelineService_->GetDescriptorSetLayout(); - if (layout == VK_NULL_HANDLE) { - logger_->Error("RenderCommandService: Descriptor set layout is not initialized"); - return; - } - - std::array layouts = {layout, layout}; - VkDescriptorSetAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; - allocInfo.descriptorPool = descriptorPool_; - allocInfo.descriptorSetCount = static_cast(layouts.size()); - allocInfo.pSetLayouts = layouts.data(); - - std::array sets{}; - if (vkAllocateDescriptorSets(deviceService_->GetDevice(), &allocInfo, sets.data()) != VK_SUCCESS) { - throw std::runtime_error("Failed to allocate descriptor sets"); - } - - defaultDescriptorSet_ = sets[0]; - postProcessDescriptorSet_ = sets[1]; -} - -void RenderCommandService::UpdateDescriptorSet(VkDescriptorSet set, VkImageView view) { - if (set == VK_NULL_HANDLE || view == VK_NULL_HANDLE || sampler_ == VK_NULL_HANDLE) { - return; - } - - VkDescriptorImageInfo imageInfo{}; - imageInfo.sampler = sampler_; - imageInfo.imageView = view; - imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - - VkWriteDescriptorSet descriptorWrite{}; - descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrite.dstSet = set; - descriptorWrite.dstBinding = 0; - descriptorWrite.dstArrayElement = 0; - descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - descriptorWrite.descriptorCount = 1; - descriptorWrite.pImageInfo = &imageInfo; - - vkUpdateDescriptorSets(deviceService_->GetDevice(), 1, &descriptorWrite, 0, nullptr); -} - -void RenderCommandService::EnsureDummyImageLayout(VkCommandBuffer commandBuffer) { - if (dummyImage_ == VK_NULL_HANDLE || dummyImageLayout_ == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { - return; - } - - VkImageMemoryBarrier barrier{}; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.oldLayout = dummyImageLayout_; - barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = dummyImage_; - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - - vkCmdPipelineBarrier(commandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, 0, nullptr, 0, nullptr, 1, &barrier); - - dummyImageLayout_ = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; -} - -void RenderCommandService::EnsureRenderGraphResources(const RenderGraphDefinition& graph) { - if (graph.resources.empty() || graph.passes.empty()) { - return; - } - - VkExtent2D extent = swapchainService_->GetSwapchainExtent(); - if (renderGraphResourceCount_ == graph.resources.size() && - renderGraphPassCount_ == graph.passes.size() && - renderGraphExtent_.width == extent.width && - renderGraphExtent_.height == extent.height) { - return; - } - - CleanupRenderGraphResources(); - RegisterRenderGraphShaders(graph); - - renderGraphResourceCount_ = graph.resources.size(); - renderGraphPassCount_ = graph.passes.size(); - renderGraphExtent_ = extent; - - VkFormat colorFormat = swapchainService_->GetSwapchainImageFormat(); - VkFormat depthFormat = swapchainService_->GetDepthFormat(); - - VkAttachmentDescription colorAttachment{}; - colorAttachment.format = colorFormat; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkAttachmentDescription depthAttachment{}; - depthAttachment.format = depthFormat; - depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkAttachmentReference colorRef{}; - colorRef.attachment = 0; - colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkAttachmentReference depthRef{}; - depthRef.attachment = 1; - depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorRef; - subpass.pDepthStencilAttachment = &depthRef; - - std::array attachments = {colorAttachment, depthAttachment}; - VkRenderPassCreateInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = static_cast(attachments.size()); - renderPassInfo.pAttachments = attachments.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - - if (vkCreateRenderPass(deviceService_->GetDevice(), &renderPassInfo, nullptr, &offscreenRenderPass_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create offscreen render pass"); - } - - for (const auto& resource : graph.resources) { - if (resource.name == "swapchain") { - continue; - } - - VkExtent2D targetExtent = ResolveExtent(resource); - if ((resource.layers > 1 || resource.mips > 1) && logger_) { - logger_->Warn("RenderCommandService: Resource '" + resource.name + - "' uses layers/mips; allocating a single-layer image"); - } - VkFormat targetFormat = ResolveColorFormat(resource.format); - if (targetFormat != colorFormat && logger_) { - logger_->Trace("RenderCommandService", "EnsureRenderGraphResources", - "Resource '" + resource.name + "' format overridden to swapchain format"); - } - if (logger_) { - logger_->Trace("RenderCommandService", "EnsureRenderGraphResources", - "Creating resource '" + resource.name + "' type=" + resource.type + - ", extent=" + std::to_string(targetExtent.width) + "x" + - std::to_string(targetExtent.height)); - } - - RenderGraphImage color{}; - CreateRenderGraphImage(color, colorFormat, targetExtent, - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - RenderGraphImage depth{}; - CreateRenderGraphImage(depth, depthFormat, targetExtent, - VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, - VK_IMAGE_ASPECT_DEPTH_BIT); - - std::array framebufferAttachments = {color.view, depth.view}; - VkFramebufferCreateInfo framebufferInfo{}; - framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferInfo.renderPass = offscreenRenderPass_; - framebufferInfo.attachmentCount = static_cast(framebufferAttachments.size()); - framebufferInfo.pAttachments = framebufferAttachments.data(); - framebufferInfo.width = targetExtent.width; - framebufferInfo.height = targetExtent.height; - framebufferInfo.layers = 1; - - if (vkCreateFramebuffer(deviceService_->GetDevice(), &framebufferInfo, nullptr, &color.framebuffer) != VK_SUCCESS) { - throw std::runtime_error("Failed to create render graph framebuffer for " + resource.name); - } - - renderGraphTargets_.emplace(resource.name, color); - renderGraphDepth_.emplace(resource.name, depth); - } -} - -void RenderCommandService::CleanupRenderGraphResources() { - logger_->Trace("RenderCommandService", "CleanupRenderGraphResources"); - - auto device = deviceService_->GetDevice(); - - for (auto& [name, image] : renderGraphTargets_) { - if (image.framebuffer != VK_NULL_HANDLE) { - vkDestroyFramebuffer(device, image.framebuffer, nullptr); - image.framebuffer = VK_NULL_HANDLE; - } - if (image.view != VK_NULL_HANDLE) { - vkDestroyImageView(device, image.view, nullptr); - image.view = VK_NULL_HANDLE; - } - if (image.image != VK_NULL_HANDLE) { - vkDestroyImage(device, image.image, nullptr); - image.image = VK_NULL_HANDLE; - } - if (image.memory != VK_NULL_HANDLE) { - vkFreeMemory(device, image.memory, nullptr); - image.memory = VK_NULL_HANDLE; - } - } - renderGraphTargets_.clear(); - - for (auto& [name, image] : renderGraphDepth_) { - if (image.view != VK_NULL_HANDLE) { - vkDestroyImageView(device, image.view, nullptr); - image.view = VK_NULL_HANDLE; - } - if (image.image != VK_NULL_HANDLE) { - vkDestroyImage(device, image.image, nullptr); - image.image = VK_NULL_HANDLE; - } - if (image.memory != VK_NULL_HANDLE) { - vkFreeMemory(device, image.memory, nullptr); - image.memory = VK_NULL_HANDLE; - } - } - renderGraphDepth_.clear(); - - if (offscreenRenderPass_ != VK_NULL_HANDLE) { - vkDestroyRenderPass(device, offscreenRenderPass_, nullptr); - offscreenRenderPass_ = VK_NULL_HANDLE; - } - - warnedPassesWithoutShader_.clear(); - warnedMissingPipelines_.clear(); - warnedMissingTargets_.clear(); - - renderGraphResourceCount_ = 0; - renderGraphPassCount_ = 0; - renderGraphExtent_ = {}; -} - -void RenderCommandService::RegisterRenderGraphShaders(const RenderGraphDefinition& graph) { - static const char* kFullscreenVertex = R"( -#version 450 - -layout(location = 0) out vec2 fragTexCoord; - -vec2 positions[3] = vec2[]( - vec2(-1.0, -1.0), - vec2(3.0, -1.0), - vec2(-1.0, 3.0) -); - -vec2 uvs[3] = vec2[]( - vec2(0.0, 0.0), - vec2(2.0, 0.0), - vec2(0.0, 2.0) -); - -void main() { - gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); - fragTexCoord = uvs[gl_VertexIndex]; -} -)"; - - static const char* kPostProcessFragment = R"( -#version 450 - -layout(location = 0) in vec2 fragTexCoord; -layout(location = 0) out vec4 outColor; - -layout(set = 0, binding = 0) uniform sampler2D inputTexture; - -layout(push_constant) uniform PushConstants { - mat4 model; - mat4 viewProj; - mat4 view; - mat4 proj; - mat4 lightViewProj; - vec3 cameraPos; - float time; - float ambientStrength; - float fogDensity; - float fogStart; - float fogEnd; - vec3 fogColor; - float gamma; - float exposure; - int enableShadows; - int enableFog; -} pc; - -void main() { - vec3 color = texture(inputTexture, fragTexCoord).rgb; - outColor = vec4(color, 1.0); -} -)"; - - static const char* kTonemapFragment = R"( -#version 450 - -layout(location = 0) in vec2 fragTexCoord; -layout(location = 0) out vec4 outColor; - -layout(set = 0, binding = 0) uniform sampler2D inputTexture; - -layout(push_constant) uniform PushConstants { - mat4 model; - mat4 viewProj; - mat4 view; - mat4 proj; - mat4 lightViewProj; - vec3 cameraPos; - float time; - float ambientStrength; - float fogDensity; - float fogStart; - float fogEnd; - vec3 fogColor; - float gamma; - float exposure; - int enableShadows; - int enableFog; -} pc; - -void main() { - vec3 color = texture(inputTexture, fragTexCoord).rgb; - color *= pc.exposure; - color = color / (color + vec3(1.0)); - color = pow(color, vec3(1.0 / max(pc.gamma, 0.01))); - outColor = vec4(color, 1.0); -} -)"; - - bool registered = false; - for (const auto& pass : graph.passes) { - if (IsScenePass(pass)) { - continue; - } - - std::string shaderKey = pass.shader; - if (shaderKey.empty()) { - shaderKey = pass.kind; - } - if (shaderKey.empty()) { - continue; - } - if (pipelineService_->HasShader(shaderKey)) { - continue; - } - - ShaderPaths paths{}; - paths.vertexSource = kFullscreenVertex; - if (shaderKey == "tonemap" || shaderKey == "tonemap_fallback") { - paths.fragmentSource = kTonemapFragment; - } else { - paths.fragmentSource = kPostProcessFragment; - } - paths.disableCulling = true; - paths.disableDepthTest = true; - if (logger_) { - logger_->Trace("RenderCommandService", "RegisterRenderGraphShaders", - "Registering fallback shader for pass=" + pass.name + - ", key=" + shaderKey + - ", cull=none, depthTest=off"); - } - pipelineService_->RegisterShader(shaderKey, paths); - registered = true; - } - - if (registered) { - VkRenderPass renderPass = swapchainService_->GetRenderPass(); - VkExtent2D extent = swapchainService_->GetSwapchainExtent(); - pipelineService_->RecreatePipelines(renderPass, extent); - CleanupDescriptorResources(); - } -} - -VkFormat RenderCommandService::ResolveColorFormat(const std::string& format) const { - (void)format; - return swapchainService_->GetSwapchainImageFormat(); -} - -VkFormat RenderCommandService::ResolveDepthFormat(const std::string& format) const { - (void)format; - return swapchainService_->GetDepthFormat(); -} - -VkExtent2D RenderCommandService::ResolveExtent(const RenderGraphResource& resource) const { - VkExtent2D swapchainExtent = swapchainService_->GetSwapchainExtent(); - if (resource.hasExplicitSize) { - return {std::max(1u, resource.explicitSize[0]), - std::max(1u, resource.explicitSize[1])}; - } - if (resource.size == "half") { - return {std::max(1u, swapchainExtent.width / 2), - std::max(1u, swapchainExtent.height / 2)}; - } - return swapchainExtent; -} - -RenderCommandService::RenderGraphImage* RenderCommandService::FindRenderTarget(const std::string& name) { - auto it = renderGraphTargets_.find(name); - if (it == renderGraphTargets_.end()) { - return nullptr; - } - return &it->second; -} - -RenderCommandService::RenderGraphImage* RenderCommandService::FindDepthTarget(const std::string& name) { - auto it = renderGraphDepth_.find(name); - if (it == renderGraphDepth_.end()) { - return nullptr; - } - return &it->second; -} - -void RenderCommandService::CreateRenderGraphImage(RenderCommandService::RenderGraphImage& image, VkFormat format, - VkExtent2D extent, VkImageUsageFlags usage, - VkImageAspectFlags aspectMask) { - auto device = deviceService_->GetDevice(); - - VkImageCreateInfo imageInfo{}; - imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.extent.width = extent.width; - imageInfo.extent.height = extent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imageInfo.usage = usage; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - - if (vkCreateImage(device, &imageInfo, nullptr, &image.image) != VK_SUCCESS) { - throw std::runtime_error("Failed to create render graph image"); - } - - VkMemoryRequirements memRequirements; - vkGetImageMemoryRequirements(device, image.image, &memRequirements); - - VkMemoryAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = deviceService_->FindMemoryType( - memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - - if (vkAllocateMemory(device, &allocInfo, nullptr, &image.memory) != VK_SUCCESS) { - throw std::runtime_error("Failed to allocate render graph image memory"); - } - - vkBindImageMemory(device, image.image, image.memory, 0); - - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = image.image; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = aspectMask; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - if (vkCreateImageView(device, &viewInfo, nullptr, &image.view) != VK_SUCCESS) { - throw std::runtime_error("Failed to create render graph image view"); - } - - image.format = format; - image.extent = extent; - image.layout = VK_IMAGE_LAYOUT_UNDEFINED; -} - -void RenderCommandService::TransitionImageLayout(VkCommandBuffer commandBuffer, - RenderCommandService::RenderGraphImage& image, - VkImageLayout newLayout, VkImageAspectFlags aspectMask) { - if (image.layout == newLayout || image.image == VK_NULL_HANDLE) { - return; - } - - VkImageMemoryBarrier barrier{}; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.oldLayout = image.layout; - barrier.newLayout = newLayout; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = image.image; - barrier.subresourceRange.aspectMask = aspectMask; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - - VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - - if (newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { - barrier.srcAccessMask = (image.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - ? VK_ACCESS_SHADER_READ_BIT : 0; - barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - srcStage = (image.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - ? VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - } else if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { - barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - dstStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; - } else if (newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - } - - vkCmdPipelineBarrier(commandBuffer, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier); - image.layout = newLayout; -} - -bool RenderCommandService::IsScenePass(const RenderGraphPass& pass) const { - return pass.kind == "gbuffer" || - pass.kind == "forward_plus" || - pass.kind == "transparent" || - pass.kind == "depth_prepass" || - pass.kind == "shadow_csm" || - pass.kind == "shadow_spot" || - pass.kind == "shadow_point"; -} - -bool RenderCommandService::IsPassEnabled(const RenderGraphPass& pass) const { - auto it = pass.settings.find("enabled"); - if (it != pass.settings.end() && it->second.type == RenderGraphValue::Type::Boolean) { - return it->second.boolean; - } - return true; -} - -std::string RenderCommandService::ResolvePassOutput(const RenderGraphPass& pass) const { - if (!pass.output.empty()) { - return pass.output; - } - if (!pass.outputs.empty()) { - auto sceneIt = pass.outputs.find("scene"); - if (sceneIt != pass.outputs.end()) { - return sceneIt->second; - } - auto albedoIt = pass.outputs.find("albedo"); - if (albedoIt != pass.outputs.end()) { - return albedoIt->second; - } - auto colorIt = pass.outputs.find("color"); - if (colorIt != pass.outputs.end()) { - return colorIt->second; - } - return pass.outputs.begin()->second; - } - return {}; -} - -std::string RenderCommandService::ResolvePassInput(const RenderGraphPass& pass) const { - if (!pass.inputs.empty()) { - auto sceneIt = pass.inputs.find("scene"); - if (sceneIt != pass.inputs.end()) { - return sceneIt->second; - } - auto inputIt = pass.inputs.find("input"); - if (inputIt != pass.inputs.end()) { - return inputIt->second; - } - auto albedoIt = pass.inputs.find("albedo"); - if (albedoIt != pass.inputs.end()) { - return albedoIt->second; - } - auto colorIt = pass.inputs.find("color"); - if (colorIt != pass.inputs.end()) { - return colorIt->second; - } - return pass.inputs.begin()->second; - } - return {}; -} - -core::PushConstants RenderCommandService::BuildFullscreenConstants(const RenderGraphPass& pass) const { - core::PushConstants constants{}; - const std::array identity = {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}; - constants.model = identity; - constants.viewProj = identity; - constants.view = identity; - constants.proj = identity; - constants.lightViewProj = identity; - constants.cameraPos = {0.0f, 0.0f, 0.0f}; - - const auto& config = configService_->GetConfig(); - constants.gamma = config.atmospherics.gamma; - constants.exposure = config.atmospherics.exposure; - constants.ambientStrength = config.atmospherics.ambientStrength; - constants.fogDensity = config.atmospherics.fogDensity; - constants.fogColor = config.atmospherics.fogColor; - constants.enableFog = 0; - constants.enableShadows = 0; - - auto applySetting = [&](const std::string& key, float& target) { - auto it = pass.settings.find(key); - if (it != pass.settings.end() && it->second.type == RenderGraphValue::Type::Number) { - target = static_cast(it->second.number); - } - }; - - applySetting("gamma", constants.gamma); - applySetting("exposure", constants.exposure); - - return constants; -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/render_command_service.hpp b/src/services/impl/render_command_service.hpp deleted file mode 100644 index 1b74719..0000000 --- a/src/services/impl/render_command_service.hpp +++ /dev/null @@ -1,149 +0,0 @@ -#pragma once - -#include "../interfaces/i_render_command_service.hpp" -#include "../interfaces/i_buffer_service.hpp" -#include "../interfaces/i_pipeline_service.hpp" -#include "../interfaces/i_vulkan_device_service.hpp" -#include "../interfaces/i_swapchain_service.hpp" -#include "../interfaces/i_config_service.hpp" -#include "../interfaces/i_gui_renderer_service.hpp" -#include "../impl/json_config_service.hpp" -#include "../interfaces/i_logger.hpp" -#include "../../di/lifecycle.hpp" -#include -#include -#include -#include - -namespace sdl3cpp::services::impl { - -/** - * @brief Render command service implementation. - * - * Small, focused service (~200 lines) for rendering orchestration. - * Handles command buffer recording and frame synchronization. - */ -class RenderCommandService : public IRenderCommandService, - public di::IShutdownable { -public: - explicit RenderCommandService(std::shared_ptr deviceService, - std::shared_ptr swapchainService, - std::shared_ptr pipelineService, - std::shared_ptr bufferService, - std::shared_ptr guiRendererService, - std::shared_ptr configService, - std::shared_ptr logger); - ~RenderCommandService() override; - - // IRenderCommandService interface - void Cleanup() override; - - bool BeginFrame(uint32_t& imageIndex) override; - void RecordCommands(uint32_t imageIndex, - const std::vector& commands, - const std::array& viewProj) override; - void RecordRenderGraph(uint32_t imageIndex, - const RenderGraphDefinition& graph, - const std::vector& commands, - const std::array& viewProj) override; - bool EndFrame(uint32_t imageIndex) override; - - VkCommandBuffer GetCurrentCommandBuffer() const override; - uint32_t GetCurrentFrameIndex() const override { - logger_->Trace("RenderCommandService", "GetCurrentFrameIndex"); - return currentFrame_; - } - uint32_t GetMaxFramesInFlight() const override { - logger_->Trace("RenderCommandService", "GetMaxFramesInFlight"); - return maxFramesInFlight_; - } - void OnSwapchainRecreated() override; - - // IShutdownable interface - void Shutdown() noexcept override; - -private: - struct RenderGraphImage { - VkImage image = VK_NULL_HANDLE; - VkDeviceMemory memory = VK_NULL_HANDLE; - VkImageView view = VK_NULL_HANDLE; - VkFramebuffer framebuffer = VK_NULL_HANDLE; - VkFormat format = VK_FORMAT_UNDEFINED; - VkExtent2D extent{}; - VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; - }; - - std::shared_ptr deviceService_; - std::shared_ptr swapchainService_; - std::shared_ptr pipelineService_; - std::shared_ptr bufferService_; - std::shared_ptr guiRendererService_; - std::shared_ptr configService_; - std::shared_ptr logger_; - - VkCommandPool commandPool_ = VK_NULL_HANDLE; - std::vector commandBuffers_; - - // Synchronization primitives - VkSemaphore imageAvailableSemaphore_ = VK_NULL_HANDLE; - VkSemaphore renderFinishedSemaphore_ = VK_NULL_HANDLE; - VkFence inFlightFence_ = VK_NULL_HANDLE; - - VkDescriptorPool descriptorPool_ = VK_NULL_HANDLE; - VkDescriptorSet defaultDescriptorSet_ = VK_NULL_HANDLE; - VkDescriptorSet postProcessDescriptorSet_ = VK_NULL_HANDLE; - VkSampler sampler_ = VK_NULL_HANDLE; - VkImage dummyImage_ = VK_NULL_HANDLE; - VkDeviceMemory dummyImageMemory_ = VK_NULL_HANDLE; - VkImageView dummyImageView_ = VK_NULL_HANDLE; - VkImageLayout dummyImageLayout_ = VK_IMAGE_LAYOUT_UNDEFINED; - - VkRenderPass offscreenRenderPass_ = VK_NULL_HANDLE; - std::unordered_map renderGraphTargets_; - std::unordered_map renderGraphDepth_; - VkExtent2D renderGraphExtent_{}; - size_t renderGraphResourceCount_ = 0; - size_t renderGraphPassCount_ = 0; - std::unordered_set warnedPassesWithoutShader_; - std::unordered_set warnedMissingPipelines_; - std::unordered_set warnedMissingTargets_; - - uint32_t currentFrame_ = 0; - uint32_t maxFramesInFlight_ = 1; // Single frame in flight for simplicity - - // Helper methods - void CreateCommandPool(); - void CreateCommandBuffers(); - void CreateSyncObjects(); - void CleanupCommandResources(); - void CleanupSyncObjects(); - - void EnsureDescriptorResources(); - void CleanupDescriptorResources(); - void CreateDescriptorPool(); - void CreateSampler(); - void CreateDummyImage(); - void AllocateDescriptorSets(); - void UpdateDescriptorSet(VkDescriptorSet set, VkImageView view); - void EnsureDummyImageLayout(VkCommandBuffer commandBuffer); - - void EnsureRenderGraphResources(const RenderGraphDefinition& graph); - void CleanupRenderGraphResources(); - void RegisterRenderGraphShaders(const RenderGraphDefinition& graph); - VkFormat ResolveColorFormat(const std::string& format) const; - VkFormat ResolveDepthFormat(const std::string& format) const; - VkExtent2D ResolveExtent(const RenderGraphResource& resource) const; - RenderGraphImage* FindRenderTarget(const std::string& name); - RenderGraphImage* FindDepthTarget(const std::string& name); - void CreateRenderGraphImage(RenderGraphImage& image, VkFormat format, VkExtent2D extent, - VkImageUsageFlags usage, VkImageAspectFlags aspectMask); - void TransitionImageLayout(VkCommandBuffer commandBuffer, RenderGraphImage& image, - VkImageLayout newLayout, VkImageAspectFlags aspectMask); - bool IsScenePass(const RenderGraphPass& pass) const; - bool IsPassEnabled(const RenderGraphPass& pass) const; - std::string ResolvePassOutput(const RenderGraphPass& pass) const; - std::string ResolvePassInput(const RenderGraphPass& pass) const; - core::PushConstants BuildFullscreenConstants(const RenderGraphPass& pass) const; -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/render_coordinator_service.cpp b/src/services/impl/render_coordinator_service.cpp index 3247c32..a70babc 100644 --- a/src/services/impl/render_coordinator_service.cpp +++ b/src/services/impl/render_coordinator_service.cpp @@ -6,7 +6,6 @@ RenderCoordinatorService::RenderCoordinatorService(std::shared_ptr logg std::shared_ptr graphicsService, std::shared_ptr sceneScriptService, std::shared_ptr shaderScriptService, - std::shared_ptr renderGraphScriptService, std::shared_ptr guiScriptService, std::shared_ptr guiService, std::shared_ptr sceneService) @@ -14,7 +13,6 @@ RenderCoordinatorService::RenderCoordinatorService(std::shared_ptr logg graphicsService_(std::move(graphicsService)), sceneScriptService_(std::move(sceneScriptService)), shaderScriptService_(std::move(shaderScriptService)), - renderGraphScriptService_(std::move(renderGraphScriptService)), guiScriptService_(std::move(guiScriptService)), guiService_(std::move(guiService)), sceneService_(std::move(sceneService)) { @@ -23,7 +21,6 @@ RenderCoordinatorService::RenderCoordinatorService(std::shared_ptr logg "graphicsService=" + std::string(graphicsService_ ? "set" : "null") + ", sceneScriptService=" + std::string(sceneScriptService_ ? "set" : "null") + ", shaderScriptService=" + std::string(shaderScriptService_ ? "set" : "null") + - ", renderGraphScriptService=" + std::string(renderGraphScriptService_ ? "set" : "null") + ", guiScriptService=" + std::string(guiScriptService_ ? "set" : "null") + ", guiService=" + std::string(guiService_ ? "set" : "null") + ", sceneService=" + std::string(sceneService_ ? "set" : "null"), @@ -44,21 +41,6 @@ void RenderCoordinatorService::RenderFrame(float time) { return; } - if (!renderGraphInitialized_) { - renderGraphInitialized_ = true; - if (renderGraphScriptService_ && renderGraphScriptService_->IsEnabled()) { - if (logger_) { - logger_->Trace("RenderCoordinatorService", "RenderFrame", - "Loading render graph from Lua"); - } - auto graph = renderGraphScriptService_->LoadRenderGraph(); - graphicsService_->SetRenderGraphDefinition(graph); - } else if (logger_) { - logger_->Trace("RenderCoordinatorService", "RenderFrame", - "Render graph disabled or unavailable"); - } - } - if (!shadersLoaded_) { if (!shaderScriptService_) { if (logger_) { diff --git a/src/services/impl/render_coordinator_service.hpp b/src/services/impl/render_coordinator_service.hpp index 22d54ac..d85eab8 100644 --- a/src/services/impl/render_coordinator_service.hpp +++ b/src/services/impl/render_coordinator_service.hpp @@ -5,7 +5,6 @@ #include "../interfaces/i_gui_script_service.hpp" #include "../interfaces/i_gui_service.hpp" #include "../interfaces/i_logger.hpp" -#include "../interfaces/i_render_graph_script_service.hpp" #include "../interfaces/i_scene_script_service.hpp" #include "../interfaces/i_scene_service.hpp" #include "../interfaces/i_shader_script_service.hpp" @@ -19,7 +18,6 @@ public: std::shared_ptr graphicsService, std::shared_ptr sceneScriptService, std::shared_ptr shaderScriptService, - std::shared_ptr renderGraphScriptService, std::shared_ptr guiScriptService, std::shared_ptr guiService, std::shared_ptr sceneService); @@ -32,7 +30,6 @@ private: std::shared_ptr graphicsService_; std::shared_ptr sceneScriptService_; std::shared_ptr shaderScriptService_; - std::shared_ptr renderGraphScriptService_; std::shared_ptr guiScriptService_; std::shared_ptr guiService_; std::shared_ptr sceneService_; @@ -40,7 +37,6 @@ private: size_t lastIndexCount_ = 0; bool shadersLoaded_ = false; bool geometryUploaded_ = false; - bool renderGraphInitialized_ = false; }; } // namespace sdl3cpp::services::impl diff --git a/src/services/impl/render_graph_script_service.cpp b/src/services/impl/render_graph_script_service.cpp deleted file mode 100644 index 016ffb6..0000000 --- a/src/services/impl/render_graph_script_service.cpp +++ /dev/null @@ -1,417 +0,0 @@ -#include "render_graph_script_service.hpp" - -#include "lua_helpers.hpp" -#include "services/interfaces/i_logger.hpp" - -#include - -#include -#include -#include - -namespace sdl3cpp::services::impl { - -RenderGraphScriptService::RenderGraphScriptService(std::shared_ptr engineService, - std::shared_ptr configService, - std::shared_ptr logger) - : engineService_(std::move(engineService)), - configService_(std::move(configService)), - logger_(std::move(logger)) { - if (logger_) { - logger_->Trace("RenderGraphScriptService", "RenderGraphScriptService", - "engineService=" + std::string(engineService_ ? "set" : "null") + - ", configService=" + std::string(configService_ ? "set" : "null")); - } -} - -bool RenderGraphScriptService::IsEnabled() const { - if (!configService_) { - return false; - } - return configService_->GetRenderGraphConfig().enabled; -} - -bool RenderGraphScriptService::HasRenderGraphFunction() const { - if (!IsEnabled()) { - return false; - } - lua_State* L = GetLuaState(); - const auto& config = GetRenderGraphConfig(); - lua_getglobal(L, config.functionName.c_str()); - bool isFunction = lua_isfunction(L, -1); - lua_pop(L, 1); - return isFunction; -} - -RenderGraphDefinition RenderGraphScriptService::LoadRenderGraph() { - if (logger_) { - logger_->Trace("RenderGraphScriptService", "LoadRenderGraph"); - } - - if (!IsEnabled()) { - if (logger_) { - logger_->Trace("RenderGraphScriptService", "LoadRenderGraph", - "renderGraphEnabled=false"); - } - return {}; - } - - if (graphLoaded_) { - return cachedGraph_; - } - - lua_State* L = GetLuaState(); - const auto& config = GetRenderGraphConfig(); - lua_getglobal(L, config.functionName.c_str()); - if (!lua_isfunction(L, -1)) { - lua_pop(L, 1); - if (logger_) { - logger_->Error("Lua render graph function '" + config.functionName + "' is missing"); - } - throw std::runtime_error("Lua render graph function '" + config.functionName + "' is missing"); - } - - if (lua_pcall(L, 0, 1, 0) != LUA_OK) { - std::string message = lua::GetLuaError(L); - lua_pop(L, 1); - if (logger_) { - logger_->Error("Lua render graph failed: " + message); - } - throw std::runtime_error("Lua render graph failed: " + message); - } - - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - if (logger_) { - logger_->Error("Render graph function did not return a table"); - } - throw std::runtime_error("Render graph function did not return a table"); - } - - cachedGraph_ = ParseRenderGraph(L, lua_gettop(L)); - graphLoaded_ = true; - lua_pop(L, 1); - - if (logger_) { - logger_->Info("Loaded render graph with resources=" + - std::to_string(cachedGraph_.resources.size()) + - ", passes=" + std::to_string(cachedGraph_.passes.size())); - } - - return cachedGraph_; -} - -lua_State* RenderGraphScriptService::GetLuaState() const { - if (logger_) { - logger_->Trace("RenderGraphScriptService", "GetLuaState"); - } - if (!engineService_) { - throw std::runtime_error("Render graph script service is missing script engine service"); - } - lua_State* state = engineService_->GetLuaState(); - if (!state) { - throw std::runtime_error("Lua state is not initialized"); - } - return state; -} - -const RenderGraphConfig& RenderGraphScriptService::GetRenderGraphConfig() const { - if (!configService_) { - throw std::runtime_error("Render graph script service is missing config service"); - } - return configService_->GetRenderGraphConfig(); -} - -RenderGraphDefinition RenderGraphScriptService::ParseRenderGraph(lua_State* L, int index) const { - RenderGraphDefinition graph; - int graphIndex = lua_absindex(L, index); - - lua_getfield(L, graphIndex, "resources"); - if (lua_istable(L, -1)) { - int resourcesIndex = lua_absindex(L, -1); - lua_pushnil(L); - while (lua_next(L, resourcesIndex) != 0) { - if (lua_isstring(L, -2) && lua_istable(L, -1)) { - std::string name = lua_tostring(L, -2); - graph.resources.push_back(ParseResource(L, lua_gettop(L), name)); - } else if (logger_) { - logger_->Warn("RenderGraphScriptService: Skipping invalid resource entry"); - } - lua_pop(L, 1); - } - } else if (!lua_isnil(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Render graph 'resources' must be a table when provided"); - } - lua_pop(L, 1); - - lua_getfield(L, graphIndex, "passes"); - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Render graph 'passes' must be a table"); - } - - int passesIndex = lua_absindex(L, -1); - size_t count = lua_rawlen(L, passesIndex); - graph.passes.reserve(count); - for (size_t i = 1; i <= count; ++i) { - lua_rawgeti(L, passesIndex, static_cast(i)); - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Render graph pass at index " + - std::to_string(i) + " must be a table"); - } - graph.passes.push_back(ParsePass(L, lua_gettop(L), i)); - lua_pop(L, 1); - } - lua_pop(L, 1); - - if (graph.passes.empty()) { - throw std::runtime_error("Render graph must define at least one pass"); - } - - return graph; -} - -RenderGraphResource RenderGraphScriptService::ParseResource(lua_State* L, int index, - const std::string& name) const { - RenderGraphResource resource; - resource.name = name; - int resourceIndex = lua_absindex(L, index); - - auto readRequiredString = [&](const char* field, std::string& target) { - lua_getfield(L, resourceIndex, field); - if (!lua_isstring(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Render graph resource '" + name + - "' field '" + field + "' must be a string"); - } - target = lua_tostring(L, -1); - lua_pop(L, 1); - }; - - auto readOptionalNumber = [&](const char* field, uint32_t& target) { - lua_getfield(L, resourceIndex, field); - if (lua_isnumber(L, -1)) { - double value = lua_tonumber(L, -1); - target = value < 0.0 ? 0u : static_cast(value); - } else if (!lua_isnil(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Render graph resource '" + name + - "' field '" + field + "' must be a number"); - } - lua_pop(L, 1); - }; - - readRequiredString("type", resource.type); - readRequiredString("format", resource.format); - - resource.size = "swapchain"; - lua_getfield(L, resourceIndex, "size"); - if (lua_isstring(L, -1)) { - resource.size = lua_tostring(L, -1); - } else if (lua_istable(L, -1)) { - int sizeIndex = lua_absindex(L, -1); - bool parsed = false; - size_t sizeLen = lua_rawlen(L, sizeIndex); - if (sizeLen >= 2) { - lua_rawgeti(L, sizeIndex, 1); - lua_rawgeti(L, sizeIndex, 2); - if (lua_isnumber(L, -2) && lua_isnumber(L, -1)) { - resource.explicitSize[0] = static_cast(lua_tonumber(L, -2)); - resource.explicitSize[1] = static_cast(lua_tonumber(L, -1)); - resource.hasExplicitSize = true; - parsed = true; - } - lua_pop(L, 2); - } - if (!parsed) { - lua_getfield(L, sizeIndex, "width"); - lua_getfield(L, sizeIndex, "height"); - if (lua_isnumber(L, -2) && lua_isnumber(L, -1)) { - resource.explicitSize[0] = static_cast(lua_tonumber(L, -2)); - resource.explicitSize[1] = static_cast(lua_tonumber(L, -1)); - resource.hasExplicitSize = true; - parsed = true; - } - lua_pop(L, 2); - } - if (!parsed) { - lua_pop(L, 1); - throw std::runtime_error("Render graph resource '" + name + - "' size must be a string or {width,height}"); - } - } else if (!lua_isnil(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Render graph resource '" + name + - "' size must be a string or table"); - } - lua_pop(L, 1); - - readOptionalNumber("layers", resource.layers); - readOptionalNumber("mips", resource.mips); - - return resource; -} - -RenderGraphPass RenderGraphScriptService::ParsePass(lua_State* L, int index, size_t passIndex) const { - RenderGraphPass pass; - int passTableIndex = lua_absindex(L, index); - - lua_getfield(L, passTableIndex, "name"); - if (lua_isstring(L, -1)) { - pass.name = lua_tostring(L, -1); - } else { - pass.name = "pass_" + std::to_string(passIndex); - } - lua_pop(L, 1); - - lua_getfield(L, passTableIndex, "kind"); - if (!lua_isstring(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Render graph pass '" + pass.name + "' missing 'kind' string"); - } - pass.kind = lua_tostring(L, -1); - lua_pop(L, 1); - - lua_getfield(L, passTableIndex, "shader"); - if (lua_isstring(L, -1)) { - pass.shader = lua_tostring(L, -1); - } else if (!lua_isnil(L, -1) && logger_) { - logger_->Warn("RenderGraphScriptService: pass '" + pass.name + "' shader must be a string"); - } - lua_pop(L, 1); - - lua_getfield(L, passTableIndex, "output"); - if (lua_isstring(L, -1)) { - pass.output = lua_tostring(L, -1); - } else if (!lua_isnil(L, -1) && logger_) { - logger_->Warn("RenderGraphScriptService: pass '" + pass.name + "' output must be a string"); - } - lua_pop(L, 1); - - std::string inputValue; - lua_getfield(L, passTableIndex, "input"); - if (lua_isstring(L, -1)) { - inputValue = lua_tostring(L, -1); - } else if (!lua_isnil(L, -1) && logger_) { - logger_->Warn("RenderGraphScriptService: pass '" + pass.name + "' input must be a string"); - } - lua_pop(L, 1); - - lua_getfield(L, passTableIndex, "inputs"); - if (lua_istable(L, -1)) { - pass.inputs = ParseStringMap(L, lua_gettop(L)); - } else if (!lua_isnil(L, -1) && logger_) { - logger_->Warn("RenderGraphScriptService: pass '" + pass.name + "' inputs must be a table"); - } - lua_pop(L, 1); - if (!inputValue.empty() && pass.inputs.find("input") == pass.inputs.end()) { - pass.inputs.emplace("input", std::move(inputValue)); - } - - lua_getfield(L, passTableIndex, "outputs"); - if (lua_istable(L, -1)) { - pass.outputs = ParseStringMap(L, lua_gettop(L)); - } else if (!lua_isnil(L, -1) && logger_) { - logger_->Warn("RenderGraphScriptService: pass '" + pass.name + "' outputs must be a table"); - } - lua_pop(L, 1); - - lua_getfield(L, passTableIndex, "settings"); - if (lua_istable(L, -1)) { - pass.settings = ParseSettingsMap(L, lua_gettop(L)); - } else if (!lua_isnil(L, -1) && logger_) { - logger_->Warn("RenderGraphScriptService: pass '" + pass.name + "' settings must be a table"); - } - lua_pop(L, 1); - - return pass; -} - -std::unordered_map RenderGraphScriptService::ParseStringMap(lua_State* L, - int index) const { - std::unordered_map result; - int mapIndex = lua_absindex(L, index); - - lua_pushnil(L); - while (lua_next(L, mapIndex) != 0) { - if (lua_isstring(L, -2) && lua_isstring(L, -1)) { - std::string key = lua_tostring(L, -2); - std::string value = lua_tostring(L, -1); - result.emplace(std::move(key), std::move(value)); - } else if (logger_) { - logger_->Warn("RenderGraphScriptService: String map entry must be string->string"); - } - lua_pop(L, 1); - } - - return result; -} - -std::unordered_map RenderGraphScriptService::ParseSettingsMap(lua_State* L, - int index) const { - std::unordered_map result; - int mapIndex = lua_absindex(L, index); - - lua_pushnil(L); - while (lua_next(L, mapIndex) != 0) { - if (!lua_isstring(L, -2)) { - lua_pop(L, 1); - continue; - } - std::string key = lua_tostring(L, -2); - RenderGraphValue value; - if (TryParseValue(L, lua_gettop(L), value)) { - result.emplace(std::move(key), std::move(value)); - } else if (logger_) { - logger_->Warn("RenderGraphScriptService: Unsupported settings value for key '" + key + "'"); - } - lua_pop(L, 1); - } - - return result; -} - -bool RenderGraphScriptService::TryParseValue(lua_State* L, int index, RenderGraphValue& outValue) const { - int absIndex = lua_absindex(L, index); - if (lua_isnumber(L, absIndex)) { - outValue.type = RenderGraphValue::Type::Number; - outValue.number = lua_tonumber(L, absIndex); - return true; - } - if (lua_isboolean(L, absIndex)) { - outValue.type = RenderGraphValue::Type::Boolean; - outValue.boolean = lua_toboolean(L, absIndex) != 0; - return true; - } - if (lua_isstring(L, absIndex)) { - outValue.type = RenderGraphValue::Type::String; - outValue.string = lua_tostring(L, absIndex); - return true; - } - if (lua_istable(L, absIndex)) { - size_t len = lua_rawlen(L, absIndex); - if (len == 0) { - return false; - } - outValue.type = RenderGraphValue::Type::Array; - outValue.array.clear(); - outValue.array.reserve(len); - for (size_t i = 1; i <= len; ++i) { - lua_rawgeti(L, absIndex, static_cast(i)); - if (!lua_isnumber(L, -1)) { - lua_pop(L, 1); - outValue.array.clear(); - return false; - } - outValue.array.push_back(lua_tonumber(L, -1)); - lua_pop(L, 1); - } - return true; - } - - return false; -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/render_graph_script_service.hpp b/src/services/impl/render_graph_script_service.hpp deleted file mode 100644 index 927d602..0000000 --- a/src/services/impl/render_graph_script_service.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "../interfaces/i_render_graph_script_service.hpp" -#include "../interfaces/i_config_service.hpp" -#include "../interfaces/i_logger.hpp" -#include "../interfaces/i_script_engine_service.hpp" -#include - -struct lua_State; - -namespace sdl3cpp::services::impl { - -/** - * @brief Loads render graph definitions from the shared Lua runtime. - */ -class RenderGraphScriptService : public IRenderGraphScriptService { -public: - RenderGraphScriptService(std::shared_ptr engineService, - std::shared_ptr configService, - std::shared_ptr logger); - - bool IsEnabled() const override; - bool HasRenderGraphFunction() const override; - RenderGraphDefinition LoadRenderGraph() override; - -private: - lua_State* GetLuaState() const; - const RenderGraphConfig& GetRenderGraphConfig() const; - RenderGraphDefinition ParseRenderGraph(lua_State* L, int index) const; - RenderGraphResource ParseResource(lua_State* L, int index, const std::string& name) const; - RenderGraphPass ParsePass(lua_State* L, int index, size_t passIndex) const; - bool TryParseValue(lua_State* L, int index, RenderGraphValue& outValue) const; - std::unordered_map ParseStringMap(lua_State* L, int index) const; - std::unordered_map ParseSettingsMap(lua_State* L, int index) const; - - std::shared_ptr engineService_; - std::shared_ptr configService_; - std::shared_ptr logger_; - bool graphLoaded_ = false; - RenderGraphDefinition cachedGraph_{}; -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/sdl_window_service.cpp b/src/services/impl/sdl_window_service.cpp index f803990..3de5cd3 100644 --- a/src/services/impl/sdl_window_service.cpp +++ b/src/services/impl/sdl_window_service.cpp @@ -1,6 +1,5 @@ #include "sdl_window_service.hpp" #include "../interfaces/i_logger.hpp" -#include #include #include #include @@ -132,7 +131,6 @@ void SdlWindowService::Shutdown() noexcept { DestroyWindow(); } - SDL_Vulkan_UnloadLibrary(); SDL_Quit(); initialized_ = false; } @@ -176,16 +174,7 @@ void SdlWindowService::CreateWindow(const WindowConfig& config) { } } - try { - logger_->Trace("SdlWindowService", "CreateWindow", "SDL_Vulkan_LoadLibrary(nullptr)"); - ThrowSdlErrorIfFailed(SDL_Vulkan_LoadLibrary(nullptr), "SDL_Vulkan_LoadLibrary failed", platformService_); - } catch (const std::exception& e) { - ShowErrorDialog("Vulkan Library Load Failed", - std::string("Failed to load Vulkan library. Make sure Vulkan drivers are installed.\n\nError: ") + e.what()); - throw; - } - - uint32_t flags = SDL_WINDOW_VULKAN; + uint32_t flags = 0; if (config.resizable) { flags |= SDL_WINDOW_RESIZABLE; } diff --git a/src/services/impl/swapchain_service.cpp b/src/services/impl/swapchain_service.cpp deleted file mode 100644 index 9c6edf6..0000000 --- a/src/services/impl/swapchain_service.cpp +++ /dev/null @@ -1,512 +0,0 @@ -#include "swapchain_service.hpp" -#include -#include -#include -#include - -namespace sdl3cpp::services::impl { - -SwapchainService::SwapchainService(std::shared_ptr deviceService, - std::shared_ptr eventBus, - std::shared_ptr logger) - : deviceService_(std::move(deviceService)), eventBus_(std::move(eventBus)), logger_(logger) { - if (logger_) { - logger_->Trace("SwapchainService", "SwapchainService", - "deviceService=" + std::string(deviceService_ ? "set" : "null") + - ", eventBus=" + std::string(eventBus_ ? "set" : "null")); - } - // Subscribe to window resize events - eventBus_->Subscribe(events::EventType::WindowResized, - [this](const events::Event& event) { OnWindowResized(event); }); -} - -SwapchainService::~SwapchainService() { - if (logger_) { - logger_->Trace("SwapchainService", "~SwapchainService"); - } - if (swapchain_ != VK_NULL_HANDLE) { - Shutdown(); - } -} - -void SwapchainService::Initialize() { - logger_->Trace("SwapchainService", "Initialize"); - // Initialization happens in CreateSwapchain() -} - -void SwapchainService::CreateSwapchain(uint32_t width, uint32_t height) { - logger_->Trace("SwapchainService", "CreateSwapchain", - "width=" + std::to_string(width) + - ", height=" + std::to_string(height)); - - currentWidth_ = width; - currentHeight_ = height; - - auto physicalDevice = deviceService_->GetPhysicalDevice(); - auto surface = deviceService_->GetSurface(); - auto device = deviceService_->GetDevice(); - - SwapchainSupportDetails support = QuerySwapchainSupport(physicalDevice, surface); - - // Validate swap chain support - if (support.formats.empty()) { - throw std::runtime_error("No surface formats available for swap chain.\n" - "This may indicate GPU driver issues or incompatible surface."); - } - if (support.presentModes.empty()) { - throw std::runtime_error("No present modes available for swap chain.\n" - "This may indicate GPU driver issues or incompatible surface."); - } - - logger_->Info("Creating swapchain with size: " + std::to_string(width) + "x" + std::to_string(height)); - - if (width == 0 || height == 0) { - logger_->Error("Invalid dimensions (" + std::to_string(width) + "x" + std::to_string(height) + ")."); - throw std::runtime_error("Invalid dimensions (" + - std::to_string(width) + "x" + std::to_string(height) + ").\n" + - "Window may be minimized or invalid."); - } - - logger_->Debug("Surface capabilities - Min extent: " + std::to_string(support.capabilities.minImageExtent.width) + "x" + std::to_string(support.capabilities.minImageExtent.height) + - ", Max extent: " + std::to_string(support.capabilities.maxImageExtent.width) + "x" + std::to_string(support.capabilities.maxImageExtent.height) + - ", Min images: " + std::to_string(support.capabilities.minImageCount) + - ", Max images: " + std::to_string(support.capabilities.maxImageCount)); - - VkSurfaceFormatKHR surfaceFormat = ChooseSurfaceFormat(support.formats); - VkPresentModeKHR presentMode = ChoosePresentMode(support.presentModes); - VkExtent2D extent = ChooseExtent(support.capabilities, width, height); - - uint32_t imageCount = support.capabilities.minImageCount + 1; - if (support.capabilities.maxImageCount > 0 && imageCount > support.capabilities.maxImageCount) { - imageCount = support.capabilities.maxImageCount; - } - logger_->TraceVariable("imageCount", static_cast(imageCount)); - - VkSwapchainCreateInfoKHR createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; - createInfo.surface = surface; - createInfo.minImageCount = imageCount; - createInfo.imageFormat = surfaceFormat.format; - createInfo.imageColorSpace = surfaceFormat.colorSpace; - createInfo.imageExtent = extent; - createInfo.imageArrayLayers = 1; - VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - if (support.capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) { - usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; - } - createInfo.imageUsage = usage; - - QueueFamilyIndices indices = deviceService_->GetQueueFamilies(); - uint32_t queueFamilyIndices[] = {indices.graphicsFamily, indices.presentFamily}; - if (indices.graphicsFamily != indices.presentFamily) { - createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; - createInfo.queueFamilyIndexCount = 2; - createInfo.pQueueFamilyIndices = queueFamilyIndices; - } else { - createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - } - - createInfo.preTransform = support.capabilities.currentTransform; - createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - createInfo.presentMode = presentMode; - createInfo.clipped = VK_TRUE; - - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapchain_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create swap chain"); - } - - vkGetSwapchainImagesKHR(device, swapchain_, &imageCount, nullptr); - images_.resize(imageCount); - vkGetSwapchainImagesKHR(device, swapchain_, &imageCount, images_.data()); - - imageFormat_ = surfaceFormat.format; - extent_ = extent; - - CreateImageViews(); - CreateDepthResources(); - CreateRenderPass(); - CreateFramebuffers(); -} - -void SwapchainService::RecreateSwapchain(uint32_t width, uint32_t height) { - logger_->Trace("SwapchainService", "RecreateSwapchain", - "width=" + std::to_string(width) + - ", height=" + std::to_string(height)); - - logger_->Info("Recreating swapchain: " + std::to_string(width) + "x" + std::to_string(height)); - - deviceService_->WaitIdle(); - CleanupSwapchainInternal(); - CreateSwapchain(width, height); -} - -VkResult SwapchainService::AcquireNextImage(VkSemaphore semaphore, uint32_t& imageIndex) { - logger_->Trace("SwapchainService", "AcquireNextImage", - "semaphoreIsNull=" + std::string(semaphore == VK_NULL_HANDLE ? "true" : "false") + - ", imageIndex=" + std::to_string(imageIndex)); - auto device = deviceService_->GetDevice(); - return vkAcquireNextImageKHR(device, swapchain_, UINT64_MAX, semaphore, - VK_NULL_HANDLE, &imageIndex); -} - -VkResult SwapchainService::Present(const std::vector& waitSemaphores, - uint32_t imageIndex) { - logger_->Trace("SwapchainService", "Present", - "waitSemaphores.size=" + std::to_string(waitSemaphores.size()) + - ", imageIndex=" + std::to_string(imageIndex)); - auto presentQueue = deviceService_->GetPresentQueue(); - - VkPresentInfoKHR presentInfo{}; - presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; - presentInfo.waitSemaphoreCount = static_cast(waitSemaphores.size()); - presentInfo.pWaitSemaphores = waitSemaphores.data(); - presentInfo.swapchainCount = 1; - presentInfo.pSwapchains = &swapchain_; - presentInfo.pImageIndices = &imageIndex; - - return vkQueuePresentKHR(presentQueue, &presentInfo); -} - -void SwapchainService::CreateImageViews() { - logger_->Trace("SwapchainService", "CreateImageViews"); - - auto device = deviceService_->GetDevice(); - - imageViews_.resize(images_.size()); - for (size_t i = 0; i < images_.size(); ++i) { - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = images_[i]; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = imageFormat_; - viewInfo.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, - VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; - viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - if (vkCreateImageView(device, &viewInfo, nullptr, &imageViews_[i]) != VK_SUCCESS) { - throw std::runtime_error("Failed to create image views"); - } - } -} - -void SwapchainService::CreateRenderPass() { - logger_->Trace("SwapchainService", "CreateRenderPass"); - - auto device = deviceService_->GetDevice(); - - std::array attachments{}; - - // Color attachment - attachments[0].format = imageFormat_; - attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; - attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - // Depth attachment - attachments[1].format = depthFormat_; - attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkAttachmentReference colorAttachmentRef{}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkAttachmentReference depthAttachmentRef{}; - depthAttachmentRef.attachment = 1; - depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - subpass.pDepthStencilAttachment = &depthAttachmentRef; - - VkSubpassDependency dependency{}; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | - VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; - dependency.srcAccessMask = 0; - dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | - VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; - dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - - VkRenderPassCreateInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = static_cast(attachments.size()); - renderPassInfo.pAttachments = attachments.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - renderPassInfo.dependencyCount = 1; - renderPassInfo.pDependencies = &dependency; - - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create render pass"); - } -} - -void SwapchainService::CreateFramebuffers() { - logger_->Trace("SwapchainService", "CreateFramebuffers"); - - auto device = deviceService_->GetDevice(); - - framebuffers_.resize(imageViews_.size()); - for (size_t i = 0; i < imageViews_.size(); ++i) { - std::array attachments = {imageViews_[i], depthImageViews_[i]}; - - VkFramebufferCreateInfo framebufferInfo{}; - framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferInfo.renderPass = renderPass_; - framebufferInfo.attachmentCount = static_cast(attachments.size()); - framebufferInfo.pAttachments = attachments.data(); - framebufferInfo.width = extent_.width; - framebufferInfo.height = extent_.height; - framebufferInfo.layers = 1; - - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &framebuffers_[i]) != VK_SUCCESS) { - throw std::runtime_error("Failed to create framebuffer"); - } - } -} - -void SwapchainService::CleanupSwapchainInternal() { - logger_->Trace("SwapchainService", "CleanupSwapchainInternal"); - - auto device = deviceService_->GetDevice(); - - for (auto framebuffer : framebuffers_) { - vkDestroyFramebuffer(device, framebuffer, nullptr); - } - framebuffers_.clear(); - - if (renderPass_ != VK_NULL_HANDLE) { - vkDestroyRenderPass(device, renderPass_, nullptr); - renderPass_ = VK_NULL_HANDLE; - } - - for (auto imageView : imageViews_) { - vkDestroyImageView(device, imageView, nullptr); - } - imageViews_.clear(); - - for (auto imageView : depthImageViews_) { - vkDestroyImageView(device, imageView, nullptr); - } - depthImageViews_.clear(); - - for (size_t i = 0; i < depthImages_.size(); ++i) { - vkDestroyImage(device, depthImages_[i], nullptr); - vkFreeMemory(device, depthImageMemories_[i], nullptr); - } - depthImages_.clear(); - depthImageMemories_.clear(); - - if (swapchain_ != VK_NULL_HANDLE) { - vkDestroySwapchainKHR(device, swapchain_, nullptr); - swapchain_ = VK_NULL_HANDLE; - } -} - -void SwapchainService::CleanupSwapchain() { - logger_->Trace("SwapchainService", "CleanupSwapchain"); - CleanupSwapchainInternal(); -} - -void SwapchainService::Shutdown() noexcept { - logger_->Trace("SwapchainService", "Shutdown"); - CleanupSwapchainInternal(); -} - -SwapchainService::SwapchainSupportDetails SwapchainService::QuerySwapchainSupport( - VkPhysicalDevice device, VkSurfaceKHR surface) { - logger_->Trace("SwapchainService", "QuerySwapchainSupport", - "deviceIsNull=" + std::string(device == VK_NULL_HANDLE ? "true" : "false") + - ", surfaceIsNull=" + std::string(surface == VK_NULL_HANDLE ? "true" : "false")); - - SwapchainSupportDetails details; - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); - - uint32_t formatCount; - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); - if (formatCount != 0) { - details.formats.resize(formatCount); - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); - } - - uint32_t presentModeCount; - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); - if (presentModeCount != 0) { - details.presentModes.resize(presentModeCount); - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, - details.presentModes.data()); - } - - return details; -} - -VkSurfaceFormatKHR SwapchainService::ChooseSurfaceFormat( - const std::vector& availableFormats) { - logger_->Trace("SwapchainService", "ChooseSurfaceFormat", - "availableFormats.size=" + std::to_string(availableFormats.size())); - - for (const auto& availableFormat : availableFormats) { - if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && - availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { - return availableFormat; - } - } - return availableFormats[0]; -} - -VkPresentModeKHR SwapchainService::ChoosePresentMode( - const std::vector& availablePresentModes) { - logger_->Trace("SwapchainService", "ChoosePresentMode", - "availablePresentModes.size=" + std::to_string(availablePresentModes.size())); - - for (const auto& availablePresentMode : availablePresentModes) { - if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { - return availablePresentMode; - } - } - return VK_PRESENT_MODE_FIFO_KHR; -} - -VkExtent2D SwapchainService::ChooseExtent(const VkSurfaceCapabilitiesKHR& capabilities, - uint32_t width, uint32_t height) { - logger_->Trace("SwapchainService", "ChooseExtent", - "width=" + std::to_string(width) + - ", height=" + std::to_string(height) + - ", minWidth=" + std::to_string(capabilities.minImageExtent.width) + - ", minHeight=" + std::to_string(capabilities.minImageExtent.height) + - ", maxWidth=" + std::to_string(capabilities.maxImageExtent.width) + - ", maxHeight=" + std::to_string(capabilities.maxImageExtent.height)); - if (capabilities.currentExtent.width != std::numeric_limits::max()) { - return capabilities.currentExtent; - } - - return VkExtent2D{ - std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), - std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) - }; -} - -VkFormat SwapchainService::FindDepthFormat() { - logger_->Trace("SwapchainService", "FindDepthFormat"); - - auto physicalDevice = deviceService_->GetPhysicalDevice(); - - std::vector candidates = { - VK_FORMAT_D32_SFLOAT, - VK_FORMAT_D32_SFLOAT_S8_UINT, - VK_FORMAT_D24_UNORM_S8_UINT - }; - - for (VkFormat format : candidates) { - VkFormatProperties props; - vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); - - if ((props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) == - VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) { - return format; - } - } - - throw std::runtime_error("Failed to find supported depth format"); -} - -bool SwapchainService::HasStencilComponent(VkFormat format) { - return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; -} - -void SwapchainService::CreateDepthResources() { - logger_->Trace("SwapchainService", "CreateDepthResources"); - - auto device = deviceService_->GetDevice(); - auto physicalDevice = deviceService_->GetPhysicalDevice(); - - depthFormat_ = FindDepthFormat(); - - VkExtent2D swapchainExtent = GetSwapchainExtent(); - depthImages_.resize(images_.size()); - depthImageMemories_.resize(images_.size()); - depthImageViews_.resize(images_.size()); - - for (size_t i = 0; i < images_.size(); ++i) { - VkImageCreateInfo imageInfo{}; - imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.extent.width = swapchainExtent.width; - imageInfo.extent.height = swapchainExtent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = depthFormat_; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - - if (vkCreateImage(device, &imageInfo, nullptr, &depthImages_[i]) != VK_SUCCESS) { - throw std::runtime_error("Failed to create depth image"); - } - - VkMemoryRequirements memRequirements; - vkGetImageMemoryRequirements(device, depthImages_[i], &memRequirements); - - VkMemoryAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = deviceService_->FindMemoryType( - memRequirements.memoryTypeBits, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - - if (vkAllocateMemory(device, &allocInfo, nullptr, &depthImageMemories_[i]) != VK_SUCCESS) { - throw std::runtime_error("Failed to allocate depth image memory"); - } - - vkBindImageMemory(device, depthImages_[i], depthImageMemories_[i], 0); - - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = depthImages_[i]; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = depthFormat_; - viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - if (HasStencilComponent(depthFormat_)) { - viewInfo.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; - } - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - if (vkCreateImageView(device, &viewInfo, nullptr, &depthImageViews_[i]) != VK_SUCCESS) { - throw std::runtime_error("Failed to create depth image view"); - } - } - - logger_->Debug("Created depth resources for " + std::to_string(images_.size()) + " images"); -} - -void SwapchainService::OnWindowResized(const events::Event& event) { - logger_->Trace("SwapchainService", "OnWindowResized", - "eventType=" + std::to_string(static_cast(event.type))); - logger_->Info("Window resized event received, swapchain recreation needed"); -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/swapchain_service.hpp b/src/services/impl/swapchain_service.hpp deleted file mode 100644 index efd374a..0000000 --- a/src/services/impl/swapchain_service.hpp +++ /dev/null @@ -1,122 +0,0 @@ -#pragma once - -#include "../interfaces/i_swapchain_service.hpp" -#include "../interfaces/i_vulkan_device_service.hpp" -#include "../interfaces/i_logger.hpp" -#include "../../di/lifecycle.hpp" -#include "../../events/i_event_bus.hpp" -#include -#include - -namespace sdl3cpp::services::impl { - -/** - * @brief Swapchain service implementation. - * - * Small, focused service (~250 lines) for Vulkan swapchain management. - * Handles swapchain creation, recreation, image views, render pass, and framebuffers. - */ -class SwapchainService : public ISwapchainService, - public di::IInitializable, - public di::IShutdownable { -public: - explicit SwapchainService(std::shared_ptr deviceService, - std::shared_ptr eventBus, - std::shared_ptr logger); - ~SwapchainService() override; - - // ISwapchainService interface - void CreateSwapchain(uint32_t width, uint32_t height) override; - void RecreateSwapchain(uint32_t width, uint32_t height) override; - void CleanupSwapchain() override; - - VkResult AcquireNextImage(VkSemaphore semaphore, uint32_t& imageIndex) override; - VkResult Present(const std::vector& waitSemaphores, - uint32_t imageIndex) override; - - VkSwapchainKHR GetSwapchain() const override { - logger_->Trace("SwapchainService", "GetSwapchain"); - return swapchain_; - } - const std::vector& GetSwapchainImages() const override { - logger_->Trace("SwapchainService", "GetSwapchainImages"); - return images_; - } - const std::vector& GetSwapchainImageViews() const override { - logger_->Trace("SwapchainService", "GetSwapchainImageViews"); - return imageViews_; - } - const std::vector& GetSwapchainFramebuffers() const override { - logger_->Trace("SwapchainService", "GetSwapchainFramebuffers"); - return framebuffers_; - } - VkFormat GetSwapchainImageFormat() const override { - logger_->Trace("SwapchainService", "GetSwapchainImageFormat"); - return imageFormat_; - } - VkFormat GetDepthFormat() const override { - logger_->Trace("SwapchainService", "GetDepthFormat"); - return depthFormat_; - } - VkExtent2D GetSwapchainExtent() const override { - logger_->Trace("SwapchainService", "GetSwapchainExtent"); - return extent_; - } - VkRenderPass GetRenderPass() const override { - logger_->Trace("SwapchainService", "GetRenderPass"); - return renderPass_; - } - - // IInitializable interface - void Initialize() override; - - // IShutdownable interface - void Shutdown() noexcept override; - -private: - std::shared_ptr deviceService_; - std::shared_ptr eventBus_; - std::shared_ptr logger_; - - VkSwapchainKHR swapchain_ = VK_NULL_HANDLE; - std::vector images_; - std::vector imageViews_; - VkFormat imageFormat_ = VK_FORMAT_UNDEFINED; - VkExtent2D extent_{}; - VkRenderPass renderPass_ = VK_NULL_HANDLE; - std::vector framebuffers_; - - // Depth buffer resources - VkFormat depthFormat_ = VK_FORMAT_UNDEFINED; - std::vector depthImages_; - std::vector depthImageMemories_; - std::vector depthImageViews_; - - uint32_t currentWidth_ = 0; - uint32_t currentHeight_ = 0; - - // Helper methods - struct SwapchainSupportDetails { - VkSurfaceCapabilitiesKHR capabilities{}; - std::vector formats; - std::vector presentModes; - }; - - SwapchainSupportDetails QuerySwapchainSupport(VkPhysicalDevice device, VkSurfaceKHR surface); - VkSurfaceFormatKHR ChooseSurfaceFormat(const std::vector& availableFormats); - VkPresentModeKHR ChoosePresentMode(const std::vector& availablePresentModes); - VkExtent2D ChooseExtent(const VkSurfaceCapabilitiesKHR& capabilities, uint32_t width, uint32_t height); - - void CreateImageViews(); - void CreateDepthResources(); - void CreateRenderPass(); - void CreateFramebuffers(); - void CleanupSwapchainInternal(); - - VkFormat FindDepthFormat(); - bool HasStencilComponent(VkFormat format); - - void OnWindowResized(const events::Event& event); -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/vulkan_device_service.cpp b/src/services/impl/vulkan_device_service.cpp deleted file mode 100644 index 1c09d05..0000000 --- a/src/services/impl/vulkan_device_service.cpp +++ /dev/null @@ -1,464 +0,0 @@ -#include "vulkan_device_service.hpp" -#include -#include -#include -#include -#include -#include - -namespace { - -VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, - VkDebugUtilsMessageTypeFlagsEXT messageType, - const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, - void* pUserData) { - auto* logger = static_cast(pUserData); - std::string message = "Vulkan validation: "; - message += pCallbackData && pCallbackData->pMessage ? pCallbackData->pMessage : "Unknown message"; - - if (logger) { - if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { - logger->Error(message); - } else if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { - logger->Warn(message); - } else { - logger->Debug(message); - } - } else { - std::cerr << message << std::endl; - } - - (void)messageType; - return VK_FALSE; -} - -VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, - const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkDebugUtilsMessengerEXT* pDebugMessenger) { - auto func = reinterpret_cast( - vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); - if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pDebugMessenger); - } - return VK_ERROR_EXTENSION_NOT_PRESENT; -} - -void DestroyDebugUtilsMessengerEXT(VkInstance instance, - VkDebugUtilsMessengerEXT debugMessenger, - const VkAllocationCallbacks* pAllocator) { - auto func = reinterpret_cast( - vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT")); - if (func != nullptr) { - func(instance, debugMessenger, pAllocator); - } -} - -} // namespace - -namespace sdl3cpp::services::impl { - -VulkanDeviceService::VulkanDeviceService(std::shared_ptr logger) - : logger_(logger) { - if (logger_) { - logger_->Trace("VulkanDeviceService", "VulkanDeviceService"); - } -} - -VulkanDeviceService::~VulkanDeviceService() { - if (logger_) { - logger_->Trace("VulkanDeviceService", "~VulkanDeviceService"); - } - if (device_ != VK_NULL_HANDLE) { - Shutdown(); - } -} - -void VulkanDeviceService::Initialize(const std::vector& deviceExtensions, - bool enableValidationLayers) { - logger_->Trace("VulkanDeviceService", "Initialize", - "deviceExtensions.size=" + std::to_string(deviceExtensions.size()) + - ", enableValidationLayers=" + std::string(enableValidationLayers ? "true" : "false")); - - if (instance_ != VK_NULL_HANDLE || surface_ != VK_NULL_HANDLE || device_ != VK_NULL_HANDLE) { - logger_->Warn("VulkanDeviceService::Initialize: Existing Vulkan handles detected; shutting down before reinitializing"); - Shutdown(); - } - - deviceExtensions_ = deviceExtensions; - validationLayersEnabled_ = enableValidationLayers; - instance_ = VK_NULL_HANDLE; - surface_ = VK_NULL_HANDLE; - physicalDevice_ = VK_NULL_HANDLE; - device_ = VK_NULL_HANDLE; - graphicsQueue_ = VK_NULL_HANDLE; - presentQueue_ = VK_NULL_HANDLE; - debugMessenger_ = VK_NULL_HANDLE; - - // Get required extensions from SDL - uint32_t extensionCount = 0; - const char* const* extensions = SDL_Vulkan_GetInstanceExtensions(&extensionCount); - if (!extensions) { - throw std::runtime_error("Failed to query Vulkan extensions from SDL"); - } - - std::vector requiredExtensions(extensions, extensions + extensionCount); - if (validationLayersEnabled_) { - auto it = std::find(requiredExtensions.begin(), requiredExtensions.end(), - VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - if (it == requiredExtensions.end()) { - requiredExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } - } - - CreateInstance(requiredExtensions); - SetupDebugMessenger(); - logger_->Trace("VulkanDeviceService", "Initialize", - "instanceCreated=" + std::string(instance_ != VK_NULL_HANDLE ? "true" : "false") + - ", selectionDeferredUntilSurface=true"); -} - -void VulkanDeviceService::CreateSurface(SDL_Window* window) { - logger_->Trace("VulkanDeviceService", "CreateSurface", - "windowIsNull=" + std::string(window ? "false" : "true")); - - if (!window) { - throw std::invalid_argument("Window cannot be null"); - } - - if (!SDL_Vulkan_CreateSurface(window, instance_, nullptr, &surface_)) { - throw std::runtime_error("Failed to create Vulkan surface"); - } - logger_->Trace("VulkanDeviceService", "CreateSurface", - "surfaceCreated=" + std::string(surface_ != VK_NULL_HANDLE ? "true" : "false")); -} - -void VulkanDeviceService::CreateInstance(const std::vector& requiredExtensions) { - logger_->Trace("VulkanDeviceService", "CreateInstance", - "requiredExtensions.size=" + std::to_string(requiredExtensions.size())); - - // Check Vulkan availability - uint32_t apiVersion = 0; - VkResult enumResult = vkEnumerateInstanceVersion(&apiVersion); - if (enumResult != VK_SUCCESS) { - std::string errorMsg = "Vulkan is not available on this system.\n\n"; - errorMsg += "Please install Vulkan drivers:\n"; - errorMsg += "- Ubuntu/Debian: sudo apt install vulkan-tools libvulkan1\n"; - errorMsg += "- Fedora: sudo dnf install vulkan-tools vulkan-loader\n"; - errorMsg += "- Arch: sudo pacman -S vulkan-tools vulkan-icd-loader\n"; - throw std::runtime_error(errorMsg); - } - - uint32_t major = VK_API_VERSION_MAJOR(apiVersion); - uint32_t minor = VK_API_VERSION_MINOR(apiVersion); - uint32_t patch = VK_API_VERSION_PATCH(apiVersion); - std::cout << "Vulkan Version: " << major << "." << minor << "." << patch << "\n"; - - if (apiVersion < VK_API_VERSION_1_2) { - throw std::runtime_error("Vulkan 1.2 or higher required"); - } - - VkApplicationInfo appInfo{}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - appInfo.pApplicationName = "SDL3 Vulkan"; - appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.pEngineName = "No Engine"; - appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_2; - - // Enable validation layers if requested - std::vector layerList; - if (validationLayersEnabled_) { - uint32_t layerCount; - vkEnumerateInstanceLayerProperties(&layerCount, nullptr); - std::vector availableLayers(layerCount); - vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); - - const char* validationLayer = "VK_LAYER_KHRONOS_validation"; - for (const auto& layer : availableLayers) { - if (strcmp(layer.layerName, validationLayer) == 0) { - layerList.push_back(validationLayer); - std::cout << "Validation layer enabled: " << validationLayer << "\n"; - break; - } - } - } - - VkInstanceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - createInfo.pApplicationInfo = &appInfo; - createInfo.enabledLayerCount = static_cast(layerList.size()); - createInfo.ppEnabledLayerNames = layerList.data(); - createInfo.enabledExtensionCount = static_cast(requiredExtensions.size()); - createInfo.ppEnabledExtensionNames = requiredExtensions.data(); - createInfo.pNext = nullptr; - - VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; - if (validationLayersEnabled_ && !layerList.empty()) { - PopulateDebugMessengerCreateInfo(debugCreateInfo); - createInfo.pNext = &debugCreateInfo; - } - - if (vkCreateInstance(&createInfo, nullptr, &instance_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create Vulkan instance"); - } -} - -void VulkanDeviceService::PickPhysicalDevice() { - const bool instanceIsNull = instance_ == VK_NULL_HANDLE; - const bool surfaceIsNull = surface_ == VK_NULL_HANDLE; - logger_->Trace("VulkanDeviceService", "PickPhysicalDevice", - "instanceIsNull=" + std::string(instanceIsNull ? "true" : "false") + - ", surfaceIsNull=" + std::string(surfaceIsNull ? "true" : "false")); - - if (instanceIsNull) { - throw std::runtime_error("Vulkan instance must be created before selecting a physical device"); - } - if (surfaceIsNull) { - throw std::runtime_error("Vulkan surface must be created before selecting a physical device"); - } - - uint32_t deviceCount = 0; - VkResult enumResult = vkEnumeratePhysicalDevices(instance_, &deviceCount, nullptr); - if (enumResult != VK_SUCCESS || deviceCount == 0) { - throw std::runtime_error("Failed to find GPUs with Vulkan support"); - } - - std::vector devices(deviceCount); - vkEnumeratePhysicalDevices(instance_, &deviceCount, devices.data()); - - std::cout << "\n=== GPU Detection ===\n"; - for (size_t i = 0; i < devices.size(); ++i) { - VkPhysicalDeviceProperties props; - vkGetPhysicalDeviceProperties(devices[i], &props); - - std::cout << "GPU " << i << ": " << props.deviceName << "\n"; - - if (IsDeviceSuitable(devices[i])) { - physicalDevice_ = devices[i]; - std::cout << " -> SELECTED\n"; - break; - } else { - std::cout << " -> UNSUITABLE\n"; - } - } - std::cout << "==================\n\n"; - - if (physicalDevice_ == VK_NULL_HANDLE) { - throw std::runtime_error("Failed to find a suitable GPU"); - } -} - -bool VulkanDeviceService::IsDeviceSuitable(VkPhysicalDevice device) const { - const bool surfaceIsNull = surface_ == VK_NULL_HANDLE; - logger_->Trace("VulkanDeviceService", "IsDeviceSuitable", - "deviceIsNull=" + std::string(device == VK_NULL_HANDLE ? "true" : "false") + - ", surfaceIsNull=" + std::string(surfaceIsNull ? "true" : "false")); - if (surface_ == VK_NULL_HANDLE) { - throw std::runtime_error("Vulkan surface must be created before checking device suitability"); - } - QueueFamilyIndices indices = FindQueueFamilies(device); - bool extensionsSupported = CheckDeviceExtensionSupport(device); - - bool swapChainAdequate = false; - if (extensionsSupported) { - // Check swapchain support - VkSurfaceCapabilitiesKHR capabilities; - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface_, &capabilities); - - uint32_t formatCount; - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface_, &formatCount, nullptr); - - uint32_t presentModeCount; - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface_, &presentModeCount, nullptr); - - swapChainAdequate = formatCount > 0 && presentModeCount > 0; - } - - return indices.IsComplete() && extensionsSupported && swapChainAdequate; -} - -QueueFamilyIndices VulkanDeviceService::FindQueueFamilies(VkPhysicalDevice device) const { - const bool surfaceIsNull = surface_ == VK_NULL_HANDLE; - logger_->Trace("VulkanDeviceService", "FindQueueFamilies", - "deviceIsNull=" + std::string(device == VK_NULL_HANDLE ? "true" : "false") + - ", surfaceIsNull=" + std::string(surfaceIsNull ? "true" : "false")); - if (surface_ == VK_NULL_HANDLE) { - throw std::runtime_error("Vulkan surface must be created before querying queue families"); - } - QueueFamilyIndices indices; - - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); - - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); - - for (uint32_t i = 0; i < queueFamilies.size(); ++i) { - if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { - indices.graphicsFamily = i; - } - - VkBool32 presentSupport = false; - vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface_, &presentSupport); - if (presentSupport) { - indices.presentFamily = i; - } - - if (indices.IsComplete()) { - break; - } - } - - return indices; -} - -bool VulkanDeviceService::CheckDeviceExtensionSupport(VkPhysicalDevice device) const { - logger_->Trace("VulkanDeviceService", "CheckDeviceExtensionSupport", - "deviceIsNull=" + std::string(device == VK_NULL_HANDLE ? "true" : "false")); - uint32_t extensionCount; - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); - - std::vector availableExtensions(extensionCount); - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); - - std::set requiredExtensions(deviceExtensions_.begin(), deviceExtensions_.end()); - - for (const auto& extension : availableExtensions) { - requiredExtensions.erase(extension.extensionName); - } - - return requiredExtensions.empty(); -} - -void VulkanDeviceService::CreateLogicalDevice() { - logger_->Trace("VulkanDeviceService", "CreateLogicalDevice"); - - if (surface_ == VK_NULL_HANDLE) { - logger_->Trace("VulkanDeviceService", "CreateLogicalDevice", "surfaceIsNull=true"); - throw std::runtime_error("Vulkan surface must be created before creating the logical device"); - } - - if (physicalDevice_ == VK_NULL_HANDLE) { - logger_->Trace("VulkanDeviceService", "CreateLogicalDevice", "physicalDeviceSelected=false"); - PickPhysicalDevice(); - } - logger_->Trace("VulkanDeviceService", "CreateLogicalDevice", - "physicalDeviceSelected=" + std::string(physicalDevice_ != VK_NULL_HANDLE ? "true" : "false")); - - QueueFamilyIndices indices = FindQueueFamilies(physicalDevice_); - - std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; - - float queuePriority = 1.0f; - for (uint32_t queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo{}; - queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueCreateInfo.queueFamilyIndex = queueFamily; - queueCreateInfo.queueCount = 1; - queueCreateInfo.pQueuePriorities = &queuePriority; - queueCreateInfos.push_back(queueCreateInfo); - } - - VkPhysicalDeviceFeatures deviceFeatures{}; - - VkDeviceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); - createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = static_cast(deviceExtensions_.size()); - createInfo.ppEnabledExtensionNames = deviceExtensions_.data(); - - if (vkCreateDevice(physicalDevice_, &createInfo, nullptr, &device_) != VK_SUCCESS) { - throw std::runtime_error("Failed to create logical device"); - } - - logger_->Info("Logical device created successfully"); - vkGetDeviceQueue(device_, indices.graphicsFamily, 0, &graphicsQueue_); - vkGetDeviceQueue(device_, indices.presentFamily, 0, &presentQueue_); -} - -void VulkanDeviceService::PopulateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) const { - createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; - createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; - createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; - createInfo.pfnUserCallback = DebugCallback; - createInfo.pUserData = logger_.get(); -} - -void VulkanDeviceService::SetupDebugMessenger() { - logger_->Trace("VulkanDeviceService", "SetupDebugMessenger", - "validationLayersEnabled=" + std::string(validationLayersEnabled_ ? "true" : "false")); - if (!validationLayersEnabled_) { - return; - } - - VkDebugUtilsMessengerCreateInfoEXT createInfo{}; - PopulateDebugMessengerCreateInfo(createInfo); - - if (CreateDebugUtilsMessengerEXT(instance_, &createInfo, nullptr, &debugMessenger_) != VK_SUCCESS) { - throw std::runtime_error("Failed to set up Vulkan debug messenger"); - } -} - -void VulkanDeviceService::Shutdown() noexcept { - logger_->Trace("VulkanDeviceService", "Shutdown"); - if (device_ != VK_NULL_HANDLE) { - vkDestroyDevice(device_, nullptr); - device_ = VK_NULL_HANDLE; - } - - if (surface_ != VK_NULL_HANDLE) { - vkDestroySurfaceKHR(instance_, surface_, nullptr); - surface_ = VK_NULL_HANDLE; - } - - if (debugMessenger_ != VK_NULL_HANDLE) { - DestroyDebugUtilsMessengerEXT(instance_, debugMessenger_, nullptr); - debugMessenger_ = VK_NULL_HANDLE; - } - - if (instance_ != VK_NULL_HANDLE) { - vkDestroyInstance(instance_, nullptr); - instance_ = VK_NULL_HANDLE; - } -} - -void VulkanDeviceService::WaitIdle() { - logger_->Trace("VulkanDeviceService", "WaitIdle"); - if (device_ != VK_NULL_HANDLE) { - vkDeviceWaitIdle(device_); - } -} - -QueueFamilyIndices VulkanDeviceService::GetQueueFamilies() const { - logger_->Trace("VulkanDeviceService", "GetQueueFamilies"); - return FindQueueFamilies(physicalDevice_); -} - -uint32_t VulkanDeviceService::FindMemoryType(uint32_t typeFilter, - VkMemoryPropertyFlags properties) const { - logger_->Trace("VulkanDeviceService", "FindMemoryType", - "typeFilter=" + std::to_string(typeFilter) + - ", properties=" + std::to_string(static_cast(properties))); - VkPhysicalDeviceMemoryProperties memProperties; - vkGetPhysicalDeviceMemoryProperties(physicalDevice_, &memProperties); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) { - if ((typeFilter & (1 << i)) && - (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("Failed to find suitable memory type"); -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/vulkan_device_service.hpp b/src/services/impl/vulkan_device_service.hpp deleted file mode 100644 index f5d5dac..0000000 --- a/src/services/impl/vulkan_device_service.hpp +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -#include "../interfaces/i_vulkan_device_service.hpp" -#include "../interfaces/i_logger.hpp" -#include "../../di/lifecycle.hpp" -#include -#include -#include - -namespace sdl3cpp::services::impl { - -/** - * @brief Vulkan device service implementation. - * - * Small, focused service (~300 lines) for Vulkan device initialization. - * Handles instance, physical device, logical device, and queue management. - */ -class VulkanDeviceService : public IVulkanDeviceService, - public di::IShutdownable { -public: - explicit VulkanDeviceService(std::shared_ptr logger); - ~VulkanDeviceService() override; - - // IVulkanDeviceService interface - void Initialize(const std::vector& deviceExtensions, - bool enableValidationLayers = false) override; - void CreateSurface(SDL_Window* window) override; - void CreateLogicalDevice() override; - void Shutdown() noexcept override; - void WaitIdle() override; - - VkInstance GetInstance() const override { - logger_->Trace("VulkanDeviceService", "GetInstance"); - return instance_; - } - VkSurfaceKHR GetSurface() const override { - logger_->Trace("VulkanDeviceService", "GetSurface"); - return surface_; - } - VkPhysicalDevice GetPhysicalDevice() const override { - logger_->Trace("VulkanDeviceService", "GetPhysicalDevice"); - return physicalDevice_; - } - VkDevice GetDevice() const override { - logger_->Trace("VulkanDeviceService", "GetDevice"); - return device_; - } - VkQueue GetGraphicsQueue() const override { - logger_->Trace("VulkanDeviceService", "GetGraphicsQueue"); - return graphicsQueue_; - } - VkQueue GetPresentQueue() const override { - logger_->Trace("VulkanDeviceService", "GetPresentQueue"); - return presentQueue_; - } - QueueFamilyIndices GetQueueFamilies() const override; - - uint32_t FindMemoryType(uint32_t typeFilter, - VkMemoryPropertyFlags properties) const override; - -private: - std::shared_ptr logger_; - - VkInstance instance_ = VK_NULL_HANDLE; - VkSurfaceKHR surface_ = VK_NULL_HANDLE; - VkPhysicalDevice physicalDevice_ = VK_NULL_HANDLE; - VkDevice device_ = VK_NULL_HANDLE; - VkQueue graphicsQueue_ = VK_NULL_HANDLE; - VkQueue presentQueue_ = VK_NULL_HANDLE; - VkDebugUtilsMessengerEXT debugMessenger_ = VK_NULL_HANDLE; - - std::vector deviceExtensions_; - bool validationLayersEnabled_ = false; - - // Helper methods - void CreateInstance(const std::vector& requiredExtensions); - void SetupDebugMessenger(); - void PopulateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) const; - void PickPhysicalDevice(); - bool IsDeviceSuitable(VkPhysicalDevice device) const; - QueueFamilyIndices FindQueueFamilies(VkPhysicalDevice device) const; - bool CheckDeviceExtensionSupport(VkPhysicalDevice device) const; -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/vulkan_graphics_backend.cpp b/src/services/impl/vulkan_graphics_backend.cpp deleted file mode 100644 index 39e9e60..0000000 --- a/src/services/impl/vulkan_graphics_backend.cpp +++ /dev/null @@ -1,301 +0,0 @@ -#include "vulkan_graphics_backend.hpp" - -#include "../../core/vertex.hpp" -#include -#include -#include -#include -#include - -namespace sdl3cpp::services::impl { - -VulkanGraphicsBackend::VulkanGraphicsBackend(std::shared_ptr deviceService, - std::shared_ptr swapchainService, - std::shared_ptr renderCommandService, - std::shared_ptr pipelineService, - std::shared_ptr bufferService, - std::shared_ptr logger) - : deviceService_(std::move(deviceService)), - swapchainService_(std::move(swapchainService)), - renderCommandService_(std::move(renderCommandService)), - pipelineService_(std::move(pipelineService)), - bufferService_(std::move(bufferService)), - logger_(std::move(logger)), - window_(nullptr), - initialized_(false), - currentImageIndex_(0), - frameCommands_(), - currentViewProj_{} { - logger_->Trace("VulkanGraphicsBackend", "VulkanGraphicsBackend", - "deviceService=" + std::string(deviceService_ ? "set" : "null") + - ", swapchainService=" + std::string(swapchainService_ ? "set" : "null") + - ", renderCommandService=" + std::string(renderCommandService_ ? "set" : "null") + - ", pipelineService=" + std::string(pipelineService_ ? "set" : "null") + - ", bufferService=" + std::string(bufferService_ ? "set" : "null")); -} - -VulkanGraphicsBackend::~VulkanGraphicsBackend() { - logger_->Trace("VulkanGraphicsBackend", "~VulkanGraphicsBackend"); - if (initialized_) { - Shutdown(); - } -} - -void VulkanGraphicsBackend::Initialize(void* window, const GraphicsConfig& config) { - logger_->Trace("VulkanGraphicsBackend", "Initialize"); - - if (initialized_) return; - - window_ = static_cast(window); - - // Initialize Vulkan device - std::vector deviceExtensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; - deviceService_->Initialize(deviceExtensions, config.enableValidationLayers); - deviceService_->CreateSurface(window_); - deviceService_->CreateLogicalDevice(); - - // Get window size for swapchain - int width, height; - SDL_GetWindowSizeInPixels(window_, &width, &height); - - // Initialize swapchain - swapchainService_->CreateSwapchain(static_cast(width), static_cast(height)); - - // Initialize render command service (this creates command pools/buffers) - // Note: RenderCommandService initialization happens in its constructor - - initialized_ = true; -} - -void VulkanGraphicsBackend::Shutdown() { - logger_->Trace("VulkanGraphicsBackend", "Shutdown"); - - if (!initialized_) return; - - // Cleanup in reverse order - renderCommandService_->Cleanup(); - pipelineService_->Cleanup(); - bufferService_->Cleanup(); - swapchainService_->CleanupSwapchain(); - deviceService_->Shutdown(); - - initialized_ = false; -} - -void VulkanGraphicsBackend::RecreateSwapchain(uint32_t width, uint32_t height) { - logger_->Trace("VulkanGraphicsBackend", "RecreateSwapchain", - "width=" + std::to_string(width) + - ", height=" + std::to_string(height)); - - if (!initialized_) { - return; - } - - if (width == 0 || height == 0) { - logger_->Warn("VulkanGraphicsBackend::RecreateSwapchain: Skipping swapchain recreation for zero size"); - return; - } - - deviceService_->WaitIdle(); - swapchainService_->RecreateSwapchain(width, height); - - VkExtent2D extent = swapchainService_->GetSwapchainExtent(); - pipelineService_->RecreatePipelines(swapchainService_->GetRenderPass(), extent); - renderCommandService_->OnSwapchainRecreated(); -} - -void VulkanGraphicsBackend::WaitIdle() { - logger_->Trace("VulkanGraphicsBackend", "WaitIdle"); - deviceService_->WaitIdle(); -} - -GraphicsDeviceHandle VulkanGraphicsBackend::CreateDevice() { - logger_->Trace("VulkanGraphicsBackend", "CreateDevice"); - // Device is already created in Initialize, just return a handle - return reinterpret_cast(deviceService_->GetDevice()); -} - -void VulkanGraphicsBackend::DestroyDevice(GraphicsDeviceHandle device) { - logger_->Trace("VulkanGraphicsBackend", "DestroyDevice"); - // Device cleanup happens in Shutdown -} - -GraphicsPipelineHandle VulkanGraphicsBackend::CreatePipeline(GraphicsDeviceHandle device, const std::string& shaderKey, const ShaderPaths& shaderPaths) { - logger_->Trace("VulkanGraphicsBackend", "CreatePipeline", "shaderKey=" + shaderKey); - - // Register shader with pipeline service - pipelineService_->RegisterShader(shaderKey, shaderPaths); - - // Compile pipeline with render pass from swapchain service - // Note: This assumes swapchain service has created the render pass - VkExtent2D extent = swapchainService_->GetSwapchainExtent(); - if (logger_) { - logger_->Trace("VulkanGraphicsBackend", "CreatePipeline", - "swapchainExtent=" + std::to_string(extent.width) + "x" + - std::to_string(extent.height)); - } - - pipelineService_->CompileAll(swapchainService_->GetRenderPass(), extent); - - // Return the pipeline handle - VkPipeline pipeline = pipelineService_->GetPipeline(shaderKey); - GraphicsPipelineHandle handle = reinterpret_cast(pipeline); - - // Store the mapping from handle to key - pipelineToShaderKey_[handle] = shaderKey; - - return handle; -} - -void VulkanGraphicsBackend::DestroyPipeline(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline) { - logger_->Trace("VulkanGraphicsBackend", "DestroyPipeline"); - // Pipeline cleanup happens in pipeline service Shutdown -} - -GraphicsBufferHandle VulkanGraphicsBackend::CreateVertexBuffer(GraphicsDeviceHandle device, const std::vector& data) { - logger_->Trace("VulkanGraphicsBackend", "CreateVertexBuffer", "data.size=" + std::to_string(data.size())); - - if (data.empty()) { - logger_->Error("VulkanGraphicsBackend::CreateVertexBuffer: No vertex data provided"); - return nullptr; - } - - if (data.size() % sizeof(core::Vertex) != 0) { - logger_->Error("VulkanGraphicsBackend::CreateVertexBuffer: Vertex data size is not aligned to Vertex"); - return nullptr; - } - - const size_t vertexCount = data.size() / sizeof(core::Vertex); - if (logger_) { - logger_->Trace("VulkanGraphicsBackend", "CreateVertexBuffer", - "vertexCount=" + std::to_string(vertexCount)); - } - - std::vector vertices(vertexCount); - std::memcpy(vertices.data(), data.data(), data.size()); - bufferService_->UploadVertexData(vertices); - - return reinterpret_cast(bufferService_->GetVertexBuffer()); -} - -GraphicsBufferHandle VulkanGraphicsBackend::CreateIndexBuffer(GraphicsDeviceHandle device, const std::vector& data) { - logger_->Trace("VulkanGraphicsBackend", "CreateIndexBuffer", "data.size=" + std::to_string(data.size())); - - if (data.empty()) { - logger_->Error("VulkanGraphicsBackend::CreateIndexBuffer: No index data provided"); - return nullptr; - } - - if (data.size() % sizeof(uint16_t) != 0) { - logger_->Error("VulkanGraphicsBackend::CreateIndexBuffer: Index data size is not aligned to uint16_t"); - return nullptr; - } - - const size_t indexCount = data.size() / sizeof(uint16_t); - if (logger_) { - logger_->Trace("VulkanGraphicsBackend", "CreateIndexBuffer", - "indexCount=" + std::to_string(indexCount)); - } - - std::vector indices(indexCount); - std::memcpy(indices.data(), data.data(), data.size()); - bufferService_->UploadIndexData(indices); - - return reinterpret_cast(bufferService_->GetIndexBuffer()); -} - -void VulkanGraphicsBackend::DestroyBuffer(GraphicsDeviceHandle device, GraphicsBufferHandle buffer) { - logger_->Trace("VulkanGraphicsBackend", "DestroyBuffer"); - // Buffer cleanup happens in buffer service Shutdown -} - -bool VulkanGraphicsBackend::BeginFrame(GraphicsDeviceHandle device) { - logger_->Trace("VulkanGraphicsBackend", "BeginFrame"); - - frameCommands_.clear(); - return renderCommandService_->BeginFrame(currentImageIndex_); -} - -bool VulkanGraphicsBackend::EndFrame(GraphicsDeviceHandle device) { - logger_->Trace("VulkanGraphicsBackend", "EndFrame"); - - // Record all accumulated commands - if (renderGraphEnabled_) { - renderCommandService_->RecordRenderGraph(currentImageIndex_, renderGraphDefinition_, - frameCommands_, currentViewProj_); - } else { - renderCommandService_->RecordCommands(currentImageIndex_, frameCommands_, currentViewProj_); - } - - // End the frame - return renderCommandService_->EndFrame(currentImageIndex_); -} - -void VulkanGraphicsBackend::SetViewProjection(const std::array& viewProj) { - logger_->Trace("VulkanGraphicsBackend", "SetViewProjection"); - currentViewProj_ = viewProj; -} - -void VulkanGraphicsBackend::SetRenderGraphDefinition(const RenderGraphDefinition& definition) { - logger_->Trace("VulkanGraphicsBackend", "SetRenderGraphDefinition", - "resources=" + std::to_string(definition.resources.size()) + - ", passes=" + std::to_string(definition.passes.size())); - renderGraphDefinition_ = definition; - renderGraphEnabled_ = !definition.passes.empty(); -} - -void VulkanGraphicsBackend::Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline, - GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer, - uint32_t indexOffset, uint32_t indexCount, int32_t vertexOffset, - const std::array& modelMatrix) { - logger_->Trace("VulkanGraphicsBackend", "Draw", - "indexOffset=" + std::to_string(indexOffset) + - ", indexCount=" + std::to_string(indexCount) + - ", vertexOffset=" + std::to_string(vertexOffset)); - - // Find the shader key for this pipeline - auto it = pipelineToShaderKey_.find(pipeline); - if (it == pipelineToShaderKey_.end()) { - logger_->Error("VulkanGraphicsBackend::Draw: Pipeline not found in mapping"); - return; - } - - // Create a render command - RenderCommand command; - command.indexOffset = indexOffset; - command.indexCount = indexCount; - command.vertexOffset = vertexOffset; - command.shaderKey = it->second; - command.modelMatrix = modelMatrix; - - // Accumulate the command for later recording - frameCommands_.push_back(command); -} - -GraphicsDeviceHandle VulkanGraphicsBackend::GetPhysicalDevice() const { - logger_->Trace("VulkanGraphicsBackend", "GetPhysicalDevice"); - return reinterpret_cast(deviceService_->GetPhysicalDevice()); -} - -std::pair VulkanGraphicsBackend::GetSwapchainExtent() const { - logger_->Trace("VulkanGraphicsBackend", "GetSwapchainExtent"); - VkExtent2D extent = swapchainService_->GetSwapchainExtent(); - return {extent.width, extent.height}; -} - -uint32_t VulkanGraphicsBackend::GetSwapchainFormat() const { - logger_->Trace("VulkanGraphicsBackend", "GetSwapchainFormat"); - return static_cast(swapchainService_->GetSwapchainImageFormat()); -} - -void* VulkanGraphicsBackend::GetCurrentCommandBuffer() const { - logger_->Trace("VulkanGraphicsBackend", "GetCurrentCommandBuffer"); - return reinterpret_cast(renderCommandService_->GetCurrentCommandBuffer()); -} - -void* VulkanGraphicsBackend::GetGraphicsQueue() const { - logger_->Trace("VulkanGraphicsBackend", "GetGraphicsQueue"); - return reinterpret_cast(deviceService_->GetGraphicsQueue()); -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/vulkan_graphics_backend.hpp b/src/services/impl/vulkan_graphics_backend.hpp deleted file mode 100644 index f2a095f..0000000 --- a/src/services/impl/vulkan_graphics_backend.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include "../interfaces/i_graphics_backend.hpp" -#include "../interfaces/i_vulkan_device_service.hpp" -#include "../interfaces/i_swapchain_service.hpp" -#include "../interfaces/i_render_command_service.hpp" -#include "../interfaces/i_pipeline_service.hpp" -#include "../interfaces/i_buffer_service.hpp" -#include "../interfaces/i_logger.hpp" -#include "../interfaces/graphics_types.hpp" -#include -#include -#include - -namespace sdl3cpp::services::impl { - -/** - * @brief Vulkan implementation of the graphics backend. - * - * Uses existing Vulkan services for device, swapchain, commands, pipelines, and buffers. - */ -class VulkanGraphicsBackend : public IGraphicsBackend { -public: - VulkanGraphicsBackend(std::shared_ptr deviceService, - std::shared_ptr swapchainService, - std::shared_ptr renderCommandService, - std::shared_ptr pipelineService, - std::shared_ptr bufferService, - std::shared_ptr logger); - ~VulkanGraphicsBackend() 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& data) override; - GraphicsBufferHandle CreateIndexBuffer(GraphicsDeviceHandle device, const std::vector& data) override; - void DestroyBuffer(GraphicsDeviceHandle device, GraphicsBufferHandle buffer) override; - - bool BeginFrame(GraphicsDeviceHandle device) override; - bool EndFrame(GraphicsDeviceHandle device) override; - - void SetViewProjection(const std::array& 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& modelMatrix) override; - - GraphicsDeviceHandle GetPhysicalDevice() const override; - std::pair GetSwapchainExtent() const override; - uint32_t GetSwapchainFormat() const override; - void* GetCurrentCommandBuffer() const override; - void* GetGraphicsQueue() const override; - -private: - std::shared_ptr deviceService_; - std::shared_ptr swapchainService_; - std::shared_ptr renderCommandService_; - std::shared_ptr pipelineService_; - std::shared_ptr bufferService_; - std::shared_ptr logger_; - - SDL_Window* window_; - bool initialized_; - uint32_t currentImageIndex_; - std::vector frameCommands_; - std::array currentViewProj_; - RenderGraphDefinition renderGraphDefinition_{}; - bool renderGraphEnabled_ = false; - std::unordered_map pipelineToShaderKey_; -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/vulkan_gui_service.cpp b/src/services/impl/vulkan_gui_service.cpp deleted file mode 100644 index f378959..0000000 --- a/src/services/impl/vulkan_gui_service.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "vulkan_gui_service.hpp" -#include "../interfaces/i_logger.hpp" -#include - -namespace sdl3cpp::services::impl { - -VulkanGuiService::VulkanGuiService(std::shared_ptr logger, - std::shared_ptr rendererService) - : logger_(std::move(logger)), - rendererService_(std::move(rendererService)) { - if (logger_) { - logger_->Trace("VulkanGuiService", "VulkanGuiService", - "rendererService=" + std::string(rendererService_ ? "set" : "null")); - } -} - -VulkanGuiService::~VulkanGuiService() { - if (logger_) { - logger_->Trace("VulkanGuiService", "~VulkanGuiService"); - } - if (initialized_) { - Shutdown(); - } -} - -void VulkanGuiService::Initialize(VkDevice device, - VkPhysicalDevice physicalDevice, - VkFormat format, - VkRenderPass renderPass, - const std::filesystem::path& resourcePath) { - logger_->Trace("VulkanGuiService", "Initialize", - "deviceIsNull=" + std::string(device == VK_NULL_HANDLE ? "true" : "false") + - ", physicalDeviceIsNull=" + std::string(physicalDevice == VK_NULL_HANDLE ? "true" : "false") + - ", format=" + std::to_string(static_cast(format)) + - ", renderPassIsNull=" + std::string(renderPass == VK_NULL_HANDLE ? "true" : "false") + - ", resourcePath=" + resourcePath.string()); - - if (initialized_) { - return; - } - - if (!rendererService_) { - throw std::runtime_error("GUI renderer service not available"); - } - rendererService_->Initialize(device, physicalDevice, format, renderPass, resourcePath); - initialized_ = true; - - logger_->Info("GUI service initialized"); -} - -void VulkanGuiService::PrepareFrame(const std::vector& commands, - uint32_t width, - uint32_t height) { - logger_->Trace("VulkanGuiService", "PrepareFrame", - "commands.size=" + std::to_string(commands.size()) + - ", width=" + std::to_string(width) + - ", height=" + std::to_string(height)); - - if (!rendererService_) { - throw std::runtime_error("GUI renderer service not available"); - } - rendererService_->PrepareFrame(commands, width, height); -} - -void VulkanGuiService::RenderToSwapchain(VkCommandBuffer commandBuffer, VkImage image) { - logger_->Trace("VulkanGuiService", "RenderToSwapchain", - "commandBufferIsNull=" + std::string(commandBuffer == VK_NULL_HANDLE ? "true" : "false") + - ", imageIsNull=" + std::string(image == VK_NULL_HANDLE ? "true" : "false")); - - if (!rendererService_) { - throw std::runtime_error("GUI renderer service not available"); - } - - rendererService_->RenderToSwapchain(commandBuffer, image); -} - -void VulkanGuiService::Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) { - logger_->Trace("VulkanGuiService", "Resize", - "width=" + std::to_string(width) + - ", height=" + std::to_string(height) + - ", format=" + std::to_string(static_cast(format)) + - ", renderPassIsNull=" + std::string(renderPass == VK_NULL_HANDLE ? "true" : "false")); - - if (rendererService_) { - rendererService_->Resize(width, height, format, renderPass); - } -} - -void VulkanGuiService::Shutdown() noexcept { - logger_->Trace("VulkanGuiService", "Shutdown"); - - if (!initialized_) { - return; - } - - if (rendererService_) { - rendererService_->Shutdown(); - } - initialized_ = false; - - logger_->Info("GUI service shutdown"); -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/vulkan_gui_service.hpp b/src/services/impl/vulkan_gui_service.hpp deleted file mode 100644 index b4c8883..0000000 --- a/src/services/impl/vulkan_gui_service.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include "../interfaces/i_gui_service.hpp" -#include "../interfaces/i_gui_renderer_service.hpp" -#include "../interfaces/i_logger.hpp" -#include "../../di/lifecycle.hpp" -#include - -namespace sdl3cpp::services::impl { - -/** - * @brief Vulkan GUI service implementation. - * - * Small wrapper service (~60 lines) around the GUI renderer service. - * Provides 2D GUI overlay rendering for SVG, text, and shapes. - */ -class VulkanGuiService : public IGuiService, - public di::IShutdownable { -public: - VulkanGuiService(std::shared_ptr logger, - std::shared_ptr rendererService); - ~VulkanGuiService() override; - - // IGuiService interface - void Initialize(VkDevice device, - VkPhysicalDevice physicalDevice, - VkFormat format, - VkRenderPass renderPass, - const std::filesystem::path& resourcePath) override; - - void PrepareFrame(const std::vector& 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() noexcept override; - -private: - std::shared_ptr logger_; - std::shared_ptr rendererService_; - bool initialized_ = false; -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/interfaces/config_types.hpp b/src/services/interfaces/config_types.hpp index c6b807b..ef50bfe 100644 --- a/src/services/interfaces/config_types.hpp +++ b/src/services/interfaces/config_types.hpp @@ -82,22 +82,8 @@ struct AtmosphericsConfig { float pbrMetallic = 0.1f; }; -/** - * @brief Lua-defined render graph configuration. - */ -struct RenderGraphConfig { - bool enabled = false; - std::string functionName = "get_render_graph"; -}; - -enum class GraphicsBackendType { - Vulkan, - Bgfx -}; - -struct GraphicsBackendConfig { - GraphicsBackendType backend = GraphicsBackendType::Vulkan; - std::string bgfxRenderer = "vulkan"; +struct BgfxConfig { + std::string renderer = "vulkan"; }; struct MaterialXConfig { @@ -133,12 +119,11 @@ struct RuntimeConfig { uint32_t height = 768; std::filesystem::path scriptPath; bool luaDebug = false; - std::string windowTitle = "SDL3 Vulkan Demo"; + std::string windowTitle = "SDL3 Bgfx Demo"; MouseGrabConfig mouseGrab{}; InputBindings inputBindings{}; AtmosphericsConfig atmospherics{}; - RenderGraphConfig renderGraph{}; - GraphicsBackendConfig graphicsBackend{}; + BgfxConfig bgfx{}; MaterialXConfig materialX{}; GuiFontConfig guiFont{}; float guiOpacity = 1.0f; diff --git a/src/services/interfaces/graphics_types.hpp b/src/services/interfaces/graphics_types.hpp index a3e9bb1..3461dc4 100644 --- a/src/services/interfaces/graphics_types.hpp +++ b/src/services/interfaces/graphics_types.hpp @@ -3,7 +3,6 @@ #include #include #include -#include #include namespace sdl3cpp::services { @@ -12,9 +11,7 @@ namespace sdl3cpp::services { * @brief Graphics service configuration. */ struct GraphicsConfig { - std::vector deviceExtensions; uint32_t preferredFormat = 0; // Backend-specific format - bool enableValidationLayers = false; }; /** @@ -48,57 +45,4 @@ struct RenderCommand { std::array modelMatrix; }; -/** - * @brief Render graph value used for Lua-defined settings. - */ -struct RenderGraphValue { - enum class Type { - Number, - Boolean, - String, - Array - }; - - Type type = Type::Number; - double number = 0.0; - bool boolean = false; - std::string string; - std::vector array; -}; - -/** - * @brief Render graph resource definition loaded from Lua. - */ -struct RenderGraphResource { - std::string name; - std::string type; - std::string format; - std::string size; - std::array explicitSize{}; - bool hasExplicitSize = false; - uint32_t layers = 1; - uint32_t mips = 1; -}; - -/** - * @brief Render graph pass definition loaded from Lua. - */ -struct RenderGraphPass { - std::string name; - std::string kind; - std::string shader; - std::string output; - std::unordered_map inputs; - std::unordered_map outputs; - std::unordered_map settings; -}; - -/** - * @brief Render graph definition loaded from Lua. - */ -struct RenderGraphDefinition { - std::vector resources; - std::vector passes; -}; - } // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_buffer_service.hpp b/src/services/interfaces/i_buffer_service.hpp deleted file mode 100644 index 83c6be4..0000000 --- a/src/services/interfaces/i_buffer_service.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -#include "../../core/vertex.hpp" -#include -#include -#include - -namespace sdl3cpp::services { - -/** - * @brief Buffer service interface. - * - * Manages Vulkan buffers for vertex and index data. - * Small, focused service (~150 lines) for buffer management. - */ -class IBufferService { -public: - virtual ~IBufferService() = default; - - /** - * @brief Upload vertex data to GPU. - * - * @param vertices Vector of vertex data - * @throws std::runtime_error if upload fails - */ - virtual void UploadVertexData(const std::vector& vertices) = 0; - - /** - * @brief Upload index data to GPU. - * - * @param indices Vector of index data - * @throws std::runtime_error if upload fails - */ - virtual void UploadIndexData(const std::vector& indices) = 0; - - /** - * @brief Cleanup all buffers. - */ - virtual void Cleanup() = 0; - - /** - * @brief Get the vertex buffer. - * - * @return VkBuffer handle - */ - virtual VkBuffer GetVertexBuffer() const = 0; - - /** - * @brief Get the index buffer. - * - * @return VkBuffer handle - */ - virtual VkBuffer GetIndexBuffer() const = 0; - - /** - * @brief Get the number of vertices. - * - * @return Vertex count - */ - virtual size_t GetVertexCount() const = 0; - - /** - * @brief Get the number of indices. - * - * @return Index count - */ - virtual size_t GetIndexCount() const = 0; - - /** - * @brief Create a Vulkan buffer with memory allocation. - * - * @param size Buffer size in bytes - * @param usage Buffer usage flags - * @param properties Memory property flags - * @param[out] buffer Created buffer handle - * @param[out] bufferMemory Allocated memory handle - * @throws std::runtime_error if creation fails - */ - virtual void CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, - VkBuffer& buffer, VkDeviceMemory& bufferMemory) = 0; -}; - -} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_config_service.hpp b/src/services/interfaces/i_config_service.hpp index 1001bfe..a8c6edc 100644 --- a/src/services/interfaces/i_config_service.hpp +++ b/src/services/interfaces/i_config_service.hpp @@ -48,11 +48,6 @@ public: */ virtual std::string GetWindowTitle() const = 0; - /** - * @brief Get required Vulkan device extensions. - * @return List of extension names - */ - virtual std::vector GetDeviceExtensions() const = 0; /** * @brief Get configured input bindings. @@ -67,16 +62,10 @@ public: virtual const MouseGrabConfig& GetMouseGrabConfig() const = 0; /** - * @brief Get render graph settings. - * @return Render graph configuration + * @brief Get bgfx settings. + * @return Bgfx configuration */ - virtual const RenderGraphConfig& GetRenderGraphConfig() const = 0; - - /** - * @brief Get graphics backend settings. - * @return Graphics backend configuration - */ - virtual const GraphicsBackendConfig& GetGraphicsBackendConfig() const = 0; + virtual const BgfxConfig& GetBgfxConfig() const = 0; /** * @brief Get MaterialX settings. diff --git a/src/services/interfaces/i_graphics_backend.hpp b/src/services/interfaces/i_graphics_backend.hpp index f36c318..0b287ca 100644 --- a/src/services/interfaces/i_graphics_backend.hpp +++ b/src/services/interfaces/i_graphics_backend.hpp @@ -33,7 +33,7 @@ using GraphicsTextureHandle = void*; * @brief Graphics backend interface for abstracted rendering. * * Provides backend-agnostic methods for device management, pipelines, buffers, and rendering. - * Implementations handle Vulkan or GXM specifics. + * Implementations handle platform-specific details (bgfx, GXM, etc.). */ class IGraphicsBackend { public: @@ -146,13 +146,6 @@ public: */ virtual void SetViewProjection(const std::array& viewProj) = 0; - /** - * @brief Set the render graph definition (optional). - * - * @param definition Render graph definition - */ - virtual void SetRenderGraphDefinition(const RenderGraphDefinition& definition) = 0; - /** * @brief Draw with a pipeline. * diff --git a/src/services/interfaces/i_graphics_service.hpp b/src/services/interfaces/i_graphics_service.hpp index 85a4445..0a28e84 100644 --- a/src/services/interfaces/i_graphics_service.hpp +++ b/src/services/interfaces/i_graphics_service.hpp @@ -61,18 +61,6 @@ public: */ virtual void LoadShaders(const std::unordered_map& shaders) = 0; - /** - * @brief Store the Lua-defined render graph definition. - * - * @param definition Render graph definition - */ - virtual void SetRenderGraphDefinition(const RenderGraphDefinition& definition) = 0; - - /** - * @brief Get the current render graph definition. - */ - virtual const RenderGraphDefinition& GetRenderGraphDefinition() const = 0; - /** * @brief Upload vertex data to GPU buffer. * diff --git a/src/services/interfaces/i_gui_renderer_service.hpp b/src/services/interfaces/i_gui_renderer_service.hpp deleted file mode 100644 index c356ed2..0000000 --- a/src/services/interfaces/i_gui_renderer_service.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "gui_types.hpp" -#include -#include -#include - -namespace sdl3cpp::services { - -class IGuiRendererService { -public: - virtual ~IGuiRendererService() = default; - - virtual void Initialize(VkDevice device, - VkPhysicalDevice physicalDevice, - VkFormat format, - VkRenderPass renderPass, - const std::filesystem::path& resourcePath) = 0; - - virtual void PrepareFrame(const std::vector& commands, - uint32_t width, - uint32_t height) = 0; - - virtual void RenderToSwapchain(VkCommandBuffer commandBuffer, VkImage image) = 0; - - virtual void Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) = 0; - - virtual void Shutdown() noexcept = 0; - - virtual bool IsReady() const = 0; -}; - -} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_gui_service.hpp b/src/services/interfaces/i_gui_service.hpp index 3876d6e..9c59a8a 100644 --- a/src/services/interfaces/i_gui_service.hpp +++ b/src/services/interfaces/i_gui_service.hpp @@ -1,9 +1,7 @@ #pragma once #include -#include #include -#include namespace sdl3cpp::services { @@ -13,29 +11,12 @@ struct GuiCommand; /** * @brief GUI rendering service interface. * - * Handles 2D GUI overlay rendering using Vulkan. - * Delegates low-level draw work to the GUI renderer service. + * Consumes GUI commands produced by scripts and prepares any per-frame data. */ class IGuiService { public: virtual ~IGuiService() = default; - /** - * @brief Initialize the GUI renderer. - * - * @param device Vulkan logical device - * @param physicalDevice Vulkan physical device - * @param format Swapchain image format - * @param renderPass Vulkan render pass for GUI pipeline creation - * @param resourcePath Path to GUI resources (fonts, SVG files, etc.) - * @throws std::runtime_error if initialization fails - */ - virtual void Initialize(VkDevice device, - VkPhysicalDevice physicalDevice, - VkFormat format, - VkRenderPass renderPass, - const std::filesystem::path& resourcePath) = 0; - /** * @brief Prepare GUI commands for rendering. * @@ -48,32 +29,6 @@ public: virtual void PrepareFrame(const std::vector& commands, uint32_t width, uint32_t height) = 0; - - /** - * @brief Render GUI overlay to swapchain image. - * - * Records rendering commands into the provided command buffer. - * - * @param commandBuffer Vulkan command buffer to record into - * @param image Target swapchain image - */ - virtual void RenderToSwapchain(VkCommandBuffer commandBuffer, VkImage image) = 0; - - /** - * @brief Handle window resize. - * - * Recreates internal resources for the new window size. - * - * @param width New width in pixels - * @param height New height in pixels - * @param format Swapchain image format - */ - virtual void Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) = 0; - - /** - * @brief Shutdown and release GPU resources. - */ - virtual void Shutdown() = 0; }; } // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_pipeline_service.hpp b/src/services/interfaces/i_pipeline_service.hpp deleted file mode 100644 index 5aa6019..0000000 --- a/src/services/interfaces/i_pipeline_service.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once - -#include "graphics_types.hpp" -#include -#include -#include -#include -#include - -namespace sdl3cpp::services { - -/** - * @brief Pipeline service interface. - * - * Manages Vulkan graphics pipelines and shader compilation. - * Small, focused service (~200 lines) for pipeline management. - */ -class IPipelineService { -public: - virtual ~IPipelineService() = default; - - /** - * @brief Register a shader program. - * - * @param key Unique identifier for the shader - * @param paths Vertex and fragment shader file paths - */ - virtual void RegisterShader(const std::string& key, const ShaderPaths& paths) = 0; - - /** - * @brief Compile all registered shaders and create pipelines. - * - * @param renderPass Render pass for pipeline compatibility - * @param extent Viewport extent - * @throws std::runtime_error if compilation fails - */ - virtual void CompileAll(VkRenderPass renderPass, VkExtent2D extent) = 0; - - /** - * @brief Recreate pipelines (e.g., after swapchain recreation). - * - * @param renderPass New render pass - * @param extent New viewport extent - * @throws std::runtime_error if recreation fails - */ - virtual void RecreatePipelines(VkRenderPass renderPass, VkExtent2D extent) = 0; - - /** - * @brief Cleanup all pipelines. - */ - virtual void Cleanup() = 0; - - /** - * @brief Get a pipeline by shader key. - * - * @param key Shader identifier - * @return VkPipeline handle - * @throws std::out_of_range if key doesn't exist - */ - virtual VkPipeline GetPipeline(const std::string& key) const = 0; - - /** - * @brief Get the pipeline layout. - * - * @return VkPipelineLayout handle - */ - virtual VkPipelineLayout GetPipelineLayout() const = 0; - - /** - * @brief Get the descriptor set layout used by pipelines. - * - * @return VkDescriptorSetLayout handle - */ - virtual VkDescriptorSetLayout GetDescriptorSetLayout() const = 0; - - /** - * @brief Check if a shader exists. - * - * @param key Shader identifier - * @return true if registered, false otherwise - */ - virtual bool HasShader(const std::string& key) const = 0; - - /** - * @brief Get the number of registered shaders. - * - * @return Shader count - */ - virtual size_t GetShaderCount() const = 0; -}; - -} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_render_command_service.hpp b/src/services/interfaces/i_render_command_service.hpp deleted file mode 100644 index 045b73d..0000000 --- a/src/services/interfaces/i_render_command_service.hpp +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once - -#include "graphics_types.hpp" -#include -#include -#include -#include -#include - -namespace sdl3cpp::services { - -/** - * @brief Render command service interface. - * - * Manages command buffer recording and frame synchronization. - * Small, focused service (~200 lines) for rendering orchestration. - */ -class IRenderCommandService { -public: - virtual ~IRenderCommandService() = default; - - /** - * @brief Cleanup command pools and buffers. - */ - virtual void Cleanup() = 0; - - /** - * @brief Begin a new frame. - * - * Waits for the previous frame to complete and acquires the next image. - * - * @param imageIndex Output parameter for the swapchain image index - * @return true if successful, false if swapchain needs recreation - */ - virtual bool BeginFrame(uint32_t& imageIndex) = 0; - - /** - * @brief Record rendering commands. - * - * @param imageIndex Swapchain image index - * @param commands Render commands to execute - * @param viewProj View-projection matrix - */ - virtual void RecordCommands(uint32_t imageIndex, - const std::vector& commands, - const std::array& viewProj) = 0; - - /** - * @brief Record rendering commands using a render graph. - * - * @param imageIndex Swapchain image index - * @param graph Render graph definition - * @param commands Render commands to execute for scene passes - * @param viewProj View-projection matrix - */ - virtual void RecordRenderGraph(uint32_t imageIndex, - const RenderGraphDefinition& graph, - const std::vector& commands, - const std::array& viewProj) = 0; - - /** - * @brief End the frame and present. - * - * Submits command buffers and presents the image. - * - * @param imageIndex Swapchain image index - * @return true if successful, false if swapchain needs recreation - */ - virtual bool EndFrame(uint32_t imageIndex) = 0; - - /** - * @brief Get the current command buffer. - * - * @return VkCommandBuffer handle - */ - virtual VkCommandBuffer GetCurrentCommandBuffer() const = 0; - - /** - * @brief Get the current frame index. - * - * @return Frame index (0 to maxFramesInFlight-1) - */ - virtual uint32_t GetCurrentFrameIndex() const = 0; - - /** - * @brief Get the maximum frames in flight. - * - * @return Max concurrent frames - */ - virtual uint32_t GetMaxFramesInFlight() const = 0; - - /** - * @brief Handle swapchain recreation. - * - * Resets command buffers/synchronization and updates GUI renderer state. - */ - virtual void OnSwapchainRecreated() = 0; -}; - -} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_render_graph_script_service.hpp b/src/services/interfaces/i_render_graph_script_service.hpp deleted file mode 100644 index 89c30d6..0000000 --- a/src/services/interfaces/i_render_graph_script_service.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "graphics_types.hpp" - -namespace sdl3cpp::services { - -/** - * @brief Loads render graph definitions from Lua. - */ -class IRenderGraphScriptService { -public: - virtual ~IRenderGraphScriptService() = default; - - /** - * @brief Check if render graph execution is enabled. - */ - virtual bool IsEnabled() const = 0; - - /** - * @brief Check whether the configured Lua function exists. - */ - virtual bool HasRenderGraphFunction() const = 0; - - /** - * @brief Load the render graph definition from Lua. - */ - virtual RenderGraphDefinition LoadRenderGraph() = 0; -}; - -} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_shader_service.hpp b/src/services/interfaces/i_shader_service.hpp deleted file mode 100644 index ec5ce3e..0000000 --- a/src/services/interfaces/i_shader_service.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include "i_graphics_service.hpp" -#include -#include -#include - -namespace sdl3cpp::services { - -/** - * @brief Shader management service interface. - * - * Handles shader compilation, pipeline creation, and shader program management. - * Consumes shader paths from the shader script service. - */ -class IShaderService { -public: - virtual ~IShaderService() = default; - - /** - * @brief Register a shader program. - * - * @param key Unique identifier for this shader program - * @param paths Vertex and fragment shader file paths - * @throws std::runtime_error if shader files don't exist - */ - virtual void RegisterShader(const std::string& key, const ShaderPaths& paths) = 0; - - /** - * @brief Compile all registered shaders and create pipelines. - * - * Must be called after all shaders are registered and before rendering. - * - * @throws std::runtime_error if compilation fails - */ - virtual void CompileAll() = 0; - - /** - * @brief Get the Vulkan pipeline for a shader program. - * - * @param key Shader program identifier - * @return VkPipeline handle - * @throws std::out_of_range if shader key doesn't exist - */ - virtual VkPipeline GetPipeline(const std::string& key) const = 0; - - /** - * @brief Check if a shader program exists. - * - * @param key Shader program identifier - * @return true if registered, false otherwise - */ - virtual bool HasShader(const std::string& key) const = 0; - - /** - * @brief Get the number of registered shader programs. - * - * @return Shader count - */ - virtual size_t GetShaderCount() const = 0; - - /** - * @brief Reload all shaders (for hot-reload support). - * - * Recompiles all shaders from their source files. - * - * @throws std::runtime_error if compilation fails - */ - virtual void ReloadAll() = 0; -}; - -} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_swapchain_service.hpp b/src/services/interfaces/i_swapchain_service.hpp deleted file mode 100644 index 68b200f..0000000 --- a/src/services/interfaces/i_swapchain_service.hpp +++ /dev/null @@ -1,129 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace sdl3cpp::services { - -/** - * @brief Swapchain support details. - */ -struct SwapchainSupportDetails { - VkSurfaceCapabilitiesKHR capabilities; - std::vector formats; - std::vector presentModes; -}; - -/** - * @brief Swapchain service interface. - * - * Manages Vulkan swapchain creation, recreation, and presentation. - * Small, focused service (~250 lines) for swapchain management. - */ -class ISwapchainService { -public: - virtual ~ISwapchainService() = default; - - /** - * @brief Create the swapchain. - * - * @param width Desired width in pixels - * @param height Desired height in pixels - * @throws std::runtime_error if swapchain creation fails - */ - virtual void CreateSwapchain(uint32_t width, uint32_t height) = 0; - - /** - * @brief Recreate the swapchain (e.g., after window resize). - * - * @param width New width in pixels - * @param height New height in pixels - * @throws std::runtime_error if recreation fails - */ - virtual void RecreateSwapchain(uint32_t width, uint32_t height) = 0; - - /** - * @brief Cleanup the swapchain. - * - * Called before recreation or shutdown. - */ - virtual void CleanupSwapchain() = 0; - - /** - * @brief Acquire the next swapchain image. - * - * @param semaphore Semaphore to signal when image is available - * @param imageIndex Output parameter for the image index - * @return VkResult (VK_SUCCESS, VK_ERROR_OUT_OF_DATE_KHR, etc.) - */ - virtual VkResult AcquireNextImage(VkSemaphore semaphore, uint32_t& imageIndex) = 0; - - /** - * @brief Present the rendered image to the screen. - * - * @param waitSemaphores Semaphores to wait on before presenting - * @param imageIndex Index of the image to present - * @return VkResult (VK_SUCCESS, VK_ERROR_OUT_OF_DATE_KHR, etc.) - */ - virtual VkResult Present(const std::vector& waitSemaphores, - uint32_t imageIndex) = 0; - - /** - * @brief Get the swapchain handle. - * - * @return VkSwapchainKHR handle - */ - virtual VkSwapchainKHR GetSwapchain() const = 0; - - /** - * @brief Get swapchain images. - * - * @return Vector of swapchain image handles - */ - virtual const std::vector& GetSwapchainImages() const = 0; - - /** - * @brief Get swapchain image views. - * - * @return Vector of image view handles - */ - virtual const std::vector& GetSwapchainImageViews() const = 0; - - /** - * @brief Get swapchain framebuffers. - * - * @return Vector of framebuffer handles - */ - virtual const std::vector& GetSwapchainFramebuffers() const = 0; - - /** - * @brief Get the swapchain image format. - * - * @return VkFormat of swapchain images - */ - virtual VkFormat GetSwapchainImageFormat() const = 0; - - /** - * @brief Get the depth buffer format. - * - * @return VkFormat of depth images - */ - virtual VkFormat GetDepthFormat() const = 0; - - /** - * @brief Get the swapchain extent (size). - * - * @return VkExtent2D with width and height - */ - virtual VkExtent2D GetSwapchainExtent() const = 0; - - /** - * @brief Get the render pass. - * - * @return VkRenderPass handle - */ - virtual VkRenderPass GetRenderPass() const = 0; -}; - -} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_vulkan_device_service.hpp b/src/services/interfaces/i_vulkan_device_service.hpp deleted file mode 100644 index c1b83ef..0000000 --- a/src/services/interfaces/i_vulkan_device_service.hpp +++ /dev/null @@ -1,135 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -// Forward declare SDL type -struct SDL_Window; - -namespace sdl3cpp::services { - -/** - * @brief Queue family indices for Vulkan. - */ -struct QueueFamilyIndices { - uint32_t graphicsFamily = UINT32_MAX; - uint32_t presentFamily = UINT32_MAX; - - bool IsComplete() const { - return graphicsFamily != UINT32_MAX && presentFamily != UINT32_MAX; - } -}; - -/** - * @brief Vulkan device service interface. - * - * Manages Vulkan instance, physical device, logical device, and queues. - * Small, focused service (~300 lines) for device initialization. - */ -class IVulkanDeviceService { -public: - virtual ~IVulkanDeviceService() = default; - - /** - * @brief Initialize Vulkan instance and store required extensions. - * - * @param deviceExtensions Required device extensions - * @param enableValidationLayers Whether to enable validation layers - * @throws std::runtime_error if initialization fails - */ - virtual void Initialize(const std::vector& deviceExtensions, - bool enableValidationLayers = false) = 0; - - /** - * @brief Create Vulkan surface for the given window. - * - * Must be called after Initialize() and after window is created. - * - * @param window SDL window for surface creation - * @throws std::runtime_error if surface creation fails - */ - virtual void CreateSurface(SDL_Window* window) = 0; - - /** - * @brief Create the logical device and retrieve queues. - * - * Must be called after CreateSurface(). This selects the physical device. - * - * @throws std::runtime_error if device creation fails - */ - virtual void CreateLogicalDevice() = 0; - - /** - * @brief Shutdown and release all Vulkan resources. - */ - virtual void Shutdown() = 0; - - /** - * @brief Wait for all device operations to complete. - */ - virtual void WaitIdle() = 0; - - /** - * @brief Get the Vulkan instance. - * - * @return VkInstance handle - */ - virtual VkInstance GetInstance() const = 0; - - /** - * @brief Get the Vulkan surface. - * - * @return VkSurfaceKHR handle - */ - virtual VkSurfaceKHR GetSurface() const = 0; - - /** - * @brief Get the physical device. - * - * @return VkPhysicalDevice handle - */ - virtual VkPhysicalDevice GetPhysicalDevice() const = 0; - - /** - * @brief Get the logical device. - * - * @return VkDevice handle - */ - virtual VkDevice GetDevice() const = 0; - - /** - * @brief Get the graphics queue. - * - * @return VkQueue handle - */ - virtual VkQueue GetGraphicsQueue() const = 0; - - /** - * @brief Get the present queue. - * - * @return VkQueue handle - */ - virtual VkQueue GetPresentQueue() const = 0; - - /** - * @brief Get queue family indices. - * - * @return Queue family indices structure - */ - virtual QueueFamilyIndices GetQueueFamilies() const = 0; - - /** - * @brief Find memory type index for allocation. - * - * @param typeFilter Memory type bits - * @param properties Required memory properties - * @return Memory type index - * @throws std::runtime_error if no suitable memory type found - */ - virtual uint32_t FindMemoryType(uint32_t typeFilter, - VkMemoryPropertyFlags properties) const = 0; -}; - -} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_window_service.hpp b/src/services/interfaces/i_window_service.hpp index cf026af..91229c8 100644 --- a/src/services/interfaces/i_window_service.hpp +++ b/src/services/interfaces/i_window_service.hpp @@ -47,8 +47,6 @@ public: /** * @brief Get the native SDL window handle. * - * Required for Vulkan surface creation and other low-level operations. - * * @return Pointer to SDL_Window, or nullptr if no window exists */ virtual SDL_Window* GetNativeHandle() const = 0; diff --git a/tests/VALIDATION_IMPROVEMENTS.md b/tests/VALIDATION_IMPROVEMENTS.md deleted file mode 100644 index 4d9ac25..0000000 --- a/tests/VALIDATION_IMPROVEMENTS.md +++ /dev/null @@ -1,243 +0,0 @@ -# Validation and Error Handling Improvements - -## Overview -This document describes the comprehensive validation and error handling improvements made to the SDL3CPlusPlus application to catch errors early and provide better user feedback before crashes occur. - -## Problem -The application was crashing with exit code 137 (SIGKILL), showing only a see-through window with no feedback on what went wrong. This made it difficult to diagnose issues. - -## Solution -Added extensive validation at every critical initialization stage with clear, actionable error messages. - ---- - -## Changes Made - -### 1. **Early Vulkan Validation** ([sdl3_app_device.cpp](src/app/sdl3_app_device.cpp)) - -#### Before: -- Directly attempted to create Vulkan instance without checking if Vulkan is available -- Generic error messages - -#### After: -- **Version Check**: Validates Vulkan is installed and checks API version before proceeding - ```cpp - VkResult enumResult = vkEnumerateInstanceVersion(&apiVersion); - if (enumResult != VK_SUCCESS) { - // Provides OS-specific installation instructions - } - ``` -- **Version Requirements**: Verifies Vulkan 1.2 or higher is available -- **Console Output**: Displays detected Vulkan version - ``` - Vulkan Version: 1.3.xxx - ``` -- **Detailed Error Messages**: Includes OS-specific installation commands for Ubuntu/Debian, Fedora, Arch -- **GPU-specific Guidance**: Separate instructions for NVIDIA and AMD GPUs - -### 2. **GPU Detection and Selection** ([sdl3_app_device.cpp](src/app/sdl3_app_device.cpp)) - -#### Before: -- Limited information about available GPUs -- Simple unsuitable device messages - -#### After: -- **Comprehensive GPU Enumeration**: Lists all detected GPUs with details - ``` - === GPU Detection === - GPU 0: NVIDIA GeForce RTX 3080 (VRAM: 10240 MB) - -> SELECTED - GPU 1: Intel UHD Graphics (VRAM: 2048 MB) - -> UNSUITABLE (missing required features) - ================== - ``` -- **Memory Information**: Shows VRAM for each GPU -- **Error Result Codes**: Returns Vulkan error codes for debugging -- **Clear Selection Feedback**: Shows which GPU was selected and why others were rejected -- **Detailed Requirements**: Lists specific missing features: - - Graphics queue support - - Present queue support - - Swapchain extension support - - Adequate swapchain formats and present modes - -### 3. **Swap Chain Validation** ([sdl3_app_swapchain.cpp](src/app/sdl3_app_swapchain.cpp)) - -#### Before: -- Assumed swap chain support was available -- No validation of window dimensions - -#### After: -- **Surface Format Validation**: Checks if surface formats are available - ```cpp - if (support.formats.empty()) { - throw std::runtime_error("No surface formats available..."); - } - ``` -- **Present Mode Validation**: Verifies present modes exist -- **Window Dimension Check**: Validates window is not minimized or has invalid size - ```cpp - if (windowWidth == 0 || windowHeight == 0) { - throw std::runtime_error("Invalid window dimensions..."); - } - ``` -- **Capability Reporting**: Displays surface capabilities: - ``` - Window size: 1024x768 - Surface capabilities: - Min extent: 1x1 - Max extent: 4096x4096 - Min images: 2 - Max images: 8 - ``` - -### 4. **Memory Allocation Validation** ([vulkan_api.cpp](src/app/vulkan_api.cpp)) - -#### Before: -- Created buffers without checking available memory -- Generic "failed to allocate" errors - -#### After: -- **Size Validation**: Checks buffer size is non-zero -- **Available Memory Check**: Compares requested size against total GPU memory - ```cpp - if (size > totalAvailable) { - throw std::runtime_error("Requested buffer size exceeds available GPU memory"); - } - ``` -- **Error Code Reporting**: Returns specific Vulkan error codes -- **Detailed Error Messages**: Provides size information and troubleshooting steps - - For `VK_ERROR_OUT_OF_DEVICE_MEMORY`: - - Close GPU-intensive applications - - Reduce window resolution - - Upgrade GPU or system memory - - For `VK_ERROR_OUT_OF_HOST_MEMORY`: - - Close other applications - - Add more RAM -- **Cleanup on Failure**: Properly destroys partially created resources - ```cpp - if (allocResult != VK_SUCCESS) { - vkDestroyBuffer(device, buffer, nullptr); - // throw detailed error... - } - ``` - -### 5. **Buffer Creation Validation** ([sdl3_app_buffers.cpp](src/app/sdl3_app_buffers.cpp)) - -#### Before: -- Attempted to create buffers even if data was empty - -#### After: -- **Data Validation**: Checks vertices/indices are loaded before buffer creation - ```cpp - if (vertices_.empty()) { - throw std::runtime_error("Cannot create vertex buffer: no vertices loaded"); - } - ``` -- **Resource Reporting**: Displays buffer sizes being allocated - ``` - Creating vertex buffer: 24 vertices (1 KB) - Creating index buffer: 36 indices (0 KB) - ``` -- **Immediate Feedback**: Uses `std::cout.flush()` to ensure messages appear immediately - -### 6. **Enhanced Error Dialog Support** - -All errors now: -- Show in console with detailed information -- Display in SDL message boxes (when available) -- Include actionable troubleshooting steps -- Reference specific error codes for debugging - ---- - -## Benefits - -1. **Early Detection**: Problems are caught before they cause crashes -2. **Clear Feedback**: Users see exactly what went wrong and where -3. **Actionable Guidance**: Error messages include specific steps to resolve issues -4. **Better Diagnostics**: Includes error codes, sizes, and system capabilities -5. **Progress Visibility**: Console output shows what the app is doing during initialization -6. **Resource Awareness**: Validates resources before attempting allocation - ---- - -## Testing - -To test the improvements: - -```bash -cd /home/rewrich/Documents/GitHub/SDL3CPlusPlus/build/Release -make -j$(nproc) -./sdl3_app --json-file-in ./config/seed_runtime.json -``` - -You should now see: -- Vulkan version detection -- GPU enumeration with memory information -- Window and swap chain validation -- Buffer creation progress -- Clear error messages if any step fails - ---- - -## Example Error Messages - -### Missing Vulkan Drivers -``` -ERROR: Vulkan is not available on this system. - -Please install Vulkan drivers: -- Ubuntu/Debian: sudo apt install vulkan-tools libvulkan1 -- Fedora: sudo dnf install vulkan-tools vulkan-loader -- Arch: sudo pacman -S vulkan-tools vulkan-icd-loader - -For NVIDIA GPUs, install: nvidia-vulkan-icd -For AMD GPUs, install: mesa-vulkan-drivers -``` - -### No Suitable GPU -``` -ERROR: Failed to find a suitable GPU. - -Found 2 GPU(s), but none meet the requirements. -GPU 0: Intel UHD Graphics (VRAM: 2048 MB) [UNSUITABLE] -GPU 1: AMD Radeon (VRAM: 4096 MB) [UNSUITABLE] - -Required features: -- Graphics queue support -- Present queue support -- Swapchain extension support (VK_KHR_swapchain) -- Adequate swapchain formats and present modes -``` - -### Out of Memory -``` -ERROR: Failed to allocate buffer memory. -Requested: 512 MB -Error code: -2 - -Out of GPU memory. Try: -- Closing other GPU-intensive applications -- Reducing window resolution -- Upgrading GPU or system memory -``` - ---- - -## Files Modified - -1. [src/app/sdl3_app_device.cpp](src/app/sdl3_app_device.cpp) - Vulkan and GPU validation -2. [src/app/sdl3_app_swapchain.cpp](src/app/sdl3_app_swapchain.cpp) - Swap chain validation -3. [src/app/vulkan_api.cpp](src/app/vulkan_api.cpp) - Memory allocation validation -4. [src/app/sdl3_app_buffers.cpp](src/app/sdl3_app_buffers.cpp) - Buffer creation validation - ---- - -## Future Improvements - -Consider adding: -- Memory usage tracking throughout app lifecycle -- Swap chain recreation validation -- Shader loading validation with detailed error messages -- Configuration file validation before initialization -- Performance monitoring and warnings for low-memory conditions diff --git a/tests/scripts/unit_cube_logic.lua b/tests/scripts/unit_cube_logic.lua index 047625f..eb4cdfb 100644 --- a/tests/scripts/unit_cube_logic.lua +++ b/tests/scripts/unit_cube_logic.lua @@ -40,19 +40,3 @@ end function compute_model_matrix(time) return identity_matrix() end - -function get_render_graph() - return { - resources = { - scene_hdr = {type = "color", format = "rgba16f", size = "swapchain"}, - depth = {type = "depth", format = "d32", size = "swapchain"}, - }, - passes = { - { - name = "gbuffer", - kind = "gbuffer", - outputs = {color = "scene_hdr", depth = "depth"}, - }, - }, - } -end