From 69cd5ee0d1fd19c286f26190b8cc3e22f53369a6 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 18 Aug 2026 20:51:29 -0400 Subject: [PATCH] [TEST] Improve the geometry shader handling --- .../frontend/maxwell/translate_program.cpp | 61 ++++++++++++------ .../frontend/maxwell/translate_program.h | 6 +- .../renderer_opengl/gl_shader_cache.cpp | 64 +++++++++---------- .../renderer_vulkan/vk_pipeline_cache.cpp | 64 +++++++++---------- .../renderer_vulkan/vk_rasterizer.cpp | 11 +++- .../renderer_vulkan/vk_scheduler.cpp | 3 +- src/video_core/vulkan_common/vulkan_device.h | 25 ++++++++ 7 files changed, 141 insertions(+), 93 deletions(-) diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp index ebc5a825dd..728d405674 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp @@ -169,11 +169,36 @@ std::map GenerateLegacyToGenericMappings( return mapping; } +struct PassthroughVertices { + u32 count; + u32 first; + u32 stride; +}; + +PassthroughVertices GetPassthroughVertices(InputTopology input_topology) { + switch (input_topology) { + case InputTopology::Points: + return {1, 0, 1}; + case InputTopology::Lines: + return {2, 0, 1}; + case InputTopology::LinesAdjacency: + return {2, 1, 1}; + case InputTopology::Triangles: + return {3, 0, 1}; + case InputTopology::TrianglesAdjacency: + return {3, 0, 2}; + } + return {3, 0, 1}; +} + void EmitGeometryPassthrough(IR::IREmitter& ir, const IR::Program& program, const Shader::VaryingState& passthrough_mask, bool passthrough_position, - std::optional passthrough_layer_attr) { - for (u32 i = 0; i < program.output_vertices; i++) { + std::optional passthrough_layer_attr, + InputTopology input_topology) { + const PassthroughVertices vertices{GetPassthroughVertices(input_topology)}; + for (u32 vertex = 0; vertex < vertices.count; vertex++) { + const u32 i = vertices.first + vertex * vertices.stride; // Assign generics from input for (u32 j = 0; j < 32; j++) { if (!passthrough_mask.Generic(j)) { @@ -208,25 +233,16 @@ void EmitGeometryPassthrough(IR::IREmitter& ir, const IR::Program& program, ir.EndPrimitive(ir.Imm32(0)); } -u32 GetOutputTopologyVertices(OutputTopology output_topology) { - switch (output_topology) { - case OutputTopology::PointList: - return 1; - case OutputTopology::LineStrip: - return 2; - default: - return 3; - } -} - -void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInfo& host_info) { +void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInfo& host_info, + InputTopology input_topology) { for (IR::Block* const block : program.blocks) { for (IR::Inst& inst : block->Instructions()) { if (inst.GetOpcode() == IR::Opcode::Epilogue) { IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)}; EmitGeometryPassthrough( ir, program, program.info.passthrough, - program.info.passthrough.AnyComponent(IR::Attribute::PositionX), {}); + program.info.passthrough.AnyComponent(IR::Attribute::PositionX), {}, + input_topology); } } } @@ -235,7 +251,8 @@ void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInf } // Anonymous namespace IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool& block_pool, - Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info) { + Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info, + InputTopology input_topology) { HostTranslateInfo normalized_host_info{host_info}; normalized_host_info.ApplyDescriptorLimitPolicy(); @@ -264,8 +281,9 @@ IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool& inst_pool, ObjectPool& block_pool, const HostTranslateInfo& host_info, IR::Program& source_program, - Shader::OutputTopology output_topology) { + Shader::OutputTopology output_topology, + InputTopology input_topology) { IR::Program program; program.stage = Stage::Geometry; program.output_topology = output_topology; - program.output_vertices = GetOutputTopologyVertices(output_topology); + program.output_vertices = GetPassthroughVertices(input_topology).count; program.is_geometry_passthrough = false; program.info.loads.mask = source_program.info.stores.mask; @@ -433,7 +452,7 @@ IR::Program GenerateGeometryPassthrough(ObjectPool& inst_pool, IR::IREmitter ir{*current_block}; EmitGeometryPassthrough(ir, program, program.info.stores, true, - source_program.info.emulated_layer); + source_program.info.emulated_layer, input_topology); IR::Block* return_block{block_pool.Create(inst_pool)}; IR::IREmitter{*return_block}.Epilogue(); diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.h b/src/shader_recompiler/frontend/maxwell/translate_program.h index fa870258b3..23c7ba1bbf 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.h +++ b/src/shader_recompiler/frontend/maxwell/translate_program.h @@ -21,7 +21,8 @@ namespace Shader::Maxwell { [[nodiscard]] IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool& block_pool, Environment& env, - Flow::CFG& cfg, const HostTranslateInfo& host_info); + Flow::CFG& cfg, const HostTranslateInfo& host_info, + InputTopology input_topology); [[nodiscard]] IR::Program MergeDualVertexPrograms(IR::Program& vertex_a, IR::Program& vertex_b, Environment& env_vertex_b); @@ -35,6 +36,7 @@ void ConvertLegacyToGeneric(IR::Program& program, const RuntimeInfo& runtime_inf ObjectPool& block_pool, const HostTranslateInfo& host_info, IR::Program& source_program, - Shader::OutputTopology output_topology); + Shader::OutputTopology output_topology, + InputTopology input_topology); } // namespace Shader::Maxwell diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index cde03d5822..1377ac4674 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -54,7 +54,7 @@ using VideoCommon::LoadPipelines; using VideoCommon::SerializePipeline; using Context = ShaderContext::Context; -constexpr u32 CACHE_VERSION = 15; +constexpr u32 CACHE_VERSION = 16; template auto MakeSpan(Container& container) { @@ -72,6 +72,25 @@ Shader::OutputTopology MaxwellToOutputTopology(Maxwell::PrimitiveTopology topolo } } +Shader::InputTopology MaxwellToInputTopology(Maxwell::PrimitiveTopology topology) { + switch (topology) { + case Maxwell::PrimitiveTopology::Points: + return Shader::InputTopology::Points; + case Maxwell::PrimitiveTopology::Lines: + case Maxwell::PrimitiveTopology::LineLoop: + case Maxwell::PrimitiveTopology::LineStrip: + return Shader::InputTopology::Lines; + case Maxwell::PrimitiveTopology::LinesAdjacency: + case Maxwell::PrimitiveTopology::LineStripAdjacency: + return Shader::InputTopology::LinesAdjacency; + case Maxwell::PrimitiveTopology::TrianglesAdjacency: + case Maxwell::PrimitiveTopology::TriangleStripAdjacency: + return Shader::InputTopology::TrianglesAdjacency; + default: + return Shader::InputTopology::Triangles; + } +} + Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key, const Shader::IR::Program& program, const Shader::IR::Program* previous_program, @@ -127,33 +146,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key, default: break; } - switch (key.gs_input_topology) { - case Maxwell::PrimitiveTopology::Points: - info.input_topology = Shader::InputTopology::Points; - break; - case Maxwell::PrimitiveTopology::Lines: - case Maxwell::PrimitiveTopology::LineLoop: - case Maxwell::PrimitiveTopology::LineStrip: - info.input_topology = Shader::InputTopology::Lines; - break; - case Maxwell::PrimitiveTopology::Triangles: - case Maxwell::PrimitiveTopology::TriangleStrip: - case Maxwell::PrimitiveTopology::TriangleFan: - case Maxwell::PrimitiveTopology::Quads: - case Maxwell::PrimitiveTopology::QuadStrip: - case Maxwell::PrimitiveTopology::Polygon: - case Maxwell::PrimitiveTopology::Patches: - info.input_topology = Shader::InputTopology::Triangles; - break; - case Maxwell::PrimitiveTopology::LinesAdjacency: - case Maxwell::PrimitiveTopology::LineStripAdjacency: - info.input_topology = Shader::InputTopology::LinesAdjacency; - break; - case Maxwell::PrimitiveTopology::TrianglesAdjacency: - case Maxwell::PrimitiveTopology::TriangleStripAdjacency: - info.input_topology = Shader::InputTopology::TrianglesAdjacency; - break; - } + info.input_topology = MaxwellToInputTopology(key.gs_input_topology); info.glasm_use_storage_buffers = glasm_use_storage_buffers; return info; } @@ -483,8 +476,10 @@ std::unique_ptr ShaderCache::CreateGraphicsPipeline( && index == u32(Maxwell::ShaderType::Geometry); if (key.unique_hashes[index] == 0 && is_emulated_stage) { auto topology = MaxwellToOutputTopology(key.gs_input_topology); - programs[index] = GenerateGeometryPassthrough(pools.inst, pools.block, host_info, - *layer_source_program, topology); + programs[index] = + GenerateGeometryPassthrough(pools.inst, pools.block, host_info, + *layer_source_program, topology, + MaxwellToInputTopology(key.gs_input_topology)); continue; } if (key.unique_hashes[index] == 0) { @@ -502,13 +497,15 @@ std::unique_ptr ShaderCache::CreateGraphicsPipeline( if (!uses_vertex_a || index != 1) { // Normal path - programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info); + programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info, + MaxwellToInputTopology(key.gs_input_topology)); total_storage_buffers += Shader::NumDescriptors(programs[index].info.storage_buffers_descriptors); } else { // VertexB path when VertexA is present. auto& program_va{programs[0]}; - auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)}; + auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info, + MaxwellToInputTopology(key.gs_input_topology))}; total_storage_buffers += Shader::NumDescriptors(program_vb.info.storage_buffers_descriptors); programs[index] = MergeDualVertexPrograms(program_va, program_vb, env); } @@ -597,7 +594,8 @@ std::unique_ptr ShaderCache::CreateComputePipeline( env.Dump(hash, key.unique_hash); } - auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)}; + auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info, + Shader::InputTopology::Points)}; const u32 num_storage_buffers{Shader::NumDescriptors(program.info.storage_buffers_descriptors)}; Shader::RuntimeInfo info; info.glasm_use_storage_buffers = num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks(); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index acb4219213..ed18587a15 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 = 18; +constexpr u32 CACHE_VERSION = 19; 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'}; @@ -152,6 +152,25 @@ Shader::AttributeType AttributeType(const FixedPipelineState& state, size_t inde return Shader::AttributeType::Disabled; } +Shader::InputTopology MaxwellToInputTopology(Maxwell::PrimitiveTopology topology) { + switch (topology) { + case Maxwell::PrimitiveTopology::Points: + return Shader::InputTopology::Points; + case Maxwell::PrimitiveTopology::Lines: + case Maxwell::PrimitiveTopology::LineLoop: + case Maxwell::PrimitiveTopology::LineStrip: + return Shader::InputTopology::Lines; + case Maxwell::PrimitiveTopology::LinesAdjacency: + case Maxwell::PrimitiveTopology::LineStripAdjacency: + return Shader::InputTopology::LinesAdjacency; + case Maxwell::PrimitiveTopology::TrianglesAdjacency: + case Maxwell::PrimitiveTopology::TriangleStripAdjacency: + return Shader::InputTopology::TrianglesAdjacency; + default: + return Shader::InputTopology::Triangles; + } +} + Shader::RuntimeInfo MakeRuntimeInfo(std::span programs, const GraphicsPipelineCacheKey& key, const Shader::IR::Program& program, @@ -270,33 +289,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program default: break; } - switch (key.state.topology) { - case Maxwell::PrimitiveTopology::Points: - info.input_topology = Shader::InputTopology::Points; - break; - case Maxwell::PrimitiveTopology::Lines: - case Maxwell::PrimitiveTopology::LineLoop: - case Maxwell::PrimitiveTopology::LineStrip: - info.input_topology = Shader::InputTopology::Lines; - break; - case Maxwell::PrimitiveTopology::Triangles: - case Maxwell::PrimitiveTopology::TriangleStrip: - case Maxwell::PrimitiveTopology::TriangleFan: - case Maxwell::PrimitiveTopology::Quads: - case Maxwell::PrimitiveTopology::QuadStrip: - case Maxwell::PrimitiveTopology::Polygon: - case Maxwell::PrimitiveTopology::Patches: - info.input_topology = Shader::InputTopology::Triangles; - break; - case Maxwell::PrimitiveTopology::LinesAdjacency: - case Maxwell::PrimitiveTopology::LineStripAdjacency: - info.input_topology = Shader::InputTopology::LinesAdjacency; - break; - case Maxwell::PrimitiveTopology::TrianglesAdjacency: - case Maxwell::PrimitiveTopology::TriangleStripAdjacency: - info.input_topology = Shader::InputTopology::TrianglesAdjacency; - break; - } + info.input_topology = MaxwellToInputTopology(key.state.topology); info.force_early_z = key.state.early_z != 0; info.y_negate = key.state.y_negate != 0; return info; @@ -793,8 +786,10 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( index == static_cast(Maxwell::ShaderType::Geometry); if (key.unique_hashes[index] == 0 && is_emulated_stage) { auto topology = MaxwellToOutputTopology(key.state.topology); - programs[index] = GenerateGeometryPassthrough(pools.inst, pools.block, host_info, - *layer_source_program, topology); + programs[index] = + GenerateGeometryPassthrough(pools.inst, pools.block, host_info, + *layer_source_program, topology, + MaxwellToInputTopology(key.state.topology)); continue; } if (key.unique_hashes[index] == 0) { @@ -807,11 +802,13 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0); if (!uses_vertex_a || index != 1) { // Normal path - programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info); + programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info, + MaxwellToInputTopology(key.state.topology)); } else { // VertexB path when VertexA is present. auto& program_va{programs[0]}; - auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)}; + auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info, + MaxwellToInputTopology(key.state.topology))}; programs[index] = MergeDualVertexPrograms(program_va, program_vb, env); } @@ -957,7 +954,8 @@ std::unique_ptr PipelineCache::CreateComputePipeline( env.Dump(hash, key.unique_hash); } - auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)}; + auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info, + Shader::InputTopology::Points)}; const VkDriverIdKHR driver_id = device.GetDriverID(); const bool needs_shared_mem_clamp = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 10522958cf..05535370b3 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -850,10 +850,15 @@ void RasterizerVulkan::WaitForIdle() { // fragment shaders can still write storage buffers. VkPipelineStageFlags flags = VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | - VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT | - VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT | - VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT; + if (device.SupportsTessellationShader()) { + flags |= VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT | + VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT; + } + if (device.SupportsGeometryShader()) { + flags |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT; + } if (device.IsExtTransformFeedbackSupported()) { flags |= VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT; } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 36ca122daa..f1f04d0b8c 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -410,6 +410,7 @@ void Scheduler::EndRenderPass() Record([num_images = num_renderpass_images, images = renderpass_images, ranges = renderpass_image_ranges, + consumer_stages = device.AttachmentConsumerStages(), has_transform_feedback = device.IsExtTransformFeedbackSupported()]( vk::CommandBuffer cmdbuf) { static constexpr VkAccessFlags SHADER_ACCESS = @@ -458,7 +459,7 @@ void Scheduler::EndRenderPass() } cmdbuf.EndRenderPass(); cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, consumer_stages, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images)); if (has_transform_feedback) { static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{ diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 6f8c3243b5..6f27e74e28 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -1023,6 +1023,31 @@ FN_MAX_LIMIT_LIST return features2.features.multiViewport; } + bool SupportsTessellationShader() const { + return features2.features.tessellationShader; + } + + bool SupportsGeometryShader() const { + return features2.features.geometryShader; + } + + VkPipelineStageFlags AttachmentConsumerStages() const { + VkPipelineStageFlags stages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + if (features2.features.tessellationShader) { + stages |= VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT | + VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT; + } + if (features2.features.geometryShader) { + stages |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT; + } + return stages; + } + /// Returns true if the device supports VK_KHR_maintenance1. bool IsKhrMaintenance1Supported() const { return extensions.maintenance1;