From 7516068662d65c37555ac1c928392504fba02b1e Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 13 Sep 2026 07:45:08 +0400 Subject: [PATCH] gpu: compile RDNA2 tessellation through native LS HS and TES stages --- .../renderer/pipeline/descriptors.cpp | 20 +- .../renderer/pipeline/pipelineCache.cpp | 61 ++-- .../renderer/pipeline/pipelineCache.h | 40 +-- .../pipeline/shaderResourceBarrier.cpp | 20 ++ .../renderer/pipeline/shaderResourceBarrier.h | 1 + .../host_gpu/renderer/pipeline/shaders.cpp | 51 ++-- src/graphics/host_gpu/renderer/render.h | 2 +- src/graphics/host_gpu/renderer/renderDraw.cpp | 54 ++-- .../shader/recompiler/ShaderRecompiler.cpp | 260 +++++++++++++++++- .../shader/recompiler/ShaderRecompiler.h | 3 + .../recompiler/backend/spirv/SpirvEmitter.cpp | 9 +- .../backend/spirv/spirvEmitterAnalysis.cpp | 3 + .../backend/spirv/spirvEmitterFlow.cpp | 97 ++++++- .../backend/spirv/spirvEmitterInstructions.h | 5 +- .../backend/spirv/spirvEmitterInternal.h | 4 + .../backend/spirv/spirvEmitterModule.cpp | 92 ++++++- .../frontend/translate/Translate.cpp | 47 +++- src/graphics/shader/recompiler/ir/ShaderIR.h | 18 +- .../recompiler/ir/opcodes/ValueOpcodes.cpp | 1 + .../recompiler/ir/opcodes/ValueOpcodes.inc | 3 + .../ir/passes/ConstantPropagation.cpp | 9 + .../ir/passes/ShaderInfoCollection.cpp | 31 ++- src/graphics/shader/shader.cpp | 95 +++++-- src/graphics/shader/shader.h | 24 +- src/graphics/shader/shaderCompiler.h | 3 + tests/ShaderRecompilerComputeTests.cpp | 243 ++++++++++++++-- 26 files changed, 1035 insertions(+), 161 deletions(-) diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index 26525ca19..e20394968 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -93,22 +93,15 @@ static const char* ShaderStageResourceName(ShaderType stage) { switch (stage) { case ShaderType::Vertex: return "Vertex"; case ShaderType::Mesh: return "Mesh"; + case ShaderType::Local: return "Local"; + case ShaderType::TessellationControl: return "Hull"; + case ShaderType::TessellationEvaluation: return "Domain"; case ShaderType::Pixel: return "Pixel"; case ShaderType::Compute: return "Compute"; default: return "Unknown"; } } -static vk::ShaderStageFlags NativeShaderStage(ShaderType stage) { - switch (stage) { - case ShaderType::Vertex: return vk::ShaderStageFlagBits::eVertex; - case ShaderType::Mesh: return vk::ShaderStageFlagBits::eMeshEXT; - case ShaderType::Pixel: return vk::ShaderStageFlagBits::eFragment; - case ShaderType::Compute: return vk::ShaderStageFlagBits::eCompute; - default: EXIT("unknown native shader stage\n"); - } -} - static Prospero::ImageType TextureType(const ShaderTextureResource& descriptor) { const auto type = descriptor.Type(); return type == Prospero::ImageType::kCube ? Prospero::ImageType::kColor2DArray : type; @@ -884,9 +877,10 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer, size_t write_count = 0; ShaderRecompiler::IR::PushData push_data; bool has_push_data = false; - constexpr auto GraphicsStages = vk::ShaderStageFlagBits::eVertex | - vk::ShaderStageFlagBits::eMeshEXT | - vk::ShaderStageFlagBits::eFragment; + constexpr auto GraphicsStages = + vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eMeshEXT | + vk::ShaderStageFlagBits::eTessellationControl | + vk::ShaderStageFlagBits::eTessellationEvaluation | vk::ShaderStageFlagBits::eFragment; vk::ShaderStageFlags push_stages = pipeline_bind_point == vk::PipelineBindPoint::eGraphics ? vk::ShaderStageFlagBits::eFragment : vk::ShaderStageFlags {}; diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index 9d68a2e9c..07cabafe8 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -236,6 +236,9 @@ struct PipelineCache::ProgramCache { switch (options.stage) { case ShaderType::Vertex: stage_name = "vs"; break; case ShaderType::Mesh: stage_name = "ms"; break; + case ShaderType::Local: stage_name = "ls"; break; + case ShaderType::TessellationControl: stage_name = "hs"; break; + case ShaderType::TessellationEvaluation: stage_name = "ds"; break; case ShaderType::Pixel: stage_name = "ps"; break; case ShaderType::Compute: stage_name = "cs"; break; default: EXIT("invalid pipeline shader stage\n"); @@ -273,7 +276,7 @@ struct PipelineCache::ProgramCache { uint32_t& push_data_cursor) { ShaderType stage; if constexpr (std::is_same_v) { - stage = input_info.mesh.threads_num[0] != 0 ? ShaderType::Mesh : ShaderType::Vertex; + stage = input_info.logical_stage; } else if constexpr (std::is_same_v) { stage = ShaderType::Pixel; } else { @@ -325,6 +328,9 @@ struct PipelineCache::ProgramCache { switch (stage) { case ShaderType::Vertex: label = "ShaderRecompiler VS"; break; case ShaderType::Mesh: label = "ShaderRecompiler MS"; break; + case ShaderType::Local: label = "ShaderRecompiler LS"; break; + case ShaderType::TessellationControl: label = "ShaderRecompiler HS"; break; + case ShaderType::TessellationEvaluation: label = "ShaderRecompiler DS"; break; case ShaderType::Pixel: label = "ShaderRecompiler PS"; break; case ShaderType::Compute: label = "ShaderRecompiler CS"; break; default: EXIT("invalid pipeline shader stage\n"); @@ -341,9 +347,9 @@ struct PipelineCache::ProgramCache { if constexpr (std::is_same_v) { options.user_data_base = 8; - if (stage == ShaderType::Mesh) { + if (stage == ShaderType::Mesh || stage == ShaderType::TessellationControl) { options.user_data_base = 0; - options.wave_size = input_info.mesh.wave_size; + options.wave_size = stage == ShaderType::Mesh ? input_info.mesh.wave_size : 64u; } } else if constexpr (std::is_same_v) { options.wave_size = input_info.wave_size; @@ -361,16 +367,19 @@ struct PipelineCache::ProgramCache { input_info.stage = {.program = &permutation.program, .resources = std::move(resources)}; permutation.program.bindings.AdvancePushData(push_data_cursor); - std::array(ShaderType::Mesh) + 1> counts {}; + std::array(ShaderType::TessellationEvaluation) + 1> counts {}; for (const auto& [key, source]: programs) { counts[static_cast(key.stage)] += source.permutations.size(); } // Guest geometry shaders are compiled through the host mesh stage. - std::printf("Shaders: VS %zu | PS %zu | CS %zu | GS %zu\n", + std::printf("Shaders: VS %zu | PS %zu | CS %zu | GS %zu | LS %zu | HS %zu | TES %zu\n", counts[static_cast(ShaderType::Vertex)], counts[static_cast(ShaderType::Pixel)], counts[static_cast(ShaderType::Compute)], - counts[static_cast(ShaderType::Mesh)]); + counts[static_cast(ShaderType::Mesh)], + counts[static_cast(ShaderType::Local)], + counts[static_cast(ShaderType::TessellationControl)], + counts[static_cast(ShaderType::TessellationEvaluation)]); return permutation.handle; } @@ -564,12 +573,18 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms( const HW::VertexShaderInfo& vertex_regs, const HW::PixelShaderInfo& pixel_regs, const HW::ShaderRegisters& sh, const HW::Context& context, const HW::UserConfig& user_config, std::span target_export_mapping, bool pixel_active, - ShaderVertexInputInfo& vertex_info, ShaderPixelInputInfo& pixel_info) { - const auto vertex_params = PrepareProgram(vertex_regs, context, user_config, vertex_info); - const bool mesh_active = vertex_info.mesh.threads_num[0] != 0; + std::array& vertex_info, ShaderPixelInputInfo& pixel_info) { + const bool tess_active = user_config.GetPrimType() == Prospero::PrimitiveType::kPatch; + std::array vertex_params; + if (tess_active) { + vertex_params = PrepareTessellationPrograms(vertex_regs, context, vertex_info); + } else { + vertex_params[0] = PrepareProgram(vertex_regs, context, user_config, vertex_info[0]); + } + const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh; if (mesh_active) { EXIT_NOT_IMPLEMENTED(!m_graphics.mesh_shader_enabled); - auto& mesh = vertex_info.mesh; + auto& mesh = vertex_info[0].mesh; mesh.host_subgroup_size = m_graphics.subgroup_size; const auto& limits = m_graphics.mesh_shader_properties; const auto logical_threads = @@ -592,7 +607,7 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms( if (context.GetClipControl().clip_disable) { const auto& viewport = context.GetScreenViewport().viewports[0]; const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits; - auto& clip = vertex_info.clip_space; + auto& clip = vertex_info[tess_active ? 2u : 0u].clip_space; clip.scale[0] = viewport.xscale; clip.scale[1] = viewport.yscale; clip.offset[0] = viewport.xoffset; @@ -610,7 +625,9 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms( if (pixel_active) { result.pixel = m_program_cache->Get(pixel_params, pixel_info, push_data_cursor); } - result.vertex = m_program_cache->Get(vertex_params, vertex_info, push_data_cursor); + for (uint32_t i = 0; i < (tess_active ? 3u : 1u); i++) { + result.vertex[i] = m_program_cache->Get(vertex_params[i], vertex_info[i], push_data_cursor); + } return result; } @@ -630,10 +647,12 @@ bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other) PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline( std::span colors, const RenderDepthInfo& depth, - const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command, + std::span vertex_info, CommandBuffer& command, const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology, - bool primitive_restart_enable, const ShaderProgram& vertex_program, - const ShaderProgram& pixel_program) { + bool primitive_restart_enable, const GraphicsPrograms& programs) { + const auto& vs_input_info = vertex_info.front(); + const auto& vertex_program = programs.vertex[0]; + const auto& pixel_program = programs.pixel; KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Gfx)", profiler::colors::DeepOrangeA200); EXIT_IF(colors.size() > RENDER_COLOR_ATTACHMENTS_MAX); @@ -651,7 +670,9 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline( const auto ps_id = ps_active ? pixel_program.id : 0; GraphicsPipelineKey key {}; - key.vs_shader_id = vs_id; + for (uint32_t i = 0; i < programs.vertex.size(); i++) { + key.vertex_shader_ids[i] = programs.vertex[i].id; + } key.ps_shader_id = ps_id; auto& static_params = key.static_params; auto& rendering = key.rendering; @@ -729,7 +750,8 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline( static_params.stencil_test_enable = depth.stencil_test_enable; static_params.stencil_front = depth.stencil_static_front; static_params.stencil_back = depth.stencil_static_back; - const bool rect_list = topology == vk::PrimitiveTopology::ePatchList; + const bool rect_list = + command.GetUserConfig().GetPrimType() == Prospero::PrimitiveType::kRectList; static_params.cull_back = !rect_list && mc.cull_back; static_params.cull_front = !rect_list && mc.cull_front; static_params.face = mc.face; @@ -780,9 +802,8 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline( auto cached = std::make_unique(); LogPipelineTrace("CreatePipelineInternal begin", vs_id, ps_id); - CreatePipelineInternal(m_graphics, *cached, rendering, key.vertex_input, vs_input_info, - vertex_program, ps_input_info, pixel_program, static_params, - m_driver_cache); + CreatePipelineInternal(m_graphics, *cached, rendering, key.vertex_input, vertex_info, + ps_input_info, programs, static_params, m_driver_cache); LogPipelineTrace("CreatePipelineInternal done", vs_id, ps_id); EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr); diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h index 310d1775a..af687dbfe 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h @@ -121,8 +121,10 @@ public: }; struct GraphicsPrograms { - ShaderProgram vertex; + std::array vertex; ShaderProgram pixel; + + [[nodiscard]] uint32_t VertexStageCount() const { return vertex[1] ? 3u : 1u; } }; GraphicsPrograms @@ -130,18 +132,18 @@ public: const HW::PixelShaderInfo& pixel_regs, const HW::ShaderRegisters& sh, const HW::Context& context, const HW::UserConfig& user_config, std::span target_export_mapping, - bool pixel_active, ShaderVertexInputInfo& vertex_info, + bool pixel_active, std::array& vertex_info, ShaderPixelInputInfo& pixel_info); ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs, const HW::ShaderRegisters& sh, ShaderComputeInputInfo& input_info); - Pipeline& - GetGraphicsPipeline(std::span colors, const RenderDepthInfo& depth, - const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command, - const ShaderPixelInputInfo* ps_input_info, - vk::PrimitiveTopology topology, bool primitive_restart_enable, - const ShaderProgram& vertex_program, const ShaderProgram& pixel_program); + Pipeline& GetGraphicsPipeline(std::span colors, + const RenderDepthInfo& depth, + std::span vertex_info, + CommandBuffer& command, const ShaderPixelInputInfo* ps_input_info, + vk::PrimitiveTopology topology, bool primitive_restart_enable, + const GraphicsPrograms& programs); Pipeline& GetComputePipeline(const ShaderComputeInputInfo& input_info, const ShaderProgram& compute_program); @@ -150,13 +152,13 @@ private: struct GraphicsPipelineKey { PipelineRenderingState rendering; - uint64_t vs_shader_id = 0; + std::array vertex_shader_ids {}; uint64_t ps_shader_id = 0; PipelineVertexInputState vertex_input; PipelineStaticParameters static_params; bool operator==(const GraphicsPipelineKey& other) const { - return rendering == other.rendering && vs_shader_id == other.vs_shader_id && + return rendering == other.rendering && vertex_shader_ids == other.vertex_shader_ids && ps_shader_id == other.ps_shader_id && vertex_input == other.vertex_input && static_params == other.static_params; } @@ -189,7 +191,9 @@ private: std::size_t operator()(const GraphicsPipelineKey& key) const { std::size_t hash = 0; PipelineKeyHash::MixRendering(hash, key.rendering); - PipelineKeyHash::Mix(hash, key.vs_shader_id); + for (const auto id: key.vertex_shader_ids) { + PipelineKeyHash::Mix(hash, id); + } PipelineKeyHash::Mix(hash, key.ps_shader_id); PipelineKeyHash::Mix(hash, key.vertex_input.binding_count); for (uint32_t i = 0; i < key.vertex_input.binding_count; i++) { @@ -219,12 +223,14 @@ private: }; void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id); -void CreatePipelineInternal( - GraphicContext& graphics, PipelineCache::Pipeline& pipeline, - const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input, - const ShaderVertexInputInfo& vs_input_info, const ShaderProgram& vertex_program, - const ShaderPixelInputInfo* ps_input_info, const ShaderProgram& pixel_program, - const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache); +void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline, + const PipelineRenderingState& rendering, + const PipelineVertexInputState& vertex_input, + std::span vertex_info, + const ShaderPixelInputInfo* ps_input_info, + const PipelineCache::GraphicsPrograms& programs, + const PipelineStaticParameters& static_params, + vk::PipelineCache driver_cache); void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline, const ShaderComputeInputInfo& input_info, vk::ShaderModule compute_module, vk::PipelineCache driver_cache); diff --git a/src/graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.cpp b/src/graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.cpp index adffbd020..f3a86a21c 100644 --- a/src/graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.cpp @@ -8,6 +8,20 @@ namespace Libs::Graphics { +vk::ShaderStageFlagBits NativeShaderStage(ShaderType stage) { + switch (stage) { + case ShaderType::Local: + case ShaderType::Vertex: return vk::ShaderStageFlagBits::eVertex; + case ShaderType::Mesh: return vk::ShaderStageFlagBits::eMeshEXT; + case ShaderType::TessellationControl: return vk::ShaderStageFlagBits::eTessellationControl; + case ShaderType::TessellationEvaluation: + return vk::ShaderStageFlagBits::eTessellationEvaluation; + case ShaderType::Pixel: return vk::ShaderStageFlagBits::eFragment; + case ShaderType::Compute: return vk::ShaderStageFlagBits::eCompute; + default: EXIT("unknown native shader stage\n"); + } +} + vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) { vk::PipelineStageFlags result = {}; if (stages & vk::ShaderStageFlagBits::eVertex) { @@ -16,6 +30,12 @@ vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) { if (stages & vk::ShaderStageFlagBits::eMeshEXT) { result |= vk::PipelineStageFlagBits::eMeshShaderEXT; } + if (stages & vk::ShaderStageFlagBits::eTessellationControl) { + result |= vk::PipelineStageFlagBits::eTessellationControlShader; + } + if (stages & vk::ShaderStageFlagBits::eTessellationEvaluation) { + result |= vk::PipelineStageFlagBits::eTessellationEvaluationShader; + } if (stages & vk::ShaderStageFlagBits::eFragment) { result |= vk::PipelineStageFlagBits::eFragmentShader; } diff --git a/src/graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h b/src/graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h index 98ab3018a..47d6e5117 100644 --- a/src/graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h +++ b/src/graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h @@ -8,6 +8,7 @@ namespace Libs::Graphics { struct ShaderStageRuntime; +vk::ShaderStageFlagBits NativeShaderStage(ShaderType stage); vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages); VulkanMemoryBarrier MakeShaderAccessDependency(); VulkanMemoryBarrier MakeShaderWriteHazardDependency(); diff --git a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp index 708d7f7ac..dc7165ef9 100644 --- a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp @@ -8,6 +8,7 @@ #include "graphics/host_gpu/renderer/debug.h" #include "graphics/host_gpu/renderer/pipeline/descriptors.h" #include "graphics/host_gpu/renderer/pipeline/pipelineCache.h" +#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h" #include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderTarget.h" @@ -205,12 +206,18 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe } // NOLINTNEXTLINE(readability-function-cognitive-complexity) -void CreatePipelineInternal( - GraphicContext& graphics, PipelineCache::Pipeline& pipeline, - const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input, - const ShaderVertexInputInfo& vs_input_info, const ShaderProgram& vertex_program, - const ShaderPixelInputInfo* ps_input_info, const ShaderProgram& pixel_program, - const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache) { +void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline, + const PipelineRenderingState& rendering, + const PipelineVertexInputState& vertex_input, + std::span vertex_info, + const ShaderPixelInputInfo* ps_input_info, + const PipelineCache::GraphicsPrograms& programs, + const PipelineStaticParameters& static_params, + vk::PipelineCache driver_cache) { + const auto& vs_input_info = vertex_info.front(); + const auto& vertex_program = programs.vertex[0]; + const auto& pixel_program = programs.pixel; + const bool tessellation = vertex_info.size() == 3; const bool ps_active = ps_input_info != nullptr; EXIT_IF(!vertex_program || (ps_active && !pixel_program)); const bool with_depth = rendering.depth_format != vk::Format::eUndefined || @@ -218,10 +225,8 @@ void CreatePipelineInternal( EXIT_IF(!vs_input_info.stage); const bool mesh = vs_input_info.stage.program->stage == ShaderType::Mesh; EXIT_NOT_IMPLEMENTED(mesh && !graphics.mesh_shader_enabled); - const auto vertex_stage = - mesh ? vk::ShaderStageFlagBits::eMeshEXT : vk::ShaderStageFlagBits::eVertex; - - const bool rect_list = !mesh && static_params.topology == vk::PrimitiveTopology::ePatchList; + const bool rect_list = + !mesh && !tessellation && static_params.topology == vk::PrimitiveTopology::ePatchList; vk::ShaderModule tess_control_shader_module = nullptr; vk::ShaderModule tess_eval_shader_module = nullptr; @@ -255,9 +260,14 @@ void CreatePipelineInternal( EXIT_NOT_IMPLEMENTED( rect_list && (tess_control_shader_module == nullptr || tess_eval_shader_module == nullptr)); - vk::PipelineShaderStageCreateInfo shader_stages[4] = { - {.stage = vertex_stage, .module = vertex_program.module, .pName = "main"}}; - uint32_t shader_stage_count = 1; + vk::PipelineShaderStageCreateInfo shader_stages[4] {}; + uint32_t shader_stage_count = 0; + for (uint32_t i = 0; i < vertex_info.size(); i++) { + shader_stages[shader_stage_count++] = {.stage = + NativeShaderStage(vertex_info[i].logical_stage), + .module = programs.vertex[i].module, + .pName = "main"}; + } if (rect_list) { shader_stages[shader_stage_count++] = {.stage = vk::ShaderStageFlagBits::eTessellationControl, @@ -432,15 +442,19 @@ void CreatePipelineInternal( color_blending.pAttachments = color_blend_attachment; std::vector descriptor_bindings; - AddLayoutBindings(descriptor_bindings, *vs_input_info.stage.program, vertex_stage); + vk::ShaderStageFlags graphics_stages = vk::ShaderStageFlagBits::eFragment; + for (const auto& stage: vertex_info) { + const auto native_stage = NativeShaderStage(stage.logical_stage); + AddLayoutBindings(descriptor_bindings, *stage.stage.program, native_stage); + graphics_stages |= native_stage; + } if (ps_active) { EXIT_IF(!ps_input_info->stage); AddLayoutBindings(descriptor_bindings, *ps_input_info->stage.program, vk::ShaderStageFlagBits::eFragment); } CreateDescriptorLayout(graphics, pipeline, descriptor_bindings); - const auto GraphicsStages = vertex_stage | vk::ShaderStageFlagBits::eFragment; - const vk::PushConstantRange push_constants {GraphicsStages, 0, + const vk::PushConstantRange push_constants {graphics_stages, 0, ShaderRecompiler::IR::NativePushConstantSize}; vk::PipelineLayoutCreateInfo pipeline_layout_info {}; @@ -524,8 +538,9 @@ void CreatePipelineInternal( pipeline_info.pVertexInputState = mesh ? nullptr : &vertex_input_info; pipeline_info.pInputAssemblyState = mesh ? nullptr : &input_assembly; vk::PipelineTessellationStateCreateInfo tessellation_state {}; - tessellation_state.patchControlPoints = 3; - pipeline_info.pTessellationState = (rect_list ? &tessellation_state : nullptr); + tessellation_state.patchControlPoints = + tessellation ? vs_input_info.tess.input_control_points : 3u; + pipeline_info.pTessellationState = (rect_list || tessellation) ? &tessellation_state : nullptr; pipeline_info.pViewportState = &viewport_state; pipeline_info.pRasterizationState = &rasterizer; pipeline_info.pMultisampleState = &multisampling; diff --git a/src/graphics/host_gpu/renderer/render.h b/src/graphics/host_gpu/renderer/render.h index 540b4668f..9ff9f8b8d 100644 --- a/src/graphics/host_gpu/renderer/render.h +++ b/src/graphics/host_gpu/renderer/render.h @@ -170,7 +170,7 @@ private: void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args); struct GraphicsBindings { - PreparedBindings vertex; + std::array vertex; std::optional pixel; }; diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index 6a21b5f32..eaf03f80f 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -453,7 +453,7 @@ struct DrawRenderState { RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {}; uint32_t color_count = 0; bool ps_active = true; - ShaderVertexInputInfo vs_input_info; + std::array vertex_info; ShaderPixelInputInfo ps_input_info; PipelineCache::GraphicsPrograms programs; }; @@ -831,6 +831,7 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw, case Prospero::PrimitiveType::kTriStrip: topology = vk::PrimitiveTopology::eTriangleStrip; break; + case Prospero::PrimitiveType::kPatch: case Prospero::PrimitiveType::kRectList: topology = vk::PrimitiveTopology::ePatchList; break; @@ -953,7 +954,7 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw, } state.programs = pipeline_cache.GetGraphicsPrograms( vertex_shader_info, pixel_shader_info, shader_regs, ctx, buffer.GetUserConfig(), - target_export_mapping, state.ps_active, state.vs_input_info, state.ps_input_info); + target_export_mapping, state.ps_active, state.vertex_info, state.ps_input_info); } static PreparedIndexBuffer PrepareIndexBuffer(CommandBuffer& buffer, @@ -1016,7 +1017,7 @@ static void LogDrawStateIfNeeded(const CommandBuffer& buffer, const DrawCallInfo LogDrawTargetState(draw.Name(), state.color_info[0], state.depth_info, buffer, state.ps_input_info, draw.index_count, 0); } - LogDrawInputState(buffer, state.color_info[0], state.vs_input_info, index_type_and_size, + LogDrawInputState(buffer, state.color_info[0], state.vertex_info[0], index_type_and_size, draw.index_count, index_addr); } @@ -1031,6 +1032,7 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_ case Prospero::PrimitiveType::kTriFan: case Prospero::PrimitiveType::kTriStrip: case Prospero::PrimitiveType::kRectList: + case Prospero::PrimitiveType::kPatch: if (draw.IsIndexed()) { vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset, emit.first_instance); @@ -1069,10 +1071,12 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff const DrawIndexBufferSource& index_source, bool primitive_restart_enable) { auto& ucfg = buffer.GetUserConfig(); - const bool mesh_active = state.vs_input_info.stage.program->stage == ShaderType::Mesh; + const auto vertex_stages = + std::span {state.vertex_info.data(), state.programs.VertexStageCount()}; + const bool mesh_active = state.vertex_info[0].stage.program->stage == ShaderType::Mesh; uint32_t mesh_groups = 0; if (mesh_active) { - const auto& mesh = state.vs_input_info.mesh; + const auto& mesh = state.vertex_info[0].mesh; if (primitive_restart_enable || mesh.primitives_per_group == 0) { EXIT("unsupported mesh draw: primitive=%u indexed=%u restart=%u\n", static_cast(ucfg.GetPrimType()), draw.IsIndexed(), primitive_restart_enable); @@ -1100,19 +1104,24 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff index_source.guest_element_size); } LogDrawPhase(draw.Name(), "PrepareBindings"); - GraphicsBindings bindings {.vertex = PrepareBindings(state.vs_input_info.stage)}; - std::array descriptor_stages {&bindings.vertex, nullptr}; + GraphicsBindings bindings; + std::array descriptor_stages {}; + uint32_t stage_count = 0; + for (uint32_t i = 0; i < vertex_stages.size(); i++) { + bindings.vertex[i] = PrepareBindings(state.vertex_info[i].stage); + descriptor_stages[stage_count++] = &bindings.vertex[i]; + } if (state.ps_active) { bindings.pixel.emplace(PrepareBindings(state.ps_input_info.stage)); - descriptor_stages[1] = &*bindings.pixel; + descriptor_stages[stage_count++] = &*bindings.pixel; } - const auto stages = std::span {descriptor_stages.data(), state.ps_active ? 2u : 1u}; + const auto stages = std::span {descriptor_stages.data(), stage_count}; PrepareGraphicsBindings(stages); PreparedVertexBuffers vertex_bindings; PreparedIndexBuffer index_binding; if (!mesh_active) { LogDrawPhase(draw.Name(), "PrepareVertexBuffers"); - vertex_bindings = AcquireVertexBuffers(buffer, state.vs_input_info); + vertex_bindings = AcquireVertexBuffers(buffer, state.vertex_info[0]); index_binding = PrepareIndexBuffer(buffer, index_source); } const auto rendering = @@ -1123,9 +1132,9 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff LogDrawPhase(draw.Name(), "CreatePipeline"); } auto& pipeline = m_context.GetPipelineCache().GetGraphicsPipeline( - std::span {state.color_info, state.color_count}, state.depth_info, state.vs_input_info, buffer, + std::span {state.color_info, state.color_count}, state.depth_info, vertex_stages, buffer, state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable, - state.programs.vertex, state.programs.pixel); + state.programs); // Resource preparation above may synchronously finish and restart the scheduler. From this // point onward, every operation targets the current command buffer and cannot touch guest @@ -1155,7 +1164,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff CommitIndexBuffer(vk_buffer, index_binding); } - SetGraphicsDynamicParams(buffer, vk_buffer, state.vs_input_info, state.color_info, + SetGraphicsDynamicParams(buffer, vk_buffer, vertex_stages.back(), state.color_info, state.color_count, state.depth_info); if (m_context.GetGraphics().attachment_feedback_loop_enabled) { vk_buffer.setAttachmentFeedbackLoopEnableEXT( @@ -1177,16 +1186,17 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff if (mesh_active) { vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1); } else { - EmitDrawPrimitives(ucfg, vk_buffer, state.vs_input_info, draw, emit); + EmitDrawPrimitives(ucfg, vk_buffer, state.vertex_info[0], draw, emit); } if (!draw.IsIndexed()) { SetDrawDebugPhase(buffer, submit_id, draw, 0x600u); } vk::PipelineStageFlags shader_write_stages = {}; - if (HasShaderBufferWrites(state.vs_input_info.stage)) { - shader_write_stages |= mesh_active ? vk::PipelineStageFlagBits::eMeshShaderEXT - : vk::PipelineStageFlagBits::eVertexShader; + for (const auto& stage: vertex_stages) { + if (HasShaderBufferWrites(stage.stage)) { + shader_write_stages |= ShaderPipelineStages(NativeShaderStage(stage.logical_stage)); + } } if (state.ps_active && HasShaderBufferWrites(state.ps_input_info.stage)) { shader_write_stages |= vk::PipelineStageFlagBits::eFragmentShader; @@ -1302,7 +1312,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs; const auto [vertex_offset, instance_offset] = indirect ? std::pair {0, args.first_instance} - : ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vs_input_info); + : ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]); DrawEmitInfo emit {}; emit.vertex_offset = vertex_offset + args.base_vertex; @@ -1374,9 +1384,9 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D } RefreshShaders(buffer, draw, state); - const bool rect_list = topology == vk::PrimitiveTopology::ePatchList; - if (rect_list && state.vs_input_info.buffers_num == 0 && - state.vs_input_info.stage.program->param_export_mask == 0 && + const bool rect_list = ucfg.GetPrimType() == Prospero::PrimitiveType::kRectList; + if (rect_list && state.vertex_info[0].buffers_num == 0 && + state.vertex_info[0].stage.program->param_export_mask == 0 && state.ps_input_info.input_num != 0) { if (graphics_debug_dump_enabled()) { LOGF("DrawIndexAuto: skipping rect-list draw with no VS param exports and PS inputs: " @@ -1393,7 +1403,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs; const auto [vertex_offset, instance_offset] = indirect ? std::pair {0, args.first_instance} - : ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vs_input_info); + : ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]); DrawEmitInfo emit {}; emit.first_vertex = static_cast(vertex_offset + static_cast(args.first_vertex)); emit.first_instance = instance_offset; diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index 54998f0e7..6f2ea7cd7 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -47,6 +47,9 @@ const char* StageName(ShaderType stage) { switch (stage) { case ShaderType::Compute: return "CS"; case ShaderType::Vertex: return "VS"; + case ShaderType::Local: return "LS"; + case ShaderType::TessellationControl: return "HS"; + case ShaderType::TessellationEvaluation: return "TES"; case ShaderType::Mesh: return "MS"; case ShaderType::Pixel: return "PS"; default: return "unknown"; @@ -211,6 +214,8 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded const ShaderVertexInputInfo* input_info, uint32_t user_data_base, uint32_t user_data_count, uint32_t wave_size) { + const uint32_t vertex_index_reg = input_info->logical_stage == ShaderType::Local ? 2u : 5u; + const uint32_t instance_index_reg = input_info->logical_stage == ShaderType::Local ? 5u : 8u; EmbeddedFetchData data; data.loads.reserve(input_info->resources_num); int32_t vertex_offset_candidate = -1; @@ -251,16 +256,17 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded // corresponding direct-draw offsets before fetching. const bool vertex_index_accumulator = IsDecodedVgpr(inst.dst) && - (inst.dst.reg == 0 || (user_data_base == 8 && inst.dst.reg == 5)); + (inst.dst.reg == 0 || (user_data_base == 8 && inst.dst.reg == vertex_index_reg)); const bool instance_index_accumulator = - IsDecodedVgpr(inst.dst) && (inst.dst.reg == (user_data_base == 8 ? 8u : 3u)); + IsDecodedVgpr(inst.dst) && + (inst.dst.reg == (user_data_base == 8 ? instance_index_reg : 3u)); uint32_t sad_zero = 0; const bool index_offset_add = - (vertex_index_accumulator || instance_index_accumulator) && - IsDecodedSgpr(inst.src0) && + (vertex_index_accumulator || instance_index_accumulator) && IsDecodedSgpr(inst.src0) && ((inst.opcode == Decoder::Opcode::V_ADD_I32 && IsDecodedVgpr(inst.src1) && inst.src1.reg == inst.dst.reg) || - (user_data_base == 8 && (inst.dst.reg == 5 || inst.dst.reg == 8) && + (user_data_base == 8 && + (inst.dst.reg == vertex_index_reg || inst.dst.reg == instance_index_reg) && inst.opcode == Decoder::Opcode::V_SAD_U32 && IsDecodedVgpr(inst.src2) && inst.src2.reg == inst.dst.reg && TryDecodedOperandConstant(sgprs, inst.src1, sad_zero) && sad_zero == 0)); @@ -391,8 +397,8 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg); } if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgpr_is_index.size() && - IsDecodedVgpr(inst.src0) && inst.src0.reg == 8 && - IsDecodedVgpr(inst.src1) && inst.src1.reg == 5) { + IsDecodedVgpr(inst.src0) && inst.src0.reg == instance_index_reg && + IsDecodedVgpr(inst.src1) && inst.src1.reg == vertex_index_reg) { vgpr_is_index[inst.dst.reg] = true; } } else if (IsEmbeddedFetchAttribPropagationAlu(inst)) { @@ -459,9 +465,7 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded return data; } -Decoder::Program DecodeFusedProgram(std::span front, std::span back, - std::vector& joined_code) { - EXIT_IF(back.empty()); +Decoder::Program DecodeFrontProgram(std::span front) { Decoder::Program result; uint32_t front_words = 0; while (front_words < front.size()) { @@ -477,7 +481,16 @@ Decoder::Program DecodeFusedProgram(std::span front, std::span front, std::span back, + std::vector& joined_code) { + EXIT_IF(back.empty()); + auto result = DecodeFrontProgram(front); + const auto front_words = static_cast(result.code.size()); + joined_code.assign(result.code.begin(), result.code.end()); joined_code.insert(joined_code.end(), back.begin(), back.end()); // The merged-stage ABI passes the back shader in s[6:7]. Give that handoff an // ordinary CFG edge, retaining both bodies in one register and LDS lifetime. @@ -500,12 +513,224 @@ Decoder::Program DecodeFusedProgram(std::span front, std::span local, std::span control, + ShaderTessellationInputInfo& info) { + using namespace Decoder; + const auto constant = [](const Operand& operand, uint32_t value) { + return (operand.kind == OperandKind::IntegerInlineConstant || + operand.kind == OperandKind::LiteralConstant) && + operand.value == value; + }; + auto decoded = DecodeFrontProgram(local); + uint32_t local_address = UINT32_MAX; + for (const auto& inst: decoded.instructions) { + if (inst.opcode == Opcode::V_MUL_U32_U24 && inst.src1.kind == OperandKind::Vgpr && + inst.src1.reg == 3u && + (inst.src0.kind == OperandKind::IntegerInlineConstant || + inst.src0.kind == OperandKind::LiteralConstant)) { + EXIT_IF(info.ls_stride != 0u && info.ls_stride != inst.src0.value); + info.ls_stride = inst.src0.value; + local_address = inst.dst.reg; + } + if (inst.family == Family::DS) { + EXIT_NOT_IMPLEMENTED(inst.src0.kind != OperandKind::Vgpr || + inst.src0.reg != local_address); + } + } + decoded = {}; + DecodeProgram(control, decoded); + uint32_t control_point = UINT32_MAX; + uint32_t control_address = UINT32_MAX; + for (const auto& inst: decoded.instructions) { + if (inst.opcode == Opcode::V_BFE_U32 && inst.src0.kind == OperandKind::Vgpr && + inst.src0.reg == 1u && constant(inst.src1, 8u) && constant(inst.src2, 5u)) { + control_point = inst.dst.reg; + } + if (inst.opcode == Opcode::V_LSHL_ADD_U32 && inst.src0.kind == OperandKind::Vgpr && + inst.src0.reg == control_point && + (inst.src1.kind == OperandKind::IntegerInlineConstant || + inst.src1.kind == OperandKind::LiteralConstant) && + inst.src1.value < 32u) { + info.hs_stride = 1u << inst.src1.value; + control_address = inst.dst.reg; + } + } + EXIT_NOT_IMPLEMENTED(local_address == UINT32_MAX || control_address == UINT32_MAX || + info.ls_stride == 0u || info.hs_stride == 0u || + (info.ls_stride & 3u) != 0u || (info.hs_stride & 3u) != 0u); + LOGF("Tessellation interface: input_cp=%u output_cp=%u ls_stride=%u hs_stride=%u\n", + info.input_control_points, info.output_control_points, info.ls_stride, info.hs_stride); +} + +namespace { + +const IR::Inst* TessellationBufferBase(const IR::Inst& inst) { + if (IR::BufferAccessOf(inst.GetOpcode()) == IR::BufferAccess::None || inst.NumArgs() <= 3u) + return nullptr; + const auto* base = inst.Arg(3).Resolve().TryInstruction(); + return base != nullptr && base->GetOpcode() == IR::ValueOpcode::TessellationBase ? base + : nullptr; +} + +IR::Value ActiveAddress(IR::Block& block, IR::Block::iterator before, IR::Value value, + IR::Value predicate, std::unordered_map& resolved) { + using namespace IR; + value = value.Resolve(); + auto* source = value.TryInstruction(); + if (source == nullptr) return value; + if (const auto found = resolved.find(source); found != resolved.end()) return found->second; + if (source->GetOpcode() == ValueOpcode::SelectU32) { + return source->Arg(0).Resolve() == predicate.Resolve() + ? ActiveAddress(block, before, source->Arg(1), predicate, resolved) + : value; + } + if (source->GetOpcode() != ValueOpcode::IAdd32 && source->GetOpcode() != ValueOpcode::ISub32) + return value; + const auto lhs = ActiveAddress(block, before, source->Arg(0), predicate, resolved); + const auto rhs = ActiveAddress(block, before, source->Arg(1), predicate, resolved); + if (lhs != source->Arg(0).Resolve() || rhs != source->Arg(1).Resolve()) { + auto copy = block.PrependNewInst(before, source->GetOpcode(), {lhs, rhs}, + source->Flags()); + value = Value(&*copy); + } + resolved.emplace(source, value); + return value; +} + +void LowerTessellationMemory(IR::Program& program, const CompileOptions& options) { + using namespace IR; + if (options.stage != ShaderType::Local && options.stage != ShaderType::TessellationControl && + options.stage != ShaderType::TessellationEvaluation) { + return; + } + const auto& tess = options.input_info.vertex->tess; + // Ring addresses can reuse data VGPRs. Their inactive values are irrelevant to + // a store guarded by the same EXEC predicate, but must remain intact elsewhere. + for (auto* block: program.blocks) { + for (auto it = block->begin(); it != block->end(); ++it) { + if (TessellationBufferBase(*it) == nullptr) continue; + std::unordered_map resolved; + it->SetArg( + 2, ActiveAddress(*block, it, it->Arg(2), it->Arg(it->NumArgs() - 1u), resolved)); + } + } + ConstantPropagationPass(program.blocks); + uint32_t reads = 0, writes = 0, factors = 0; + for (auto* block: program.blocks) { + for (auto it = block->begin(); it != block->end(); ++it) { + auto& inst = *it; + const auto shared = SharedAccessOf(inst.GetOpcode()); + const auto buffer = BufferAccessOf(inst.GetOpcode()); + if (shared == SharedAccess::None && buffer == BufferAccess::None) { + continue; + } + const auto& memory = program.memory_info.at(inst.Flags().index); + bool write = false; + uint32_t components = 0; + TessellationAttribute kind; + Value address, predicate; + if (shared != SharedAccess::None && memory.kind == ResourceKind::Lds) { + write = shared == SharedAccess::Write; + EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u || + (options.stage == ShaderType::Local + ? !write + : options.stage != ShaderType::TessellationControl || + shared != SharedAccess::Read)); + kind = write ? TessellationAttribute::LocalOutput + : TessellationAttribute::ControlInput; + components = SharedComponentCount(inst.GetOpcode()); + address = inst.Arg(0); + predicate = inst.Arg(inst.NumArgs() - 1u); + } else if (buffer != BufferAccess::None && memory.kind == ResourceKind::Buffer) { + const auto* base = TessellationBufferBase(inst); + if (base == nullptr) { + continue; + } + EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u || memory.formatted || memory.idxen || + !memory.offen || buffer == BufferAccess::Atomic); + write = buffer == BufferAccess::Write; + components = BufferComponentCount(inst.GetOpcode()); + address = inst.Arg(2).Resolve(); + predicate = inst.Arg(inst.NumArgs() - 1u); + if (base->Arg(0).U32() == 1u) { + EXIT_NOT_IMPLEMENTED(!write || + options.stage != ShaderType::TessellationControl); + kind = TessellationAttribute::Factor; + factors += components; + } else { + kind = write ? TessellationAttribute::ControlOutput + : TessellationAttribute::EvaluationInput; + EXIT_NOT_IMPLEMENTED(write + ? options.stage != ShaderType::TessellationControl + : options.stage != ShaderType::TessellationEvaluation); + if (address.IsImmediate() && + address.U32() >= tess.hs_stride * tess.output_control_points) { + EXIT_NOT_IMPLEMENTED(!write); + kind = TessellationAttribute::PatchOutput; + } + } + } else { + continue; + } + const auto emit = [&](ValueOpcode opcode, std::initializer_list args) { + return Value(&*block->PrependNewInst(it, opcode, args)); + }; + std::array values; + for (uint32_t component = 0; component < components; component++) { + const auto offset = memory.offset + 4u * component; + const auto byte_address = + offset == 0u ? address : emit(ValueOpcode::IAdd32, {address, Value(offset)}); + if (write) { + Value data = shared != SharedAccess::None ? inst.Arg(component + 1u) + : components == 1u + ? inst.Arg(4) + : emit(components == 2u ? ValueOpcode::CompositeExtractU32x2 + : components == 3u ? ValueOpcode::CompositeExtractU32x3 + : ValueOpcode::CompositeExtractU32x4, + {inst.Arg(4), Value(component)}); + emit(ValueOpcode::SetTessellationAttribute, + {Value(static_cast(kind)), byte_address, data, predicate}); + writes++; + } else { + values[component] = + emit(ValueOpcode::GetTessellationAttribute, + {Value(static_cast(kind)), byte_address, predicate}); + reads++; + } + } + if (!write) { + Value replacement = values[0]; + if (components == 2u) + replacement = + emit(ValueOpcode::CompositeConstructU32x2, {values[0], values[1]}); + if (components == 3u) + replacement = emit(ValueOpcode::CompositeConstructU32x3, + {values[0], values[1], values[2]}); + if (components == 4u) + replacement = emit(ValueOpcode::CompositeConstructU32x4, + {values[0], values[1], values[2], values[3]}); + inst.ReplaceUsesWith(replacement); + } + inst.Invalidate(); + } + } + ConstantPropagationPass(program.blocks); + RemoveIdentities(program.blocks); + EliminateDeadCode(program.blocks); + LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u\n", StageName(options.stage), + reads, writes, factors); +} + +} // namespace + TranslateResult TranslateProgram(std::span code, const CompileOptions& options) { if (code.empty()) { EXIT("shader recompiler input is empty\n"); } if (options.stage != ShaderType::Compute && options.stage != ShaderType::Vertex && - options.stage != ShaderType::Pixel && options.stage != ShaderType::Mesh) { + options.stage != ShaderType::Pixel && options.stage != ShaderType::Mesh && + options.stage != ShaderType::Local && options.stage != ShaderType::TessellationControl && + options.stage != ShaderType::TessellationEvaluation) { EXIT("shader recompiler received unsupported stage %u\n", static_cast(options.stage)); } @@ -525,6 +750,12 @@ TranslateResult TranslateProgram(std::span code, const CompileOp std::vector joined_code; if (!options.back_code.empty()) { decoded = DecodeFusedProgram(code, options.back_code, joined_code); + } else if (options.stage == ShaderType::Local) { + decoded = DecodeFrontProgram(code); + // The separately compiled hull half runs in the next Vulkan stage. + auto& handoff = decoded.instructions.back(); + handoff.opcode = Decoder::Opcode::S_ENDPGM; + handoff.src_count = 0; } else { Decoder::DecodeProgram(code, decoded); } @@ -568,8 +799,8 @@ TranslateResult TranslateProgram(std::span code, const CompileOp } EmbeddedFetchData embedded_fetch; - if (options.stage == ShaderType::Vertex && options.input_info.vertex != nullptr && - options.input_info.vertex->fetch_embedded) { + if ((options.stage == ShaderType::Vertex || options.stage == ShaderType::Local) && + options.input_info.vertex != nullptr && options.input_info.vertex->fetch_embedded) { embedded_fetch = DetectEmbeddedVertexFetch( decoded, options.input_info.vertex, options.user_data_base, static_cast(options.user_data.size()), options.wave_size); @@ -608,6 +839,7 @@ TranslateResult TranslateProgram(std::span code, const CompileOp IR::RemoveIdentities(ir.blocks); IR::EliminateDeadCode(ir.blocks); } + LowerTessellationMemory(ir, options); IR::BuildSrtPlan(ir); IR::EliminateDeadCode(ir.blocks); IR::TrackResources(ir); diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.h b/src/graphics/shader/recompiler/ShaderRecompiler.h index 29b3a40e7..8b096eee4 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.h +++ b/src/graphics/shader/recompiler/ShaderRecompiler.h @@ -37,6 +37,9 @@ struct CompileResult { IR::Program program; }; +void AnalyzeTessellationPrograms(std::span local, std::span control, + ShaderTessellationInputInfo& info); + [[nodiscard]] TranslateResult TranslateProgram(std::span code, const CompileOptions& options); [[nodiscard]] CompileResult CompileProgram(TranslateResult translated, diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp index 519f1859a..86147d567 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp @@ -277,7 +277,8 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program break; } case IR::ValueOpcode::LaneId: - requirements.subgroup_local_invocation_id = true; + requirements.subgroup_local_invocation_id |= + program.stage != ShaderType::TessellationControl; break; case IR::ValueOpcode::ImageQueryLod: requirements.compute_derivatives = true; break; case IR::ValueOpcode::ImageGatherRaw: @@ -306,8 +307,10 @@ std::vector EmitProgram(const IR::Program& program, using namespace Emitter; if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex && - program.stage != ShaderType::Pixel && program.stage != ShaderType::Mesh) { - Fail(program, "binary SPIR-V emitter supports compute, vertex, and pixel shaders"); + program.stage != ShaderType::Pixel && program.stage != ShaderType::Mesh && + program.stage != ShaderType::Local && program.stage != ShaderType::TessellationControl && + program.stage != ShaderType::TessellationEvaluation) { + Fail(program, "binary SPIR-V emitter received an unsupported shader stage"); } if (!program.srt_plan_complete || !program.resource_tracking_complete || !program.shader_info_complete || !program.binding_layout_complete) { diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp index 481d1965f..2f12d0ec1 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp @@ -250,7 +250,10 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_ spv::ExecutionModel ExecutionModelForStage(ShaderType stage) { switch (stage) { + case ShaderType::Local: case ShaderType::Vertex: return spv::ExecutionModelVertex; + case ShaderType::TessellationControl: return spv::ExecutionModelTessellationControl; + case ShaderType::TessellationEvaluation: return spv::ExecutionModelTessellationEvaluation; case ShaderType::Mesh: return spv::ExecutionModelMeshEXT; // MeshEXT case ShaderType::Pixel: return spv::ExecutionModelFragment; default: return spv::ExecutionModelGLCompute; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index 8624668de..039c5d32a 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -51,6 +51,7 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c return bits; } if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex || + kind == IR::StageInputKind::InvocationId || kind == IR::StageInputKind::PrimitiveId || kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId) { const auto value = state.builder.AllocateId(); const auto bits = state.builder.AllocateId(); @@ -58,7 +59,7 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value); return bits; } - if (kind == IR::StageInputKind::FragCoord) { + if (kind == IR::StageInputKind::FragCoord || kind == IR::StageInputKind::TessCoord) { const auto pointer = state.builder.AllocateId(); const auto value = state.builder.AllocateId(); const auto bits = state.builder.AllocateId(); @@ -138,7 +139,7 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) { if (input == nullptr || input->variable_id == 0) { return ConstantU32(state, 0); } - if (state.program.stage == ShaderType::Vertex) { + if (state.program.stage == ShaderType::Vertex || state.program.stage == ShaderType::Local) { return EmitVertexParameterComponentU32(state, *input, chan & 3u); } const auto load_per_vertex = [&](uint32_t vertex) { @@ -517,11 +518,95 @@ uint32_t EmitIdentity(ValueEmitContext&, uint32_t value) { void EmitVoid(ValueEmitContext&) {} void EmitBarrier(EmitterState& state) { - const auto semantics = - spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask; + const auto tessellation = state.program.stage == ShaderType::TessellationControl; + const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup; + const auto semantics = tessellation ? spv::MemorySemanticsMaskNone + : spv::MemorySemanticsAcquireReleaseMask | + spv::MemorySemanticsWorkgroupMemoryMask; state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup), - ConstantU32(state, spv::ScopeWorkgroup), - ConstantU32(state, semantics)); + ConstantU32(state, memory_scope), ConstantU32(state, semantics)); +} + +uint32_t EmitLaneId(EmitterState& state) { + return state.program.stage == ShaderType::TessellationControl + ? EmitBuiltinU32(state, IR::StageInputKind::InvocationId, 0) + : EmitSubgroupLocalInvocationId(state); +} + +namespace { + +uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) { + auto& state = ctx.state; + using Attribute = IR::TessellationAttribute; + const auto kind = static_cast(inst.Arg(0).U32()); + const auto& tess = state.input_info.vertex->tess; + const auto variable = state.tess_variables.at(static_cast(kind)); + EXIT_IF(variable == 0); + const auto input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput; + const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput; + const auto pointer = state.builder.AllocateId(); + if (kind == Attribute::Factor) { + EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate()); + const auto index = inst.Arg(1).U32() / 4u; + const auto outer = index < 3u; + EXIT_NOT_IMPLEMENTED(index >= 4u); + state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)), + pointer, outer ? variable : state.tess_inner_variable, + ConstantU32(state, outer ? index : index - 3u)); + return pointer; + } + auto address = ctx.Arg(inst, 1); + if (kind == Attribute::PatchOutput) { + address = + EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, state.tess_patch_base)); + } + const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput; + const auto stride = local ? tess.ls_stride : tess.hs_stride; + const auto offset = kind == Attribute::PatchOutput ? address + : EmitBinaryU32(state, spv::OpUMod, address, + ConstantU32(state, stride)); + const auto attribute = + EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4)); + const auto component = + EmitBinaryU32(state, spv::OpBitwiseAnd, + EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 2)), + ConstantU32(state, 3)); + const auto type = TypePointer(state, storage, TypeU32(state)); + if (kind == Attribute::LocalOutput || kind == Attribute::PatchOutput) { + state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, attribute, + component); + } else { + const auto vertex = + kind == Attribute::ControlOutput + ? EmitBuiltinU32(state, IR::StageInputKind::InvocationId, 0) + : EmitBinaryU32(state, spv::OpUDiv, address, ConstantU32(state, stride)); + state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, vertex, attribute, + component); + } + return pointer; +} + +} // namespace + +uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) { + return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, 2), [&] { + const auto value = ctx.state.builder.AllocateId(); + ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value, + TessellationPointer(ctx, inst)); + return value; + }); +} + +void EmitSetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) { + EmitIfCondition(ctx.state, ctx.Arg(inst, 3), [&] { + auto value = ctx.Arg(inst, 2); + if (inst.Arg(0).U32() == static_cast(IR::TessellationAttribute::Factor)) { + const auto floating = ctx.state.builder.AllocateId(); + ctx.state.builder.AddFunction(spv::OpBitcast, TypeF32(ctx.state), floating, value); + value = floating; + } + ctx.state.builder.AddFunction(spv::OpStore, TessellationPointer(ctx, inst), value); + }); } uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst) { diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h index 6bda42b6f..805074430 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h @@ -155,6 +155,8 @@ inline constexpr auto EmitInstPrefetch = EmitVoid; void EmitBarrier(EmitterState& state); void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst); uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst); +uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst); +void EmitSetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst); uint32_t EmitGetUserData(EmitterState& state, IR::ScalarReg reg); uint32_t EmitGetBuiltin(ValueEmitContext& ctx, IR::Value kind, IR::Value index); uint32_t EmitUndefU1(EmitterState& state, const IR::Inst& inst); @@ -165,7 +167,7 @@ inline constexpr auto EmitUndefU64 = EmitUndefU1; uint32_t EmitDppMoveU32(ValueEmitContext& ctx, const IR::Inst& inst); uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& inst); uint32_t EmitWqmU64(EmitterState& state, uint32_t value); -inline constexpr auto EmitLaneId = EmitSubgroupLocalInvocationId; +uint32_t EmitLaneId(EmitterState& state); uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate); uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst); uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst); @@ -271,6 +273,7 @@ inline constexpr auto EmitImageAtomicOr32 = EmitImage; inline constexpr auto EmitImageAtomicXor32 = EmitImage; void EmitUnreachable(ValueEmitContext& ctx, const IR::Inst& inst); inline constexpr auto EmitPhi = EmitUnreachable; +inline constexpr auto EmitTessellationBase = EmitUnreachable; inline constexpr auto EmitGetThreadBitScalarRegister = EmitUnreachable; inline constexpr auto EmitSetThreadBitScalarRegister = EmitUnreachable; inline constexpr auto EmitGetScalarMaskTag = EmitUnreachable; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index 3b692ca04..632c9b0db 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -80,6 +80,10 @@ struct EmitterState { Builder builder; const IR::Program& program; ShaderStageInputInfo input_info; + std::array tess_variables {}; + uint32_t tess_inner_variable = 0; + uint32_t tess_patch_base = 0; + const SpirvRequirements requirements; uint32_t lane_count = 1; uint32_t lane_half = 0; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index 375acaf23..fa5a81d59 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -290,7 +290,8 @@ uint32_t GlslStd450(EmitterState& state) { } VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location) { - if (state.program.stage != ShaderType::Vertex || location >= ShaderVertexInputInfo::RES_MAX || + if ((state.program.stage != ShaderType::Vertex && state.program.stage != ShaderType::Local) || + location >= ShaderVertexInputInfo::RES_MAX || location >= static_cast(state.input_info.vertex->resources_num)) { return VertexInputScalarKind::Float; } @@ -347,6 +348,9 @@ uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::Storag uint32_t BuiltInForInput(IR::StageInputKind kind) { switch (kind) { case IR::StageInputKind::VertexIndex: return spv::BuiltInVertexIndex; + case IR::StageInputKind::InvocationId: return spv::BuiltInInvocationId; + case IR::StageInputKind::PrimitiveId: return spv::BuiltInPrimitiveId; + case IR::StageInputKind::TessCoord: return spv::BuiltInTessCoord; case IR::StageInputKind::InstanceIndex: return spv::BuiltInInstanceIndex; case IR::StageInputKind::FragCoord: return spv::BuiltInFragCoord; case IR::StageInputKind::FrontFacing: return spv::BuiltInFrontFacing; @@ -387,6 +391,8 @@ void DefineInputs(EmitterState& state) { uint32_t type = TypeU32(state); switch (input.kind) { case IR::StageInputKind::VertexIndex: + case IR::StageInputKind::InvocationId: + case IR::StageInputKind::PrimitiveId: case IR::StageInputKind::InstanceIndex: case IR::StageInputKind::Layer: case IR::StageInputKind::SampleId: type = TypeI32(state); break; @@ -394,11 +400,13 @@ void DefineInputs(EmitterState& state) { case IR::StageInputKind::LocalInvocationId: case IR::StageInputKind::GlobalInvocationId: type = TypeU32Vector(state, 3); break; case IR::StageInputKind::FragCoord: type = TypeF32Vector(state, 4); break; + case IR::StageInputKind::TessCoord: case IR::StageInputKind::BaryCoordSmooth: case IR::StageInputKind::BaryCoordNoPerspective: type = TypeF32Vector(state, 3); break; case IR::StageInputKind::FrontFacing: type = TypeBool(state); break; case IR::StageInputKind::Parameter: - if (state.program.stage == ShaderType::Vertex) { + if (state.program.stage == ShaderType::Vertex || + state.program.stage == ShaderType::Local) { type = VertexParameterScalarType( state, VertexParameterScalarKind(state, input.location)); const auto components = VertexParameterComponentCount(input); @@ -536,6 +544,70 @@ void DefineOutputs(EmitterState& state) { } } +void DefineTessellationInterfaces(EmitterState& state) { + using Attribute = IR::TessellationAttribute; + std::array used {}; + uint32_t patch_begin = UINT32_MAX, patch_end = 0; + for (const auto* block: state.program.blocks) { + for (const auto& inst: *block) { + if (inst.GetOpcode() != IR::ValueOpcode::GetTessellationAttribute && + inst.GetOpcode() != IR::ValueOpcode::SetTessellationAttribute) + continue; + const auto kind = inst.Arg(0).U32(); + used.at(kind) = true; + if (kind == static_cast(Attribute::PatchOutput)) { + EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate()); + patch_begin = std::min(patch_begin, inst.Arg(1).U32()); + patch_end = std::max(patch_end, inst.Arg(1).U32() + 4u); + } + } + } + if (std::ranges::none_of(used, [](bool value) { return value; })) return; + const auto& tess = state.input_info.vertex->tess; + const auto array = [&](uint32_t type, uint32_t count) { + return state.builder.Type(spv::OpTypeArray, type, ConstantU32(state, count)); + }; + for (uint32_t index = 0; index < used.size(); index++) { + if (!used[index]) continue; + const auto kind = static_cast(index); + const bool input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput; + const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput; + uint32_t type; + if (kind == Attribute::Factor) { + type = array(TypeF32(state), 4u); + } else if (kind == Attribute::PatchOutput) { + state.tess_patch_base = patch_begin; + type = array(TypeU32Vector(state, 4), (patch_end - patch_begin + 15u) / 16u); + } else { + const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput; + const auto stride = local ? tess.ls_stride : tess.hs_stride; + type = array(TypeU32Vector(state, 4), (stride + 15u) / 16u); + if (kind != Attribute::LocalOutput) { + type = array(type, local ? tess.input_control_points : tess.output_control_points); + } + } + auto& variable = state.tess_variables[index]; + variable = DefineInterfaceVariable(state, type, storage, "tess_attributes"); + if (kind == Attribute::Factor) { + state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn, + spv::BuiltInTessLevelOuter); + state.tess_inner_variable = + DefineInterfaceVariable(state, array(TypeF32(state), 2u), storage, "tess_inner"); + state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable, + spv::DecorationBuiltIn, spv::BuiltInTessLevelInner); + state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable, + spv::DecorationPatch); + } else { + state.builder.AddAnnotation( + spv::OpDecorate, variable, spv::DecorationLocation, + kind == Attribute::PatchOutput ? (tess.hs_stride + 15u) / 16u : 0u); + } + if (kind == Attribute::Factor || kind == Attribute::PatchOutput) { + state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPatch); + } + } +} + } // namespace void DefineModule(EmitterState& state) { @@ -543,6 +615,7 @@ void DefineModule(EmitterState& state) { state.program.info.outputs.size()); DefineInputs(state); DefineOutputs(state); + DefineTessellationInterfaces(state); DefineDescriptors(state); if (state.requirements.function_lds) { state.lds_variable = state.builder.AllocateId(); @@ -564,6 +637,21 @@ void DefineModule(EmitterState& state) { state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputPrimitivesEXT, state.input_info.vertex->mesh.max_primitives); } + if (state.program.stage == ShaderType::TessellationControl || + state.program.stage == ShaderType::TessellationEvaluation) { + const auto& tess = state.input_info.vertex->tess; + EXIT_NOT_IMPLEMENTED(tess.domain != 1u || tess.partitioning != 2u || + tess.output_topology != 2u); + state.builder.RequireCapability(spv::CapabilityTessellation); + if (state.program.stage == ShaderType::TessellationControl) { + state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices, + tess.output_control_points); + } else { + state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeTriangles); + state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSpacingFractionalOdd); + state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeVertexOrderCw); + } + } state.entry_label = state.builder.AllocateId(); state.builder.RequireCapability(spv::CapabilityShader); diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index 168f66b4d..68f20ef75 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -898,11 +898,15 @@ void ValidateTranslateOptions(const TranslateOptions& options) { if (options.wave_size != 32u && options.wave_size != 64u) { EXIT("shader translation requires wave32 or wave64, got %u", options.wave_size); } - if (options.embedded_fetch != nullptr && options.stage != ShaderType::Vertex) { - EXIT("embedded fetch requires the vertex shader stage"); + if (options.embedded_fetch != nullptr && options.stage != ShaderType::Vertex && + options.stage != ShaderType::Local) { + EXIT("embedded fetch requires a vertex or local shader"); } switch (options.stage) { case ShaderType::Vertex: + case ShaderType::Local: + case ShaderType::TessellationControl: + case ShaderType::TessellationEvaluation: case ShaderType::Mesh: if (options.input_info.vertex == nullptr) { EXIT("vertex shader translation has no vertex input metadata"); @@ -941,6 +945,9 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& result.user_data_count = options.user_data_count; switch (options.stage) { case ShaderType::Vertex: + case ShaderType::Local: + case ShaderType::TessellationControl: + case ShaderType::TessellationEvaluation: result.scratch_dwords = options.input_info.vertex->scratch_size_dwords; break; case ShaderType::Mesh: @@ -1164,6 +1171,42 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& entry_ir.SetVectorReg( static_cast(8), entry_ir.IAdd(draw(2), builtin(IR::StageInputKind::WorkgroupId, 1))); + } else if (options.stage == ShaderType::Local) { + entry_ir.SetScalarReg(static_cast(3), IR::U32(IR::Value(64u))); + entry_ir.SetVectorReg(static_cast(2), + builtin(IR::StageInputKind::VertexIndex)); + entry_ir.SetVectorReg(static_cast(3), IR::U32(IR::Value(0u))); + entry_ir.SetVectorReg(static_cast(5), + builtin(IR::StageInputKind::InstanceIndex)); + } else if (options.stage == ShaderType::TessellationControl) { + const auto& tess = options.input_info.vertex->tess; + entry_ir.SetScalarReg( + static_cast(2), + IR::U32(entry_ir.Emit(IR::ValueOpcode::TessellationBase, {IR::Value(0u)}))); + entry_ir.SetScalarReg( + static_cast(4), + IR::U32(entry_ir.Emit(IR::ValueOpcode::TessellationBase, {IR::Value(1u)}))); + entry_ir.SetScalarReg(static_cast(3), + IR::U32(IR::Value(0x81010000u | tess.input_control_points | + (tess.output_control_points << 8u)))); + entry_ir.SetVectorReg(static_cast(0), + builtin(IR::StageInputKind::PrimitiveId)); + entry_ir.SetVectorReg( + static_cast(1), + entry_ir.ShiftLeftLogical(builtin(IR::StageInputKind::InvocationId), + IR::U32(IR::Value(8u)))); + } else if (options.stage == ShaderType::TessellationEvaluation) { + entry_ir.SetScalarReg(static_cast(3), IR::U32(IR::Value(64u))); + entry_ir.SetScalarReg( + static_cast(4), + IR::U32(entry_ir.Emit(IR::ValueOpcode::TessellationBase, {IR::Value(0u)}))); + entry_ir.SetVectorReg(static_cast(5), + builtin(IR::StageInputKind::TessCoord, 0)); + entry_ir.SetVectorReg(static_cast(6), + builtin(IR::StageInputKind::TessCoord, 1)); + entry_ir.SetVectorReg(static_cast(7), IR::U32(IR::Value(0u))); + entry_ir.SetVectorReg(static_cast(8), + builtin(IR::StageInputKind::PrimitiveId)); } else if (options.stage == ShaderType::Pixel) { const auto* ps = options.input_info.pixel; if (ps->ps_perspective_center_vgpr != UINT32_MAX) { diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 04fb74abd..a8be5e5ef 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -150,8 +150,20 @@ struct SampledResourcePair { bool operator==(const SampledResourcePair& other) const = default; }; +enum class TessellationAttribute { + LocalOutput, + ControlInput, + ControlOutput, + EvaluationInput, + PatchOutput, + Factor +}; + enum class StageInputKind { VertexIndex, + InvocationId, + PrimitiveId, + TessCoord, InstanceIndex, FragCoord, FrontFacing, @@ -287,8 +299,12 @@ static_assert(sizeof(PushData) == 128); constexpr uint32_t NativePushConstantSize = sizeof(PushData); [[nodiscard]] constexpr uint32_t NativeBinding(ShaderType stage, DescriptorBindingKind kind) { + const uint32_t group = stage == ShaderType::Pixel ? 1u + : stage == ShaderType::TessellationControl ? 2u + : stage == ShaderType::TessellationEvaluation ? 3u + : 0u; return static_cast(kind) + - (stage == ShaderType::Pixel ? static_cast(DescriptorBindingKind::Count) : 0u); + group * static_cast(DescriptorBindingKind::Count); } [[nodiscard]] constexpr ImageResourceClass ImageBindingResourceClass(DescriptorBindingKind kind) { diff --git a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp index 18afc7874..2e360a8d5 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp +++ b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp @@ -90,6 +90,7 @@ bool HasSideEffects(ValueOpcode opcode) { case ValueOpcode::Reference: case ValueOpcode::ReferenceU32: case ValueOpcode::SetAttribute: + case ValueOpcode::SetTessellationAttribute: case ValueOpcode::MeshAllocate: case ValueOpcode::Barrier: return true; default: return false; diff --git a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc index a393de243..d13cd08d5 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc +++ b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc @@ -10,6 +10,9 @@ VALUE_OPCODE(GetUserData, U32, ScalarReg) VALUE_OPCODE(GetShaderBase, U64) VALUE_OPCODE(MeshDrawParameter, U32, U32) VALUE_OPCODE(MeshAllocate, Void, U32) +VALUE_OPCODE(TessellationBase, U32, U32) +VALUE_OPCODE(GetTessellationAttribute, U32, U32, U32, U1) +VALUE_OPCODE(SetTessellationAttribute, Void, U32, U32, U32, U1) VALUE_OPCODE(GetBuiltin, U32, U32, U32) VALUE_OPCODE(GetThreadBitScalarRegister, U1, ScalarReg) VALUE_OPCODE(SetThreadBitScalarRegister, Void, ScalarReg, U1) diff --git a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp index 6abbb8095..67151ff56 100644 --- a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp @@ -228,6 +228,15 @@ void FoldInstruction(Block& block, Block::iterator instruction, const auto offset = Arg(inst, 1); const auto count = Arg(inst, 2); auto* source = value.TryInstruction(); + if (source != nullptr && source->GetOpcode() == ValueOpcode::ShiftLeftLogical32 && + IsImmediate(offset, Type::U32) && IsImmediate(count, Type::U32)) { + const auto shift = Arg(*source, 1); + if (IsImmediate(shift, Type::U32) && shift.U32() < 32u && + offset.U32() <= shift.U32() && count.U32() <= shift.U32() - offset.U32()) { + Replace(inst, Value(0u)); + return; + } + } if (source != nullptr && source->GetOpcode() == ValueOpcode::GetBuiltin && source->Arg(0) == Value(static_cast(StageInputKind::PackedAncillary)) && IsImmediate(offset, Type::U32) && IsImmediate(count, Type::U32) && count.U32() != 0u) { diff --git a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp index d390f344c..e63c43d9f 100644 --- a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp @@ -43,6 +43,9 @@ void AddOutput(ShaderInfo& info, StageOutputKind kind, uint32_t index, uint32_t void ValidateOptions(const Program& program, ShaderStageInputInfo input_info) { switch (program.stage) { case ShaderType::Vertex: + case ShaderType::Local: + case ShaderType::TessellationControl: + case ShaderType::TessellationEvaluation: case ShaderType::Mesh: if (input_info.vertex == nullptr) { return Fail("vertex shader has no input metadata"); @@ -81,7 +84,8 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_ !inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) { return Fail("typed attribute reference is not constant"); } - if (program.stage == ShaderType::Vertex && + if ((program.stage == ShaderType::Vertex || + program.stage == ShaderType::Local) && (inst.Arg(1).U32() >= 4u || inst.Arg(0).U32() >= static_cast(input_info.vertex->resources_num))) { @@ -124,6 +128,8 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_ } break; case StageInputKind::VertexIndex: + case StageInputKind::InvocationId: + case StageInputKind::PrimitiveId: case StageInputKind::InstanceIndex: case StageInputKind::FrontFacing: case StageInputKind::LocalInvocationIndex: @@ -142,6 +148,7 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_ return Fail("typed barycentric component is out of range"); } break; + case StageInputKind::TessCoord: case StageInputKind::WorkgroupId: case StageInputKind::LocalInvocationId: case StageInputKind::GlobalInvocationId: @@ -161,8 +168,10 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_ } const auto& exp = program.export_info[index]; if (exp.kind == ExportTargetKind::Position && exp.index != 0 && exp.en != 0 && - program.stage != ShaderType::Vertex && program.stage != ShaderType::Mesh) { - return Fail("auxiliary position export requires the vertex or mesh stage"); + program.stage != ShaderType::Vertex && program.stage != ShaderType::Mesh && + program.stage != ShaderType::TessellationEvaluation) { + return Fail("auxiliary position export requires a vertex, mesh, or " + "tessellation evaluation shader"); } break; } @@ -249,6 +258,10 @@ void CollectComputeInputs(const ShaderComputeInputInfo* compute, ShaderInfo& inf void CollectBuiltinInputs(const Program& program, ShaderInfo& info) { for (const auto* block: program.blocks) { for (const auto& inst: *block) { + if (program.stage == ShaderType::TessellationControl && + inst.GetOpcode() == ValueOpcode::LaneId) { + AddInput(info, StageInputKind::InvocationId, 0, 1, "gl_InvocationID"); + } if (inst.GetOpcode() != ValueOpcode::GetBuiltin) { continue; } @@ -260,6 +273,13 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) { case StageInputKind::InstanceIndex: AddInput(info, kind, 0, 1, "gl_InstanceIndex"); break; + case StageInputKind::InvocationId: + AddInput(info, kind, 0, 1, "gl_InvocationID"); + break; + case StageInputKind::PrimitiveId: + AddInput(info, kind, 0, 1, "gl_PrimitiveID"); + break; + case StageInputKind::TessCoord: AddInput(info, kind, 0, 3, "gl_TessCoord"); break; case StageInputKind::FragCoord: AddInput(info, kind, 0, 4, "gl_FragCoord"); break; case StageInputKind::FrontFacing: AddInput(info, kind, 0, 1, "gl_FrontFacing"); @@ -380,7 +400,10 @@ void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info) { }); }); switch (program.stage) { - case ShaderType::Vertex: CollectVertexInputs(program, input_info.vertex, next); break; + case ShaderType::Vertex: + case ShaderType::Local: CollectVertexInputs(program, input_info.vertex, next); break; + case ShaderType::TessellationControl: + case ShaderType::TessellationEvaluation: case ShaderType::Mesh: break; case ShaderType::Pixel: CollectPixelInputs(program, input_info.pixel, next); break; case ShaderType::Compute: CollectComputeInputs(input_info.compute, next); break; diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index ca7b6c4bf..cfa7b5d76 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -11,6 +11,7 @@ #include "graphics/guest_gpu/graphicsRun.h" #include "graphics/guest_gpu/hardwareContext.h" #include "graphics/host_gpu/renderer/renderContext.h" +#include "graphics/shader/recompiler/ShaderRecompiler.h" #include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h" #include "graphics/shader/shaderCompiler.h" #include "graphics/shader/shaderVertexMetadata.h" @@ -511,33 +512,28 @@ static uint32_t ShaderCalcPsSystemInputBase(const HW::ShaderRegisters& regs) { return reg; } -static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo& regs, - const HW::ShaderRegisters& sh, - const ShaderMappedData& data, - ShaderVertexInputInfo& info) { +static bool ShaderGetStaticVertexInputInfo(uint64_t shader_addr, const HW::UserSgprInfo& user_sgpr, + uint32_t user_sgpr_num, const HW::ShaderRegisters& sh, + const ShaderMappedData& data, + ShaderVertexInputInfo& info) { KYTY_PROFILER_FUNCTION(); info = {}; info.pa_cl_vs_out_cntl = sh.m_paClVsOutCntl; - EXIT_NOT_IMPLEMENTED(regs.es_regs.data_addr == 0); - - uint64_t shader_addr = regs.es_regs.data_addr; - const HW::UserSgprInfo& user_sgpr = regs.gs_user_sgpr; - auto user_sgpr_num = regs.gs_regs.rsrc2.user_sgpr; + EXIT_NOT_IMPLEMENTED(shader_addr == 0); info.scratch_size_dwords = data.scratch_size_dwords; if (data.user_data == nullptr) { - LOGF("ShaderGetInputInfoVS(): no AGC user data for shader=0x%016" PRIx64 " es=0x%016" PRIx64 - " gs=0x%016" PRIx64 " user_sgpr_num=%u\n", - shader_addr, regs.es_regs.data_addr, regs.gs_regs.data_addr, - static_cast(user_sgpr_num)); + LOGF("ShaderGetVertexInputInfo(): no AGC user data for shader=0x%016" PRIx64 + " user_sgpr_num=%u\n", + shader_addr, user_sgpr_num); } ShaderVertexMetadata metadata; std::string metadata_error; if (!ShaderReadVertexMetadata(data, HW::UserSgprInfo::SGPRS_MAX, metadata, &metadata_error)) { - LOGF("ShaderGetInputInfoVS(): invalid AGC metadata shader=0x%016" PRIx64 ": %s\n", + LOGF("ShaderGetVertexInputInfo(): invalid AGC metadata shader=0x%016" PRIx64 ": %s\n", shader_addr, metadata_error.c_str()); return false; } @@ -556,7 +552,7 @@ static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo& regs, (static_cast(user_sgpr.value[metadata.vertex_buffer_reg + 1]) << 32u)); if (attrib == nullptr || buffer == nullptr) { - LOGF("ShaderGetInputInfoVS(): null vertex table pointer shader=0x%016" PRIx64 "\n", + LOGF("ShaderGetVertexInputInfo(): null vertex table pointer shader=0x%016" PRIx64 "\n", shader_addr); return false; } @@ -673,6 +669,12 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr), data); if ((context.GetShaderStages() & 0x20u) == 0) { - if (!ShaderGetStaticInputInfoVS(regs, sh, data, info)) { + if (!ShaderGetStaticVertexInputInfo(regs.es_regs.data_addr, regs.gs_user_sgpr, + regs.gs_regs.rsrc2.user_sgpr, sh, data, info)) { EXIT("failed to prepare vertex shader program\n"); } return params; @@ -759,6 +762,7 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context& // its user-data pointer in s0:s1. params.user_data.insert(params.user_data.begin(), 8u, 0u); info = {}; + info.logical_stage = ShaderType::Mesh; info.pa_cl_vs_out_cntl = sh.m_paClVsOutCntl; auto& mesh = info.mesh; mesh.input_primitive = static_cast(user_config.GetPrimType()); @@ -806,6 +810,65 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context& return params; } +std::array +PrepareTessellationPrograms(const HW::VertexShaderInfo& regs, const HW::Context& context, + std::array& input_info) { + const auto& sh = context.GetShaderRegisters(); + const auto local = ShaderGetMappedData(regs.ls_regs.data_addr, "ShaderGetInputInfoLS():"); + const auto control = ShaderGetMappedData(regs.hs_regs.data_addr, "ShaderGetInputInfoHS():"); + const auto evaluation = ShaderGetMappedData(regs.es_regs.data_addr, "ShaderGetInputInfoTES():"); + EXIT_NOT_IMPLEMENTED(local.type != Prospero::ShaderBinaryType::kHsFront || + control.type != Prospero::ShaderBinaryType::kHsBack || + evaluation.type != Prospero::ShaderBinaryType::kGs); + EXIT_NOT_IMPLEMENTED((context.GetShaderStages() & 0x4u) == 0 || + (context.GetShaderStages() & 0x00600020u) != 0); + EXIT_IF(regs.hs_regs.user_data_addr == 0); + + const auto local_users = std::span(regs.hs_user_sgpr.value, regs.hs_regs.rsrc2.user_sgpr); + const auto evaluation_users = std::span(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr); + std::array params { + GetShaderParams(regs.ls_regs.data_addr, "ShaderRecompiler LS", + GetDeclaredShaderHash(regs.ls_regs.data_addr), local_users, local), + GetShaderParams(regs.hs_regs.data_addr, "ShaderRecompiler HS", + GetDeclaredShaderHash(regs.hs_regs.data_addr), local_users, control), + GetShaderParams(regs.es_regs.data_addr, "ShaderRecompiler TES", + GetDeclaredShaderHash(regs.es_regs.data_addr), evaluation_users, + evaluation), + }; + // The fused HS back half receives its separate user-data address in s0:s1. + // RDNA2 reserves s0:s7 before the native HS user SGPRs. + params[1].user_data.insert(params[1].user_data.begin(), 8u, 0u); + params[1].user_data[0] = static_cast(regs.hs_regs.user_data_addr); + params[1].user_data[1] = static_cast(regs.hs_regs.user_data_addr >> 32u); + + input_info = {}; + if (!ShaderGetStaticVertexInputInfo(regs.ls_regs.data_addr, regs.hs_user_sgpr, + regs.hs_regs.rsrc2.user_sgpr, sh, local, input_info[0])) { + EXIT("failed to prepare local shader program\n"); + } + input_info[0].logical_stage = ShaderType::Local; + input_info[1].logical_stage = ShaderType::TessellationControl; + input_info[1].scratch_size_dwords = control.scratch_size_dwords; + input_info[2].logical_stage = ShaderType::TessellationEvaluation; + input_info[2].scratch_size_dwords = evaluation.scratch_size_dwords; + input_info[2].pa_cl_vs_out_cntl = sh.m_paClVsOutCntl; + + ShaderTessellationInputInfo tess { + .input_control_points = (sh.m_vgtLsHsConfig >> 8u) & 0x3fu, + .output_control_points = (sh.m_vgtLsHsConfig >> 14u) & 0x3fu, + .domain = sh.m_vgtTfParam & 0x3u, + .partitioning = (sh.m_vgtTfParam >> 2u) & 0x3u, + .output_topology = (sh.m_vgtTfParam >> 5u) & 0x3u, + }; + EXIT_IF(tess.input_control_points == 0 || tess.input_control_points > 32 || + tess.output_control_points == 0 || tess.output_control_points > 32); + ShaderRecompiler::AnalyzeTessellationPrograms(params[0].code, params[1].code, tess); + for (auto& stage: input_info) { + stage.tess = tess; + } + return params; +} + ShaderParams PrepareProgram( const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh, std::span target_export_mapping, diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index 7b02eebf0..cac8a3deb 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -33,7 +33,17 @@ struct ComputeShaderInfo; struct ShaderRegisters; } // namespace HW -enum class ShaderType { Unknown, Vertex, Pixel, Fetch, Compute, Mesh }; +enum class ShaderType { + Unknown, + Vertex, + Pixel, + Fetch, + Compute, + Mesh, + Local, + TessellationControl, + TessellationEvaluation +}; namespace ShaderRecompiler::IR { struct CompiledShaderInfo; @@ -99,6 +109,16 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo { } }; +struct ShaderTessellationInputInfo { + uint32_t input_control_points = 0; + uint32_t output_control_points = 0; + uint32_t ls_stride = 0; + uint32_t hs_stride = 0; + uint32_t domain = 0; + uint32_t partitioning = 0; + uint32_t output_topology = 0; +}; + struct ShaderVertexInputInfo { static constexpr int RES_MAX = 32; @@ -106,6 +126,7 @@ struct ShaderVertexInputInfo { ShaderVertexDestination resources_dst[RES_MAX]; ShaderVertexInputBuffer buffers[RES_MAX]; ShaderStageRuntime stage; + ShaderType logical_stage = ShaderType::Vertex; int resources_num = 0; int fetch_attrib_reg = 0; int fetch_buffer_reg = 0; @@ -114,6 +135,7 @@ struct ShaderVertexInputInfo { uint32_t pa_cl_vs_out_cntl = 0; ShaderClipSpaceTransform clip_space; ShaderMeshInputInfo mesh; + ShaderTessellationInputInfo tess; bool fetch_external = false; bool fetch_embedded = false; }; diff --git a/src/graphics/shader/shaderCompiler.h b/src/graphics/shader/shaderCompiler.h index 13a47864b..a53f82864 100644 --- a/src/graphics/shader/shaderCompiler.h +++ b/src/graphics/shader/shaderCompiler.h @@ -30,6 +30,9 @@ void BuildStageStaticKey(const ShaderComputeInputInfo& input_info, std::vector +PrepareTessellationPrograms(const HW::VertexShaderInfo& regs, const HW::Context& context, + std::array& input_info); ShaderParams PrepareProgram( const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh, std::span target_export_mapping, diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 919170478..c921710f1 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -401,7 +401,16 @@ struct RenderExecutorTestAccess { const ShaderStageRuntime &vertex, const ShaderStageRuntime &pixel, bool pixel_active) { - return executor.PrepareGraphicsBindings(vertex, pixel, pixel_active); + RenderExecutor::GraphicsBindings result; + result.vertex[0] = executor.PrepareBindings(vertex); + std::array stages{&result.vertex[0], nullptr}; + if (pixel_active) { + result.pixel.emplace(executor.PrepareBindings(pixel)); + stages[1] = &*result.pixel; + } + executor.PrepareGraphicsBindings( + std::span{stages.data(), pixel_active ? 2u : 1u}); + return result; } static PipelineCache::Pipeline @@ -9741,7 +9750,7 @@ public: ordered_bindings.pixel->images[0].image_id; Require( name, "VS-before-PS retained-owner order", - ordered_bindings.vertex.images[0].image_id == storage_id && + ordered_bindings.vertex[0].images[0].image_id == storage_id && ordered_sampled_id != storage_id && RenderExecutorTestAccess::BoundImagesInOrder(executor, storage_id, ordered_sampled_id), @@ -9753,7 +9762,7 @@ public: RenderExecutorTestAccess::PrepareGraphicsBindings( executor, storage_runtime, sampled_runtime, true); const auto &storage_binding = - graphics_bindings.vertex.images[0]; + graphics_bindings.vertex[0].images[0]; const auto &sampled_binding = graphics_bindings.pixel->images[0]; Require(name, "storage final acquisition", @@ -9775,7 +9784,7 @@ public: "the production graphics binding path did not complete vertex " "storage acquisition before pixel sampling"); descriptor_pipelines.push_back(RenderExecutorTestAccess::CommitBindings( - executor, scheduler.Current(), graphics_bindings.vertex, + executor, scheduler.Current(), graphics_bindings.vertex[0], *graphics_bindings.pixel)); Require(name, "early writable alias retention", texture_cache.GetImage(storage_id).backing.state.access_mask == @@ -9815,15 +9824,15 @@ public: "an already sampled image was not promoted when a later " "storage alias bound the same backing"); descriptor_pipelines.push_back(RenderExecutorTestAccess::CommitBindings( - executor, scheduler.Current(), writable_alias_bindings.vertex, + executor, scheduler.Current(), writable_alias_bindings.vertex[0], *writable_alias_bindings.pixel)); Require( name, "forced-general descriptor capture", - writable_alias_bindings.vertex.images[0].layout == + writable_alias_bindings.vertex[0].images[0].layout == vk::ImageLayout::eGeneral && writable_alias_bindings.pixel->images[0].layout == vk::ImageLayout::eGeneral && - MakeImageInfo(writable_alias_bindings.vertex.images[0]) + MakeImageInfo(writable_alias_bindings.vertex[0].images[0]) .imageLayout == vk::ImageLayout::eGeneral && MakeImageInfo(writable_alias_bindings.pixel->images[0]) .imageLayout == vk::ImageLayout::eGeneral && @@ -10240,8 +10249,8 @@ public: const auto bounds_rendering = RenderExecutorTestAccess::AcquireRenderTargets( executor, scheduler.Current(), &no_color, 0, bounds_depth, bounds_bindings.pixel); descriptor_pipelines.push_back(RenderExecutorTestAccess::CommitBindings( - executor, scheduler.Current(), bounds_bindings.vertex, *bounds_bindings.pixel)); - const auto &vertex_depth = bounds_bindings.vertex.images[0]; + executor, scheduler.Current(), bounds_bindings.vertex[0], *bounds_bindings.pixel)); + const auto &vertex_depth = bounds_bindings.vertex[0].images[0]; const auto &pixel_depth = bounds_bindings.pixel->images[0]; constexpr auto readonly_layout = vk::ImageLayout::eDepthReadOnlyOptimal; Require(name, "deferred clear with sampled read-only depth bounds", @@ -10581,7 +10590,7 @@ public: RenderExecutorTestAccess::AcquireRenderTargets( executor, scheduler.Current(), &no_color, 0, shared_depth, shared_bindings.pixel); descriptor_pipelines.push_back(RenderExecutorTestAccess::CommitBindings( - executor, scheduler.Current(), shared_bindings.vertex, + executor, scheduler.Current(), shared_bindings.vertex[0], *shared_bindings.pixel)); const auto expected_layout = stencil_write @@ -10592,7 +10601,7 @@ public: vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite; const auto &shared_image = texture_cache.GetImage(shared_depth.image_id); - const auto &vertex_image = shared_bindings.vertex.images[0]; + const auto &vertex_image = shared_bindings.vertex[0].images[0]; const auto &pixel_image = shared_bindings.pixel->images[0]; Require(name, "sampled depth and stencil attachment layout", shared_depth.image_id == phased_depth.image_id && @@ -12173,8 +12182,9 @@ public: mode.provoking_vtx_last = provoking_last; registers.SetModeControl(mode); return context.GetPipelineCache().GetGraphicsPipeline( - std::span{&color, 1u}, depth, vertex, scheduler.Current(), &pixel, - vk::PrimitiveTopology::eTriangleList, false, vertex_shader, pixel_shader); + std::span{&color, 1u}, depth, std::span{&vertex, 1u}, scheduler.Current(), &pixel, + vk::PrimitiveTopology::eTriangleList, false, + PipelineCache::GraphicsPrograms{{vertex_shader}, pixel_shader}); }; auto &filled = pipeline(true, 2, 2); const auto draw = [&](const PipelineCache::Pipeline &selected) { @@ -12196,7 +12206,7 @@ public: feedback_enabled == (depth_feedback && depth.depth_write_enable), "feedback was not selected only for an overlapping fragment depth read/write"); RenderExecutorTestAccess::CommitBindings( - executor, command, selected, bindings.vertex, *bindings.pixel); + executor, command, selected, bindings.vertex[0], *bindings.pixel); rendering.color_attachments[0].is_clear = true; command.BeginRendering(rendering); auto cmd = command.Handle(); @@ -25504,10 +25514,160 @@ void CheckPs5GameExampleImageClearRuntimeShape() { std::printf("[host] %-32s ok\n", "Ps5GameExampleImageClear"); } +void CheckTessellationPrograms() { + using namespace ShaderRecompiler; + const char *name = "TessellationPrograms"; + std::array, 3> code; + auto &local = code[0]; + // The fused LS addresses one 124-byte control point using its v3 ordinal. + local.push_back(EncodeVop2(0x0b, 21, 255u, 3)); + local.push_back(124); + local.push_back(EncodeDs0(0x0e, (3u << 8u) | 1u)); + local.push_back(EncodeDs1Ex(0, 5, 2, 21)); + local.push_back(EncodeSop1(0x20, 0, 6)); + local.push_back(0xffffffffu); // Shader footer is not executable after LS handoff. + + auto &control = code[1]; + AppendVop3(&control, 0x365, 0, 193u, InlineU32(0)); + AppendVop3(&control, 0x366, 0, 193u, Vgpr(0)); + control.push_back(EncodeSopp(0x0a)); + control.push_back(EncodeVopc(0xd1, 249u, 3)); + control.push_back(EncodeVopcSdwa(0, 0, 0, 6, 1, 0, 0, 0, 0, 0, 0, 0, 1)); + AppendVop3(&control, 0x148, 32, Vgpr(1), InlineU32(0), InlineU32(8)); + AppendVop3(&control, 0x148, 34, Vgpr(1), InlineU32(8), InlineU32(5)); + AppendVop3(&control, 0x346, 35, Vgpr(34), InlineU32(7), InlineU32(0)); + control.push_back(EncodeVop2(0x0b, 29, 255u, 34)); + control.push_back(124); + control.push_back(EncodeDs0(0x37, (3u << 8u) | 1u)); + control.push_back(EncodeDs1Ex(10, 0, 0, 29)); + control.push_back(EncodeMubuf0(0x1d, 28)); + control.push_back(EncodeMubuf1(10, 2, 35, 2)); + // The HS prologue's wave lane computation must refer to its control point. + control.push_back(EncodeMubuf0(0x1c, 120)); + control.push_back(EncodeMubuf1(0, 2, 35, 2)); + // The game reuses previously written data VGPRs for CP0-only ring addresses. + AppendVMovLiteral(&control, 4, 0x42280000u); + AppendVMovLiteral(&control, 5, 0x422c0000u); + control.push_back(EncodeVopc(0xd4, InlineU32(1), 34)); + control.push_back(EncodeVop2(0x1a, 4, InlineU32(4), 32)); + control.push_back(EncodeVop2(0x1a, 6, InlineU32(6), 32)); + control.push_back(EncodeVop2(0x26, 5, 255u, 6)); + control.push_back(0x7fc0); + for (u32 i = 0; i < 4; i++) { + AppendVMovLiteral(&control, i, std::bit_cast(static_cast(i + 1))); + } + control.push_back(EncodeMubuf0(0x1e)); + control.push_back(EncodeMubuf1(0, 2, 4, 4)); + for (u32 i = 0; i < 4; i++) { + control.push_back(EncodeMubuf0(0x1c, i * 16)); + control.push_back(EncodeMubuf1(i, 2, 5, 2)); + } + AppendEnd(&control); + + auto &evaluation = code[2]; + evaluation.push_back(EncodeMubuf0(0x0d, 28)); + evaluation.push_back(EncodeMubuf1(10, 2, 7, 4)); + evaluation.push_back(EncodeMubuf0(0x0d, 256 + 28)); + evaluation.push_back(EncodeMubuf1(12, 2, 7, 4)); + evaluation.push_back(EncodeExp0(0x0c, 0xf)); + evaluation.push_back(EncodeExp1(5, 6, 10, 12)); + AppendEnd(&evaluation); + + ShaderTessellationInputInfo tess{.input_control_points = 3, + .output_control_points = 3, + .domain = 1, + .partitioning = 2, + .output_topology = 2}; + AnalyzeTessellationPrograms(local, control, tess); + Require(name, "decoded interface", tess.ls_stride == 124 && tess.hs_stride == 128, + "captured LS and HS address arithmetic must produce distinct strides"); + + constexpr std::array stages{ShaderType::Local, ShaderType::TessellationControl, + ShaderType::TessellationEvaluation}; + for (u32 stage = 0; stage < stages.size(); stage++) { + ShaderVertexInputInfo vertex; + vertex.logical_stage = stages[stage]; + vertex.tess = tess; + CompileOptions options; + options.stage = stages[stage]; + options.input_info.vertex = &vertex; + auto translated = TranslateProgram(code[stage], options); + std::set local_offsets, evaluation_offsets, factor_offsets, patch_offsets; + for (const auto *block : translated.program.blocks) { + for (const auto &inst : *block) { + if (inst.GetOpcode() != IR::ValueOpcode::GetTessellationAttribute && + inst.GetOpcode() != IR::ValueOpcode::SetTessellationAttribute) { + continue; + } + if (!inst.Arg(1).IsImmediate()) { + continue; + } + switch (static_cast(inst.Arg(0).U32())) { + case IR::TessellationAttribute::LocalOutput: + local_offsets.insert(inst.Arg(1).U32()); + break; + case IR::TessellationAttribute::EvaluationInput: + evaluation_offsets.insert(inst.Arg(1).U32()); + break; + case IR::TessellationAttribute::Factor: + factor_offsets.insert(inst.Arg(1).U32()); + break; + case IR::TessellationAttribute::PatchOutput: + patch_offsets.insert(inst.Arg(1).U32()); + break; + default: + break; + } + } + } + Require(name, "separate DS offsets", stage != 0 || local_offsets == std::set{4, 12}, + "DS_WRITE2 must preserve both independently encoded offsets"); + Require(name, "offchip component addressing", + stage != 2 || evaluation_offsets == std::set{28, 284}, + "TES control point and component offsets must survive lowering"); + Require(name, "triangle factor layout", + stage != 1 || factor_offsets == std::set{0, 4, 8, 12}, + "the four packed guest factors must retain their order"); + Require(name, "predicated patch addresses", + stage != 1 || patch_offsets == std::set{0x7fc0, 0x7fd0, 0x7fe0, 0x7ff0}, + "CP0 patch stores must use the address assigned on their active predicate"); + auto result = CompileProgram(std::move(translated), options, {}); + ValidateSpirv(name, result.spirv); + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2); + std::string assembly; + Require(name, "disassembly", tools.Disassemble(result.spirv, &assembly), + "failed to disassemble tessellation shader"); + Require(name, "native local inputs", + stage != 0 || (assembly.find("VertexIndex") != std::string::npos && + assembly.find("InstanceIndex") != std::string::npos), + "LS v2/v5 must read native vertex/instance indices"); + Require(name, "control point lane", + stage != 1 || (assembly.find("InvocationId") != std::string::npos && + assembly.find("SubgroupLocalInvocationId") == std::string::npos), + "HS lane masks must use control point invocation IDs"); + Require(name, "control point barrier", + stage != 1 || (assembly.find("OpMemoryModel Logical GLSL450") != std::string::npos && + assembly.find("OpControlBarrier %uint_2 %uint_4 %uint_0") != std::string::npos), + "HS barriers must match GLSL450 control point synchronization semantics"); + Require(name, "factor builtins", + stage != 1 || (assembly.find("TessLevelOuter") != std::string::npos && + assembly.find("TessLevelInner") != std::string::npos), + "triangle factors require outer[4] and inner[2] Vulkan builtins"); + Require(name, "native domain and winding", + stage != 2 || (assembly.find("TessCoord") != std::string::npos && + assembly.find("Triangles") != std::string::npos && + assembly.find("SpacingFractionalOdd") != std::string::npos && + assembly.find("VertexOrderCw") != std::string::npos), + "TES must preserve guest triangle domain, partitioning, and winding"); + } + std::printf("[host] %-32s ok\n", name); +} + void CheckEmbeddedFetchVertexOffset() { const auto MakeFetch = [](std::initializer_list> adds, std::optional> late_add = {}, - u32 accumulator_vgpr = 0, bool ngg_sad = false) { + u32 accumulator_vgpr = 0, bool ngg_sad = false, + bool local = false) { std::vector code; code.push_back(EncodeSMovB32(0, InlineU32(0))); code.push_back(EncodeSmem0(0x02u, 20, 4)); @@ -25526,7 +25686,7 @@ void CheckEmbeddedFetchVertexOffset() { if (ngg_sad) { AppendOffsets(); } - code.push_back(EncodeVop2(0x01u, 0, Vgpr(8), 5)); + code.push_back(EncodeVop2(0x01u, 0, Vgpr(local ? 5 : 8), local ? 2 : 5)); if (!ngg_sad) { AppendOffsets(); } @@ -25535,26 +25695,41 @@ void CheckEmbeddedFetchVertexOffset() { if (late_add.has_value()) { AppendVop3B(&code, 0x30fu, 0, 0, late_add->first, Vgpr(late_add->second)); } - AppendEnd(&code); + if (local) { + AppendVMovU32(&code, 20, 0); + code.push_back(EncodeDs0(0x0e, 1u << 8u)); + code.push_back(EncodeDs1Ex(0, 10, 9, 20)); + code.push_back(EncodeDs0(0x0e, (3u << 8u) | 2u)); + code.push_back(EncodeDs1Ex(0, 12, 11, 20)); + code.push_back(EncodeSop1(0x20, 0, 6)); + code.push_back(0xffffffffu); + } else { + AppendEnd(&code); + } return code; }; const auto Compile = [&](const char *name, const std::vector &code, - u32 slot10) { + u32 slot10, ShaderType stage = ShaderType::Vertex, + Prospero::BufferFormat format = Prospero::BufferFormat::k32_32_32_32Float) { std::array user_data{}; user_data[10] = slot10; ShaderVertexInputInfo vertex; + vertex.logical_stage = stage; + vertex.tess = {.input_control_points = 3, .output_control_points = 3, + .ls_stride = 124, .hs_stride = 128, .domain = 1, + .partitioning = 2, .output_topology = 2}; vertex.fetch_embedded = true; vertex.fetch_buffer_reg = 0; vertex.fetch_attrib_reg = 2; vertex.resources_num = 1; vertex.resources[0].fields[3] = - BufferFormat(Prospero::BufferFormat::k32_32_32_32Float) << 12u | DstSel(4, 5, 6, 7); + BufferFormat(format) << 12u | DstSel(4, 5, 6, 7); vertex.resources_dst[0].attr_id = 0; vertex.resources_dst[0].registers_num = 4; ShaderRecompiler::CompileOptions options; - options.stage = ShaderType::Vertex; + options.stage = stage; options.user_data_base = 8; options.user_data = user_data; options.input_info.vertex = &vertex; @@ -25605,6 +25780,28 @@ void CheckEmbeddedFetchVertexOffset() { return ResolveDrawOffsets(0, vertex).second; }; + for (const auto format : {Prospero::BufferFormat::k32_32_32_32Float, + Prospero::BufferFormat::k32_32_32_32UInt}) { + const auto local = Compile("LocalEmbeddedFetch", MakeFetch({}, {}, 0, false, true), + 0, ShaderType::Local, format); + ValidateSpirv("LocalEmbeddedFetch", local.spirv); + Require("LocalEmbeddedFetch", "live vertex attributes", + std::ranges::any_of(local.program.info.inputs, [](const auto &input) { + return input.kind == ShaderRecompiler::IR::StageInputKind::Parameter && + input.location == 0; + }), "LS indexed fetch must consume native vertex attributes"); + if (format == Prospero::BufferFormat::k32_32_32_32UInt) { + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2); + std::string assembly; + Require("LocalEmbeddedFetch", "disassembly", tools.Disassemble(local.spirv, &assembly), + "failed to disassemble unsigned LS fetch"); + Require("LocalEmbeddedFetch", "unsigned input type", + assembly.find("OpTypePointer Input %v4uint") != std::string::npos && + assembly.find("OpConvertUToF") == std::string::npos, + "unsigned LS attributes must preserve their native integer bits"); + } + } + const auto valid = Compile("EmbeddedFetchVertexOffset", MakeFetch({{18, 0}}), 7); Require( @@ -29355,6 +29552,11 @@ int main(int argc, char **argv) { std::setvbuf(stdout, nullptr, _IONBF, 0); EnsureConfigInitialized(); CheckLeastRecentlyUsedCacheOrdering(); + if (argc == 2 && std::strcmp(argv[1], "--tessellation-only") == 0) { + CheckTessellationPrograms(); + CheckEmbeddedFetchVertexOffset(); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--cmpx-lt-u16-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, VectorVopcCmpxLtU16CapturedSdwaExecMask()); @@ -29777,6 +29979,7 @@ int main(int argc, char **argv) { CheckPm4CeCompletion(vulkan.RuntimeRenderer()); CheckEmbeddedFetchVertexOffset(); CheckEmbeddedFetchLaneSpill(); + CheckTessellationPrograms(); CheckRectListShaders(); CheckIndirectImageKeySwitch(); CheckPs5GameExampleImageClearRuntimeShape();