From 86a586025ff0864e24a96abba689f271ca20e76a Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 2 Aug 2026 23:06:25 +0200 Subject: [PATCH] renderer: expand rectangle lists with tessellation Remove the legacy NGG rectangle workaround and its configuration toggle. --- src/common/emulatorConfig.cpp | 4 - src/common/emulatorConfig.h | 2 - .../renderer/pipeline/pipelineCache.cpp | 5 +- .../host_gpu/renderer/pipeline/shaders.cpp | 76 +++- src/graphics/host_gpu/renderer/renderDraw.cpp | 66 +-- .../recompiler/emitter/SpirvBuilder.cpp | 4 +- .../shader/recompiler/emitter/SpirvBuilder.h | 3 +- .../emitter/spirvEmitterAnalysis.cpp | 45 +- .../recompiler/emitter/spirvEmitterInternal.h | 3 - src/graphics/shader/rectListShader.cpp | 397 ++++++++++++++++++ src/graphics/shader/rectListShader.h | 22 + src/graphics/shader/shader.cpp | 37 ++ src/graphics/shader/shader.h | 5 + .../forms/configuration_edit_dialog.ui | 13 - src/launcher/include/configuration.h | 5 - src/launcher/src/configurationEditDialog.cpp | 2 - src/launcher/src/mainDialog.cpp | 1 - src/main.cpp | 7 - tests/ShaderRecompilerComputeTests.cpp | 87 ++++ 19 files changed, 649 insertions(+), 135 deletions(-) create mode 100644 src/graphics/shader/rectListShader.cpp create mode 100644 src/graphics/shader/rectListShader.h diff --git a/src/common/emulatorConfig.cpp b/src/common/emulatorConfig.cpp index d40df0f..b376d79 100644 --- a/src/common/emulatorConfig.cpp +++ b/src/common/emulatorConfig.cpp @@ -89,10 +89,6 @@ bool RenderDocEnabled() { return g_config->renderdoc_enabled; } -bool NggRectlistDrawEnabled() { - return g_config->ngg_rectlist_draw_enabled; -} - bool ReadbackLinearImagesEnabled() { return g_config->readback_linear_images; } diff --git a/src/common/emulatorConfig.h b/src/common/emulatorConfig.h index 8407f0a..2e54604 100644 --- a/src/common/emulatorConfig.h +++ b/src/common/emulatorConfig.h @@ -35,7 +35,6 @@ struct ConfigOptions { ProfilerDirection profiler_direction = ProfilerDirection::None; bool spirv_debug_printf_enabled = false; bool renderdoc_enabled = false; - bool ngg_rectlist_draw_enabled = true; bool readback_linear_images = false; }; @@ -64,7 +63,6 @@ ProfilerDirection GetProfilerDirection(); bool SpirvDebugPrintfEnabled(); bool RenderDocEnabled(); -bool NggRectlistDrawEnabled(); bool ReadbackLinearImagesEnabled(); } // namespace Config diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index 9442af3..a187c37 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -154,8 +154,9 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline( for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) { static_params.color_mask[i] = color_mask[i]; } - static_params.cull_back = mc.cull_back; - static_params.cull_front = mc.cull_front; + const bool rect_list = topology == vk::PrimitiveTopology::ePatchList; + static_params.cull_back = !rect_list && mc.cull_back; + static_params.cull_front = !rect_list && mc.cull_front; static_params.face = mc.face; for (uint32_t i = 0; i < color_count; i++) { diff --git a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp index ed55297..d027319 100644 --- a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp @@ -14,6 +14,7 @@ #include "graphics/host_gpu/renderer/renderTarget.h" #include "graphics/host_gpu/vulkanCommon.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" +#include "graphics/shader/rectListShader.h" #include "graphics/shader/shader.h" #include @@ -463,8 +464,12 @@ void CreatePipelineInternal( uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active) { EXIT_IF(ps_active && ps_input_info == nullptr); - vk::ShaderModule vert_shader_module = nullptr; - vk::ShaderModule frag_shader_module = nullptr; + const bool rect_list = static_params.topology == vk::PrimitiveTopology::ePatchList; + + vk::ShaderModule vert_shader_module = nullptr; + vk::ShaderModule tess_control_shader_module = nullptr; + vk::ShaderModule tess_eval_shader_module = nullptr; + vk::ShaderModule frag_shader_module = nullptr; vk::ShaderModuleCreateInfo create_info {}; @@ -491,8 +496,33 @@ void CreatePipelineInternal( } EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess); } + if (rect_list) { + const auto shaders = + BuildRectListShaders(vs_input_info, ps_active ? ps_input_info : nullptr); + create_info.codeSize = shaders.control.size() * 4; + create_info.pCode = shaders.control.data(); + result = + graphics.device.createShaderModule(&create_info, nullptr, &tess_control_shader_module); + if (graphics_debug_dump_enabled()) { + LOGF("PipelineTrace: vkCreateShaderModule RectList TCS done result=%s module=%p\n", + VulkanToString(result).c_str(), static_cast(tess_control_shader_module)); + } + EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess); + + create_info.codeSize = shaders.evaluation.size() * 4; + create_info.pCode = shaders.evaluation.data(); + result = + graphics.device.createShaderModule(&create_info, nullptr, &tess_eval_shader_module); + if (graphics_debug_dump_enabled()) { + LOGF("PipelineTrace: vkCreateShaderModule RectList TES done result=%s module=%p\n", + VulkanToString(result).c_str(), static_cast(tess_eval_shader_module)); + } + EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess); + } EXIT_NOT_IMPLEMENTED(vert_shader_module == nullptr); + EXIT_NOT_IMPLEMENTED( + rect_list && (tess_control_shader_module == nullptr || tess_eval_shader_module == nullptr)); EXIT_NOT_IMPLEMENTED(ps_active && frag_shader_module == nullptr); vk::PipelineShaderStageCreateInfo vert_shader_stage_info {}; @@ -525,9 +555,28 @@ void CreatePipelineInternal( frag_shader_stage_info); } - vk::PipelineShaderStageCreateInfo shader_stages[] = {vert_shader_stage_info, - frag_shader_stage_info}; - const uint32_t shader_stage_count = ps_active ? 2u : 1u; + vk::PipelineShaderStageCreateInfo tess_control_shader_stage_info {}; + tess_control_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo; + tess_control_shader_stage_info.stage = vk::ShaderStageFlagBits::eTessellationControl; + tess_control_shader_stage_info.module = tess_control_shader_module; + tess_control_shader_stage_info.pName = "main"; + + vk::PipelineShaderStageCreateInfo tess_eval_shader_stage_info {}; + tess_eval_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo; + tess_eval_shader_stage_info.stage = vk::ShaderStageFlagBits::eTessellationEvaluation; + tess_eval_shader_stage_info.module = tess_eval_shader_module; + tess_eval_shader_stage_info.pName = "main"; + + vk::PipelineShaderStageCreateInfo shader_stages[4] = {}; + uint32_t shader_stage_count = 0; + shader_stages[shader_stage_count++] = vert_shader_stage_info; + if (rect_list) { + shader_stages[shader_stage_count++] = tess_control_shader_stage_info; + shader_stages[shader_stage_count++] = tess_eval_shader_stage_info; + } + if (ps_active) { + shader_stages[shader_stage_count++] = frag_shader_stage_info; + } vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX]; vk::VertexInputBindingDescription input_desc[ShaderVertexInputInfo::RES_MAX]; @@ -929,10 +978,13 @@ void CreatePipelineInternal( pipeline_info.pStages = shader_stages; pipeline_info.pVertexInputState = &vertex_input_info; pipeline_info.pInputAssemblyState = &input_assembly; - pipeline_info.pTessellationState = nullptr; - pipeline_info.pViewportState = &viewport_state; - pipeline_info.pRasterizationState = &rasterizer; - pipeline_info.pMultisampleState = &multisampling; + vk::PipelineTessellationStateCreateInfo tessellation_state {}; + tessellation_state.sType = vk::StructureType::ePipelineTessellationStateCreateInfo; + tessellation_state.patchControlPoints = 3; + pipeline_info.pTessellationState = (rect_list ? &tessellation_state : nullptr); + pipeline_info.pViewportState = &viewport_state; + pipeline_info.pRasterizationState = &rasterizer; + pipeline_info.pMultisampleState = &multisampling; pipeline_info.pDepthStencilState = (static_params.with_depth ? &depth_stencil_info : nullptr); pipeline_info.pColorBlendState = &color_blending; pipeline_info.pDynamicState = &dynamic_state; @@ -968,6 +1020,12 @@ void CreatePipelineInternal( if (frag_shader_module != nullptr) { graphics.device.destroyShaderModule(frag_shader_module, nullptr); } + if (tess_control_shader_module != nullptr) { + graphics.device.destroyShaderModule(tess_control_shader_module, nullptr); + } + if (tess_eval_shader_module != nullptr) { + graphics.device.destroyShaderModule(tess_eval_shader_module, nullptr); + } graphics.device.destroyShaderModule(vert_shader_module, nullptr); } diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index d12b5ed..13fade0 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -2,7 +2,6 @@ #include "common/assert.h" #include "common/common.h" -#include "common/emulatorConfig.h" #include "common/file.h" #include "common/logging/log.h" #include "common/profiler.h" @@ -650,11 +649,9 @@ static bool ConsumeMetadataColorOperation(const RenderCommandBuffer& buffer) { } struct DrawEmitInfo { - bool indexed = false; - bool draw_prim7_as_ngg = false; - uint32_t draw_vertex_count = 0; - int32_t vertex_offset = 0; - uint32_t first_vertex = 0; + bool indexed = false; + int32_t vertex_offset = 0; + uint32_t first_vertex = 0; }; struct DrawIndexBufferSource { @@ -767,7 +764,7 @@ static void SetDrawDebugPhase(RenderCommandBuffer& buffer, uint64_t submit_id, draw.flags, draw.instance_count, draw.first_instance); } -static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw, bool use_ngg_rectlist_draw, +static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw, vk::PrimitiveTopology& topology) { topology = vk::PrimitiveTopology::ePointList; @@ -791,8 +788,7 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw, bool use topology = vk::PrimitiveTopology::eTriangleStrip; break; case Prospero::PrimitiveType::kRectList: - topology = (auto_draw && use_ngg_rectlist_draw ? vk::PrimitiveTopology::eTriangleStrip - : vk::PrimitiveTopology::eTriangleList); + topology = vk::PrimitiveTopology::ePatchList; break; case Prospero::PrimitiveType::kRectListLegacy: if (!auto_draw) { @@ -991,20 +987,6 @@ static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCa // LogDrawTextureState(draw.name, state.color_info[0], state.ps_input_info); } -static bool IsHostExpandedRectListDrawSupported(const ShaderVertexInputInfo& vs_input_info, - const DrawCallInfo& draw, - const DrawEmitInfo& emit) { - if (!emit.draw_prim7_as_ngg) { - return true; - } - - if (vs_input_info.buffers_num != 0) { - return false; - } - - return draw.index_count == 3 || draw.index_count == emit.draw_vertex_count; -} - static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_buffer, const ShaderVertexInputInfo& vs_input_info, const DrawCallInfo& draw, const DrawEmitInfo& emit) { @@ -1017,22 +999,12 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_ case Prospero::PrimitiveType::kTriList: case Prospero::PrimitiveType::kTriFan: case Prospero::PrimitiveType::kTriStrip: - if (emit.indexed) { - vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset, - draw.first_instance); - } else { - vk_buffer.draw(draw.index_count, draw.instance_count, emit.first_vertex, - draw.first_instance); - } - break; case Prospero::PrimitiveType::kRectList: if (emit.indexed) { vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset, draw.first_instance); } else { - EXIT_NOT_IMPLEMENTED( - !IsHostExpandedRectListDrawSupported(vs_input_info, draw, emit)); - vk_buffer.draw(emit.draw_vertex_count, draw.instance_count, emit.first_vertex, + vk_buffer.draw(draw.index_count, draw.instance_count, emit.first_vertex, draw.first_instance); } break; @@ -1201,7 +1173,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, hw_check(buffer); vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList; - if (!GetDrawTopology(ucfg, false, false, topology)) { + if (!GetDrawTopology(ucfg, false, topology)) { return; } @@ -1337,20 +1309,15 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u return; } - vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList; - const bool use_ngg_rectlist_draw = Config::NggRectlistDrawEnabled(); - - if (!GetDrawTopology(ucfg, true, use_ngg_rectlist_draw, topology)) { + vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList; + if (!GetDrawTopology(ucfg, true, topology)) { ResetBindings(); return; } - const bool draw_prim7_as_ngg = - (use_ngg_rectlist_draw && - ucfg.GetPrimType() == Prospero::GpuEnumValue(Prospero::PrimitiveType::kRectList)); - RefreshShaders(buffer, draw, false, state); - if (draw_prim7_as_ngg && state.vs_input_info.buffers_num == 0 && + const bool rect_list = topology == vk::PrimitiveTopology::ePatchList; + if (rect_list && state.vs_input_info.buffers_num == 0 && state.vs_input_info.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: " @@ -1367,13 +1334,10 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u Prospero::GpuEnumValue(Prospero::PrimitiveType::kRectListLegacy), 0, nullptr); - const uint32_t draw_vertex_count = (draw_prim7_as_ngg ? 4u : index_count); - const auto vertex_offset = ResolveVertexOffset(ucfg.GetIndexOffset(), state.vs_input_info) + - static_cast(first_vertex); - DrawEmitInfo emit {}; - emit.draw_prim7_as_ngg = draw_prim7_as_ngg; - emit.draw_vertex_count = draw_vertex_count; - emit.first_vertex = static_cast(vertex_offset); + const auto vertex_offset = ResolveVertexOffset(ucfg.GetIndexOffset(), state.vs_input_info) + + static_cast(first_vertex); + DrawEmitInfo emit {}; + emit.first_vertex = static_cast(vertex_offset); DrawIndexBufferSource index_source {}; ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false, false, diff --git a/src/graphics/shader/recompiler/emitter/SpirvBuilder.cpp b/src/graphics/shader/recompiler/emitter/SpirvBuilder.cpp index b4885ff..c6ae631 100644 --- a/src/graphics/shader/recompiler/emitter/SpirvBuilder.cpp +++ b/src/graphics/shader/recompiler/emitter/SpirvBuilder.cpp @@ -19,7 +19,7 @@ static void AppendInstructionWords(std::vector& section, const uint32_ section.insert(section.end(), words + 1, words + words_num); } -Builder::Builder() { +Builder::Builder(uint32_t version): m_version(version) { m_debug.reserve(InitialSpirvSectionReserve); m_annotations.reserve(InitialSpirvSectionReserve); m_types.reserve(InitialSpirvSectionReserve); @@ -138,7 +138,7 @@ std::vector Builder::Build() const { m_debug.size() + m_annotations.size() + m_types.size() + m_functions.size()); module.push_back(0x07230203u); - module.push_back(0x00010300u); + module.push_back(m_version); module.push_back(0u); module.push_back(m_next_id); module.push_back(0u); diff --git a/src/graphics/shader/recompiler/emitter/SpirvBuilder.h b/src/graphics/shader/recompiler/emitter/SpirvBuilder.h index 5cd1fd4..787865a 100644 --- a/src/graphics/shader/recompiler/emitter/SpirvBuilder.h +++ b/src/graphics/shader/recompiler/emitter/SpirvBuilder.h @@ -10,7 +10,7 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv { class Builder { public: - Builder(); + explicit Builder(uint32_t version = 0x00010300u); ~Builder() = default; KYTY_CLASS_DEFAULT_COPY(Builder); @@ -39,6 +39,7 @@ private: static void AppendString(std::vector& words, const char* text); uint32_t m_next_id = 1; + uint32_t m_version = 0; std::vector m_capabilities; std::vector m_extensions; std::vector m_ext_inst_imports; diff --git a/src/graphics/shader/recompiler/emitter/spirvEmitterAnalysis.cpp b/src/graphics/shader/recompiler/emitter/spirvEmitterAnalysis.cpp index 9fc861e..7a57aab 100644 --- a/src/graphics/shader/recompiler/emitter/spirvEmitterAnalysis.cpp +++ b/src/graphics/shader/recompiler/emitter/spirvEmitterAnalysis.cpp @@ -7,51 +7,30 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { uint32_t PixelParameterMappedLocation(const EmitterState& state, uint32_t attr) { const auto* ps = state.pixel_input_info; - if (state.stage != ShaderType::Pixel || ps == nullptr || attr >= ps->input_num) { + if (state.stage != ShaderType::Pixel || ps == nullptr) { return attr; } - // VINTRP ATTR selects the PS input slot. SPI_PS_INPUT_CNTL maps that slot to a - // VS parameter export, which is the SPIR-V location we must link against. - return ps->interpolator_settings[attr] & PsInputOffsetMask; + return ShaderPixelParameterMappedLocation(*ps, attr); } uint32_t PixelParameterLocation(const EmitterState& state, uint32_t attr) { - bool used_locations[32] = {}; - + std::array active_inputs {}; + uint32_t active_count = 0; for (const auto& input: state.inputs) { - if (input.kind != IR::StageInputKind::Parameter) { - continue; - } - - auto location = PixelParameterMappedLocation(state, input.location); - if (location < std::size(used_locations) && used_locations[location]) { - auto fallback_location = input.location; - while (fallback_location < std::size(used_locations) && - used_locations[fallback_location]) { - fallback_location++; - } - EXIT_NOT_IMPLEMENTED(fallback_location >= std::size(used_locations)); - location = fallback_location; - } - - if (input.location == attr) { - return location; - } - - if (location < std::size(used_locations)) { - used_locations[location] = true; + if (input.kind == IR::StageInputKind::Parameter) { + active_inputs[active_count++] = input.location; } } - - return PixelParameterMappedLocation(state, attr); + return state.stage == ShaderType::Pixel && state.pixel_input_info != nullptr + ? ShaderPixelParameterLocation(*state.pixel_input_info, + {active_inputs.data(), active_count}, attr) + : attr; } bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr) { const auto* ps = state.pixel_input_info; - if (state.stage != ShaderType::Pixel || ps == nullptr || attr >= ps->input_num) { - return false; - } - return (ps->interpolator_settings[attr] & PsInputFlatShade) != 0; + return state.stage == ShaderType::Pixel && ps != nullptr && + ShaderPixelParameterIsFlat(*ps, attr); } void SetError(std::string* error, const char* message) { diff --git a/src/graphics/shader/recompiler/emitter/spirvEmitterInternal.h b/src/graphics/shader/recompiler/emitter/spirvEmitterInternal.h index 71d1619..5233dda 100644 --- a/src/graphics/shader/recompiler/emitter/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/emitter/spirvEmitterInternal.h @@ -425,9 +425,6 @@ struct EmitterState { std::map float_constants; }; -constexpr uint32_t PsInputOffsetMask = 0x0000001fu; -constexpr uint32_t PsInputFlatShade = 0x00000400u; - enum class VertexInputScalarKind { Float, Sint, Uint }; constexpr uint32_t NoImageComponent = 0xffffffffu; diff --git a/src/graphics/shader/rectListShader.cpp b/src/graphics/shader/rectListShader.cpp new file mode 100644 index 0000000..3a7ad1c --- /dev/null +++ b/src/graphics/shader/rectListShader.cpp @@ -0,0 +1,397 @@ +#include "graphics/shader/rectListShader.h" + +#include "common/assert.h" +#include "graphics/shader/recompiler/emitter/SpirvBuilder.h" +#include "graphics/shader/recompiler/ir/ShaderIR.h" +#include "graphics/shader/shader.h" +#include "spirv/unified1/spirv.hpp11" + +#include +#include +#include +#include + +namespace Libs::Graphics { + +namespace { + +using ShaderRecompiler::Spirv::Builder; + +constexpr uint32_t SpirvVersion15 = 0x00010500u; + +template +constexpr uint32_t Word(T value) { + return static_cast(value); +} + +struct Parameter { + uint32_t input_location = 0; + uint32_t output_location = 0; + bool flat = false; +}; + +std::vector GetParameters(const ShaderVertexInputInfo& vertex_info, + const ShaderPixelInputInfo* pixel_info) { + if (pixel_info == nullptr) { + return {}; + } + EXIT_IF(pixel_info->input_num > ShaderVertexInputInfo::RES_MAX); + EXIT_IF(pixel_info->stage.program == nullptr); + + std::vector active_inputs; + for (const auto& input: pixel_info->stage.program->info.inputs) { + if (input.kind == ShaderRecompiler::IR::StageInputKind::Parameter) { + active_inputs.push_back(input.location); + } + } + + std::vector parameters; + for (const auto input: active_inputs) { + const auto input_location = ShaderPixelParameterMappedLocation(*pixel_info, input); + if ((vertex_info.param_export_mask & (1u << input_location)) != 0) { + parameters.push_back({input_location, + ShaderPixelParameterLocation(*pixel_info, active_inputs, input), + ShaderPixelParameterIsFlat(*pixel_info, input)}); + } + } + return parameters; +} + +class RectListEmitter { +public: + RectListEmitter(const std::vector& parameters_, spv::ExecutionModel model) + : parameters(parameters_) { + builder.AddMemoryModel( + {Word(spv::AddressingModel::Logical), Word(spv::MemoryModel::GLSL450)}); + + void_type = Type(spv::Op::OpTypeVoid); + uint_type = Type(spv::Op::OpTypeInt, 32u, 0u); + int_type = Type(spv::Op::OpTypeInt, 32u, 1u); + float_type = Type(spv::Op::OpTypeFloat, 32u); + vec4_float_type = Type(spv::Op::OpTypeVector, float_type, 4u); + function_type = Type(spv::Op::OpTypeFunction, void_type); + + per_vertex_type = Type(spv::Op::OpTypeStruct, vec4_float_type); + builder.AddAnnotation({Word(spv::Op::OpMemberDecorate), per_vertex_type, 0u, + Word(spv::Decoration::BuiltIn), Word(spv::BuiltIn::Position)}); + builder.AddAnnotation( + {Word(spv::Op::OpDecorate), per_vertex_type, Word(spv::Decoration::Block)}); + + ptr_input_vec4_float = Pointer(spv::StorageClass::Input, vec4_float_type); + ptr_output_vec4_float = Pointer(spv::StorageClass::Output, vec4_float_type); + if (model == spv::ExecutionModel::TessellationControl) { + bool_type = Type(spv::Op::OpTypeBool); + vec2_bool_type = Type(spv::Op::OpTypeVector, bool_type, 2u); + vec2_float_type = Type(spv::Op::OpTypeVector, float_type, 2u); + ptr_output_float = Pointer(spv::StorageClass::Output, float_type); + } else { + vec3_float_type = Type(spv::Op::OpTypeVector, float_type, 3u); + ptr_input_float = Pointer(spv::StorageClass::Input, float_type); + } + } + + std::vector EmitControl() { + DefineEntry(spv::ExecutionModel::TessellationControl); + const auto float_one = Constant(float_type, std::bit_cast(1.0f)); + + for (uint32_t i = 0; i < 4; i++) { + Store(Access(ptr_output_float, tess_outer, Int(i)), float_one); + } + for (uint32_t i = 0; i < 2; i++) { + Store(Access(ptr_output_float, tess_inner, Int(i)), float_one); + } + + std::array positions {}; + for (uint32_t i = 0; i < positions.size(); i++) { + positions[i] = + Load(vec4_float_type, Access(ptr_input_vec4_float, gl_in, Int(i), Int(0))); + } + + std::array coordinate_equal {}; + for (uint32_t i = 0; i < coordinate_equal.size(); i++) { + const auto left = Result(spv::Op::OpVectorShuffle, vec2_float_type, positions[i], + positions[i], 0u, 1u); + const auto right = Result(spv::Op::OpVectorShuffle, vec2_float_type, + positions[(i + 1u) % 3u], positions[(i + 1u) % 3u], 0u, 1u); + coordinate_equal[i] = Result(spv::Op::OpFOrdEqual, vec2_bool_type, left, right); + } + + std::array barycentric {}; + std::array edge_vertex {}; + const auto float_minus_one = Constant(float_type, std::bit_cast(-1.0f)); + for (uint32_t i = 0; i < edge_vertex.size(); i++) { + const auto previous = (i + 2u) % 3u; + const auto xy = Result( + spv::Op::OpLogicalAnd, bool_type, + Result(spv::Op::OpCompositeExtract, bool_type, coordinate_equal[i], 0u), + Result(spv::Op::OpCompositeExtract, bool_type, coordinate_equal[previous], 1u)); + const auto yx = Result( + spv::Op::OpLogicalAnd, bool_type, + Result(spv::Op::OpCompositeExtract, bool_type, coordinate_equal[i], 1u), + Result(spv::Op::OpCompositeExtract, bool_type, coordinate_equal[previous], 0u)); + edge_vertex[i] = Result(spv::Op::OpLogicalOr, bool_type, xy, yx); + barycentric[i] = + Result(spv::Op::OpSelect, float_type, edge_vertex[i], float_minus_one, float_one); + } + + auto vertex_index = Result(spv::Op::OpSelect, int_type, edge_vertex[2], Int(2), Int(0)); + vertex_index = Result(spv::Op::OpSelect, int_type, edge_vertex[1], Int(1), vertex_index); + const auto invocation = Load(int_type, invocation_id); + const auto is_fourth = Result(spv::Op::OpIEqual, bool_type, invocation, Int(3)); + const auto index = + Result(spv::Op::OpSMod, int_type, + Result(spv::Op::OpIAdd, int_type, vertex_index, invocation), Int(3)); + + const auto position3 = Interpolate(positions[0], positions[1], positions[2], barycentric); + const auto position = + Result(spv::Op::OpSelect, vec4_float_type, is_fourth, position3, + Load(vec4_float_type, Access(ptr_input_vec4_float, gl_in, index, Int(0)))); + Store(Access(ptr_output_vec4_float, gl_out, invocation, Int(0)), position); + + for (uint32_t i = 0; i < parameters.size(); i++) { + const auto input0 = + Load(vec4_float_type, Access(ptr_input_vec4_float, inputs[i], Int(0))); + if (parameters[i].flat) { + Store(Access(ptr_output_vec4_float, outputs[i], invocation), input0); + continue; + } + const auto input1 = + Load(vec4_float_type, Access(ptr_input_vec4_float, inputs[i], Int(1))); + const auto input2 = + Load(vec4_float_type, Access(ptr_input_vec4_float, inputs[i], Int(2))); + const auto input3 = Interpolate(input0, input1, input2, barycentric); + const auto value = + Result(spv::Op::OpSelect, vec4_float_type, is_fourth, input3, + Load(vec4_float_type, Access(ptr_input_vec4_float, inputs[i], index))); + Store(Access(ptr_output_vec4_float, outputs[i], invocation), value); + } + + Emit(spv::Op::OpReturn); + Emit(spv::Op::OpFunctionEnd); + return builder.Build(); + } + + std::vector EmitEvaluation() { + DefineEntry(spv::ExecutionModel::TessellationEvaluation); + + const auto x = Load(float_type, Access(ptr_input_float, tess_coord, Int(0))); + const auto y = Load(float_type, Access(ptr_input_float, tess_coord, Int(1))); + const auto index = Result( + spv::Op::OpIAdd, int_type, + Result(spv::Op::OpIMul, int_type, Result(spv::Op::OpConvertFToS, int_type, y), Int(2)), + Result(spv::Op::OpConvertFToS, int_type, x)); + + const auto position = + Load(vec4_float_type, Access(ptr_input_vec4_float, gl_in, index, Int(0))); + Store(Access(ptr_output_vec4_float, gl_out, Int(0)), position); + for (uint32_t i = 0; i < parameters.size(); i++) { + Store(outputs[i], + Load(vec4_float_type, Access(ptr_input_vec4_float, inputs[i], index))); + } + + Emit(spv::Op::OpReturn); + Emit(spv::Op::OpFunctionEnd); + return builder.Build(); + } + +private: + template + uint32_t Type(spv::Op opcode, Args... operands) { + const auto id = builder.AllocateId(); + builder.AddType({Word(opcode), id, Word(operands)...}); + return id; + } + + uint32_t Constant(uint32_t type, uint32_t value) { + const auto id = builder.AllocateId(); + builder.AddType({Word(spv::Op::OpConstant), type, id, value}); + return id; + } + + uint32_t Pointer(spv::StorageClass storage, uint32_t type) { + return Type(spv::Op::OpTypePointer, storage, type); + } + + uint32_t Array(uint32_t type, uint32_t size) { + return Type(spv::Op::OpTypeArray, type, Uint(size)); + } + + template + uint32_t Result(spv::Op opcode, uint32_t type, Args... operands) { + const auto id = builder.AllocateId(); + builder.AddFunction({Word(opcode), type, id, Word(operands)...}); + return id; + } + + template + uint32_t ResultWithoutType(spv::Op opcode, Args... operands) { + const auto id = builder.AllocateId(); + builder.AddFunction({Word(opcode), id, Word(operands)...}); + return id; + } + + template + void Emit(spv::Op opcode, Args... operands) { + builder.AddFunction({Word(opcode), Word(operands)...}); + } + + template + uint32_t Access(uint32_t pointer_type, uint32_t base, Args... indices) { + return Result(spv::Op::OpAccessChain, pointer_type, base, Word(indices)...); + } + + uint32_t Load(uint32_t type, uint32_t pointer) { + return Result(spv::Op::OpLoad, type, pointer); + } + + void Store(uint32_t pointer, uint32_t value) { Emit(spv::Op::OpStore, pointer, value); } + + uint32_t Int(uint32_t value) { + auto& id = int_constants[value]; + if (id == 0) { + id = Constant(int_type, value); + } + return id; + } + + uint32_t Uint(uint32_t value) { + auto& id = uint_constants[value]; + if (id == 0) { + id = Constant(uint_type, value); + } + return id; + } + + uint32_t AddInterface(spv::StorageClass storage, uint32_t type) { + const auto variable = builder.AllocateId(); + builder.AddType( + {Word(spv::Op::OpVariable), Pointer(storage, type), variable, Word(storage)}); + interfaces.push_back(variable); + return variable; + } + + void Decorate(uint32_t target, spv::Decoration decoration, uint32_t value) { + builder.AddAnnotation({Word(spv::Op::OpDecorate), target, Word(decoration), value}); + } + + void DefineEntry(spv::ExecutionModel model) { + builder.AddCapability({Word(spv::Capability::Shader)}); + builder.AddCapability({Word(spv::Capability::Tessellation)}); + main = Result(spv::Op::OpFunction, void_type, spv::FunctionControlMask::MaskNone, + function_type); + if (model == spv::ExecutionModel::TessellationControl) { + builder.AddExecutionMode({main, Word(spv::ExecutionMode::OutputVertices), 4u}); + } else { + builder.AddExecutionMode({main, Word(spv::ExecutionMode::Quads)}); + builder.AddExecutionMode({main, Word(spv::ExecutionMode::SpacingEqual)}); + builder.AddExecutionMode({main, Word(spv::ExecutionMode::VertexOrderCw)}); + } + DefineInputs(model); + DefineOutputs(model); + builder.AddEntryPoint(Word(model), main, "main", interfaces); + ResultWithoutType(spv::Op::OpLabel); + } + + void DefineInputs(spv::ExecutionModel model) { + const auto tess_control = model == spv::ExecutionModel::TessellationControl; + if (tess_control) { + invocation_id = AddInterface(spv::StorageClass::Input, int_type); + Decorate(invocation_id, spv::Decoration::BuiltIn, Word(spv::BuiltIn::InvocationId)); + } else { + tess_coord = AddInterface(spv::StorageClass::Input, vec3_float_type); + Decorate(tess_coord, spv::Decoration::BuiltIn, Word(spv::BuiltIn::TessCoord)); + } + gl_in = + AddInterface(spv::StorageClass::Input, Array(per_vertex_type, tess_control ? 3u : 4u)); + + inputs.resize(parameters.size()); + std::array locations {}; + for (uint32_t i = 0; i < parameters.size(); i++) { + const auto location = + tess_control ? parameters[i].input_location : parameters[i].output_location; + if (tess_control && locations[location] != 0) { + inputs[i] = locations[location]; + continue; + } + inputs[i] = AddInterface(spv::StorageClass::Input, + Array(vec4_float_type, tess_control ? 3u : 4u)); + Decorate(inputs[i], spv::Decoration::Location, location); + locations[location] = inputs[i]; + } + } + + void DefineOutputs(spv::ExecutionModel model) { + const auto tess_control = model == spv::ExecutionModel::TessellationControl; + if (tess_control) { + gl_out = AddInterface(spv::StorageClass::Output, Array(per_vertex_type, 4u)); + tess_inner = AddInterface(spv::StorageClass::Output, Array(float_type, 2u)); + Decorate(tess_inner, spv::Decoration::BuiltIn, Word(spv::BuiltIn::TessLevelInner)); + builder.AddAnnotation( + {Word(spv::Op::OpDecorate), tess_inner, Word(spv::Decoration::Patch)}); + tess_outer = AddInterface(spv::StorageClass::Output, Array(float_type, 4u)); + Decorate(tess_outer, spv::Decoration::BuiltIn, Word(spv::BuiltIn::TessLevelOuter)); + builder.AddAnnotation( + {Word(spv::Op::OpDecorate), tess_outer, Word(spv::Decoration::Patch)}); + } else { + gl_out = AddInterface(spv::StorageClass::Output, per_vertex_type); + } + + outputs.resize(parameters.size()); + for (uint32_t i = 0; i < parameters.size(); i++) { + outputs[i] = AddInterface(spv::StorageClass::Output, + tess_control ? Array(vec4_float_type, 4u) : vec4_float_type); + Decorate(outputs[i], spv::Decoration::Location, parameters[i].output_location); + } + } + + uint32_t Interpolate(uint32_t v0, uint32_t v1, uint32_t v2, + const std::array& barycentric) { + const auto p0 = Result(spv::Op::OpVectorTimesScalar, vec4_float_type, v0, barycentric[0]); + const auto p1 = Result(spv::Op::OpVectorTimesScalar, vec4_float_type, v1, barycentric[1]); + const auto p2 = Result(spv::Op::OpVectorTimesScalar, vec4_float_type, v2, barycentric[2]); + return Result(spv::Op::OpFAdd, vec4_float_type, p0, + Result(spv::Op::OpFAdd, vec4_float_type, p1, p2)); + } + + Builder builder {SpirvVersion15}; + const std::vector& parameters; + std::vector interfaces; + std::vector inputs; + std::vector outputs; + std::array int_constants {}; + std::array uint_constants {}; + uint32_t main = 0; + uint32_t void_type = 0; + uint32_t bool_type = 0; + uint32_t uint_type = 0; + uint32_t int_type = 0; + uint32_t float_type = 0; + uint32_t vec2_bool_type = 0; + uint32_t vec2_float_type = 0; + uint32_t vec3_float_type = 0; + uint32_t vec4_float_type = 0; + uint32_t function_type = 0; + uint32_t per_vertex_type = 0; + uint32_t ptr_input_float = 0; + uint32_t ptr_input_vec4_float = 0; + uint32_t ptr_output_float = 0; + uint32_t ptr_output_vec4_float = 0; + uint32_t gl_in = 0; + uint32_t gl_out = 0; + uint32_t tess_inner = 0; + uint32_t tess_outer = 0; + uint32_t tess_coord = 0; + uint32_t invocation_id = 0; +}; + +} // namespace + +RectListShaders BuildRectListShaders(const ShaderVertexInputInfo& vertex_info, + const ShaderPixelInputInfo* pixel_info) { + const auto parameters = GetParameters(vertex_info, pixel_info); + RectListEmitter control(parameters, spv::ExecutionModel::TessellationControl); + RectListEmitter evaluation(parameters, spv::ExecutionModel::TessellationEvaluation); + return {control.EmitControl(), evaluation.EmitEvaluation()}; +} + +} // namespace Libs::Graphics diff --git a/src/graphics/shader/rectListShader.h b/src/graphics/shader/rectListShader.h new file mode 100644 index 0000000..ea19a46 --- /dev/null +++ b/src/graphics/shader/rectListShader.h @@ -0,0 +1,22 @@ +#ifndef EMULATOR_SRC_GRAPHICS_SHADER_RECTLISTSHADER_H_ +#define EMULATOR_SRC_GRAPHICS_SHADER_RECTLISTSHADER_H_ + +#include +#include + +namespace Libs::Graphics { + +struct ShaderPixelInputInfo; +struct ShaderVertexInputInfo; + +struct RectListShaders { + std::vector control; + std::vector evaluation; +}; + +RectListShaders BuildRectListShaders(const ShaderVertexInputInfo& vertex_info, + const ShaderPixelInputInfo* pixel_info); + +} // namespace Libs::Graphics + +#endif // EMULATOR_SRC_GRAPHICS_SHADER_RECTLISTSHADER_H_ diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index 8e26d64..67804b0 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -45,6 +45,42 @@ namespace Libs::Graphics { +namespace { + +constexpr uint32_t PsInputOffsetMask = 0x0000001fu; +constexpr uint32_t PsInputFlatShade = 0x00000400u; + +} // namespace + +uint32_t ShaderPixelParameterMappedLocation(const ShaderPixelInputInfo& info, uint32_t input) { + return input < info.input_num ? info.interpolator_settings[input] & PsInputOffsetMask : input; +} + +uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info, + std::span active_inputs, uint32_t input) { + std::array used_locations {}; + for (const auto active_input: active_inputs) { + auto location = ShaderPixelParameterMappedLocation(info, active_input); + if (location < used_locations.size() && used_locations[location]) { + location = active_input; + while (location < used_locations.size() && used_locations[location]) { + location++; + } + EXIT_NOT_IMPLEMENTED(location >= used_locations.size()); + } + + if (active_input == input) { + return location; + } + used_locations[location] = true; + } + return ShaderPixelParameterMappedLocation(info, input); +} + +bool ShaderPixelParameterIsFlat(const ShaderPixelInputInfo& info, uint32_t input) { + return input < info.input_num && (info.interpolator_settings[input] & PsInputFlatShade) != 0; +} + struct ShaderBinaryInfo { uint8_t signature[7]; uint8_t version; @@ -1606,6 +1642,7 @@ ShaderId ShaderGetIdPS(const HW::PixelShaderInfo& regs, const ShaderPixelInputIn ret.ids.push_back(static_cast(input_info.ps_pos_z)); ret.ids.push_back(static_cast(input_info.ps_pos_w)); ret.ids.push_back(static_cast(input_info.ps_front_face)); + ret.ids.push_back(static_cast(input_info.ps_no_perspective)); ret.ids.push_back(static_cast(input_info.ps_pixel_kill_enable)); ret.ids.push_back(static_cast(input_info.ps_sample_mask_export_enable)); ret.ids.push_back(static_cast(input_info.ps_early_z)); diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index 5ba6da0..7bcd976 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -122,6 +122,11 @@ struct ShaderPixelInputInfo { bool HasPositionInput() const { return ps_pos_x || ps_pos_y || ps_pos_z || ps_pos_w; } }; +uint32_t ShaderPixelParameterMappedLocation(const ShaderPixelInputInfo& info, uint32_t input); +uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info, + std::span active_inputs, uint32_t input); +bool ShaderPixelParameterIsFlat(const ShaderPixelInputInfo& info, uint32_t input); + struct ShaderSharp { uint16_t offset_dw : 15; uint16_t size : 1; diff --git a/src/launcher/forms/configuration_edit_dialog.ui b/src/launcher/forms/configuration_edit_dialog.ui index 7b3d31b..d9a4074 100644 --- a/src/launcher/forms/configuration_edit_dialog.ui +++ b/src/launcher/forms/configuration_edit_dialog.ui @@ -78,19 +78,6 @@ - - - - Use the NGG 4-vertex path for rect-list DrawIndexAuto primitive 7 - - - Use NGG rect-list draw - - - true - - - diff --git a/src/launcher/include/configuration.h b/src/launcher/include/configuration.h index 17e2f67..2321498 100644 --- a/src/launcher/include/configuration.h +++ b/src/launcher/include/configuration.h @@ -94,7 +94,6 @@ public: QString printf_output_file = "_kyty.txt"; ProfilerDirection profiler_direction = ProfilerDirection::None; bool renderdoc_enabled = false; - bool ngg_rectlist_draw_enabled = true; QString elf = QStringLiteral("eboot.bin"); @@ -112,7 +111,6 @@ public: printf_output_file = other.printf_output_file; profiler_direction = other.profiler_direction; renderdoc_enabled = other.renderdoc_enabled; - ngg_rectlist_draw_enabled = other.ngg_rectlist_draw_enabled; } void CopyFrom(const Configuration& other) { @@ -147,7 +145,6 @@ public: KYTY_CFG_SET(printf_output_file); KYTY_CFG_SET(profiler_direction); KYTY_CFG_SET(renderdoc_enabled); - KYTY_CFG_SET(ngg_rectlist_draw_enabled); KYTY_CFG_SET(elf); } @@ -169,8 +166,6 @@ public: KYTY_CFG_GET(printf_output_file); KYTY_CFG_GET(profiler_direction); KYTY_CFG_GET(renderdoc_enabled); - ngg_rectlist_draw_enabled = - s->value("ngg_rectlist_draw_enabled", ngg_rectlist_draw_enabled).toBool(); elf = s->value("elf", elf).toString(); } }; diff --git a/src/launcher/src/configurationEditDialog.cpp b/src/launcher/src/configurationEditDialog.cpp index 13141ee..6aea1ae 100644 --- a/src/launcher/src/configurationEditDialog.cpp +++ b/src/launcher/src/configurationEditDialog.cpp @@ -124,7 +124,6 @@ void ConfigurationEditDialog::Init(const Configuration& info) { m_ui->checkBox_shader_validation->setChecked(info.shader_validation_enabled); m_ui->checkBox_vulkan_validation->setChecked(info.vulkan_validation_enabled); m_ui->checkBox_renderdoc_capture->setChecked(info.renderdoc_enabled); - m_ui->checkBox_ngg_rectlist_draw->setChecked(info.ngg_rectlist_draw_enabled); ListInit(m_ui->comboBox_shader_optimization_type, info.shader_optimization_type); ListInit(m_ui->comboBox_shader_log_direction, info.shader_log_direction); m_ui->lineEdit_shader_log_folder->setText(info.shader_log_folder); @@ -245,7 +244,6 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) { info.vulkan_validation_enabled = ui.checkBox_vulkan_validation->isChecked(); info.shader_validation_enabled = ui.checkBox_shader_validation->isChecked(); info.renderdoc_enabled = ui.checkBox_renderdoc_capture->isChecked(); - info.ngg_rectlist_draw_enabled = ui.checkBox_ngg_rectlist_draw->isChecked(); info.shader_optimization_type = TextToEnum( ui.comboBox_shader_optimization_type->currentText()); info.shader_log_direction = TextToEnum( diff --git a/src/launcher/src/mainDialog.cpp b/src/launcher/src/mainDialog.cpp index 63502af..fa3c9b2 100644 --- a/src/launcher/src/mainDialog.cpp +++ b/src/launcher/src/mainDialog.cpp @@ -216,7 +216,6 @@ static QStringList CreateEmulatorArgs(const Configuration& info) { args << "--printf-output-file" << info.printf_output_file; args << "--profiler-direction" << EnumToText(info.profiler_direction); args << "--spirv-debug-printf" << "false"; - args << "--ngg-rectlist-draw" << BoolArg(info.ngg_rectlist_draw_enabled); if (info.renderdoc_enabled) { args << "--rd"; } diff --git a/src/main.cpp b/src/main.cpp index 94fb17f..124d77b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,8 +59,6 @@ static void PrintUsage() { ::printf(" --printf-output-file Guest printf output file.\n"); ::printf(" --profiler-direction None or Network.\n"); ::printf(" --spirv-debug-printf Enable SPIR-V debug printf.\n"); - ::printf(" --ngg-rectlist-draw Draw rect-list auto draws using the NGG " - "4-vertex path.\n"); ::printf( " --readback-linear-images Read back writable linear images on submit.\n"); ::printf(" --rd Enable RenderDoc capture.\n"); @@ -220,11 +218,6 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he ::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str()); return false; } - } else if (arg == "--ngg-rectlist-draw") { - if (!ParseBool(value, options.config.ngg_rectlist_draw_enabled)) { - ::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str()); - return false; - } } else if (arg == "--readback-linear-images") { if (!ParseBool(value, options.config.readback_linear_images)) { ::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str()); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index ec7246c..f56f9a7 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -39,6 +39,7 @@ #include "graphics/shader/recompiler/emitter/SpirvBuilder.h" #include "graphics/shader/recompiler/emitter/SpirvEmitter.h" #include "graphics/shader/recompiler/ir/BindingLayout.h" +#include "graphics/shader/rectListShader.h" #include "graphics/shader/shader.h" #include "kernel/memory.h" #include "spirv-tools/libspirv.hpp" @@ -771,6 +772,91 @@ void ValidateSpirv(const char* shader_name, const std::vector& spirv) { } } +size_t CountText(const std::string& text, const std::string& needle) { + size_t count = 0; + for (size_t offset = 0; (offset = text.find(needle, offset)) != std::string::npos; + offset += needle.size()) { + count++; + } + return count; +} + +void CheckRectListShaders() { + constexpr const char* name = "RectListShaders"; + + auto program = std::make_shared(); + program->info.inputs.push_back( + {ShaderRecompiler::IR::StageInputKind::Parameter, 0, 4, "in_param_0"}); + program->info.inputs.push_back( + {ShaderRecompiler::IR::StageInputKind::Parameter, 1, 4, "in_param_1"}); + + ShaderVertexInputInfo vertex {}; + vertex.param_export_mask = 1u; + ShaderPixelInputInfo pixel {}; + pixel.input_num = 2; + pixel.interpolator_settings[0] = 0x400u; + pixel.interpolator_settings[1] = 0; + pixel.stage.program = program; + HW::PixelShaderInfo ps_regs {}; + const auto perspective_id = ShaderGetIdPS(ps_regs, pixel, false); + pixel.ps_no_perspective = true; + const auto no_perspective_id = ShaderGetIdPS(ps_regs, pixel, false); + pixel.ps_no_perspective = false; + Require(name, "pipeline identity", perspective_id != no_perspective_id, + "pixel interpolation mode must participate in the shader and pipeline key"); + + const std::array active_inputs = {0, 1}; + Require(name, "duplicate mapping", + ShaderPixelParameterLocation(pixel, active_inputs, 0) == 0 && + ShaderPixelParameterLocation(pixel, active_inputs, 1) == 1, + "duplicate pixel mappings must receive distinct effective output locations"); + + const auto shaders = BuildRectListShaders(vertex, &pixel); + Require(name, "SPIR-V version", + shaders.control.size() > 1 && shaders.control[1] == 0x00010500u && + shaders.evaluation.size() > 1 && shaders.evaluation[1] == 0x00010500u, + "shadPS4-compatible vector selection requires SPIR-V 1.5"); + ValidateSpirv(name, shaders.control); + ValidateSpirv(name, shaders.evaluation); + + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2); + std::string control_text; + std::string evaluation_text; + Require(name, "control disassembly", tools.Disassemble(shaders.control, &control_text), + "failed to disassemble rectangle-list tessellation control shader"); + Require(name, "evaluation disassembly", + tools.Disassemble(shaders.evaluation, &evaluation_text), + "failed to disassemble rectangle-list tessellation evaluation shader"); + Require(name, "control execution mode", + control_text.find("TessellationControl") != std::string::npos && + control_text.find("OutputVertices 4") != std::string::npos, + "rectangle-list control shader must produce four control points"); + Require(name, "evaluation execution modes", + evaluation_text.find("TessellationEvaluation") != std::string::npos && + evaluation_text.find("Quads") != std::string::npos && + evaluation_text.find("SpacingEqual") != std::string::npos && + evaluation_text.find("VertexOrderCw") != std::string::npos, + "rectangle-list evaluation shader has the wrong patch modes"); + Require(name, "no geometry stage", + control_text.find("Geometry") == std::string::npos && + evaluation_text.find("Geometry") == std::string::npos, + "rectangle-list expansion must not use geometry shaders"); + Require(name, "flat broadcast", + CountText(control_text, "OpVectorTimesScalar") == 6 && + CountText(control_text, "OpSelect %v4float") == 2, + "flat parameters must use guest vertex zero instead of reconstructed values"); + Require(name, "remapped interface", + CountText(control_text, " Location 0") == 2 && + CountText(control_text, " Location 1") == 1 && + CountText(evaluation_text, " Location 0") == 2 && + CountText(evaluation_text, " Location 1") == 2, + "duplicate pixel mappings must share one vertex input and keep distinct patch outputs"); + + const auto position_only = BuildRectListShaders(vertex, nullptr); + ValidateSpirv(name, position_only.control); + ValidateSpirv(name, position_only.evaluation); +} + void CheckSpirvText(const TestCase& test, const std::vector& spirv) { if (test.required_spirv.empty() && test.forbidden_spirv.empty()) { return; @@ -17502,6 +17588,7 @@ int main(int argc, char** argv) { CheckPm4CeCompletion(vulkan.RuntimeRenderer()); CheckEmbeddedFetchVertexOffset(); CheckEmbeddedFetchLaneSpill(); + CheckRectListShaders(); CheckPs5GameExampleImageClearRuntimeShape(); vulkan.CheckSchedulerTimeline(); vulkan.CheckGpuMappedRangeLifecycle();