From d0a6e951ff33f207fc97416e6111427bdf2d6dab Mon Sep 17 00:00:00 2001 From: Aydar Kamaltdinov Date: Mon, 31 Aug 2026 11:25:25 +0200 Subject: [PATCH] shader_recompiler: gl_PrimitiveId scatter for geometry-stage stream compaction (#4330) Guest shaders that reserve a compacted output slot via subgroup ballot -> popcount -> atomic add cannot preserve the originating primitives' relative order once run through the single-invocation subgroup fallback for stages without subgroup support. gl_PrimitiveId is already hardware-guaranteed unique and monotonically increasing per primitive, so CompactionFallbackPass rewrites that pattern into a gl_PrimitiveId-indexed scatter instead. Confirmed fixing corrupted/order-scrambled compacted draws in NieR Automata on a device without geometry-stage subgroup support. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4330 --- src/shader_recompiler/CMakeLists.txt | 1 + .../frontend/maxwell/translate_program.cpp | 1 + src/shader_recompiler/host_translate_info.h | 1 + .../ir_opt/compaction_fallback_pass.cpp | 69 +++++++++++++++++++ src/shader_recompiler/ir_opt/passes.h | 1 + .../renderer_opengl/gl_shader_cache.cpp | 1 + .../renderer_vulkan/vk_pipeline_cache.cpp | 1 + 7 files changed, 75 insertions(+) create mode 100644 src/shader_recompiler/ir_opt/compaction_fallback_pass.cpp diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt index 1e6a4d173a..3b587c8c6b 100644 --- a/src/shader_recompiler/CMakeLists.txt +++ b/src/shader_recompiler/CMakeLists.txt @@ -217,6 +217,7 @@ add_library(shader_recompiler STATIC frontend/maxwell/translate_program.h host_translate_info.h ir_opt/collect_shader_info_pass.cpp + ir_opt/compaction_fallback_pass.cpp ir_opt/conditional_barrier_pass.cpp ir_opt/constant_propagation_pass.cpp ir_opt/dead_code_elimination_pass.cpp diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp index ebc5a825dd..6178094a91 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp @@ -299,6 +299,7 @@ IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool MAX_BALLOT_SEARCH_DEPTH || value.IsImmediate()) { + return false; + } + IR::Inst* const producer{value.Inst()}; + if (producer->GetOpcode() == IR::Opcode::SubgroupBallot) { + return true; + } + if (producer->GetOpcode() == IR::Opcode::Phi) { + return false; + } + const size_t num_args{producer->NumArgs()}; + for (size_t index = 0; index < num_args; ++index) { + if (DependsOnSubgroupBallot(producer->Arg(index), depth + 1)) { + return true; + } + } + return false; +} + +void RewriteAtomic(IR::Block& block, IR::Inst& inst) { + const auto insert_point{IR::Block::InstructionList::s_iterator_to(inst)}; + IR::IREmitter ir{block, insert_point}; + + const IR::U32 amount{inst.Arg(2)}; + const IR::U32 primitive_id{ir.GetAttributeU32(IR::Attribute::PrimitiveId)}; + const IR::U32 new_index{ir.IMul(primitive_id, amount)}; + const IR::U32 new_atomic_value{ir.IAdd(new_index, amount)}; + + const IR::Value umax_result{&*block.PrependNewInst( + insert_point, IR::Opcode::StorageAtomicUMax32, + {inst.Arg(0), inst.Arg(1), new_atomic_value})}; + static_cast(umax_result); + + inst.ReplaceUsesWith(new_index); +} + +} // Anonymous namespace + +void CompactionFallbackPass(IR::Program& program, const HostTranslateInfo& host_info) { + if (program.stage != Stage::Geometry || host_info.support_subgroup_in_geometry_stage) { + return; + } + for (IR::Block* const block : program.post_order_blocks) { + for (IR::Inst& inst : block->Instructions()) { + if (inst.GetOpcode() != IR::Opcode::StorageAtomicIAdd32) { + continue; + } + if (!DependsOnSubgroupBallot(inst.Arg(2), 0)) { + continue; + } + RewriteAtomic(*block, inst); + } + } +} + +} // namespace Shader::Optimization diff --git a/src/shader_recompiler/ir_opt/passes.h b/src/shader_recompiler/ir_opt/passes.h index 1e637cb23c..ab78b19a44 100644 --- a/src/shader_recompiler/ir_opt/passes.h +++ b/src/shader_recompiler/ir_opt/passes.h @@ -13,6 +13,7 @@ struct HostTranslateInfo; namespace Shader::Optimization { void CollectShaderInfoPass(Environment& env, IR::Program& program); +void CompactionFallbackPass(IR::Program& program, const HostTranslateInfo& host_info); void ConditionalBarrierPass(IR::Program& program); void ConstantPropagationPass(Environment& env, IR::Program& program); void DeadCodeEliminationPass(IR::Program& program); diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index df69eb97f5..9371cd7c50 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -269,6 +269,7 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_viewport_index_layer = device.HasVertexViewportLayer(), .support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(), .support_conditional_barrier = device.SupportsConditionalBarriers(), + .support_subgroup_in_geometry_stage = true, } { host_info.ApplyDescriptorLimitPolicy(); if (use_asynchronous_shaders) { diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 642b9fa223..a6005a7c5b 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -479,6 +479,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(), .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), .support_conditional_barrier = device.SupportsConditionalBarriers(), + .support_subgroup_in_geometry_stage = profile.SupportsSubgroupStage(Shader::Stage::Geometry), }; host_info.ApplyDescriptorLimitPolicy();