From 14b6160fcd00c7d98a375907b9b03912f3accad6 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 27 Sep 2026 01:15:26 -0400 Subject: [PATCH] first try on dynamic rendering unused attachments --- .../renderer_vulkan/fixed_pipeline_state.cpp | 46 ++++++++++++++++--- .../renderer_vulkan/fixed_pipeline_state.h | 4 +- .../renderer_vulkan/vk_pipeline_cache.cpp | 6 ++- .../renderer_vulkan/vk_scheduler.cpp | 31 +++++++++---- src/video_core/shader_cache.cpp | 11 +++++ src/video_core/shader_cache.h | 2 + .../vulkan_common/vulkan_device.cpp | 6 +++ src/video_core/vulkan_common/vulkan_device.h | 8 +++- 8 files changed, 94 insertions(+), 20 deletions(-) diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index 82f2fad005..aadcf9f241 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -88,6 +88,18 @@ bool ComputeAttachment0DualSourceBlend(const Maxwell& regs) { : uses_dual_source(regs.blend); } +bool WritesColorTarget(const Maxwell& regs, size_t index, bool static_masks) { + if (!static_masks || (index == 0 && regs.anti_alias_alpha_control.alpha_to_coverage != 0)) { + return true; + } + size_t mask_index = index; + if (regs.color_mask_common) { + mask_index = 0; + } + const auto& mask = regs.color_mask[mask_index]; + return mask.R || mask.G || mask.B || mask.A; +} + void RefreshXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell& regs) { std::ranges::transform(regs.transform_feedback.controls, state.layouts.begin(), [](const auto& layout) { @@ -101,7 +113,8 @@ void RefreshXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell& } } // Anonymous namespace -void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFeatures& features) { +void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFeatures& features, + u32 color_outputs) { const Maxwell& regs = maxwell3d.regs; const auto topology_ = maxwell3d.draw_manager.draw_state.topology; @@ -141,8 +154,13 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe regs.alpha_test_enabled != 0 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always_GL; alpha_test_func.Assign(PackComparisonOp(test_func)); early_z.Assign(regs.mandated_early_z != 0 ? 1 : 0); - depth_enabled.Assign(regs.zeta_enable != 0 ? 1 : 0); - depth_format.Assign(static_cast(regs.zeta.format)); + const bool drop_unused = features.has_dynamic_rendering_unused_attachments; + const bool depth_used = regs.depth_test_enable || regs.depth_bounds_enable || + regs.stencil_enable || extended_dynamic_state; + if (regs.zeta_enable != 0 && regs.zeta.Address() != 0 && (depth_used || !drop_unused)) { + depth_enabled.Assign(1); + depth_format.Assign(static_cast(regs.zeta.format)); + } y_negate.Assign(regs.window_origin.mode != Maxwell::WindowOrigin::Mode::UpperLeft ? 1 : 0); bool use_last_provoking_vertex = false; @@ -179,8 +197,18 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe line_stipple_pattern = regs.line_stipple_params.pattern; } - for (size_t i = 0; i < regs.rt.size(); ++i) { - color_formats[i] = static_cast(regs.rt[i].format); + const bool static_masks = + !features.has_extended_dynamic_state_3_blend && !features.has_color_write_enable; + const auto previous_formats = color_formats; + color_formats.fill(static_cast(Tegra::RenderTargetFormat::NONE)); + const size_t num_targets = (std::min)(size_t{regs.rt_control.count}, color_formats.size()); + for (size_t index = 0; index < num_targets; ++index) { + const bool used = ((color_outputs >> index) & 1) != 0 && + WritesColorTarget(regs, index, static_masks); + if (regs.rt[index].Address() == 0 || (drop_unused && !used)) { + continue; + } + color_formats[index] = static_cast(regs.rt[index].format); } alpha_test_ref = 0; if (regs.alpha_test_enabled != 0) { @@ -251,11 +279,15 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe dynamic_state.Refresh2(regs, topology_, extended_dynamic_state_2); } if (!extended_dynamic_state_3_blend) { - if (maxwell3d.dirty.flags[Dirty::Blending]) { + if (maxwell3d.dirty.flags[Dirty::Blending] || color_formats != previous_formats) { maxwell3d.dirty.flags[Dirty::Blending] = false; for (size_t index = 0; index < attachments.size(); ++index) { - attachments[index].Refresh(regs, index); auto& attachment = attachments[index]; + attachment.raw = 0; + if (color_formats[index] == static_cast(Tegra::RenderTargetFormat::NONE)) { + continue; + } + attachment.Refresh(regs, index); if (color_write_enable_dynamic && attachment.mask_r == 0 && attachment.mask_g == 0 && attachment.mask_b == 0 && attachment.mask_a == 0) { diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index 3f10c8f2e5..6e114ec111 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -38,6 +38,7 @@ struct DynamicFeatures { bool has_provoking_vertex_first_mode; bool has_provoking_vertex_last_mode; bool has_provoking_vertex_tf_preserve; + bool has_dynamic_rendering_unused_attachments; }; struct FixedPipelineState { @@ -255,7 +256,8 @@ struct FixedPipelineState { VideoCommon::TransformFeedbackState xfb_state; - void Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFeatures& features); + void Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFeatures& features, + u32 color_outputs); size_t Hash() const noexcept; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 4f42474a05..dd55b576e8 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -62,7 +62,7 @@ using VideoCommon::FileEnvironment; using VideoCommon::GenericEnvironment; using VideoCommon::GraphicsEnvironment; -constexpr u32 CACHE_VERSION = 19; +constexpr u32 CACHE_VERSION = 20; constexpr size_t VULKAN_CACHE_FLUSH_PIPELINES = 128; constexpr size_t VULKAN_CACHE_FLUSH_MIN_SECONDS = 30; constexpr std::array VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'}; @@ -551,6 +551,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, device.SupportsProvokingVertexLastMode(); dynamic_features.has_provoking_vertex_tf_preserve = device.SupportsTransformFeedbackProvokingVertexPreservation(); + dynamic_features.has_dynamic_rendering_unused_attachments = + device.IsExtDynamicRenderingUnusedAttachmentsSupported(); } PipelineCache::~PipelineCache() { @@ -566,7 +568,7 @@ GraphicsPipeline* PipelineCache::CurrentGraphicsPipeline() { current_pipeline = nullptr; return nullptr; } - graphics_key.state.Refresh(*maxwell3d, dynamic_features); + graphics_key.state.Refresh(*maxwell3d, dynamic_features, fragment_color_outputs); if (current_pipeline) { GraphicsPipeline* const next{current_pipeline->Next(graphics_key)}; diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 024b75e584..36f0bef803 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -244,24 +244,37 @@ void Scheduler::RequestComputeDispatchContext() { void Scheduler::RelaxAttachmentOps(RenderingAttachments& attachments) const { const bool load_op_none = device.IsLoadOpNoneSupported(); + const bool drop_unused = device.IsExtDynamicRenderingUnusedAttachmentsSupported(); const auto relax = [&](VkRenderingAttachmentInfo& attachment, u32 bit) { - if (attachment.imageView == VK_NULL_HANDLE || - attachment.resolveMode != VK_RESOLVE_MODE_NONE || + if (attachment.imageView == VK_NULL_HANDLE) { + return true; + } + if (attachment.resolveMode != VK_RESOLVE_MODE_NONE || attachment.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR || attachment.storeOp != VK_ATTACHMENT_STORE_OP_STORE || (attachments_written & bit) != 0) { - return; + return false; } attachment.storeOp = VK_ATTACHMENT_STORE_OP_NONE; - if (load_op_none && (attachments_touched & bit) == 0) { - attachment.loadOp = VK_ATTACHMENT_LOAD_OP_NONE; - } + return (attachments_touched & bit) == 0; }; for (u32 slot = 0; slot < attachments.num_colors; ++slot) { - relax(attachments.colors[slot], 1u << slot); + VkRenderingAttachmentInfo& color = attachments.colors[slot]; + if (!relax(color, 1u << slot)) { + continue; + } + if (drop_unused) { + color.imageView = VK_NULL_HANDLE; + } else if (load_op_none) { + color.loadOp = VK_ATTACHMENT_LOAD_OP_NONE; + } + } + const bool depth_unused = relax(attachments.depth, DEPTH_ATTACHMENT_BIT); + const bool stencil_unused = relax(attachments.stencil, STENCIL_ATTACHMENT_BIT); + if (drop_unused && depth_unused && stencil_unused) { + attachments.depth.imageView = VK_NULL_HANDLE; + attachments.stencil.imageView = VK_NULL_HANDLE; } - relax(attachments.depth, DEPTH_ATTACHMENT_BIT); - relax(attachments.stencil, STENCIL_ATTACHMENT_BIT); } void Scheduler::PublishComputeWrites() { diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp index f1aff2810f..d2d6ffeca1 100644 --- a/src/video_core/shader_cache.cpp +++ b/src/video_core/shader_cache.cpp @@ -49,6 +49,7 @@ bool ShaderCache::RefreshStages(std::array& unique_hashes) { return last_shaders_valid; } dirty[VideoCommon::Dirty::Shaders] = false; + fragment_color_outputs = 0; const GPUVAddr base_addr{maxwell3d->regs.program_region.Address()}; for (size_t index = 0; index < Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram; ++index) { @@ -78,6 +79,9 @@ bool ShaderCache::RefreshStages(std::array& unique_hashes) { } shader_infos[index] = shader_info; unique_hashes[index] = shader_info->unique_hash; + if (program == Tegra::Engines::Maxwell3D::Regs::ShaderType::Pixel) { + fragment_color_outputs = shader_info->color_outputs; + } } last_shaders_valid = true; return true; @@ -241,6 +245,13 @@ const ShaderInfo* ShaderCache::MakeShaderInfo(GenericEnvironment& env, VAddr cpu info->unique_hash = env.CalculateHash(); info->size_bytes = env.ReadSizeBytes(); } + if (env.ShaderStage() == Shader::Stage::Fragment) { + for (u32 target = 0; target < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; ++target) { + if (env.SPH().ps.HasOutputComponents(target)) { + info->color_outputs |= 1U << target; + } + } + } const size_t size_bytes{info->size_bytes}; const ShaderInfo* const result{info.get()}; Register(std::move(info), cpu_addr, size_bytes); diff --git a/src/video_core/shader_cache.h b/src/video_core/shader_cache.h index 997f2b0371..aec003d166 100644 --- a/src/video_core/shader_cache.h +++ b/src/video_core/shader_cache.h @@ -36,6 +36,7 @@ class GenericEnvironment; struct ShaderInfo { u64 unique_hash{}; size_t size_bytes{}; + u32 color_outputs{}; }; class ShaderCache : public VideoCommon::ChannelSetupCaches { @@ -98,6 +99,7 @@ protected: std::array shader_infos{}; bool last_shaders_valid = false; + u32 fragment_color_outputs{}; private: /// @brief Tries to obtain a cached shader starting in a given address diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 756e6a49a2..76bfe29fdf 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -1247,6 +1247,12 @@ void Device::RemoveUnsuitableExtensions() { RemoveExtensionFeatureIfUnsuitable(extensions.descriptor_buffer, features.descriptor_buffer, VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME); + extensions.dynamic_rendering_unused_attachments = + features.dynamic_rendering_unused_attachments.dynamicRenderingUnusedAttachments; + RemoveExtensionFeatureIfUnsuitable(extensions.dynamic_rendering_unused_attachments, + features.dynamic_rendering_unused_attachments, + VK_EXT_DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_EXTENSION_NAME); + // VK_EXT_extended_dynamic_state extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState; RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state, diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 69f21a6f50..e023e95fc2 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -61,6 +61,8 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE(EXT, DepthBiasControl, DEPTH_BIAS_CONTROL, depth_bias_control) \ FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \ FEATURE(EXT, DescriptorBuffer, DESCRIPTOR_BUFFER, descriptor_buffer) \ + FEATURE(EXT, DynamicRenderingUnusedAttachments, DYNAMIC_RENDERING_UNUSED_ATTACHMENTS, \ + dynamic_rendering_unused_attachments) \ FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \ FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \ FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \ @@ -670,11 +672,15 @@ FN_MAX_LIMIT_LIST return extensions.depth_clip_control; } - /// Returns true if the device supports VK_EXT_depth_bias_control. bool IsLoadOpNoneSupported() const { return extensions.load_store_op_none || extensions.load_store_op_none_ext; } + bool IsExtDynamicRenderingUnusedAttachmentsSupported() const { + return extensions.dynamic_rendering_unused_attachments; + } + + /// Returns true if the device supports VK_EXT_depth_bias_control. bool IsExtDepthBiasControlSupported() const { return extensions.depth_bias_control; }