diff --git a/src/render/backend/filter_chain_controller.cpp b/src/render/backend/filter_chain_controller.cpp index bedbba7..7dd2fc9 100644 --- a/src/render/backend/filter_chain_controller.cpp +++ b/src/render/backend/filter_chain_controller.cpp @@ -487,16 +487,17 @@ auto align_adapter_output(FilterChainController::FilterChainSlot& slot, return {}; } -auto create_and_load_slot(const FilterChainController::AdapterBuildConfig& config, +auto create_and_load_slot(const FilterChainController::VulkanDeviceInfo& device_info, + const FilterChainController::ChainConfig& chain_config, const std::filesystem::path& preset_path) -> Result { FilterChainController::FilterChainSlot new_slot; - GOGGLES_TRY(initialize_slot(new_slot, config.device_info)); + GOGGLES_TRY(initialize_slot(new_slot, device_info)); if (preset_path.empty()) { - GOGGLES_TRY(load_passthrough_into_slot(new_slot, config.chain_config)); + GOGGLES_TRY(load_passthrough_into_slot(new_slot, chain_config)); } else { - GOGGLES_TRY(load_preset_into_slot(new_slot, preset_path, config.chain_config)); + GOGGLES_TRY(load_preset_into_slot(new_slot, preset_path, chain_config)); } return std::move(new_slot); @@ -559,18 +560,17 @@ void shutdown_retired_adapter_tracker(FilterChainController::RetiredAdapterTrack } // namespace -auto FilterChainController::recreate_filter_chain(const AdapterBuildConfig& config) - -> Result { +auto FilterChainController::recreate_filter_chain(const VulkanDeviceInfo& device_info, + ChainConfig chain_config) -> Result { GOGGLES_PROFILE_FUNCTION(); - auto requested_config = config; - requested_config.chain_config.initial_stage_mask = + chain_config.initial_stage_mask = stage_mask_from_policy(prechain_policy_enabled, effect_stage_policy_enabled); - requested_config.chain_config.initial_prechain_width = source_resolution.width; - requested_config.chain_config.initial_prechain_height = source_resolution.height; + chain_config.initial_prechain_width = source_resolution.width; + chain_config.initial_prechain_height = source_resolution.height; authoritative_output_target = OutputTarget{ - .format = static_cast(requested_config.chain_config.target_format), + .format = static_cast(chain_config.target_format), .extent = vk::Extent2D{}, }; @@ -580,7 +580,7 @@ auto FilterChainController::recreate_filter_chain(const AdapterBuildConfig& conf shutdown_slot(active_slot); - auto slot_result = create_and_load_slot(requested_config, preset_path); + auto slot_result = create_and_load_slot(device_info, chain_config, preset_path); if (!slot_result) { return nonstd::make_unexpected(slot_result.error()); } @@ -705,7 +705,8 @@ void FilterChainController::load_shader_preset(const std::filesystem::path& new_ } auto FilterChainController::reload_shader_preset(std::filesystem::path new_preset_path, - AdapterBuildConfig config) -> Result { + const VulkanDeviceInfo& device_info, + ChainConfig chain_config) -> Result { GOGGLES_PROFILE_FUNCTION(); if (pending_chain_ready.load(std::memory_order_acquire)) { @@ -720,22 +721,23 @@ auto FilterChainController::reload_shader_preset(std::filesystem::path new_prese } pending_preset_path = new_preset_path; - config.chain_config.initial_stage_mask = + chain_config.initial_stage_mask = stage_mask_from_policy(prechain_policy_enabled, effect_stage_policy_enabled); - config.chain_config.initial_prechain_width = source_resolution.width; - config.chain_config.initial_prechain_height = source_resolution.height; + chain_config.initial_prechain_width = source_resolution.width; + chain_config.initial_prechain_height = source_resolution.height; const auto requested_output_target = authoritative_output_target; auto requested_controls = authoritative_control_overrides.empty() ? snapshot_adapter_controls(active_slot) : authoritative_control_overrides; pending_load_future = util::JobSystem::submit( - [this, build_config = std::move(config), requested_preset_path = std::move(new_preset_path), - requested_output_target, + [this, device_info, chain_config = std::move(chain_config), + requested_preset_path = std::move(new_preset_path), requested_output_target, requested_controls = std::move(requested_controls)]() -> Result { GOGGLES_PROFILE_SCOPE("AsyncShaderLoad"); - auto slot_result = create_and_load_slot(build_config, requested_preset_path); + auto slot_result = + create_and_load_slot(device_info, chain_config, requested_preset_path); if (!slot_result) { GOGGLES_LOG_ERROR("Failed to create filter chain adapter"); return nonstd::make_unexpected(slot_result.error()); @@ -881,8 +883,8 @@ void FilterChainController::set_stage_policy( } void FilterChainController::set_prechain_resolution( - const PrechainResolutionConfig& config, const std::function& wait_for_safe_rebuild) { - if (source_resolution == config.requested_resolution) { + vk::Extent2D resolution, const std::function& wait_for_safe_rebuild) { + if (source_resolution == resolution) { return; } @@ -890,13 +892,13 @@ void FilterChainController::set_prechain_resolution( wait_for_safe_rebuild(); } - source_resolution = config.requested_resolution; + source_resolution = resolution; if (active_slot.chain) { - active_slot.prechain_width = config.requested_resolution.width; - active_slot.prechain_height = config.requested_resolution.height; - goggles_fc_extent_2d_t resolution{.width = config.requested_resolution.width, - .height = config.requested_resolution.height}; - auto result = active_slot.chain.set_prechain_resolution(&resolution); + active_slot.prechain_width = resolution.width; + active_slot.prechain_height = resolution.height; + goggles_fc_extent_2d_t fc_resolution{.width = resolution.width, + .height = resolution.height}; + auto result = active_slot.chain.set_prechain_resolution(&fc_resolution); if (!result) { GOGGLES_LOG_WARN("Failed to set prechain resolution: {}", result.error().message); } diff --git a/src/render/backend/filter_chain_controller.hpp b/src/render/backend/filter_chain_controller.hpp index b15b54a..92f4c97 100644 --- a/src/render/backend/filter_chain_controller.hpp +++ b/src/render/backend/filter_chain_controller.hpp @@ -50,21 +50,13 @@ struct FilterChainController { uint32_t integer_scale = 1; }; - struct AdapterBuildConfig { - VulkanDeviceInfo device_info; - ChainConfig chain_config; - }; - struct OutputTarget { vk::Format format = vk::Format::eUndefined; vk::Extent2D extent; }; - struct PrechainResolutionConfig { - vk::Extent2D requested_resolution; - }; - - [[nodiscard]] auto recreate_filter_chain(const AdapterBuildConfig& config) -> Result; + [[nodiscard]] auto recreate_filter_chain(const VulkanDeviceInfo& device_info, + ChainConfig chain_config) -> Result; [[nodiscard]] auto retarget_filter_chain(const OutputTarget& output_target) -> Result; void shutdown(const std::function& wait_for_gpu_idle); @@ -72,7 +64,8 @@ struct FilterChainController { const std::filesystem::path& new_preset_path, const std::function& wait_for_safe_rebuild = std::function{}); [[nodiscard]] auto reload_shader_preset(std::filesystem::path new_preset_path, - AdapterBuildConfig config) -> Result; + const VulkanDeviceInfo& device_info, + ChainConfig chain_config) -> Result; void advance_frame(); void check_pending_chain_swap(const std::function& wait_all_frames); @@ -82,7 +75,7 @@ struct FilterChainController { set_stage_policy(bool prechain_enabled, bool effect_stage_enabled, const std::function& wait_for_safe_rebuild = std::function{}); void set_prechain_resolution( - const PrechainResolutionConfig& config, + vk::Extent2D resolution, const std::function& wait_for_safe_rebuild = std::function{}); [[nodiscard]] auto handle_resize(vk::Extent2D target_extent) -> Result; [[nodiscard]] auto record(const RecordParams& record_params) -> Result; diff --git a/src/render/backend/vulkan_backend.cpp b/src/render/backend/vulkan_backend.cpp index 4b99348..640c5d2 100644 --- a/src/render/backend/vulkan_backend.cpp +++ b/src/render/backend/vulkan_backend.cpp @@ -71,9 +71,7 @@ void VulkanBackend::initialize_settings(const RenderSettings& settings) { m_integer_scale = settings.integer_scale; update_target_fps(settings.target_fps); m_filter_chain_controller.set_prechain_resolution( - backend_internal::FilterChainController::PrechainResolutionConfig{ - .requested_resolution = vk::Extent2D{settings.source_width, settings.source_height}, - }); + vk::Extent2D{settings.source_width, settings.source_height}); } auto VulkanBackend::create(SDL_Window* window, bool enable_validation, @@ -237,7 +235,7 @@ auto VulkanBackend::is_srgb_format(vk::Format format) -> bool { auto VulkanBackend::init_filter_chain() -> Result { GOGGLES_PROFILE_FUNCTION(); - return m_filter_chain_controller.recreate_filter_chain(make_filter_chain_build_config()); + return m_filter_chain_controller.recreate_filter_chain(make_device_info(), make_chain_config()); } void VulkanBackend::load_shader_preset(const std::filesystem::path& preset_path) { @@ -245,11 +243,8 @@ void VulkanBackend::load_shader_preset(const std::filesystem::path& preset_path) } void VulkanBackend::set_prechain_resolution(uint32_t width, uint32_t height) { - m_filter_chain_controller.set_prechain_resolution( - backend_internal::FilterChainController::PrechainResolutionConfig{ - .requested_resolution = vk::Extent2D{width, height}, - }, - [this]() { wait_all_frames(); }); + m_filter_chain_controller.set_prechain_resolution(vk::Extent2D{width, height}, + [this]() { wait_all_frames(); }); } auto VulkanBackend::record_render_commands(vk::CommandBuffer cmd, uint32_t image_index, @@ -559,8 +554,8 @@ auto VulkanBackend::reload_shader_preset(const std::filesystem::path& preset_pat return make_error(ErrorCode::vulkan_init_failed, "Backend not initialized"); } - return m_filter_chain_controller.reload_shader_preset(preset_path, - make_filter_chain_build_config()); + return m_filter_chain_controller.reload_shader_preset(preset_path, make_device_info(), + make_chain_config()); } void VulkanBackend::set_filter_chain_policy(const FilterChainStagePolicy& policy) { @@ -568,26 +563,24 @@ void VulkanBackend::set_filter_chain_policy(const FilterChainStagePolicy& policy [this]() { wait_all_frames(); }); } -auto VulkanBackend::make_filter_chain_build_config() const - -> backend_internal::FilterChainController::AdapterBuildConfig { - return backend_internal::FilterChainController::AdapterBuildConfig{ - .device_info = - backend_internal::FilterChainController::VulkanDeviceInfo{ - .physical_device = m_vulkan_context.physical_device, - .device = m_vulkan_context.device, - .graphics_queue = m_vulkan_context.graphics_queue, - .graphics_queue_family_index = m_vulkan_context.graphics_queue_family, - .cache_dir = m_cache_dir.string(), - }, - .chain_config = - backend_internal::FilterChainController::ChainConfig{ - .target_format = static_cast(m_render_output.swapchain_format), - .frames_in_flight = backend_internal::RenderOutput::MAX_FRAMES_IN_FLIGHT, - .initial_prechain_width = - m_filter_chain_controller.current_prechain_resolution().width, - .initial_prechain_height = - m_filter_chain_controller.current_prechain_resolution().height, - }, +auto VulkanBackend::make_device_info() const + -> backend_internal::FilterChainController::VulkanDeviceInfo { + return { + .physical_device = m_vulkan_context.physical_device, + .device = m_vulkan_context.device, + .graphics_queue = m_vulkan_context.graphics_queue, + .graphics_queue_family_index = m_vulkan_context.graphics_queue_family, + .cache_dir = m_cache_dir.string(), + }; +} + +auto VulkanBackend::make_chain_config() const + -> backend_internal::FilterChainController::ChainConfig { + return { + .target_format = static_cast(m_render_output.swapchain_format), + .frames_in_flight = backend_internal::RenderOutput::MAX_FRAMES_IN_FLIGHT, + .initial_prechain_width = m_filter_chain_controller.current_prechain_resolution().width, + .initial_prechain_height = m_filter_chain_controller.current_prechain_resolution().height, }; } diff --git a/src/render/backend/vulkan_backend.hpp b/src/render/backend/vulkan_backend.hpp index dff78a6..0f1e40a 100644 --- a/src/render/backend/vulkan_backend.hpp +++ b/src/render/backend/vulkan_backend.hpp @@ -103,8 +103,10 @@ class VulkanBackend { void update_target_fps(uint32_t target_fps) { m_render_output.set_target_fps(target_fps); } [[nodiscard]] auto init_filter_chain() -> Result; - [[nodiscard]] auto make_filter_chain_build_config() const - -> backend_internal::FilterChainController::AdapterBuildConfig; + [[nodiscard]] auto make_device_info() const + -> backend_internal::FilterChainController::VulkanDeviceInfo; + [[nodiscard]] auto make_chain_config() const + -> backend_internal::FilterChainController::ChainConfig; [[nodiscard]] auto record_render_commands(vk::CommandBuffer cmd, uint32_t image_index, const UiRenderCallback& ui_callback = nullptr) diff --git a/tests/render/test_filter_boundary_contracts.cpp b/tests/render/test_filter_boundary_contracts.cpp index a7a7785..94a0cb0 100644 --- a/tests/render/test_filter_boundary_contracts.cpp +++ b/tests/render/test_filter_boundary_contracts.cpp @@ -217,7 +217,8 @@ TEST_CASE("Async swap and resize safety contract coverage", "[filter_chain][asyn REQUIRE(backend_text.has_value()); REQUIRE(controller_text.has_value()); REQUIRE(render_output_text.has_value()); - REQUIRE(backend_text->find("make_filter_chain_build_config()") != std::string::npos); + REQUIRE(backend_text->find("make_device_info()") != std::string::npos); + REQUIRE(backend_text->find("make_chain_config()") != std::string::npos); const auto check_swap_pos = controller_text->find("void FilterChainController::check_pending_chain_swap("); @@ -269,9 +270,8 @@ TEST_CASE("Async swap and resize safety contract coverage", "[filter_chain][asyn std::string::npos); REQUIRE(controller_text->find("authoritative_control_overrides = snapshot_adapter_controls(") != std::string::npos); - REQUIRE(controller_text->find("source_resolution = config.requested_resolution;") != - std::string::npos); - REQUIRE(controller_text->find("active_slot.chain.set_prechain_resolution(&resolution)") != + REQUIRE(controller_text->find("source_resolution = resolution;") != std::string::npos); + REQUIRE(controller_text->find("active_slot.chain.set_prechain_resolution(&fc_resolution)") != std::string::npos); REQUIRE(controller_text->find("resolve_initial_prechain_resolution") == std::string::npos); @@ -773,8 +773,9 @@ TEST_CASE("filter_chain controller uses standalone API boundary", REQUIRE(controller_prechain_pos != std::string::npos); REQUIRE(controller_handle_resize_pos != std::string::npos); // set_prechain_resolution uses set_prechain_resolution API, not resize - REQUIRE(controller_cpp_text->find("active_slot.chain.set_prechain_resolution(&resolution)", - controller_prechain_pos) != std::string::npos); + REQUIRE( + controller_cpp_text->find("active_slot.chain.set_prechain_resolution(&fc_resolution)", + controller_prechain_pos) != std::string::npos); // handle_resize uses chain.resize, not set_prechain_resolution REQUIRE(controller_cpp_text->find("active_slot.chain.resize(&extent)", controller_handle_resize_pos) != std::string::npos); diff --git a/tests/render/test_filter_chain_retarget.cpp b/tests/render/test_filter_chain_retarget.cpp index 5bb6f43..cade88e 100644 --- a/tests/render/test_filter_chain_retarget.cpp +++ b/tests/render/test_filter_chain_retarget.cpp @@ -172,10 +172,15 @@ auto find_filter_control(const std::vector return nullptr; } +struct TestBuildConfig { + goggles::render::backend_internal::FilterChainController::VulkanDeviceInfo device_info; + goggles::render::backend_internal::FilterChainController::ChainConfig chain_config; +}; + auto make_adapter_build_config(const VulkanRuntimeFixture& fixture, const std::filesystem::path& cache_dir, VkFormat target_format = VK_FORMAT_B8G8R8A8_UNORM) - -> goggles::render::backend_internal::FilterChainController::AdapterBuildConfig { + -> TestBuildConfig { auto dev_info = fixture.device_info(); dev_info.cache_dir = cache_dir.string(); return { @@ -195,7 +200,7 @@ void configure_controller_runtime( const std::filesystem::path& preset_path) { controller.preset_path = preset_path; controller.set_stage_policy(true, false); - controller.set_prechain_resolution({.requested_resolution = {2u, 3u}}); + controller.set_prechain_resolution(vk::Extent2D{2u, 3u}); const auto controls = controller.list_filter_controls(goggles::fc::FilterControlStage::prechain); @@ -261,7 +266,8 @@ TEST_CASE("Controller retarget preserves active runtime without swap signaling", auto controller = goggles::render::backend_internal::FilterChainController{}; auto build_config = make_adapter_build_config(fixture, cache_dir_guard.dir); - REQUIRE(controller.recreate_filter_chain(build_config).has_value()); + REQUIRE(controller.recreate_filter_chain(build_config.device_info, build_config.chain_config) + .has_value()); configure_controller_runtime(controller, preset_path); REQUIRE_FALSE(controller.consume_chain_swapped()); @@ -291,7 +297,8 @@ TEST_CASE("Controller retarget failure keeps the previous runtime usable", auto controller = goggles::render::backend_internal::FilterChainController{}; auto build_config = make_adapter_build_config(fixture, cache_dir_guard.dir); - REQUIRE(controller.recreate_filter_chain(build_config).has_value()); + REQUIRE(controller.recreate_filter_chain(build_config.device_info, build_config.chain_config) + .has_value()); configure_controller_runtime(controller, preset_path); const auto invalid_retarget = controller.retarget_filter_chain( @@ -324,10 +331,14 @@ TEST_CASE("Pending reload swaps only after activation and preserves authoritativ auto controller = goggles::render::backend_internal::FilterChainController{}; auto build_config = make_adapter_build_config(fixture, cache_dir_guard.dir); - REQUIRE(controller.recreate_filter_chain(build_config).has_value()); + REQUIRE(controller.recreate_filter_chain(build_config.device_info, build_config.chain_config) + .has_value()); configure_controller_runtime(controller, preset_path); - REQUIRE(controller.reload_shader_preset(preset_path, build_config).has_value()); + REQUIRE( + controller + .reload_shader_preset(preset_path, build_config.device_info, build_config.chain_config) + .has_value()); wait_for_reload_start(controller); REQUIRE(controller.pending_chain_ready.load(std::memory_order_acquire)); REQUIRE_FALSE(controller.consume_chain_swapped()); @@ -365,10 +376,14 @@ TEST_CASE("Explicit reload failure preserves the previous runtime", auto controller = goggles::render::backend_internal::FilterChainController{}; auto build_config = make_adapter_build_config(fixture, cache_dir_guard.dir); - REQUIRE(controller.recreate_filter_chain(build_config).has_value()); + REQUIRE(controller.recreate_filter_chain(build_config.device_info, build_config.chain_config) + .has_value()); configure_controller_runtime(controller, preset_path); - REQUIRE(controller.reload_shader_preset(missing_preset_path, build_config).has_value()); + REQUIRE(controller + .reload_shader_preset(missing_preset_path, build_config.device_info, + build_config.chain_config) + .has_value()); using namespace std::chrono_literals; REQUIRE(controller.pending_load_future.valid()); @@ -409,9 +424,10 @@ TEST_CASE("Reload across different control surfaces skips stale restore warnings auto controller = goggles::render::backend_internal::FilterChainController{}; auto build_config = make_adapter_build_config(fixture, cache_dir_guard.dir); - REQUIRE(controller.recreate_filter_chain(build_config).has_value()); + REQUIRE(controller.recreate_filter_chain(build_config.device_info, build_config.chain_config) + .has_value()); controller.set_stage_policy(true, true); - controller.set_prechain_resolution({.requested_resolution = {2u, 3u}}); + controller.set_prechain_resolution(vk::Extent2D{2u, 3u}); controller.load_shader_preset(preset_path); const auto prechain_controls_before = @@ -422,7 +438,8 @@ TEST_CASE("Reload across different control surfaces skips stale restore warnings controller.authoritative_control_overrides.push_back( {.control_id = std::numeric_limits::max(), .value = 1.0F}); - REQUIRE(controller.reload_shader_preset({}, build_config).has_value()); + REQUIRE(controller.reload_shader_preset({}, build_config.device_info, build_config.chain_config) + .has_value()); wait_for_reload_start(controller); controller.check_pending_chain_swap([] {}); REQUIRE(controller.consume_chain_swapped()); @@ -491,7 +508,7 @@ TEST_CASE("Structural live updates rebuild the active adapter contract", controller_text->find("FilterChainController::handle_resize(", prechain_update_pos); REQUIRE(prechain_update_pos != std::string::npos); REQUIRE(prechain_update_end != std::string::npos); - REQUIRE(controller_text->find("active_slot.chain.set_prechain_resolution(&resolution)", + REQUIRE(controller_text->find("active_slot.chain.set_prechain_resolution(&fc_resolution)", prechain_update_pos) != std::string::npos); // resize must NOT appear inside set_prechain_resolution (it belongs in handle_resize) const auto resize_call_pos = @@ -500,11 +517,11 @@ TEST_CASE("Structural live updates rebuild the active adapter contract", // reload_shader_preset must propagate stage mask and prechain dimensions into the build config REQUIRE(controller_text->find( - "config.chain_config.initial_stage_mask =", + "chain_config.initial_stage_mask =", controller_text->find("auto FilterChainController::reload_shader_preset(")) != std::string::npos); REQUIRE(controller_text->find( - "config.chain_config.initial_prechain_width = source_resolution.width;", + "chain_config.initial_prechain_width = source_resolution.width;", controller_text->find("auto FilterChainController::reload_shader_preset(")) != std::string::npos); }