From 39a87268a7d3dffa1ebe11f183b2aedaf89eeaa1 Mon Sep 17 00:00:00 2001 From: Andreas Heider Date: Sat, 7 Nov 2026 10:49:22 +0000 Subject: [PATCH] vulkan: mesh and task stages now require the guest warp size Graphics pipelines built the required-subgroup-size struct but never attached it (it was [[maybe_unused]]). Compute pipelines already require 32 when they use warp ops. Mainly applies to intel, because there we have 16-wide pipelines. Foliage rendered as glowing white on Intel ANV. Its leaf mesh shaders compact visible leaves with Ballot + BitCount(ballot & LtMask). ANV compiled the large colour-pass shader 16-wide, so the two halves of each workgroup wrote the same slots, while the small pre-pass shader ran 32-wide. The colour pass then failed its depth-EQUAL test against the pre-pass, and the sky showed through. Signed-off-by: Andreas Heider Assisted-by: Claude Opus 5.5 (debugging with RenderDoc, fix) --- src/shader_recompiler/shader_info.h | 6 ++++++ src/video_core/renderer_vulkan/vk_compute_pipeline.cpp | 4 +--- .../renderer_vulkan/vk_graphics_pipeline.cpp | 10 ++++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index 894ae47011..482fcc8a42 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h @@ -382,6 +382,12 @@ struct Info { /// and storage buffer words (see EvaluateHandleProgram), evaluated for each draw. std::vector> handle_programs; ImageDescriptors image_descriptors; + + /// Whether the shader uses warp-level operations, which observe the subgroup size. + [[nodiscard]] bool UsesWarpOps() const noexcept { + return uses_subgroup_vote || uses_subgroup_invocation_id || uses_subgroup_shuffles || + uses_subgroup_mask || uses_quad_shuffles || uses_fswzadd; + } }; template diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index c425106bcf..acbb088166 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -90,9 +90,7 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk }; // Only warp-level ops observe the subgroup size. Forcing 32 on other shaders makes Intel // compile SIMD32, which can spill a register-heavy kernel for minutes (Mario Kart World). - const bool uses_warp_ops = info.uses_subgroup_vote || info.uses_subgroup_invocation_id || - info.uses_subgroup_shuffles || info.uses_subgroup_mask || - info.uses_quad_shuffles || info.uses_fswzadd; + const bool uses_warp_ops = info.UsesWarpOps(); VkPipelineCreateFlags flags{}; if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) { flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index fc7b96567d..f634ec412c 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -1184,7 +1184,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { .dynamicStateCount = static_cast(dynamic_states.size()), .pDynamicStates = dynamic_states.data(), }; - [[maybe_unused]] const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{ + const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT, .pNext = nullptr, .requiredSubgroupSize = GuestWarpSize, @@ -1194,7 +1194,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { if (!spv_modules[stage]) { continue; } - [[maybe_unused]] auto& stage_ci = + auto& stage_ci = shader_stages.emplace_back(VkPipelineShaderStageCreateInfo{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .pNext = nullptr, @@ -1205,6 +1205,12 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { .pName = "main", .pSpecializationInfo = nullptr, }); + if ((stage_ci.stage == VK_SHADER_STAGE_MESH_BIT_EXT || + stage_ci.stage == VK_SHADER_STAGE_TASK_BIT_EXT) && + stage_infos[stage].UsesWarpOps() && device.IsExtSubgroupSizeControlSupported() && + device.IsGuestWarpSizeSupported(stage_ci.stage)) { + stage_ci.pNext = &subgroup_size_ci; + } } VkPipelineCreateFlags flags{}; if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) {