diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 8184f3dca4..e341d55be9 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1777,6 +1777,8 @@ target_include_directories(agc_driver_color1555_target_tests PRIVATE $) +target_include_directories(agc_driver_depth_range_remap_draw_tests PRIVATE $) + foreach(stage IN ITEMS vert frag) set(agcProvokingShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/tests/shaders/ProvokingVertex.${stage}) set(agcProvokingShaderSpv ${agcTestShaderGeneratedRoot}/ProvokingVertex_${stage}.spv) @@ -2834,6 +2836,20 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_depth_range_remap_tests EXCLUDE_FROM_ALL tests/DepthRangeRemap.cpp) +target_link_libraries(agc_depth_range_remap_tests PRIVATE shader_recompiler_agc_driver libc) +set_target_properties(agc_depth_range_remap_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_depth_range_remap_tests) +if(BUILD_TESTING) + set_target_properties(agc_depth_range_remap_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_depth_range_remap COMMAND agc_depth_range_remap_tests) + set_tests_properties(agc_depth_range_remap PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1") + if(WIN32) + set_tests_properties(agc_depth_range_remap PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_position_invariance_tests EXCLUDE_FROM_ALL tests/PositionInvariance.cpp) target_link_libraries(agc_position_invariance_tests PRIVATE shader_recompiler_agc_driver libc) set_target_properties(agc_position_invariance_tests PROPERTIES CXX_EXTENSIONS OFF) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index e09e99c489..c65e4ef54c 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -160,8 +160,8 @@ private: void reportDrawRelocation(); static bool sameVertexInfo(const ShaderRecompiler::ShaderVertexStageInfo& a, const ShaderRecompiler::ShaderVertexStageInfo& b); static bool sameDecode(const DrawDecode& a, const DrawDecode& b); - std::shared_ptr decodeDraw(const QueueState& queue, const Submission& submission); - void resolveDrawDecode(const QueueState& queue, const Submission& submission, std::shared_ptr& decode, bool registerKey, std::uint64_t drawKey, bool profile); + std::shared_ptr decodeDraw(const QueueState& queue, const Submission& submission, const Graphics::Context& context); + void resolveDrawDecode(const QueueState& queue, const Submission& submission, const Graphics::Context& context, std::shared_ptr& decode, bool registerKey, std::uint64_t drawKey, bool profile); void lookupDraw(const Submission& submission, const std::shared_ptr& localDevice, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector& programs, const std::vector& roles, const std::vector>& vertexInfos, bool useDrawEntries, bool registerKey, bool profile, std::uint64_t& drawKey, std::shared_ptr& entry, std::vector>& matched, std::vector>& matchedRegions, bool& drawHit, bool& verifyHit, DrawPhaseTiming& phaseTiming, std::array& phaseMs, const std::shared_ptr& dataEntry, DrawDataCandidates& dataCandidates, std::vector& stageData); std::shared_ptr materializeDrawStage(std::size_t i, std::uint32_t pushOffset, const QueueState& queue, const Submission& submission, const std::vector& programs, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector>& vertexInfos, std::vector& memory, const std::vector& linked, const Pm4::DrawParameters& drawParameters, const std::shared_ptr& localDevice, ShaderMemory& shaderMemory, std::vector& stageCaptures, std::vector& recompiled, bool drawHit, const std::vector>& matched, const std::vector>& matchedRegions, bool profile, std::uint64_t dumpTarget, std::uint64_t dumpSlot1, std::uint64_t& captures, DrawPhaseTiming& phaseTiming, std::array& phaseMs, std::string& rejected); static void recordStageDataPositions(DispatchVariant& variant, const ShaderRecompiler::ResourceCapture& capture, const std::vector& decodeReads); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp index cc6d4a0e28..8fcbd334dd 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp @@ -331,9 +331,7 @@ public: void EnqueueDraw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots = {}); bool PrebuildGraphicsLibraries(const Graphics::ShaderStages& stages, std::span shaders); -private: - VulkanDevice(const PresentationWindow* window, bool compileOnly); - friend struct VulkanDeviceTestAccess; +public: // The device's Graphics::Context: a copy of the one built at setup (its instance functions // resolved then, its function table filled then), or with APS5_NO_CONTEXT_CACHE=1 built anew. Graphics::Context graphicsContext() const; @@ -341,6 +339,10 @@ private: std::uint32_t computeSubgroupSize(const ShaderRecompiler::RecompileResult& shader) const; VkPipelineShaderStageCreateFlags computeStageFlags(const ShaderRecompiler::RecompileResult& shader) const; std::shared_ptr createComputePipeline(const Graphics::Context& context, const ShaderRecompiler::RecompileResult& shader, VkDescriptorSetLayout setLayout, std::span layoutKey, VkShaderStageFlags pushStages, std::uint64_t programAddress); + +private: + VulkanDevice(const PresentationWindow* window, bool compileOnly); + friend struct VulkanDeviceTestAccess; ShaderRecompiler::SpirvTarget buildTarget() const; Graphics::Context buildContext() const; // Body of Dispatch and DispatchIndirect: `arguments` 0 dispatches x, y, z groups. diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp index 89d2eaf6df..7d11469870 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp @@ -8,6 +8,7 @@ #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "Optimization/ResourceProgram.hpp" #include +#include #include #include #include @@ -162,7 +163,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe if (program.merged) InitializeMerged(queue, program, program.mergedPointer, program.mergedPointerRequired, false); } if (verifyDrawDataHits()) { - const auto full = decodeDraw(queue, submission); + const auto full = decodeDraw(queue, submission, localDevice->graphicsContext()); if (!sameDecode(*refreshed, *full)) { std::size_t differing = 0; while (differing < full->programs.size() && differing < refreshed->programs.size() && full->programs[differing].userData == refreshed->programs[differing].userData) ++differing; @@ -173,7 +174,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe } decode = std::move(refreshed); } - resolveDrawDecode(queue, submission, decode, registerKey, drawKey, profile); + resolveDrawDecode(queue, submission, localDevice->graphicsContext(), decode, registerKey, drawKey, profile); const auto& graphics = decode->state; auto pixel = decode->pixel; const bool writesPrograms = graphics.stages.mesh || drawParameters.indirect || !userPatches.empty(); @@ -246,6 +247,9 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe decodeReads[i].clear(); vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, &decodeReads[i], false, 8u - program.firstUserSgpr); if (vertexInfos[i]) vertexInfos[i]->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, readRegister(queue.context, 0x207), program.tailContinuation.get()) | graphics.layerExports; + if (vertexInfos[i]) { + vertexInfos[i]->depthRemapMode = graphics.depthRemap.mode; + } }; if (!registerKey) { for (std::size_t i = 0; i < programs.size(); ++i) decodeVertexInfo(i); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp index bb78ea9ba8..7662c966f0 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp @@ -23,7 +23,7 @@ std::shared_ptr Driver::materializeDraw program.binary, {waveSize, program.firstUserSgpr, program.userData, std::nullopt, program.binary.stage == Stage::Fragment ? std::optional(pixel) : std::nullopt, vertexInfos[i], memory, RegisteredFloatMode(*program.snapshot)}, localDevice->Target(), - {0, 0, pushOffset, (graphics.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : graphics.stages.tessellation && pushOffset < Graphics::PipelinePushSlotBytes ? ShaderRecompiler::TessellationLevelPushOffsetBytes : Graphics::PipelinePushSlotBytes) - pushOffset % Graphics::PipelinePushSlotBytes}, + {0, 0, pushOffset, (graphics.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : graphics.stages.tessellation && pushOffset < Graphics::PipelinePushSlotBytes ? ShaderRecompiler::TessellationLevelPushOffsetBytes : graphics.depthRemap.mode != 0u ? ShaderRecompiler::DepthRemapPushOffsetBytes : Graphics::PipelinePushSlotBytes) - pushOffset % Graphics::PipelinePushSlotBytes}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, graphics.stages.mesh, graphics.stages.tessellation, {drawParameters.indexAddress, drawParameters.indexCount, drawParameters.indexSize, drawParameters.instanceCount}} }; const auto waitedBefore = traceCapSync() || profile ? Graphics::Recorder::ThreadWaitedMs() : 0.0; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp index 8317f9f4ac..8e6b809a5d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp @@ -122,9 +122,10 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const } } -std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Submission& submission) { +std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Submission& submission, const Graphics::Context& context) { auto product = std::make_shared(); product->state = Graphics::DecodeState(queue, VertexWorkgroupRequired(queue, *submission.shaders)); + Graphics::ApplyDepthRemap(context, product->state); DecodeGraphicsPrograms(*product, queue, *submission.shaders, false, true); product->pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(product->state), Graphics::PixelProgramSkipped(queue)); product->pixel.targetExportPacking = Graphics::ExportPackings(product->state); @@ -146,11 +147,11 @@ std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Su return product; } -void Driver::resolveDrawDecode(const QueueState& queue, const Submission& submission, std::shared_ptr& decode, bool registerKey, std::uint64_t drawKey, bool profile) { +void Driver::resolveDrawDecode(const QueueState& queue, const Submission& submission, const Graphics::Context& context, std::shared_ptr& decode, bool registerKey, std::uint64_t drawKey, bool profile) { if (decode == nullptr || verifyDrawRecipe()) { std::vector readLog; const Graphics::RegisterReadLogScope logScope(verifyDrawRecipe() && registerKey ? &readLog : nullptr); - auto fresh = decodeDraw(queue, submission); + auto fresh = decodeDraw(queue, submission, context); if (verifyDrawRecipe() && registerKey) { std::uint64_t facadeMismatches = 0; for (const auto read : readLog) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp index 9ef82e2572..fb9768d170 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp @@ -214,6 +214,7 @@ void Driver::traceDrawKeyLookup(const std::array& components, bool Driver::sameVertexInfo(const ShaderRecompiler::ShaderVertexStageInfo& a, const ShaderRecompiler::ShaderVertexStageInfo& b) { if (a.resourcesNum != b.resourcesNum || a.fetchAttribReg != b.fetchAttribReg || a.fetchBufferReg != b.fetchBufferReg || a.fetchEmbedded != b.fetchEmbedded || a.paClVsOutCntl != b.paClVsOutCntl) return false; + if (a.depthRemapMode != b.depthRemapMode) return false; for (std::uint32_t i = 0; i < a.resourcesNum && i < a.resources.size(); ++i) { if (a.resources[i].fields != b.resources[i].fields) return false; const auto& x = a.resourcesDst[i]; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index c575c86d88..ad0dfa3e8d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -1331,7 +1331,7 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod std::vector prepared; std::uint32_t pushOffset = 0; const bool fixedSlots = target.fixedPushSlots; - const auto capacity = decoded.state.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : decoded.state.stages.tessellation ? ShaderRecompiler::TessellationLevelPushOffsetBytes : Graphics::PipelinePushSlotBytes; + const auto capacity = decoded.state.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : decoded.state.stages.tessellation ? ShaderRecompiler::TessellationLevelPushOffsetBytes : decoded.state.depthRemap.mode != 0u ? ShaderRecompiler::DepthRemapPushOffsetBytes : Graphics::PipelinePushSlotBytes; for (std::size_t index = 0; index < decoded.programs.size(); ++index) { if (decoded.roles[index] == ShaderRecompiler::ProgramRole::GeometryBack) continue; const auto& program = decoded.programs[index]; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index b4d9106aac..4db2ca6287 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -3518,6 +3518,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::buildTarget() const { target.float64DenormPreserve = state->float64DenormPreserve; target.float16DenormPreserve = state->float16DenormPreserve; target.spannedWaves = state->spannedWaves; + target.depthRangeRemap = !state->depthRangeUnrestricted; target.fixedPushSlots = state->graphicsPipelineLibrary; target.registrationModules = state->graphicsPipelineLibrary; target.robustBufferAccess2 = state->robustBufferAccess2; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp index 76e24b0e23..2ddac45e80 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp @@ -73,7 +73,7 @@ inline VkShaderStageFlags PushConstantStages(std::span sha VkShaderStageFlags result = 0; for (const auto& shader : shaders) { Require(shader.program != nullptr, "missing compiled shader"); - if (!shader.program->pushConstants.empty() || shader.stage == ShaderRecompiler::ShaderStage::Mesh || shader.stage == ShaderRecompiler::ShaderStage::TessellationControl) result |= VulkanStage(shader.stage); + if (!shader.program->pushConstants.empty() || shader.program->readsDepthRemapPush || shader.stage == ShaderRecompiler::ShaderStage::Mesh || shader.stage == ShaderRecompiler::ShaderStage::TessellationControl) result |= VulkanStage(shader.stage); } return result; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index 24af65b65e..a2fc0e5b04 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -125,6 +125,16 @@ struct State { VkViewport viewport; bool negativeOneToOne; bool depthClamp = false; + float depthScale = 1; + float depthOffset = 0; + float depthClampMin = 0; + float depthClampMax = 1; + struct DepthRemap { + std::uint32_t mode = 0; + float scale = 1; + float offset = 0; + bool operator==(const DepthRemap&) const = default; + } depthRemap; bool alphaToCoverage = false; bool ditheredAlphaToCoverage = false; bool perSampleShading = false; @@ -150,6 +160,7 @@ inline constexpr std::uint32_t PositionExportLayoutBits = 0xffffu | (1u << 21u) ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup = false); State DecodeState(const QueueState& queue, bool vertexWorkgroup = false); +void ApplyDepthRemap(const Context& context, State& state); std::array SampleLocations(const State& state); std::array PixelSampleLocations(const State& state, const ShaderRecompiler::ShaderPixelStageInfo& pixel); VkExtent2D SampleLocationGrid(const State& state); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 472cab744a..6aed5b4efe 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1632,6 +1632,15 @@ struct RecordedDraw { std::vector>> stencilReplay; }; +// A remapped viewport depth range reads its scale and offset from the draw's push block (the vertex shader's +// RemapPositionDepth), so draws that differ only in them share one pipeline; the draw's own state supplies them. +void patchDepthRemapPush(std::array& block, const State& state) { + if (state.depthRemap.mode == 0u) return; + static_assert(ShaderRecompiler::DepthRemapPushOffsetBytes + ShaderRecompiler::DepthRemapPushBytes <= PipelinePushSlotBytes); + const std::array words{state.depthRemap.scale, state.depthRemap.offset}; + std::memcpy(block.data() + ShaderRecompiler::DepthRemapPushOffsetBytes, words.data(), sizeof(words)); +} + void recordStencilReplay(const Context& context, VkCommandBuffer commands, const State& state, const Pm4::DrawParameters& draw, const DrawInputs& inputs, std::span>> replay, std::span shaders, const ShaderResources& resources, const std::array* bytes = nullptr, VkShaderStageFlags stages = 0) { State selected = state; auto block = bytes != nullptr ? *bytes : AssemblePushConstants(shaders); @@ -1639,6 +1648,7 @@ void recordStencilReplay(const Context& context, VkCommandBuffer commands, const resources.PatchPushConstants(block); stages = PushConstantStages(shaders); } + patchDepthRemapPush(block, state); const auto fragment = std::ranges::find_if(shaders, [](const CompiledShader& shader) { return shader.stage == ShaderRecompiler::ShaderStage::Fragment && !shader.program->stencilReferences.empty(); }); Require(fragment != shaders.end(), "stencil replay fragment stage is missing"); const auto referenceOffset = fragment->pushConstantOffset + fragment->program->stencilReferenceDword * sizeof(std::uint32_t); @@ -1676,6 +1686,7 @@ void pushDrawConstants(const Pipeline& pipeline, VkCommandBuffer commands, const resources.PatchPushConstants(block); stages = PushConstantStages(shaders); } + patchDepthRemapPush(block, state); if (state.stages.tessellation) { for (const auto& shader : shaders) Require(shader.program->pushConstants.empty() || shader.pushConstantOffset >= PipelinePushSlotBytes || shader.pushConstantOffset + shader.program->pushConstants.size() <= ShaderRecompiler::TessellationLevelPushOffsetBytes, "a stage's push constants overlap the tessellation levels"); const std::array levels{state.stages.minTessellationLevel, state.stages.maxTessellationLevel}; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp index 77d4566132..5657d3d0bb 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp @@ -35,7 +35,7 @@ static_assert(sizeof(VkStencilOpState) == 28, "VkStencilOpState changed: update static_assert(sizeof(VkVertexInputBindingDescription) == 12, "VkVertexInputBindingDescription changed: update the recipe codec"); static_assert(sizeof(VkVertexInputAttributeDescription) == 16, "VkVertexInputAttributeDescription changed: update the recipe codec"); #if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__) -static_assert(sizeof(State) == 752, "State changed: update CanonicalPipelineState and the recipe codec"); +static_assert(sizeof(State) == 784, "State changed: update CanonicalPipelineState and the recipe codec"); static_assert(sizeof(ShaderStages) == 112, "ShaderStages changed: update the recipe codec"); static_assert(sizeof(ShaderRecompiler::MeshConfiguration) == 40, "MeshConfiguration changed: update the recipe codec"); static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "TessellationConfiguration changed: update the recipe codec"); @@ -44,7 +44,7 @@ static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "Tessel constexpr std::uint32_t FileMagic = 0x4c4a5041u; // Bumped when the recipe codec, CanonicalPipelineState or the store key change in a way the // layout guards above do not catch (a field renumbered or reinterpreted at the same size). -constexpr std::uint32_t FileFormat = 3u; +constexpr std::uint32_t FileFormat = 4u; constexpr std::uint32_t ModuleRecord = 1u; constexpr std::uint32_t GraphicsRecord = 2u; constexpr std::uint32_t ComputeRecord = 3u; @@ -216,6 +216,7 @@ void encodeState(Writer& writer, const State& state) { writer.Bool(state.sampleLocationGrid); writer.Bool(state.perSampleShading); for (const auto mask : state.quadSampleMask) writer.Raw(mask); + writer.U32(state.depthRemap.mode); } void decodeState(Reader& reader, State& state) { @@ -274,6 +275,7 @@ void decodeState(Reader& reader, State& state) { state.sampleLocationGrid = reader.Bool(); state.perSampleShading = reader.Bool(); for (auto& mask : state.quadSampleMask) mask = reader.Raw(); + state.depthRemap.mode = reader.U32(); } void encodeRecipe(Writer& writer, const GraphicsPipelineRecipe& recipe) { @@ -1037,6 +1039,7 @@ State CanonicalPipelineState(const State& state) { canonical.sampleLocationGrid = state.sampleLocationGrid; canonical.perSampleShading = state.perSampleShading; canonical.quadSampleMask = state.quadSampleMask; + canonical.depthRemap.mode = state.depthRemap.mode; return canonical; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp index 76085491b7..98f2cbbc8c 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp @@ -541,7 +541,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi } } } else if (variable.storage == spv::StorageClassPushConstant) { - Require(!push && (!shader.pushConstants.empty() || mesh || (control && !rectListControl)) && (type[0] & 0xffffu) == spv::OpTypeStruct, "invalid push constant interface"); + Require(!push && (!shader.pushConstants.empty() || shader.readsDepthRemapPush || mesh || (control && !rectListControl)) && (type[0] & 0xffffu) == spv::OpTypeStruct, "invalid push constant interface"); push = true; Require(type.size() == 3 && module.decorations[typeId].block, "push constant variable must be a Block struct with exactly one member"); const auto offset = module.offsets.find({typeId, 0}); @@ -613,7 +613,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi Require(shader.vertexAttributes.empty(), "vertex attribute metadata is invalid for this stage"); } Require(descriptors.size() == shader.bindings.size(), "recompiler binding metadata contains undeclared resources"); - Require(push == (!shader.pushConstants.empty() || mesh || (control && !rectListControl && !barycentricPatches)), "recompiler push constant metadata disagrees with SPIR-V"); + Require(push == (!shader.pushConstants.empty() || shader.readsDepthRemapPush || mesh || (control && !rectListControl && !barycentricPatches)), "recompiler push constant metadata disagrees with SPIR-V"); for (const auto id : module.interface) Require(module.variables.contains(id), "entry point interface contains an unknown variable"); if (mesh) Require(module.primitiveIndices == state.stages.mesh->outputPrimitiveSize, "mesh shader does not export primitive indices"); if (stage == Stage::Vertex || mesh || evaluation || geometry) Require(module.position, "vertex shader does not export position"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 36293b21a8..a8fbe900c2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -1061,6 +1061,32 @@ ReversedBlend TranslateReversedBlend(std::uint32_t blendControl, std::uint8_t co return {blend, routing->code}; } +void ApplyDepthRemap(const Context& context, State& state) { + const auto& viewport = state.viewport; + const bool inRange = viewport.minDepth >= 0 && viewport.minDepth <= 1 && viewport.maxDepth >= 0 && viewport.maxDepth <= 1; + if (context.depthRangeUnrestricted || inRange) return; + Require(state.stages.path == ShaderPath::Vertex && !state.stages.mesh && !state.stages.tessellation, "viewport depth outside [0, 1] is remapped on the vertex path only"); + Require(context.depthClamp, "viewport depth outside [0, 1] needs depth clamping to stand in for the console's clamp"); + const float clampMin = std::clamp(state.depthClampMin, 0.0f, 1.0f); + const float clampMax = std::clamp(state.depthClampMax, 0.0f, 1.0f); + const bool floatDepth = state.depth.has_value() && (state.depth->format == VK_FORMAT_D32_SFLOAT || state.depth->format == VK_FORMAT_D32_SFLOAT_S8_UINT); + if ((clampMin != state.depthClampMin || clampMax != state.depthClampMax) && floatDepth && (state.depthWrite || state.depthTest)) { + static bool reported = false; + if (!reported) { + reported = true; + std::fprintf(stderr, "[gpu] viewport depth clamp bounds [%g, %g] on a float depth target are clipped to [0, 1]: the device has no VK_EXT_depth_range_unrestricted\n", state.depthClampMin, state.depthClampMax); + } + } + const float range = clampMax - clampMin; + const float scale = range > 0.0f ? state.depthScale / range : state.depthScale; + const float offset = range > 0.0f ? (state.depthOffset - clampMin) / range : state.depthOffset; + state.depthRemap = {state.negativeOneToOne ? 2u : 1u, scale, offset}; + state.viewport.minDepth = clampMin; + state.viewport.maxDepth = clampMax; + state.negativeOneToOne = false; + state.depthClamp = true; +} + State DecodeState(const QueueState& queue, bool vertexWorkgroup) { if (auto reason = DepthMaintenanceRejection(queue); !reason.empty()) throw std::runtime_error(reason); const auto& cx = queue.context; @@ -1295,6 +1321,10 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { throw std::runtime_error(message.str()); } Require(readFloat(cx, 0xb4) <= readFloat(cx, 0xb5), "inverted viewport depth clamp bounds"); + result.depthScale = zs; + result.depthOffset = zo; + result.depthClampMin = readFloat(cx, 0xb4); + result.depthClampMax = readFloat(cx, 0xb5); result.viewport = collapsed ? VkViewport{xo, yo, 1, 1, minDepth, maxDepth} : VkViewport{xo - xs, yo - ys, 2 * xs, 2 * ys, minDepth, maxDepth}; if (const auto renderControl = find(cx, 0x000); result.depth && renderControl != cx.end() && (renderControl->second & 1u) != 0) { result.viewport.minDepth = result.depth->clearDepth; diff --git a/core/libs/prx/libSceAgcDriver/tests/DepthRangeRemap.cpp b/core/libs/prx/libSceAgcDriver/tests/DepthRangeRemap.cpp new file mode 100644 index 0000000000..8452a2ae1a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/DepthRangeRemap.cpp @@ -0,0 +1,166 @@ +#include "Recompiler.hpp" +#include "PipelineSpecialization.hpp" +#include "Optimization/ResourceProgram.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +struct ModuleFacts { + std::map specializationDefaults; + std::uint32_t clipDistanceLength = 0; + bool clipDistanceBuiltIn = false; + std::size_t multiplies = 0; + bool pushConstantVariable = false; + std::set pushDwordLoads; +}; + +ModuleFacts inspect(std::span words) { + ModuleFacts facts; + std::map specIds; + std::map specValues; + std::map constants; + std::map arrayLengths; + std::map pointerTargets; + std::map variableTypes; + std::set clipVariables; + std::set pushVariables; + std::vector pushIndexIds; + for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) { + const auto op = static_cast(words[at] & 0xffffu); + const auto count = words[at] >> 16u; + if (op == spv::OpDecorate && count == 4 && words[at + 2] == spv::DecorationSpecId) specIds[words[at + 1]] = words[at + 3]; + if (op == spv::OpDecorate && count == 4 && words[at + 2] == spv::DecorationBuiltIn && words[at + 3] == spv::BuiltInClipDistance) clipVariables.insert(words[at + 1]); + if (op == spv::OpSpecConstant && count == 4) specValues[words[at + 2]] = words[at + 3]; + if (op == spv::OpConstant && count == 4) constants[words[at + 2]] = words[at + 3]; + if (op == spv::OpTypeArray && count == 4) arrayLengths[words[at + 1]] = words[at + 3]; + if (op == spv::OpTypePointer && count == 4) pointerTargets[words[at + 1]] = words[at + 3]; + if (op == spv::OpVariable && count >= 4) variableTypes[words[at + 2]] = words[at + 1]; + if (op == spv::OpVariable && count >= 4 && words[at + 3] == spv::StorageClassPushConstant) pushVariables.insert(words[at + 2]); + if (op == spv::OpAccessChain && count == 6 && pushVariables.contains(words[at + 3])) pushIndexIds.push_back(words[at + 5]); + if (op == spv::OpFMul) ++facts.multiplies; + } + for (const auto& [id, specId] : specIds) { + if (const auto value = specValues.find(id); value != specValues.end()) facts.specializationDefaults[specId] = value->second; + } + facts.pushConstantVariable = !pushVariables.empty(); + for (const auto id : pushIndexIds) { + if (const auto value = constants.find(id); value != constants.end()) facts.pushDwordLoads.insert(value->second); + } + for (const auto variable : clipVariables) { + facts.clipDistanceBuiltIn = true; + const auto pointer = variableTypes.find(variable); + if (pointer == variableTypes.end()) continue; + const auto array = pointerTargets.find(pointer->second); + if (array == pointerTargets.end()) continue; + const auto length = arrayLengths.find(array->second); + if (length == arrayLengths.end()) continue; + const auto constant = constants.find(length->second); + if (constant != constants.end()) facts.clipDistanceLength = constant->second; + } + return facts; +} + +ModuleFacts inspect(const ShaderRecompiler::RecompileResult& result) { + return inspect(result.spirv.Words()); +} + +constexpr std::array PositionExport{0xf80008cfu, 0u, 0xbf810000u}; + +ShaderRecompiler::RecompileRequest baseRequest(bool remap) { + using namespace ShaderRecompiler; + RecompileRequest request{}; + request.shader = {ShaderStage::Vertex, 0x10000u, PositionExport, 0, {}}; + request.context.waveSize = 64; + ShaderVertexStageInfo vertex{}; + vertex.depthRemapMode = 2u; + request.context.vertex = vertex; + request.target.vulkanVersion = 0x00401000u; + request.target.spirvVersion = 0x00010300u; + request.target.subgroupSize = 64; + static constexpr std::array capabilities{spv::CapabilityShader, spv::CapabilityClipDistance}; + request.target.supportedCapabilities = capabilities; + request.target.depthRangeRemap = remap; + request.layout.pushConstantSizeBytes = ShaderRecompiler::DepthRemapPushOffsetBytes; + request.useCache = false; + return request; +} + +void require(bool condition, const char* what) { + if (!condition) throw std::runtime_error(what); +} + +void preparedTransformTests() { + using namespace ShaderRecompiler; + auto request = baseRequest(true); + request.useCache = true; + const auto handle = PrepareShader(request); + const auto materialize = [&](std::uint32_t mode) { + auto moved = request; + moved.context.vertex->depthRemapMode = mode; + const auto invocation = PreparedShaderInvocation::TryCreate(moved, handle); + require(invocation.has_value(), "the prepared vertex shader did not match a request of its own mode"); + return invocation->Materialize(*invocation->Capture(SrtRuntime{})); + }; + const auto prepared = inspect(GetPreparedArtifact(*handle).spirv.Words()); + require(prepared.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase) && !prepared.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 1u) && !prepared.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 2u), "only the depth remap mode may be a specialization constant of the prepared module"); + const auto first = materialize(2u); + require(materialize(2u)->PipelineVariantId() == first->PipelineVariantId(), "the same depth remap mode did not reuse its materialized vertex shader"); + auto other = request; + other.context.vertex->depthRemapMode = 1u; + require(!PreparedShaderInvocation::TryCreate(other, handle).has_value(), "a prepared vertex shader matched a request of another depth remap mode"); +} + +void pushedTransformTests() { + using namespace ShaderRecompiler; + const auto remapped = Recompile(baseRequest(true)); + const auto plain = Recompile(baseRequest(false)); + const auto facts = inspect(remapped); + require(!facts.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 1u) && !facts.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 2u), "the depth remap scale or offset is a specialization constant"); + require(facts.pushConstantVariable && facts.pushDwordLoads.contains(DepthRemapPushOffsetBytes / 4u) && facts.pushDwordLoads.contains(DepthRemapPushOffsetBytes / 4u + 1u), "the remapped vertex shader does not read the scale and offset from the push block"); + require(!inspect(plain).pushConstantVariable, "a vertex shader without a depth remap gained a push constant block"); + auto crowded = baseRequest(true); + crowded.layout.pushConstantSizeBytes = DepthRemapPushOffsetBytes + 4u; + bool refused = false; + try { + static_cast(Recompile(crowded)); + } catch (const std::runtime_error&) { + refused = true; + } + require(refused, "a remapped vertex shader with push data over the depth remap words was accepted"); +} + +} + +int main() { + using namespace ShaderRecompiler; + try { + const auto remapped = inspect(Recompile(baseRequest(true))); + const auto plain = inspect(Recompile(baseRequest(false))); + require(remapped.clipDistanceBuiltIn && remapped.clipDistanceLength == plain.clipDistanceLength + 2u, "the remap did not add its two clip distances"); + require(remapped.multiplies >= plain.multiplies + 2u, "the remap does not scale the clip z and offset it by w"); + require(!plain.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase) && !remapped.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 3u), "unexpected specialization constants"); + preparedTransformTests(); + pushedTransformTests(); + std::cout << "Depth range remap tests passed\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << "Depth range remap test failed: " << error.what() << '\n'; + for (const bool remap : {true, false}) { + const auto facts = inspect(Recompile(baseRequest(remap))); + std::cerr << " remap=" << remap << ": spec ids"; + for (const auto& [id, value] : facts.specializationDefaults) std::cerr << ' ' << id << '=' << value; + std::cerr << "; clip builtin " << facts.clipDistanceBuiltIn << " length " << facts.clipDistanceLength << "; fmul " << facts.multiplies << '\n'; + } + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index f8ad1dc3b5..5611ce3b9f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -11,6 +11,7 @@ #include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" +#include "ShaderDiskCache.hpp" #include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" #include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp" @@ -3901,6 +3902,57 @@ void quadPixelMaskTests() { expectFailure([&] { AgcDriver::Graphics::ValidateSampleMask(context, varying, storing); }, "differs between the pixels of a quad"); } +void DepthRemapTests() { + auto queue = makeState(); + queue.context[0x204] = 0; + queue.context[0x113] = std::bit_cast(1.0f); + queue.context[0x114] = 0; + AgcDriver::Graphics::Context remapping{}; + remapping.depthClamp = true; + auto state = AgcDriver::Graphics::DecodeState(queue); + Require(state.negativeOneToOne && state.viewport.minDepth == -1 && state.viewport.maxDepth == 1 && state.depthRemap.mode == 0, "a negative-one-to-one depth transform decoded with a remap"); + AgcDriver::Graphics::ApplyDepthRemap(remapping, state); + Require(state.depthRemap.mode == 2 && state.depthRemap.scale == 1.0f && state.depthRemap.offset == 0.0f, "the remap did not take the viewport's depth scale and offset"); + Require(state.viewport.minDepth == 0 && state.viewport.maxDepth == 1 && !state.negativeOneToOne && state.depthClamp, "the remapped viewport does not clamp at the console's bounds in zero-to-one clip space"); + AgcDriver::Graphics::Context unrestricted{}; + unrestricted.depthRangeUnrestricted = true; + auto kept = AgcDriver::Graphics::DecodeState(queue); + AgcDriver::Graphics::ApplyDepthRemap(unrestricted, kept); + Require(kept.depthRemap.mode == 0 && kept.viewport.minDepth == -1 && kept.negativeOneToOne && !kept.depthClamp, "an unrestricted device remapped the depth range"); + AgcDriver::Graphics::Context noClamp{}; + auto refused = AgcDriver::Graphics::DecodeState(queue); + expectFailure([&] { AgcDriver::Graphics::ApplyDepthRemap(noClamp, refused); }, "depth clamping"); + queue.context[0x113] = std::bit_cast(0.5f); + queue.context[0x114] = std::bit_cast(0.5f); + auto inRange = AgcDriver::Graphics::DecodeState(queue); + AgcDriver::Graphics::ApplyDepthRemap(remapping, inRange); + Require(inRange.depthRemap.mode == 0 && inRange.negativeOneToOne && !inRange.depthClamp, "an in-range depth transform was remapped"); + queue.context[0x204] = 0x80000u; + queue.context[0x113] = std::bit_cast(2.0f); + queue.context[0x114] = std::bit_cast(-0.5f); + queue.context[0xb4] = std::bit_cast(0.25f); + queue.context[0xb5] = std::bit_cast(0.75f); + auto bounded = AgcDriver::Graphics::DecodeState(queue); + AgcDriver::Graphics::ApplyDepthRemap(remapping, bounded); + Require(bounded.depthRemap.mode == 1 && bounded.depthRemap.scale == 4.0f && bounded.depthRemap.offset == -1.5f && bounded.viewport.minDepth == 0.25f && bounded.viewport.maxDepth == 0.75f, "a zero-to-one remap did not divide its transform by the clamp range it gives the viewport"); + queue.context[0xb4] = std::bit_cast(-1.0f); + queue.context[0xb5] = std::bit_cast(2.0f); + auto clipped = AgcDriver::Graphics::DecodeState(queue); + AgcDriver::Graphics::ApplyDepthRemap(remapping, clipped); + Require(clipped.depthRemap.mode == 1 && clipped.viewport.minDepth == 0 && clipped.viewport.maxDepth == 1, "clamp bounds beyond [0, 1] were not clipped to the representable depth range"); + Require(clipped.depthRemap.scale == 2.0f && clipped.depthRemap.offset == -0.5f, "clamp bounds clipped to [0, 1] changed the depth transform"); + auto unorm = AgcDriver::Graphics::DecodeState(queue); + unorm.depth = AgcDriver::Graphics::DepthTarget{0x10000, 0, {64, 64}, VK_FORMAT_D16_UNORM, 0.0f, 0}; + unorm.depthTest = unorm.depthWrite = true; + AgcDriver::Graphics::ApplyDepthRemap(remapping, unorm); + Require(unorm.viewport.minDepth == 0 && unorm.viewport.maxDepth == 1, "a normalized depth target refused clipped clamp bounds"); + auto floating = AgcDriver::Graphics::DecodeState(queue); + floating.depth = AgcDriver::Graphics::DepthTarget{0x10000, 0, {64, 64}, VK_FORMAT_D32_SFLOAT, 0.0f, 0}; + floating.depthWrite = true; + AgcDriver::Graphics::ApplyDepthRemap(remapping, floating); + Require(floating.depthRemap.mode == 1 && floating.viewport.minDepth == 0 && floating.viewport.maxDepth == 1 && floating.depthClamp, "a written float depth target refused clipped clamp bounds"); +} + void DepthClipTests() { auto queue = makeState(); const auto direct = AgcDriver::Graphics::DecodeState(queue); @@ -5185,6 +5237,16 @@ void pushConstantTests() { fragment.pushConstants.clear(); shaders[1].pushConstantOffset = 999; Require(AgcDriver::Graphics::PushConstantStages(shaders) == VK_SHADER_STAGE_VERTEX_BIT, "empty push constants contributed a stage"); + { + ShaderRecompiler::RecompileResult reading; + reading.readsDepthRemapPush = true; + std::vector encoded; + ShaderRecompiler::ShaderDiskCache::EncodeResult(reading, encoded); + ShaderRecompiler::RecompileResult replayed; + Require(ShaderRecompiler::ShaderDiskCache::DecodeResult(encoded, replayed), "a result that reads the depth remap push words does not decode"); + const std::array remapped{{{ShaderRecompiler::ShaderStage::Vertex, &replayed, 0}}}; + Require(replayed.pushConstants.empty() && AgcDriver::Graphics::PushConstantStages(remapped) == VK_SHADER_STAGE_VERTEX_BIT, "a journalled vertex shader that reads the depth remap push words lost its push stage"); + } Require(AgcDriver::Graphics::AssemblePushConstants(shaders)[8] == std::byte{0}, "empty push constants were copied"); shaders[1].program = nullptr; expectFailure([&] { AgcDriver::Graphics::AssemblePushConstants(shaders); }, "missing compiled shader"); @@ -9524,6 +9586,7 @@ int main(int argc, char** argv) { hardwareScreenOffsetTests(); srgb8TargetTests(); DepthClipTests(); + DepthRemapTests(); DepthStencilTests(); OneDimensionalColorTests(); htileDepthClearTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index 851ea691d5..f335fb2c69 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -125,6 +125,7 @@ void requireSameResult(const RecompileResult& left, const RecompileResult& right const std::string prefix = std::string(what) + ": "; require(sameBindings(left.bindings, right.bindings), prefix + "bindings differ"); require(left.pushConstants == right.pushConstants, prefix + "push constants differ"); + require(left.readsDepthRemapPush == right.readsDepthRemapPush, prefix + "depth remap push reads differ"); require(left.specialization == right.specialization, prefix + "specialization constants differ"); require(left.workgroupMemoryDwords == right.workgroupMemoryDwords, prefix + "workgroup memory stride differs"); require(left.poisonedSrtReads == right.poisonedSrtReads, prefix + "poisoned SRT read counts differ"); @@ -196,6 +197,7 @@ RecompileResult sampleResult() { result.bindings[1].role = DescriptorRole::GuestBuffers; result.bindings[1].imageShape.reset(); result.pushConstants = {std::byte{1}, std::byte{0xff}, std::byte{0}, std::byte{0x80}, std::byte{7}}; + result.readsDepthRemapPush = true; result.bdaAbiVersion = 3; result.memoryOffsetDword = 7; result.hostSubgroupSize = 32; diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 6f279076fb..d35e99fba5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -4731,7 +4731,7 @@ void verifyPixelRequestSerialization() { minimal.context.pixel->layerExported = true; minimal.context.pixel->ditheredAlphaToCoverage = true; const auto encoded = serializer.Serialize(minimal); - require(requestPrefix(encoded, 8u) == "NVNQQSIAAAA=", "new requests did not use serialization version 34"); + require(requestPrefix(encoded, 8u) == "NVNQQSMAAAA=", "new requests did not use serialization version 35"); const std::array fallbacks{0x40u, 0x1ac4u}; auto withFallbacks = minimal; withFallbacks.context.linkedCallFallbacks = fallbacks; @@ -4849,7 +4849,7 @@ void verifyPixelRequestSerialization() { for (std::size_t bytes = 0; bytes < 45u; ++bytes) { expectFailure([&] { static_cast(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag, primitive class, layer export, dither or front-face flag, quad pixel mask or quad sample mask was accepted"); } - for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQSMAAAA="}) { + for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQSQAAAA="}) { expectFailure([&] { static_cast(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted"); } static constexpr std::string_view version18Request{ @@ -4999,6 +4999,7 @@ void verifyVertexInfoVersion19() { require(std::equal(maskedBytes.begin(), maskedBytes.begin() + static_cast(maskOffset + 1u), sampledBytes.begin()) && sampledBytes[maskOffset + 1u] == 0x1u && sampledBytes[maskOffset + 5u] == 0x2u && sampledBytes[maskOffset + 9u] == 0x4u && sampledBytes[maskOffset + 13u] == 0x8u, "the quad sample masks are not the four words after the quad pixel mask"); auto version30 = sampledBytes; version30.pop_back(); + version30.pop_back(); version30.erase(version30.begin() + static_cast(maskOffset + 1u + sampleMaskBytes + 4u), version30.begin() + static_cast(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes)); version30[4] = 30u; const auto replay30 = serializer.Deserialize(encodeBase64(version30)); @@ -5006,31 +5007,36 @@ void verifyVertexInfoVersion19() { auto spanned = sampledPixel; spanned.target.spannedWaves = true; auto version32 = decodeBase64(serializer.Serialize(spanned)); - require(version32.back() == 1u, "the spanned-waves flag is not the last request byte"); + require(version32.size() > 1u && version32.back() == 0u && version32[version32.size() - 2u] == 1u, "the spanned-waves flag is not the byte before the depth remap flag"); + version32.pop_back(); version32.erase(version32.begin() + static_cast(maskOffset + 1u + sampleMaskBytes + 4u), version32.begin() + static_cast(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes)); version32[4] = 32u; const auto replay32 = serializer.Deserialize(encodeBase64(version32)); require(replay32.request.context.pixel.has_value() && !replay32.request.context.pixel->iteratesSamples && replay32.request.context.pixel->sampleLocations == std::array{} && replay32.request.target.spannedWaves && serializer.Serialize(replay32.request) == serializer.Serialize(spanned), "a version 32 request read pixel iteration or sample locations, or lost its spanned-waves flag"); auto version29 = sampledBytes; version29.pop_back(); + version29.pop_back(); version29.erase(version29.begin() + static_cast(maskOffset + 1u + sampleMaskBytes), version29.begin() + static_cast(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes)); version29[4] = 29u; const auto replay29 = serializer.Deserialize(encodeBase64(version29)); require(replay29.request.context.pixel.has_value() && replay29.request.context.pixel->quadSampleMask == std::array{0x1u, 0x2u, 0x4u, 0x8u} && replay29.request.context.pixel->reversedBlend == 0u && decodeBase64(serializer.Serialize(replay29.request)) == sampledBytes, "a version 29 request read a reversed-order blend code or misread the fields after the quad sample masks"); auto version28 = sampledBytes; version28.pop_back(); + version28.pop_back(); version28.erase(version28.begin() + static_cast(maskOffset + 1u), version28.begin() + static_cast(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes)); version28[4] = 28u; const auto replay28 = serializer.Deserialize(encodeBase64(version28)); require(replay28.request.context.pixel.has_value() && replay28.request.context.pixel->quadPixelMask == 0x2u && replay28.request.context.pixel->quadSampleMask == std::array{0xffffu, 0xffffu, 0xffffu, 0xffffu} && replay28.request.context.pixel->reversedBlend == 0u && decodeBase64(serializer.Serialize(replay28.request)) == maskedBytes, "a version 28 request read quad sample masks or a reversed-order blend code, or misread the fields after them"); auto version27 = maskedBytes; version27.pop_back(); + version27.pop_back(); version27.erase(version27.begin() + static_cast(maskOffset), version27.begin() + static_cast(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes)); version27[4] = 27u; const auto replay27 = serializer.Deserialize(encodeBase64(version27)); require(replay27.request.context.pixel.has_value() && replay27.request.context.pixel->quadPixelMask == 0xfu && replay27.request.context.pixel->reversedBlend == 0u && serializer.Serialize(replay27.request) == encodedPixel, "a version 27 request read a quad pixel mask or reversed-order blend code, or misread the fields after it"); auto legacyPixel = plainPixel; legacyPixel.pop_back(); + legacyPixel.pop_back(); legacyPixel.erase(legacyPixel.begin() + static_cast(allBitsOffset), legacyPixel.begin() + static_cast(allBitsOffset + 2u + sampleMaskBytes + 4u + iterationBytes)); legacyPixel[4] = 26u; const auto replay26 = serializer.Deserialize(encodeBase64(legacyPixel)); @@ -5061,6 +5067,56 @@ void verifyVertexInfoVersion19() { } } +void verifyDepthRemapRequests() { + using namespace ShaderRecompiler; + const std::array code{0xbf810000u}; + const RequestSerializer serializer; + RecompileRequest vertex{}; + vertex.shader = {ShaderStage::Vertex, 0x10000u, code, 0, {}}; + vertex.context.waveSize = 64; + vertex.context.vertex = ShaderVertexStageInfo{}; + vertex.context.vertex->paClVsOutCntl = 0x89abcdefu; + const auto plain = serializer.Serialize(vertex); + vertex.target.depthRangeRemap = true; + vertex.context.vertex->depthRemapMode = 2u; + const auto remapped = serializer.Serialize(vertex); + const auto replay = serializer.Deserialize(remapped); + require(replay.request.target.depthRangeRemap && replay.request.context.vertex.has_value() && replay.request.context.vertex->paClVsOutCntl == 0x89abcdefu, "the depth remap target flag or the vertex info did not round-trip"); + require(replay.request.context.vertex->depthRemapMode == 2u, "the depth remap mode did not round-trip"); + require(serializer.Serialize(replay.request) == remapped, "a version 35 depth remap request changed on re-serialization"); + const auto plainBytes = decodeBase64(plain); + const auto remappedBytes = decodeBase64(remapped); + require(plainBytes.size() == remappedBytes.size() && plainBytes.size() > 5u && std::equal(plainBytes.begin(), plainBytes.end() - 5, remappedBytes.begin()), "the depth remap flag and mode are not the last 5 bytes of a vertex request"); + for (const std::uint8_t legacyVersion : {32u, 34u}) { + auto legacyBytes = remappedBytes; + legacyBytes[4] = legacyVersion; + legacyBytes.resize(legacyBytes.size() - 5u); + const auto legacy = serializer.Deserialize(encodeBase64(legacyBytes)); + require(!legacy.request.target.depthRangeRemap && legacy.request.context.vertex.has_value() && legacy.request.context.vertex->depthRemapMode == 0u, ("a version " + std::to_string(legacyVersion) + " vertex request read depth remap fields").c_str()); + require(serializer.Serialize(legacy.request) == plain, ("a version " + std::to_string(legacyVersion) + " vertex request misread the fields before the depth remap").c_str()); + } + auto truncated = remappedBytes; + truncated.pop_back(); + expectFailure([&] { static_cast(serializer.Deserialize(encodeBase64(truncated))); }, "truncated data", "a truncated depth remap mode was accepted"); + auto trailing = remappedBytes; + trailing.push_back(0u); + expectFailure([&] { static_cast(serializer.Deserialize(encodeBase64(trailing))); }, "unexpected trailing data", "a byte after the depth remap fields was accepted"); + RecompileRequest pixel{}; + pixel.shader = {ShaderStage::Fragment, 0x20000u, code, 0, {}}; + pixel.context.waveSize = 64; + pixel.context.pixel = ShaderPixelStageInfo{}; + pixel.target.depthRangeRemap = true; + const auto pixelReplay = serializer.Deserialize(serializer.Serialize(pixel)); + require(pixelReplay.request.target.depthRangeRemap && !pixelReplay.request.context.vertex.has_value(), "a pixel request lost the depth remap target flag or gained vertex info"); + std::vector key; + std::vector other; + RecompileCacheKey::Build(vertex, key); + auto zeroToOne = vertex; + zeroToOne.context.vertex->depthRemapMode = 1u; + RecompileCacheKey::Build(zeroToOne, other); + require(key != other, "the depth remap mode did not change the shader cache key"); +} + void verifyLegacyPixelRequests() { using namespace ShaderRecompiler; static constexpr std::array legacyPixelRequests{ @@ -7193,6 +7249,7 @@ int main(int argc, char** argv) { verifyCanonicalVersion20And21Requests(); verifyPixelRequestSerialization(); verifyVertexInfoVersion19(); + verifyDepthRemapRequests(); verifyPositionExportComponents(); verifyLegacyPixelRequests(); verifyPixelExportReplay(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthRangeRemapDraw.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthRangeRemapDraw.cpp new file mode 100644 index 0000000000..831ec0b751 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthRangeRemapDraw.cpp @@ -0,0 +1,257 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "Recompiler.hpp" +#include "Optimization/ResourceProgram.hpp" +#include "DepthFastClearHarness.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +namespace Harness = DepthFastClearHarness; +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +struct Transform { + bool negativeOneToOne; + float scale; + float offset; + float clampMin; + float clampMax; +}; + +struct Case { + Transform transform; + float z; + bool drawn; + float probe; + bool covered; + bool remapOnly; + const char* what; +}; + +constexpr Transform Plain{false, 1.0f, 0.0f, 0.0f, 1.0f}; + +alignas(4096) std::array, 32> Depths{}; +alignas(256) std::array, 3>, 128> VertexSets{}; +std::size_t vertexSets = 0; + +// A vertex shader that reads no user data: the fullscreen triangle comes from the vertex index (v5), and the +// clip z is the literal at index 9. Its program carries no push data of its own. +alignas(256) std::array FetchlessCode{ + 0x36020a81u, 0x34020282u, 0x4a0202c1u, 0x7e020b01u, 0x2c040a81u, 0x34040482u, 0x4a0404c1u, 0x7e040b02u, + 0x7e0602ffu, 0u, 0x7e0802f2u, 0xf80008cfu, 0x04030201u, 0xbf810000u, +}; + +std::uint32_t Draw(AgcDriver::VulkanDevice& device, std::uint64_t depth, const Transform& transform, bool remap, float z, bool write, bool fetchless = false, bool targetRemap = false, bool prepared = false) { + Require(vertexSets < VertexSets.size(), "the test ran out of vertex sets"); + auto& vertices = VertexSets[vertexSets++]; + vertices = {{{-1.0f, -1.0f, z, 1.0f}, {3.0f, -1.0f, z, 1.0f}, {-1.0f, 3.0f, z, 1.0f}}}; + Harness::Pixels.fill(std::byte{0}); + + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(Harness::Pixels.data()), {Harness::Width, Harness::Height}, VK_FORMAT_R8G8B8A8_UNORM, Harness::Pixels.size(), 0xe4u}; + state.colors = {state.color}; + state.hasColorTarget = true; + state.depth = AgcDriver::Graphics::DepthTarget{depth, 0, {Harness::Width, Harness::Height}, VK_FORMAT_D32_SFLOAT, 1.0f, 0}; + state.depthTest = true; + state.depthWrite = write; + state.depthCompare = write ? VK_COMPARE_OP_ALWAYS : VK_COMPARE_OP_LESS; + state.renderExtent = {Harness::Width, Harness::Height}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + const float minDepth = transform.negativeOneToOne ? transform.offset - transform.scale : transform.offset; + state.viewport = {0, static_cast(Harness::Height), static_cast(Harness::Width), -static_cast(Harness::Height), minDepth, transform.offset + transform.scale}; + state.negativeOneToOne = transform.negativeOneToOne; + state.depthScale = transform.scale; + state.depthOffset = transform.offset; + state.depthClampMin = transform.clampMin; + state.depthClampMax = transform.clampMax; + state.scissor = {{0, 0}, {Harness::Width, Harness::Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + state.blends = {state.blend}; + state.blendConstants = {}; + auto context = device.graphicsContext(); + context.depthRangeUnrestricted = !remap; + AgcDriver::Graphics::ApplyDepthRemap(context, state); + const bool inRange = minDepth >= 0.0f && transform.offset + transform.scale <= 1.0f; + Require((state.depthRemap.mode != 0u) == (remap && !inRange), "the draw did not take the depth path the test asked for"); + + auto target = device.Target(); + target.depthRangeRemap = state.depthRemap.mode != 0u || targetRemap; + ShaderRecompiler::ShaderVertexStageInfo vertexInfo{}; + vertexInfo.depthRemapMode = state.depthRemap.mode; + std::vector vertexUserData(4, 0u); + if (fetchless) { + FetchlessCode[9] = std::bit_cast(z); + } else { + const auto vertexBuffer = Harness::BufferDescriptor(vertices.data(), 16u, static_cast(vertices.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + } + const std::span vertexCode = fetchless ? std::span(FetchlessCode) : std::span(Harness::VertexCode); + const std::array vertexMemory{{{reinterpret_cast(vertexCode.data()), std::as_bytes(vertexCode)}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(vertexCode.data()), vertexCode, 0, {}}, + {32, 0, vertexUserData, std::nullopt, std::nullopt, vertexInfo, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = prepared; + std::shared_ptr vertexHolder; + if (prepared) { + const auto handle = ShaderRecompiler::PrepareShader(vertex); + const auto invocation = ShaderRecompiler::PreparedShaderInvocation::TryCreate(vertex, handle); + Require(invocation.has_value(), "the prepared vertex shader did not match its own request"); + vertexHolder = invocation->Materialize(*invocation->Capture(ShaderRecompiler::SrtRuntime{std::span(vertexUserData)})); + } else { + vertexHolder = std::make_shared(ShaderRecompiler::Recompile(vertex)); + } + const auto& vertexResult = *vertexHolder; + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + if (fetchless) Require(vertexPush == 0u, "the fetchless vertex shader carries push data of its own"); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.wave32 = true; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY); + pixel.posX = true; + pixel.posY = true; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + std::vector pixelUserData(4, 0u); + const std::array pixelMemory{{{reinterpret_cast(Harness::PixelCode.data()), std::as_bytes(std::span(Harness::PixelCode))}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(Harness::PixelCode.data()), Harness::PixelCode, 0, {}}, + {32, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)} + }}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(vertices.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); + device.WaitIdle(); + std::uint32_t covered = 0; + for (std::size_t index = 0; index < Harness::Pixels.size() / 4u; ++index) { + if (std::to_integer(Harness::Pixels[index * 4u]) == 255u) ++covered; + } + return covered; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto context = device->graphicsContext(); + if (!context.depthClamp) { + std::puts("skipped, the device has no depth clamping"); + return VulkanTestSkipped; + } + constexpr float Step = 1.0f / 64.0f; + constexpr Transform minecraft{true, 1.0f, 0.0f, -1.0f, 1.0f}; + constexpr Transform widened{false, 2.0f, -0.5f, 0.0f, 1.0f}; + constexpr Transform bounded{false, 2.0f, -0.5f, 0.25f, 0.75f}; + constexpr std::array cases{{ + {minecraft, 0.5f - Step, true, 0.5f, false, false, "negative-one-to-one depth just below 0.5"}, + {minecraft, 0.5f + Step, true, 0.5f, true, false, "negative-one-to-one depth just above 0.5"}, + {minecraft, -1.5f, false, 0.5f, true, false, "negative-one-to-one clip z in front of the near plane"}, + {minecraft, 1.5f, false, 0.5f, true, false, "negative-one-to-one clip z behind the far plane"}, + {widened, 0.5f - Step / 2.0f, true, 0.5f, false, false, "scaled zero-to-one depth just below 0.5"}, + {widened, 0.5f + Step / 2.0f, true, 0.5f, true, false, "scaled zero-to-one depth just above 0.5"}, + {widened, -0.125f, false, 0.5f, true, false, "zero-to-one clip z in front of the near plane"}, + {widened, 1.125f, false, 0.5f, true, false, "zero-to-one clip z behind the far plane"}, + {bounded, 0.4f, true, 0.35f, false, false, "depth 0.3 inside clamp bounds [0.25, 0.75], probed at 0.35"}, + {bounded, 0.4f, true, 0.25f, true, false, "depth 0.3 inside clamp bounds [0.25, 0.75], probed at 0.25"}, + {bounded, 0.6f, true, 0.65f, true, false, "depth 0.7 inside clamp bounds [0.25, 0.75], probed at 0.65"}, + {bounded, 0.2f, true, 0.2f, true, true, "depth -0.1 clamped to 0.25, probed at 0.2"}, + {bounded, 0.8f, true, 0.8f, false, true, "depth 1.1 clamped to 0.75, probed at 0.8"}, + }}; + std::size_t surfaces = 0; + std::size_t ran = 0; + for (const bool remap : {true, false}) { + if (!remap && !context.depthRangeUnrestricted) continue; + for (const auto& test : cases) { + if (!remap && (test.remapOnly || (test.transform.negativeOneToOne && !context.depthClipControl))) continue; + Require(surfaces < Depths.size(), "the test ran out of depth surfaces"); + const auto depth = static_cast(reinterpret_cast(Depths[surfaces++].data())); + const auto drawn = Draw(*device, depth, test.transform, remap, test.z, true); + const auto passed = Draw(*device, depth, Plain, false, test.probe, false); + const auto path = remap ? "remapped" : "unrestricted"; + if (drawn != (test.drawn ? Harness::Covered : 0u) || passed != (test.covered ? Harness::Covered : 0u)) { + char detail[256]; + std::snprintf(detail, sizeof(detail), "%s (%s path): the draw covered %u of %u pixels (expected %s) and the probe passed %u (expected %s)", test.what, path, drawn, Harness::Covered, test.drawn ? "all" : "none", passed, test.covered ? "all" : "none"); + throw std::runtime_error(detail); + } + ++ran; + } + } + // Scale and offset reach the vertex shader through the draw's push block, so two draws whose depth remap + // differs only in them build one pipeline between them: the second finds it in the store, and both + // store the depth they should. + { + constexpr Transform widerScale{false, 4.0f, -1.5f, 0.0f, 1.0f}; + Require(surfaces + 2u <= Depths.size(), "the test ran out of depth surfaces"); + const auto depthA = static_cast(reinterpret_cast(Depths[surfaces++].data())); + const auto depthB = static_cast(reinterpret_cast(Depths[surfaces++].data())); + const auto first = Draw(*device, depthA, widened, true, 0.5f + Step / 2.0f, true); + const auto before = AgcDriver::Graphics::PipelineStoreTotals(); + const auto second = Draw(*device, depthB, widerScale, true, 0.5f + Step / 4.0f, true); + const auto after = AgcDriver::Graphics::PipelineStoreTotals(); + Require(first == Harness::Covered && second == Harness::Covered, "a draw with a remapped depth did not cover the target"); + Require(after.misses == before.misses && after.hits > before.hits, "a draw that differs only in the depth scale and offset built another pipeline"); + for (const auto depth : {depthA, depthB}) { + Require(Draw(*device, depth, Plain, false, 0.5f, false) == Harness::Covered, "a depth remapped by a shared pipeline was stored below 0.5 + 1/64"); + Require(Draw(*device, depth, Plain, false, 0.5f + 2.0f * Step, false) == 0u, "a depth remapped by a shared pipeline was stored above 0.5 + 1/64"); + } + ++ran; + } + // A remapped vertex shader with no push data of its own, and a fragment shader with none: the pipeline's + // push stages must still include the vertex stage (a non-GPL layout names them), or the scale and offset + // reach no one. + { + Require(surfaces < Depths.size(), "the test ran out of depth surfaces"); + const auto depth = static_cast(reinterpret_cast(Depths[surfaces++].data())); + const auto drawn = Draw(*device, depth, widened, true, 0.5f + Step / 2.0f, true, true); + Require(drawn == Harness::Covered, "a fetchless remapped vertex shader did not cover the target"); + Require(Draw(*device, depth, Plain, false, 0.5f, false) == Harness::Covered, "a fetchless remapped vertex shader stored a depth below 0.5 + 1/64, so its scale and offset did not arrive"); + Require(Draw(*device, depth, Plain, false, 0.5f + 2.0f * Step, false) == 0u, "a fetchless remapped vertex shader stored a depth above 0.5 + 1/64"); + ++ran; + } + // On a device without VK_EXT_depth_range_unrestricted the target remaps every vertex shader, so an ordinary + // in-range draw (mode 0) with no push data of its own still carries the remap's push block: it must validate + // and draw, from a direct build and from a prepared one. + for (const bool prepared : {false, true}) { + Require(surfaces < Depths.size(), "the test ran out of depth surfaces"); + const auto depth = static_cast(reinterpret_cast(Depths[surfaces++].data())); + const auto drawn = Draw(*device, depth, Plain, false, 0.5f + Step / 2.0f, true, true, true, prepared); + Require(drawn == Harness::Covered, prepared ? "a fetchless mode 0 vertex shader on a remapping target, prepared, did not cover the target" : "a fetchless mode 0 vertex shader on a remapping target did not cover the target"); + Require(Draw(*device, depth, Plain, false, 0.5f, false) == Harness::Covered && Draw(*device, depth, Plain, false, 0.5f + 2.0f * Step, false) == 0u, "a fetchless mode 0 vertex shader on a remapping target stored the wrong depth"); + ++ran; + } + std::printf("depth range remap draw tests passed (%zu cases, unrestricted path %s)\n", ran, context.depthRangeUnrestricted ? "compared" : "absent"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/CacheKey.hpp b/core/shader/recompiler/CacheKey.hpp index 02615983a5..b6e0d9b920 100644 --- a/core/shader/recompiler/CacheKey.hpp +++ b/core/shader/recompiler/CacheKey.hpp @@ -213,6 +213,7 @@ private: append(key, value.fetchBufferReg); append(key, value.fetchEmbedded); append(key, value.paClVsOutCntl); + append(key, value.depthRemapMode); if (value.resourcesNum > value.resources.size()) throw std::runtime_error("Shader cache: invalid vertex resource count"); append(key, value.resourcesNum); for (std::uint32_t i = 0; i < value.resourcesNum; ++i) { @@ -262,6 +263,7 @@ private: append(key, value.srgbDecodeFormats); append(key, value.narrowSubgroupClock); append(key, value.float64DenormPreserve); + append(key, value.depthRangeRemap); append(key, value.registrationModules); append(key, value.registrationWordLimit); append(key, value.vertexSubgroups); diff --git a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp index 8c7726bc71..2fbafdd113 100644 --- a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp +++ b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp @@ -794,7 +794,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const std::string buffer; Writer writer(buffer); writer.WriteU32(0x41505335u); - writer.WriteU32(34u); + writer.WriteU32(35u); writeShaderBinary(writer, request.shader); writeGuestContext(writer, request.context); writeSpirvTarget(writer, request.target); @@ -820,6 +820,10 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const writer.WriteBool(request.target.float16DenormPreserve); writer.WriteU32Span(request.context.linkedCallFallbacks); writer.WriteBool(request.target.spannedWaves); + writer.WriteBool(request.target.depthRangeRemap); + if (request.context.vertex.has_value()) { + writer.WriteU32(request.context.vertex->depthRemapMode); + } return base64Encode(buffer); } @@ -828,7 +832,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const Reader reader(decoded); if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature"); const auto version = reader.ReadU32(); - if (version < 1u || version > 34u) throw std::runtime_error("unsupported recompile request serialization version"); + if (version < 1u || version > 35u) throw std::runtime_error("unsupported recompile request serialization version"); DeserializedRequest result{}; result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader); result.request.context = readGuestContext(reader, result, version); @@ -865,6 +869,13 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const result.request.context.linkedCallFallbacks = result.linkedCallFallbacks; } if (version >= 32u) result.request.target.spannedWaves = reader.ReadBool(); + if (version >= 35u) { + result.request.target.depthRangeRemap = reader.ReadBool(); + if (result.vertex.has_value()) { + result.vertex->depthRemapMode = reader.ReadU32(); + result.request.context.vertex = result.vertex; + } + } if (version >= 21u && reader.Remaining() != 0u) throw std::runtime_error("RequestSerializer: unexpected trailing data"); return result; } diff --git a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp index 259ac9f2f6..64abe7b50e 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp @@ -69,6 +69,10 @@ struct ShaderStageRuntime { } }; +struct ShaderDepthRemap { + std::uint32_t mode = 0; +}; + struct ShaderClipSpaceTransform { float scale[2] = {}; float offset[2] = {}; @@ -137,6 +141,7 @@ struct ShaderVertexInputInfo { std::uint32_t hostSubgroupSize = 64; std::uint32_t paClVsOutCntl = 0; ShaderClipSpaceTransform clipSpace; + ShaderDepthRemap depthRemap; ShaderMeshInputInfo mesh; ShaderTessellationInputInfo tess; bool fetchExternal = false; diff --git a/core/shader/recompiler/PipelineSpecialization.hpp b/core/shader/recompiler/PipelineSpecialization.hpp index e47d9879be..35059904f2 100644 --- a/core/shader/recompiler/PipelineSpecialization.hpp +++ b/core/shader/recompiler/PipelineSpecialization.hpp @@ -30,6 +30,8 @@ inline constexpr std::uint32_t RasterPrimitive = 8233u; inline constexpr std::uint32_t LayerExported = 8234u; inline constexpr std::uint32_t StencilReferenceOffset = 8235u; inline constexpr std::uint32_t ReversedBlend = 8236u; +inline constexpr std::uint32_t DepthRemapBase = 8240u; +inline constexpr std::uint32_t DepthRemapWords = 1u; inline constexpr std::uint32_t HeapCountBase = 8256u; inline constexpr std::uint32_t MipCountBase = 8320u; inline constexpr std::uint32_t DescriptorIndexBase = 16384u; diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index 7bcb5465a6..bfdeb070e2 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -557,6 +557,7 @@ CompiledVariant emitVariant(const RecompileRequest& request, IrProgram& program) targetOptions.nonConstantImageOffsets = request.target.nonConstantImageOffsets; targetOptions.narrowSubgroupClock = request.target.narrowSubgroupClock; targetOptions.float64DenormPreserve = request.target.float64DenormPreserve; + targetOptions.depthRangeRemap = request.target.depthRangeRemap; targetOptions.registrationModules = registration; targetOptions.fragmentDescriptorSet = request.target.registrationModules; targetOptions.float16DenormPreserve = request.target.float16DenormPreserve; @@ -935,6 +936,13 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil if (variant.info.info.rasterPrimitive) moduleKey.push_back(static_cast(request.context.pixel->rasterPrimitive)); if (variant.info.info.layerExported) moduleKey.push_back(request.context.pixel->layerExported ? 1u : 0u); } + const bool depthRemap = request.target.depthRangeRemap && request.shader.stage == ShaderStage::Vertex && request.context.vertex.has_value(); + if (depthRemap) { + if (request.context.vertex->depthRemapMode != 0u && request.layout.pushConstantOffsetBytes % (RuntimeAbi::PushConstantDwords * 4u) + request.layout.pushConstantSizeBytes > DepthRemapPushOffsetBytes) { + throw std::runtime_error("a remapped vertex shader's push data must end before the depth remap words"); + } + moduleKey.push_back(request.context.vertex->depthRemapMode); + } result.vertexAttributes.reserve(result.vertexInputs.size()); std::array vertexClasses{}; std::array runtimeSelectors{}; @@ -963,7 +971,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil if (artifact.registrationModule) runtimeSelectors.at(input.location / RuntimeAbi::VertexSelectorsPerDword) |= (selectors & 0xfffu) << (input.location % RuntimeAbi::VertexSelectorsPerDword * 16u); } } - if (!plan->bindings.specialization.empty() || variant.bindings.layout.UsesPushData() || !artifact.vertexInputPatches.empty() || request.context.pixel) { + if (!plan->bindings.specialization.empty() || variant.bindings.layout.UsesPushData() || !artifact.vertexInputPatches.empty() || request.context.pixel || depthRemap) { { std::shared_lock lock(plan->mutex); if (const auto found = plan->modules.find(moduleKey); found != plan->modules.end()) entry = found->second; @@ -985,6 +993,9 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil if (variant.info.info.rasterPrimitive) constants.push_back({PipelineSpecialization::RasterPrimitive, moduleKey[index++]}); if (variant.info.info.layerExported) constants.push_back({PipelineSpecialization::LayerExported, moduleKey[index++]}); } + if (depthRemap) { + for (std::uint32_t word = 0; word < PipelineSpecialization::DepthRemapWords; ++word) constants.push_back({PipelineSpecialization::DepthRemapBase + word, moduleKey[index++]}); + } if (!artifact.vertexInputPatches.empty()) { for (const auto& input : result.vertexInputs) { const auto selectors = moduleKey[index++]; @@ -1023,6 +1034,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil result.workgroupMemoryDwords = WorkgroupMemoryStrideDwords(variant.info.info); result.bindings = std::move(bindings.bindings); result.pushConstants = std::move(bindings.pushConstants); + result.readsDepthRemapPush = depthRemap; if (!artifact.stencilReferences.empty()) { if (artifact.stencilReferenceDword * sizeof(std::uint32_t) != result.pushConstants.size() || result.pushConstants.size() + sizeof(std::uint32_t) > request.layout.pushConstantSizeBytes) throw std::runtime_error("stencil replay push constant lies outside the stage range"); result.pushConstants.resize(result.pushConstants.size() + sizeof(std::uint32_t)); diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index 7c111f66e3..c75ce97a3b 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -211,6 +211,7 @@ struct ShaderVertexStageInfo { std::uint32_t fetchBufferReg; bool fetchEmbedded; std::uint32_t paClVsOutCntl = 0; + std::uint32_t depthRemapMode = 0; }; struct ShaderFloatMode { @@ -286,6 +287,7 @@ struct SpirvTarget { bool float16DenormPreserve = false; bool spannedWaves = false; bool robustBufferAccess2 = false; + bool depthRangeRemap = false; }; struct BindingLayout { @@ -342,6 +344,8 @@ inline constexpr std::uint32_t MeshDrawPushOffsetBytes = 104; inline constexpr std::uint32_t MeshDrawPushBytes = 24; inline constexpr std::uint32_t TessellationLevelPushOffsetBytes = 120; inline constexpr std::uint32_t TessellationLevelPushBytes = 8; +inline constexpr std::uint32_t DepthRemapPushOffsetBytes = 104; +inline constexpr std::uint32_t DepthRemapPushBytes = 8; inline constexpr std::uint32_t MeshArgumentAddressDword = 4; inline constexpr std::uint32_t MeshArgumentIndexCountDword = 3; inline constexpr std::uint32_t MeshArgumentFirstIndexDword = 4; @@ -641,6 +645,7 @@ struct ShaderInvocation { std::uint64_t specializationId = 0; std::vector bindings; std::vector pushConstants; + bool readsDepthRemapPush = false; std::vector vertexAttributes; std::uint32_t poisonedSrtReads = 0; std::vector imageTablePoison; diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index b95a4fccb5..f830045b52 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -85,8 +85,8 @@ namespace ShaderRecompiler::ShaderDiskCache { #if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__) static_assert(sizeof(CompiledShaderArtifact) == 240, "CompiledShaderArtifact changed: update the artifact encoder"); -static_assert(sizeof(ShaderInvocation) == 200, "ShaderInvocation changed: update the invocation encoder"); -static_assert(sizeof(RecompileResult) == 472, "RecompileResult changed: update EncodeResult and DecodeResult"); +static_assert(sizeof(ShaderInvocation) == 208, "ShaderInvocation changed: update the invocation encoder"); +static_assert(sizeof(RecompileResult) == 480, "RecompileResult changed: update EncodeResult and DecodeResult"); static_assert(sizeof(DescriptorBinding) == 656, "DescriptorBinding changed: update the binding encoder"); static_assert(sizeof(VertexAttribute) == 32, "VertexAttribute changed: update the attribute encoder"); static_assert(sizeof(VertexInput) == 16, "VertexInput changed: update the vertex input encoder"); @@ -565,6 +565,7 @@ void encodeInvocation(Writer& writer, const ShaderInvocation& invocation) { writer.List(invocation.bindings, encodeBinding); writer.Value(invocation.pushConstants.size()); for (const auto byte : invocation.pushConstants) writer.Value(static_cast(byte)); + writer.Value(invocation.readsDepthRemapPush); writer.List(invocation.vertexAttributes, [](Writer& out, const VertexAttribute& attribute) { out.Value(attribute.location); out.Value(attribute.components); @@ -604,6 +605,7 @@ void decodeInvocation(Reader& reader, ShaderInvocation& invocation) { reader.Values(pushConstants); invocation.pushConstants.resize(pushConstants.size()); if (!pushConstants.empty()) std::memcpy(invocation.pushConstants.data(), pushConstants.data(), pushConstants.size()); + reader.Value(invocation.readsDepthRemapPush); reader.List(invocation.vertexAttributes, 32, [](Reader& in, VertexAttribute& attribute) { in.Value(attribute.location); in.Value(attribute.components); diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp index 7f7ccbdec5..385638dd21 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp @@ -22,6 +22,7 @@ struct SpirvTargetOptions { bool nonConstantImageOffsets = false; bool narrowSubgroupClock = false; bool float64DenormPreserve = false; + bool depthRangeRemap = false; bool registrationModules = false; bool fragmentDescriptorSet = false; bool float16DenormPreserve = false; diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp index 8202209f9d..a22348854b 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp @@ -105,6 +105,8 @@ struct SpirvEmitterState { std::uint32_t spannedExchangeVariable = 0; bool narrowSubgroupClock = false; bool float64DenormPreserve = false; + bool depthRangeRemap = false; + std::uint32_t depthRemapClipIndex = 0; bool registrationModules = false; bool fragmentDescriptorSet = false; bool float16DenormPreserve = false; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp index 050316a6f7..b946eccbcf 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp @@ -257,6 +257,7 @@ std::vector SpirvEmitter::Emit(const IrProgram& program, const Sh state.nonConstantImageOffsets = target.nonConstantImageOffsets; state.narrowSubgroupClock = target.narrowSubgroupClock; state.float64DenormPreserve = target.float64DenormPreserve; + state.depthRangeRemap = target.depthRangeRemap; state.registrationModules = target.registrationModules; state.fragmentDescriptorSet = target.fragmentDescriptorSet; state.float16DenormPreserve = target.float16DenormPreserve; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp index ad0ada9b66..89d3b5dfd3 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp @@ -327,6 +327,11 @@ void DefineOutputs(SpirvEmitterState& state) { DefineMeshOutputs(state); return; } + const bool depthRemap = state.depthRangeRemap && state.program.Resources().stage == IrShaderStage::Vertex; + if (depthRemap) { + state.depthRemapClipIndex = clipDistanceCount; + clipDistanceCount += 2u; + } const auto stage = state.program.Resources().stage; const bool exportsPosition = std::ranges::any_of(state.outputs, [](const SpirvOutputBinding& output) { return output.kind == StageOutputKind::Position; }); if (exportsPosition && (stage == IrShaderStage::Vertex || stage == IrShaderStage::TessellationEvaluation) && clipDistanceCount + cullDistanceCount < MaxClipDistances) { @@ -343,6 +348,7 @@ void DefineOutputs(SpirvEmitterState& state) { } return variable; }; + if (depthRemap) BuiltIn(state.clipDistanceVariable, F32ArrayType(state, clipDistanceCount), "gl_ClipDistance", spv::BuiltInClipDistance); for (auto& binding : state.outputs) { switch (binding.kind) { case StageOutputKind::Position: @@ -413,7 +419,7 @@ void DefineOutputs(SpirvEmitterState& state) { void DefineDescriptors(SpirvEmitterState& state) { const IrBindingLayout& layout = state.program.Metadata().bindings; const IrShaderStage stage = state.program.Resources().stage; - if (layout.UsesPushData() || state.stencilReplay || stage == IrShaderStage::Mesh || stage == IrShaderStage::TessellationControl) { + if (layout.UsesPushData() || state.stencilReplay || stage == IrShaderStage::Mesh || stage == IrShaderStage::TessellationControl || (state.depthRangeRemap && stage == IrShaderStage::Vertex)) { const auto type = PushConstantBlockType(state); state.pushConstantVariable = state.module.DefineGlobalVariable(TypePointer(state, spv::StorageClassPushConstant, type), spv::StorageClassPushConstant); state.module.AddName(type, "BufferResource"); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index 44cc74aac8..72da3ee81a 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -571,6 +571,59 @@ std::uint32_t ConvertClipCoordinate(SpirvEmitterState& state, std::uint32_t coor return ndc; } +std::uint32_t RemapPositionDepth(SpirvEmitterState& state, std::uint32_t position) { + const auto& remap = state.inputInfo.vertex->depthRemap; + const auto f32 = TypeF32(state); + const auto u32 = TypeU32(state); + const auto boolean = TypeBool(state); + std::array components{}; + for (std::uint32_t i = 0; i < 4u; i++) { + components.at(i) = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeExtract, f32, components.at(i), position, i); + } + const auto mode = state.module.SpecializationConstant(u32, PipelineSpecialization::DepthRemapBase, remap.mode); + if (state.pushConstantVariable == 0u) throw std::runtime_error("depth remap push constants were not declared"); + const auto pushFloat = [&](std::uint32_t dword) { + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state), pointer, state.pushConstantVariable, ConstantU32(state, 0u), ConstantU32(state, DepthRemapPushOffsetBytes / 4u + dword)); + const auto bits = state.module.AllocateId(); + state.module.AddFunction(spv::OpLoad, u32, bits, pointer); + const auto value = state.module.AllocateId(); + state.module.AddFunction(spv::OpBitcast, f32, value, bits); + return value; + }; + const auto scale = pushFloat(0u); + const auto offset = pushFloat(1u); + const auto scaled = state.module.AllocateId(); + state.module.AddFunction(spv::OpFMul, f32, scaled, components[2], scale); + const auto shifted = state.module.AllocateId(); + state.module.AddFunction(spv::OpFMul, f32, shifted, components[3], offset); + const auto remapped = state.module.AllocateId(); + state.module.AddFunction(spv::OpFAdd, f32, remapped, scaled, shifted); + const auto active = state.module.AllocateId(); + state.module.AddFunction(spv::OpINotEqual, boolean, active, mode, ConstantU32(state, 0u)); + const auto z = Select(state, f32, active, remapped, components[2]); + const auto symmetric = state.module.AllocateId(); + state.module.AddFunction(spv::OpIEqual, boolean, symmetric, mode, ConstantU32(state, 2u)); + const auto nearSymmetric = state.module.AllocateId(); + state.module.AddFunction(spv::OpFAdd, f32, nearSymmetric, components[3], components[2]); + const auto near = Select(state, f32, symmetric, nearSymmetric, components[2]); + const auto far = state.module.AllocateId(); + state.module.AddFunction(spv::OpFSub, f32, far, components[3], components[2]); + const auto inside = ConstantF32Value(state, 1.0f); + const auto storeDistance = [&](std::uint32_t index, std::uint32_t distance) { + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, f32), pointer, state.clipDistanceVariable, ConstantU32(state, index)); + state.module.AddFunction(spv::OpStore, pointer, Select(state, f32, active, distance, inside)); + }; + if (state.clipDistanceVariable == 0u) throw std::runtime_error("depth remap clip distances were not declared"); + storeDistance(state.depthRemapClipIndex, near); + storeDistance(state.depthRemapClipIndex + 1u, far); + const auto converted = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4u), converted, components[0], components[1], z, components[3]); + return converted; +} + std::uint32_t ConvertPositionToClipSpace(SpirvEmitterState& state, std::uint32_t position) { if (state.inputInfo.vertex == nullptr) { throw std::runtime_error("vertex input info is missing for clip-space conversion"); @@ -1588,6 +1641,9 @@ void EmitSetAttribute(SpirvValueEmitContext& ctx, const IrValue& inst) { if (state.inputInfo.vertex->clipSpace.enabled) { value = ConvertPositionToClipSpace(state, value); } + if (state.depthRangeRemap && state.program.Resources().stage == IrShaderStage::Vertex) { + value = RemapPositionDepth(state, value); + } } if (exp.kind == ExportTargetKind::Position && state.zeroPositionClipIndex) { StoreZeroPositionClipDistance(state, value); diff --git a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp index 9c438ed56a..67274b8d66 100644 --- a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp +++ b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp @@ -174,6 +174,7 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues vertexStorage.hostSubgroupSize = hostSubgroupSize; vertexStorage.fetchEmbedded = vertex.fetchEmbedded; vertexStorage.paClVsOutCntl = vertex.paClVsOutCntl; + vertexStorage.depthRemap = {vertex.depthRemapMode}; vertexStorage.fetchExternal = false; vertexStorage.fetchAttribReg = static_cast(vertex.fetchAttribReg); vertexStorage.fetchBufferReg = static_cast(vertex.fetchBufferReg); diff --git a/core/shader/recompiler/tests/RequestSerializerTests.cpp b/core/shader/recompiler/tests/RequestSerializerTests.cpp index a8e6be3671..b8b50d5c35 100644 --- a/core/shader/recompiler/tests/RequestSerializerTests.cpp +++ b/core/shader/recompiler/tests/RequestSerializerTests.cpp @@ -207,11 +207,11 @@ int main() { Require(request.graphics->draw.indexAddress == 0xabcdef3400u && request.graphics->draw.indexCount == 57u && request.graphics->draw.indexElementBytes == 4u && request.graphics->draw.instanceCount == 9u); Require(request.target.float16DenormPreserve == preserve); auto upgraded = bytes; - upgraded[4] = 34u; + upgraded[4] = 35u; if (fixture.pixel) upgraded.insert(upgraded.begin() + 258u, {0u, 0u, 0u}); if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(upgraded); if (fixture.tessellation) upgraded.insert(upgraded.end() - 35u, 5u, 0u); - upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u, 0u); + upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u + 5u, 0u); Require(serializer.Serialize(request) == EncodeBase64(upgraded)); auto truncated = bytes; truncated.pop_back(); @@ -246,11 +246,11 @@ int main() { layerRejected = std::string_view(error.what()) == "RequestSerializer: truncated data"; } Require(layerRejected); - layered[4] = 34u; + layered[4] = 35u; layered.insert(layered.begin() + 259u, {0u, 0u}); InsertPixelDefaultsAfterFrontFace(layered); if (fixture.tessellation) layered.insert(layered.end() - 35u, 5u, 0u); - layered.insert(layered.end(), sizeof(std::uint64_t) + 1u, 0u); + layered.insert(layered.end(), sizeof(std::uint64_t) + 1u + 5u, 0u); Require(serializer.Serialize(layerReplay.request) == EncodeBase64(layered)); } } @@ -271,10 +271,10 @@ int main() { Require(replay.request.context.pixel->rasterPrimitive == ShaderRecompiler::RasterPrimitive::Rectangle); Require(replay.request.context.vertex->paClVsOutCntl == 0x00050a05u); Require(replay.request.target.float16DenormPreserve == preserve); - bytes[4] = 34u; + bytes[4] = 35u; bytes.insert(bytes.begin() + 259u, {0u, 0u}); InsertPixelDefaultsAfterFrontFace(bytes); - bytes.insert(bytes.end(), sizeof(std::uint64_t) + 1u, 0u); + bytes.insert(bytes.end(), sizeof(std::uint64_t) + 1u + 5u, 0u); Require(serializer.Serialize(replay.request) == EncodeBase64(bytes)); auto truncated23 = DecodeBase64(encoded23); @@ -395,10 +395,10 @@ int main() { Require(request.graphics->draw.indexAddress == 0xabcdef3400u && request.graphics->draw.indexCount == 57u && request.graphics->draw.indexElementBytes == 4u && request.graphics->draw.instanceCount == 9u); Require(request.target.float16DenormPreserve == preserve); auto upgraded = bytes; - upgraded[4] = 34u; + upgraded[4] = 35u; if (fixture.pixel) upgraded.insert(upgraded.begin() + 259u, {0u, 0u}); if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(upgraded); - upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u, 0u); + upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u + 5u, 0u); Require(serializer.Serialize(request) == EncodeBase64(upgraded)); for (const bool dither : {false, true}) { if (!fixture.pixel && dither) continue; @@ -408,12 +408,12 @@ int main() { const auto replay25 = serializer.Deserialize(EncodeBase64(main25)); Require(!fixture.pixel || (replay25.request.context.pixel->layerExported == layer && replay25.request.context.pixel->ditheredAlphaToCoverage == dither && replay25.request.context.pixel->reversedBlend == 0u)); Require(replay25.request.target.float16DenormPreserve == preserve && (!fixture.tessellation || replay25.request.graphics->tessellation->primitiveGroupSize == 37u)); - main25[4] = 34u; + main25[4] = 35u; if (fixture.pixel) { main25.insert(main25.begin() + 260u, 0u); InsertPixelDefaultsAfterFrontFace(main25); } - main25.insert(main25.end(), sizeof(std::uint64_t) + 1u, 0u); + main25.insert(main25.end(), sizeof(std::uint64_t) + 1u + 5u, 0u); Require(serializer.Serialize(replay25.request) == EncodeBase64(main25)); } } @@ -427,9 +427,9 @@ int main() { const auto replay = serializer.Deserialize(encoded); Require(replay.request.target.float16DenormPreserve == preserve); auto bytes = DecodeBase64(encoded); - Require(bytes.size() >= 8u && bytes[4] == 34u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u); + Require(bytes.size() >= 8u && bytes[4] == 35u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u); auto future = bytes; - future[4] = 35u; + future[4] = 36u; bool futureRejected = false; try { static_cast(serializer.Deserialize(EncodeBase64(future))); diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index decbdff511..9f9fdce1e6 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -187,3 +187,4 @@ - [Unclamped normalized color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - CB_COLOR_INFO targets with BLEND_CLAMP clear (bit 15) on UNORM and SNORM formats are accepted when blending is disabled (ENABLE clear or BLEND_BYPASS set). Vulkan converts unblended shader values and clamps to the normalized range automatically; blending into an unclamped normalized target remains unmodeled because Vulkan always clamps inputs prior to blending. - [Thick SW_4KB_S_X volumes](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp) use AMD addrlib's GFX10 non-RB+ 16-pipe `GFX10_SW_4K_S3_X_PATINFO` equations for 1, 2, 4, 8 and 16-byte elements, including packed mip tails. The layout follows [AMD PAL c5e80007](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp); it has not been measured on console hardware. Other thick swizzle modes outside the supported families still throw. Mipmapped 3D color targets remain refused by the color-register decoder. +- [Viewport depth range remap](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - on a device without `VK_EXT_depth_range_unrestricted` a viewport depth range outside [0, 1] (Minecraft PPSA17221 draws its world with `PA_CL_VPORT_ZSCALE` 1, `ZOFFSET` 0 in negative-one-to-one clip space) is folded into the vertex shader: `RemapPositionDepth` scales clip z so z/w is the console's depth, the pipeline's depth clamp applies the console's `PA_SC_VPORT_ZMIN/ZMAX` clipped to [0, 1] (exact for a normalized depth target or none; Minecraft clamps its float depth target to [-1, 1], so a depth below 0 is stored as 0 there, which only a later test or read at exactly 0 can tell, and the first such draw is reported once), and two extra `gl_ClipDistance` elements restore the near and far planes depth clamping removes. The mode is a pipeline specialization constant (`PipelineSpecialization::DepthRemapBase`); scale and offset are read from the draw's push block (two words at byte 104, `DepthRemapPushOffsetBytes`, so a remapped draw's stage push data ends before it), so draws that differ only in them share one vertex module and one pipeline. The remap serves the vertex path only; mesh and tessellation draws with such a range still throw. Whether the console clamps before or after its depth bias and how it treats a clamp range narrower than the transform's are not measured.