diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp index ca00752f2..925da125e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp @@ -74,6 +74,16 @@ alignas(256) constexpr std::array HullDescendingRecords{ 0x0202070b, 0xbf810000, }; +alignas(256) constexpr std::array HullPatchConstant{ + 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, + 0x02151101, 0x362402ff, 0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000, + 0x02000015, 0x162c2890, 0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0xe0781000, 0x02020216, + 0x7da82881, 0xbe94047e, 0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80008, 0x06000013, 0xd8dc1008, + 0x07000013, 0xbf8cc07f, 0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, 0x34142484, 0xbefe0414, + 0xe0781000, 0x0403180a, 0x4a162481, 0x161616ff, 0x000000d0, 0x4c1616ff, 0x00008000, 0xe0741000, + 0x0202070b, 0xe0701064, 0x0202060b, 0xbf810000, +}; + alignas(256) constexpr std::array HullUnguardedFactors{ 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, 0x02151101, 0x362402ff, 0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000, @@ -122,12 +132,39 @@ alignas(256) constexpr std::array HullAndSaveexec{ 0xbf810000, }; -alignas(256) constexpr std::array DomainPatchRead{ +alignas(256) constexpr std::array DomainPatchRead{ 0xf8000941, 0x00000000, 0x34020e84, 0x16020283, 0xe0341000, 0x04030a01, 0xe0341010, 0x04030c01, - 0xe0341020, 0x04030e01, 0x4a060e81, 0x16060690, 0x4c0606ff, 0x00008000, 0xe0341000, 0x04030c03, - 0xbf8c3f70, 0x08040af2, 0x08040d02, 0x10201505, 0x3e201906, 0x3e201d02, 0x10221705, 0x3e221b06, - 0x3e221f02, 0xd5510012, 0x040a0d05, 0x102424ff, 0x40400000, 0x7e260280, 0x7e2802f2, 0xf80008cf, - 0x14131110, 0xf800020f, 0x14131214, 0xbf810000, + 0xe0341020, 0x04030e01, 0x4a060e81, 0x160606ff, 0x000000d0, 0x4c0606ff, 0x00008000, 0xe0301064, + 0x04031503, 0xbf8c3f70, 0x08040af2, 0x08040d02, 0x10201505, 0x3e201906, 0x3e201d02, 0x10221705, + 0x3e221b06, 0x3e221f02, 0xd5510012, 0x040a0d05, 0x102424ff, 0x40400000, 0x102a2aff, 0x3e000000, + 0x7e260280, 0x7e2802f2, 0xf80008cf, 0x14131110, 0xf800020f, 0x14151214, 0xbf810000, +}; + +alignas(256) constexpr std::array DomainNeighbourRecord{ + 0xf8000941, 0x00000000, 0x34020e84, 0x16020283, 0xe0341000, 0x04030a01, 0xe0341010, 0x04030c01, + 0xe0341020, 0x04030e01, 0x4a060e81, 0x160606ff, 0x000000d0, 0x4c0606ff, 0x00008000, 0xe0301064, + 0x04031503, 0xe0301134, 0x04031603, 0xbf8c3f70, 0x062a2b16, 0x08040af2, 0x08040d02, 0x10201505, + 0x3e201906, 0x3e201d02, 0x10221705, 0x3e221b06, 0x3e221f02, 0xd5510012, 0x040a0d05, 0x102424ff, + 0x40400000, 0x102a2aff, 0x3e000000, 0x7e260280, 0x7e2802f2, 0xf80008cf, 0x14131110, 0xf800020f, + 0x14151214, 0xbf810000, +}; + +alignas(256) constexpr std::array DomainFixedPatchRead{ + 0xf8000941, 0x00000000, 0x34020e84, 0x16020283, 0xe0341000, 0x04030a01, 0xe0341010, 0x04030c01, + 0xe0341020, 0x04030e01, 0x4a060e81, 0x160606ff, 0x000000d0, 0x4c0606ff, 0x00008000, 0xe0301064, + 0x04031503, 0xe0300064, 0x04031600, 0xbf8c3f70, 0x062a2b16, 0x08040af2, 0x08040d02, 0x10201505, + 0x3e201906, 0x3e201d02, 0x10221705, 0x3e221b06, 0x3e221f02, 0xd5510012, 0x040a0d05, 0x102424ff, + 0x40400000, 0x102a2aff, 0x3e000000, 0x7e260280, 0x7e2802f2, 0xf80008cf, 0x14131110, 0xf800020f, + 0x14151214, 0xbf810000, +}; + +alignas(256) constexpr std::array DomainNeighbourPoint{ + 0xf8000941, 0x00000000, 0x34020e84, 0x16020283, 0xe0341000, 0x04030a01, 0xe0341010, 0x04030c01, + 0xe0341020, 0x04030e01, 0x4a060e81, 0x160606ff, 0x000000d0, 0x4c0606ff, 0x00008000, 0xe0301064, + 0x04031503, 0xe0301030, 0x04031601, 0xbf8c3f70, 0x062a2b16, 0x08040af2, 0x08040d02, 0x10201505, + 0x3e201906, 0x3e201d02, 0x10221705, 0x3e221b06, 0x3e221f02, 0xd5510012, 0x040a0d05, 0x102424ff, + 0x40400000, 0x102a2aff, 0x3e000000, 0x7e260280, 0x7e2802f2, 0xf80008cf, 0x14131110, 0xf800020f, + 0x14151214, 0xbf810000, }; alignas(256) constexpr std::array DomainCulledPrimitive{ @@ -362,6 +399,25 @@ void RequireLevelClamps(AgcDriver::VulkanDevice& device, const ShaderRecompiler: Require(CoveredPixels(1, false) == 0, "VGT_HOS_MIN_TESS_LEVEL 5 raised the patch with level 0 instead of culling it"); } +const PatchDraw RecordPatches{{3u, 3u, 1u, 2u, 2u, 63u}, Indices}; + +void RequirePatchConstants(AgcDriver::VulkanDevice& device, const ShaderRecompiler::SpirvTarget& target) { + std::uint32_t cursor = 0; + const Stages stages{ + Compile(target, ShaderStage::Local, LocalCode, cursor, RecordPatches), + Compile(target, ShaderStage::TessellationControl, HullPatchConstant, cursor, RecordPatches), + Compile(target, ShaderStage::TessellationEvaluation, DomainPatchRead, cursor, RecordPatches), + Compile(target, ShaderStage::Fragment, PixelCode, cursor, RecordPatches), + }; + Draw(device, stages, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, RecordPatches); + const auto center = Row(-0.5f + 1.0f / 3.0f); + for (const auto& [patch, blue] : {std::pair{0u, 32}, std::pair{2u, 159}}) { + const auto pixel = PixelAt(Column(patch, 0.25f), center); + Require(pixel[0] == 255 && std::abs(pixel[2] - blue) <= 1, "patch " + std::to_string(patch) + " did not take its blue channel from the patch constant its hull wrote, " + PixelText(pixel)); + } + Require(PixelAt(Column(1, 0.25f), center) == Background, "the patch with level 0 was not culled"); +} + void RequireQuads(AgcDriver::VulkanDevice& device, const ShaderRecompiler::SpirvTarget& target) { const auto center = Row(0.0f); std::array frontFacing{}; @@ -449,20 +505,24 @@ int main() { ExpectRefusal(target, ShaderStage::TessellationControl, HullSquaredAddress, "not a whole number of control points"); ExpectRefusal(target, ShaderStage::TessellationControl, HullHalfSlotIndex, "not a whole number of control points"); ExpectRefusal(target, ShaderStage::TessellationControl, HullAndSaveexec, "lane mask"); - ExpectRefusal(target, ShaderStage::TessellationEvaluation, DomainPatchRead, "patch constants"); + ExpectRefusal(target, ShaderStage::TessellationEvaluation, DomainNeighbourRecord, "more than one patch-constant record", RecordPatches); + ExpectRefusal(target, ShaderStage::TessellationEvaluation, DomainFixedPatchRead, "not indexed by its relative patch", RecordPatches); + ExpectRefusal(target, ShaderStage::TessellationEvaluation, DomainNeighbourPoint, "neither its patch's control points nor a patch-constant record", RecordPatches); ExpectRefusal(target, ShaderStage::TessellationEvaluation, DomainCulledPrimitive, "passthrough primitive"); ExpectRefusal(target, ShaderStage::Local, LocalIndexAsData, "outside a tessellation ring or LDS address"); ExpectRefusal(target, ShaderStage::TessellationControl, HullCode, "outside its patch's quad factors", {{3u, 3u, 2u, 2u, 2u}, Indices}); ExpectRefusal(target, ShaderStage::TessellationControl, HullDescendingRecords, "not affine"); ExpectRefusal(target, ShaderStage::TessellationControl, HullDescendingRecords, "not affine", {{3u, 3u, 1u, 2u, 2u, 158u}, Indices}); + ExpectRefusal(target, ShaderStage::TessellationControl, HullDescendingRecords, "outside its control points and patch constants", {{3u, 3u, 1u, 2u, 2u, 129u}, Indices}); RequirePatches(*device, target, HullCode, "hull program"); RequirePatches(*device, target, HullRestoredExecSaved, "hull program saving its restored exec"); RequirePatches(*device, target, HullDynamicIndex, "hull program reading a control point at a computed index"); - RequirePatches(*device, target, HullDescendingRecords, "hull program with descending patch records in 157-patch groups", {{3u, 3u, 1u, 2u, 2u, 157u}, Indices}); + RequirePatches(*device, target, HullDescendingRecords, "hull program with descending patch records in 128-patch groups", {{3u, 3u, 1u, 2u, 2u, 128u}, Indices}); RequireCounterClockwise(*device, target); RequireLevelClamps(*device, target); RequireQuads(*device, target); + RequirePatchConstants(*device, target); device.reset(); RequireRegisteredStages(); std::puts("Tessellation tests passed"); diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ProgramMetadata.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ProgramMetadata.hpp index d47c9d416..3a5e28dd4 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ProgramMetadata.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ProgramMetadata.hpp @@ -30,6 +30,7 @@ struct IrProgramMetadata { std::uint32_t vectorWindowSize = 0; bool vectorWindowIndexedWrites = false; std::uint32_t tessellationControlStride = 0; + std::uint32_t tessellationPatchStride = 0; }; } diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp index 6431b6a40..75ac2a8e4 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp @@ -13,7 +13,8 @@ enum class TessellationAttribute { ControlOutput, EvaluationInput, PatchOutput, - Factor + Factor, + PatchInput }; enum class TessellationBaseKind { diff --git a/core/shader/recompiler/Optimization/src/TessellationLowering.cpp b/core/shader/recompiler/Optimization/src/TessellationLowering.cpp index 6b3854848..b25f9fe61 100644 --- a/core/shader/recompiler/Optimization/src/TessellationLowering.cpp +++ b/core/shader/recompiler/Optimization/src/TessellationLowering.cpp @@ -449,6 +449,7 @@ public: deadCodeEliminator.Eliminate(program); program.Metadata().tessellationLocalStride = localStride; program.Metadata().tessellationControlStride = controlStride; + program.Metadata().tessellationPatchStride = patchStride; } private: @@ -512,19 +513,29 @@ private: void derive() { const auto inputs = static_cast(tessellation.inputControlPoints); const auto outputs = static_cast(tessellation.outputControlPoints); - for (const auto& access : accesses) { + for (auto& access : accesses) { if (access.kind == TessellationAttribute::LocalOutput) agree(localStride, access.address.relative, access.pc); if (access.kind == TessellationAttribute::ControlInput && access.address.vertex != 0) agree(localStride, access.address.vertex, access.pc); if (access.kind == TessellationAttribute::ControlInput && access.address.dynamic && access.address.relative > 0 && access.address.relative % inputs == 0) agree(localStride, access.address.relative / inputs, access.pc); if (access.kind == TessellationAttribute::ControlOutput && access.address.vertex != 0) agree(controlStride, access.address.vertex, access.pc); if (access.kind == TessellationAttribute::EvaluationInput) { - if (access.address.relative <= 0 || access.address.relative % outputs != 0) Fail("domain shader reads of patch constants or other patches are unsupported", access.pc); - agree(controlStride, access.address.relative / outputs, access.pc); + const auto end = access.address.constant + 4 * static_cast(access.components); + if (access.address.relative > 0 && access.address.relative % outputs == 0 && access.address.constant >= 0 && end <= access.address.relative) { + agree(controlStride, access.address.relative / outputs, access.pc); + } else { + access.kind = TessellationAttribute::PatchInput; + } } } - std::optional patchStride; + std::optional recordStride; std::int64_t patchLowest = AddressLimit; std::int64_t patchHighest = 0; + const auto record = [&](const Access& access, std::int64_t end) { + if (recordStride && *recordStride != access.address.relative) Fail("inconsistent patch-constant stride", access.pc); + recordStride = access.address.relative; + patchLowest = std::min(patchLowest, access.address.constant); + patchHighest = std::max(patchHighest, end); + }; for (auto& access : accesses) { const auto& address = access.address; const auto end = address.constant + 4 * static_cast(access.components); @@ -539,11 +550,8 @@ private: break; case TessellationAttribute::ControlOutput: if (address.vertex == 0) { - if (controlStride == 0u || address.constant < outputs * controlStride) Fail("the hull shader writes off-chip memory outside its control points and patch constants", access.pc); - if (patchStride && *patchStride != address.relative) Fail("inconsistent patch-constant stride", access.pc); - patchStride = address.relative; - patchLowest = std::min(patchLowest, address.constant); - patchHighest = std::max(patchHighest, end); + if (controlStride == 0u || evaluator.Lowest(address) < static_cast(tessellation.patchesPerGroup) * outputs * controlStride) Fail("the hull shader writes off-chip memory outside its control points and patch constants", access.pc); + record(access, end); access.kind = TessellationAttribute::PatchOutput; requireFirstInvocation(access); } else if (address.relative != outputs * controlStride || address.constant < 0 || end > controlStride) { @@ -560,11 +568,19 @@ private: requireFirstInvocation(access); break; } + case TessellationAttribute::PatchInput: + if (controlStride == 0u) Fail("the domain shader reads patch constants but no control point, so their locations are unknown", access.pc); + if (address.relative == 0) Fail("the domain shader reads off-chip memory that is not indexed by its relative patch", access.pc); + if (evaluator.Lowest(address) < static_cast(tessellation.patchesPerGroup) * outputs * controlStride) Fail("the domain shader reads off-chip memory that is neither its patch's control points nor a patch-constant record", access.pc); + record(access, end); + break; case TessellationAttribute::PatchOutput: break; } } - if (patchStride && (*patchStride == 0 || std::abs(*patchStride) < patchHighest - patchLowest)) Fail("patch-constant records of neighbouring patches overlap", 0u); + if (!recordStride) return; + if (*recordStride == 0 || std::abs(*recordStride) < patchHighest - patchLowest) Fail(stage == IrShaderStage::TessellationEvaluation ? "the domain shader reads more than one patch-constant record" : "patch-constant records of neighbouring patches overlap", 0u); + patchStride = static_cast(std::abs(*recordStride)); } bool dynamicInputAddress(IrValue* user, IrValue* value) { @@ -625,7 +641,9 @@ private: IrValue& componentAddress(const Access& access, std::uint32_t component) { if (access.address.dynamic) return insert(access, IrOpcode::IAdd32, IrType::U32, {access.addressValue, &constant(access.memoryOffset + 4u * component)}); - IrValue& offset = constant(static_cast(access.address.constant) + 4u * component); + auto address = access.address.constant + 4 * static_cast(component); + if (access.kind == TessellationAttribute::PatchOutput || access.kind == TessellationAttribute::PatchInput) address -= FloorDiv(address, patchStride) * patchStride; + IrValue& offset = constant(static_cast(address)); if (access.kind != TessellationAttribute::ControlInput || access.address.vertex == 0) return offset; IrValue& invocation = insert(access, IrOpcode::GetBuiltin, IrType::U32, {&constant(static_cast(StageInputKind::InvocationId)), &constant(0u)}); IrValue& scaled = insert(access, IrOpcode::IMul32, IrType::U32, {&invocation, &constant(static_cast(access.address.vertex))}); @@ -690,6 +708,7 @@ private: std::vector accesses; std::uint32_t localStride = 0; std::uint32_t controlStride = 0; + std::uint32_t patchStride = 0; }; } diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp index a26c20a54..cff026b91 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp @@ -81,9 +81,8 @@ struct SpirvEmitterState { SpirvModule module; const IrProgram& program; ShaderStageInputInfo inputInfo; - std::array tessVariables {}; + std::array tessVariables {}; std::uint32_t tessInnerVariable = 0; - std::uint32_t tessPatchBase = 0; SpirvRequirements requirements; std::uint32_t laneCount = 1; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index 9f828127f..5fdcb6401 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -516,7 +516,7 @@ std::uint32_t TessellationPointer(SpirvValueEmitContext& ctx, const IrValue& ins if (variable == 0u) { throw std::runtime_error("tessellation attribute has no interface variable"); } - const bool input = kind == TessellationAttribute::ControlInput || kind == TessellationAttribute::EvaluationInput; + const bool input = kind == TessellationAttribute::ControlInput || kind == TessellationAttribute::EvaluationInput || kind == TessellationAttribute::PatchInput; const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput; const auto pointer = state.module.AllocateId(); if (kind == TessellationAttribute::Factor) { @@ -532,10 +532,8 @@ std::uint32_t TessellationPointer(SpirvValueEmitContext& ctx, const IrValue& ins state.module.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)), pointer, outer ? variable : state.tessInnerVariable, ConstantU32(state, outer ? index : index - outerFactors)); return pointer; } - auto address = ctx.Arg(inst, 1); - if (kind == TessellationAttribute::PatchOutput) { - address = EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, state.tessPatchBase)); - } + const auto address = ctx.Arg(inst, 1); + const bool patch = kind == TessellationAttribute::PatchOutput || kind == TessellationAttribute::PatchInput; const bool local = kind == TessellationAttribute::LocalOutput || kind == TessellationAttribute::ControlInput; const auto stride = local ? state.program.Metadata().tessellationLocalStride : state.program.Metadata().tessellationControlStride; const auto type = TypePointer(state, storage, TypeU32(state)); @@ -553,10 +551,10 @@ std::uint32_t TessellationPointer(SpirvValueEmitContext& ctx, const IrValue& ins state.module.AddFunction(spv::OpAccessChain, type, pointer, variable, vertex, ConstantU32(state, constant % stride / 16u), ConstantU32(state, constant % 16u / 4u)); return pointer; } - const auto offset = kind == TessellationAttribute::PatchOutput ? address : EmitBinaryU32(state, spv::OpUMod, address, ConstantU32(state, stride)); + const auto offset = patch ? address : EmitBinaryU32(state, spv::OpUMod, address, ConstantU32(state, stride)); const auto attribute = EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4u)); const auto component = EmitBinaryU32(state, spv::OpBitwiseAnd, EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 2u)), ConstantU32(state, 3u)); - if (kind == TessellationAttribute::LocalOutput || kind == TessellationAttribute::PatchOutput) { + if (kind == TessellationAttribute::LocalOutput || patch) { state.module.AddFunction(spv::OpAccessChain, type, pointer, variable, attribute, component); } else { const auto vertex = kind == TessellationAttribute::ControlOutput ? EmitLaneId(state) : inPatch(EmitBinaryU32(state, spv::OpUDiv, address, ConstantU32(state, stride))); @@ -568,23 +566,13 @@ std::uint32_t TessellationPointer(SpirvValueEmitContext& ctx, const IrValue& ins } void DefineTessellationInterfaces(SpirvEmitterState& state) { - std::array used {}; - std::uint32_t patchBegin = std::numeric_limits::max(); - std::uint32_t patchEnd = 0u; + std::array used {}; for (const IrBlock* block : state.program.BlockOrder()) { for (const IrValue* inst : block->Instructions()) { if (inst->Opcode() != IrOpcode::GetTessellationAttribute && inst->Opcode() != IrOpcode::SetTessellationAttribute) { continue; } - const auto kind = inst->Argument(0)->ImmediateU32(); - used.at(kind) = true; - if (kind == static_cast(TessellationAttribute::PatchOutput)) { - if (!inst->Argument(1)->HasImmediate()) { - throw std::runtime_error("tessellation patch output offset must be immediate"); - } - patchBegin = std::min(patchBegin, inst->Argument(1)->ImmediateU32()); - patchEnd = std::max(patchEnd, inst->Argument(1)->ImmediateU32() + 4u); - } + used.at(inst->Argument(0)->ImmediateU32()) = true; } } if (std::none_of(used.begin(), used.end(), [](bool value) { return value; })) { @@ -599,14 +587,14 @@ void DefineTessellationInterfaces(SpirvEmitterState& state) { continue; } const auto kind = static_cast(index); - const bool input = kind == TessellationAttribute::ControlInput || kind == TessellationAttribute::EvaluationInput; + const bool input = kind == TessellationAttribute::ControlInput || kind == TessellationAttribute::EvaluationInput || kind == TessellationAttribute::PatchInput; + const bool patch = kind == TessellationAttribute::PatchOutput || kind == TessellationAttribute::PatchInput; const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput; std::uint32_t type; if (kind == TessellationAttribute::Factor) { type = array(TypeF32(state), 4u); - } else if (kind == TessellationAttribute::PatchOutput) { - state.tessPatchBase = patchBegin; - type = array(TypeU32Vector(state, 4u), (patchEnd - patchBegin + 15u) / 16u); + } else if (patch) { + type = array(TypeU32Vector(state, 4u), (state.program.Metadata().tessellationPatchStride + 15u) / 16u); } else { const bool local = kind == TessellationAttribute::LocalOutput || kind == TessellationAttribute::ControlInput; const auto stride = local ? state.program.Metadata().tessellationLocalStride : state.program.Metadata().tessellationControlStride; @@ -623,9 +611,9 @@ void DefineTessellationInterfaces(SpirvEmitterState& state) { state.module.AddAnnotation(spv::OpDecorate, state.tessInnerVariable, spv::DecorationBuiltIn, spv::BuiltInTessLevelInner); state.module.AddAnnotation(spv::OpDecorate, state.tessInnerVariable, spv::DecorationPatch); } else { - state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationLocation, kind == TessellationAttribute::PatchOutput ? (state.program.Metadata().tessellationControlStride + 15u) / 16u : 0u); + state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationLocation, patch ? (state.program.Metadata().tessellationControlStride + 15u) / 16u : 0u); } - if (kind == TessellationAttribute::Factor || kind == TessellationAttribute::PatchOutput) { + if (kind == TessellationAttribute::Factor || patch) { state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPatch); } } diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index e0109d65c..a56627860 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -84,6 +84,7 @@ - [Hull programs](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) start from the hull launch registers (user data, ring bases, v0, v1); on the console they start with what the local program leaves, and that it leaves them unchanged is not checked. - [Quad tessellation factors](../../../core/shader/recompiler/Optimization/src/TessellationLowering.cpp) are taken as outer[0..3] then inner[0..1], 24 bytes per patch, as [Mesa stores them](https://github.com/mihawk-99/PS5_Mesa/blob/b3588f78dd7fd46fbadc7ecd487e5f14d4213e4b/src/amd/common/nir/ac_nir_lower_tess_io_to_mem.c); unmeasured on the console. - [VGT_HOS_MIN/MAX_TESS_LEVEL](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) clamp positive tessellation factors (inner ones to at least 1); outer factors that are zero, negative or NaN still cull the patch and such inner factors become max(HOS_MIN, 1). The console's clamp and cull order is not measured. +- [Patch constants](../../../core/shader/recompiler/Optimization/src/TessellationLowering.cpp) that hull programs write and domain programs read are placed by their off-chip address modulo the record stride, as the two compile apart; a domain read of a field the hull never writes, or of only a neighbouring patch's record, is not detected. - The [output modifier](../../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) (`mul:2`, `mul:4`, `div:2`) follows the shader's float mode as measured on RDNA2 (gfx1035): the hardware applies it only with IEEE mode off and output denormals of the result's precision flushed (bit 1 of the `FLOAT_MODE` denormal field clear), and ignores it otherwise. On f32 results it is applied as the hardware does with f32 denormals flushed; a shader without a known float mode is translated with IEEE mode off and f32 denormals flushed, the mode titles use. On f16 and f64 results it is ignored, and it throws in the modes where the hardware applies it. - The [clamp modifier](../../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follows `DX10_CLAMP`, measured on RDNA2 (gfx1035) for f32, f16, packed f16 and f64 results at IEEE=0 and IEEE=1: with `DX10_CLAMP`=1 (and in shaders without a known float mode) a NaN result clamps to +0, including the quieted NaN that `v_pk_min_f16` and `v_pk_max_f16` return for a signaling NaN operand with IEEE=1 (measured on RDNA2 gfx1036 at both `IEEE_MODE` and both `DX10_CLAMP` settings); with `DX10_CLAMP`=0 a NaN result is written unchanged and every other value clamps to [0, 1] as before. f64 results follow the same rule on hardware, but a clamped f64 result with `DX10_CLAMP`=0 throws until it has an execution test. With `DX10_CLAMP`=0 the NaN's bits are those the translation already produces for the instruction without clamp, so where those are left to the driver (f32 and scalar f16 arithmetic) the payload is the driver's, not the hardware's input propagation. `v_dot2_f32_f16` and the integer clamps are unchanged. - [FP16_OVFL](../../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follows the shader's float mode as measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f16 denormals kept and flushed: with `FP16_OVFL`=1 an f16 result that rounds to ±infinity is written as ±0x7bff unless a source operand is infinite, so `v_rcp_f16`, `v_rsq_f16` and `v_log_f16` of ±0 and `v_exp_f16` of 16 or more are clamped while an infinite input passes through, and `v_div_fixup_f16` keeps the infinity it writes for a zero denominator or an infinite numerator and clamps any other infinite quotient, a NaN one included. `v_cvt_pkrtz_f16_f32` rounds toward zero and never overflows. The sources are checked as the f32 values the translation computes with; the rule is applied to `v_fma_mix*_f16` with f32 sources and to `v_interp_p2_f16` without a measurement of infinite f32 sources or parameters. Round modes other than nearest even are not modelled, so the overflow threshold is the nearest-even one (65520).