feat(agc): read hull patch constants in tessellation domain programs
TessellationLowering refused every domain-program read of the off-chip ring outside its patch's control points, so domain programs that read the hull's patch constants, like the quad terrain domain in Ghost of Yotei at 0x8000 - 208 * (rel + 1), aborted. Such reads now lower to a new PatchInput kind and become Patch inputs. Hull outputs and domain inputs place a constant by its off-chip address modulo the record stride, in a record-sized array at location ceil(hsStride / 16); each program derives both on its own, so the two interfaces match although they compile separately. Domain reads at an address that does not move with the relative patch, across more than one record or inside the control-point region still throw, as does a domain program that reads patch constants but no control point. Hull patch-constant writes now take the same bound: above the control points of all NUM_PATCHES patches, not just one patch's.
This commit is contained in:
parent
25a83722c7
commit
247162dc4f
|
|
@ -74,6 +74,16 @@ alignas(256) constexpr std::array<std::uint32_t, 50> HullDescendingRecords{
|
|||
0x0202070b, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 52> 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<std::uint32_t, 46> 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<std::uint32_t, 49> HullAndSaveexec{
|
|||
0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 36> DomainPatchRead{
|
||||
alignas(256) constexpr std::array<std::uint32_t, 39> 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<std::uint32_t, 42> 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<std::uint32_t, 42> 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<std::uint32_t, 42> 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<std::uint32_t, 32> 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<bool, 2> 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");
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ struct IrProgramMetadata {
|
|||
std::uint32_t vectorWindowSize = 0;
|
||||
bool vectorWindowIndexedWrites = false;
|
||||
std::uint32_t tessellationControlStride = 0;
|
||||
std::uint32_t tessellationPatchStride = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ enum class TessellationAttribute {
|
|||
ControlOutput,
|
||||
EvaluationInput,
|
||||
PatchOutput,
|
||||
Factor
|
||||
Factor,
|
||||
PatchInput
|
||||
};
|
||||
|
||||
enum class TessellationBaseKind {
|
||||
|
|
|
|||
|
|
@ -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<std::int64_t>(tessellation.inputControlPoints);
|
||||
const auto outputs = static_cast<std::int64_t>(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<std::int64_t>(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<std::int64_t> patchStride;
|
||||
std::optional<std::int64_t> 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<std::int64_t>(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<std::int64_t>(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<std::int64_t>(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::uint32_t>(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<std::uint32_t>(access.address.constant) + 4u * component);
|
||||
auto address = access.address.constant + 4 * static_cast<std::int64_t>(component);
|
||||
if (access.kind == TessellationAttribute::PatchOutput || access.kind == TessellationAttribute::PatchInput) address -= FloorDiv(address, patchStride) * patchStride;
|
||||
IrValue& offset = constant(static_cast<std::uint32_t>(address));
|
||||
if (access.kind != TessellationAttribute::ControlInput || access.address.vertex == 0) return offset;
|
||||
IrValue& invocation = insert(access, IrOpcode::GetBuiltin, IrType::U32, {&constant(static_cast<std::uint32_t>(StageInputKind::InvocationId)), &constant(0u)});
|
||||
IrValue& scaled = insert(access, IrOpcode::IMul32, IrType::U32, {&invocation, &constant(static_cast<std::uint32_t>(access.address.vertex))});
|
||||
|
|
@ -690,6 +708,7 @@ private:
|
|||
std::vector<Access> accesses;
|
||||
std::uint32_t localStride = 0;
|
||||
std::uint32_t controlStride = 0;
|
||||
std::uint32_t patchStride = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,9 +81,8 @@ struct SpirvEmitterState {
|
|||
SpirvModule module;
|
||||
const IrProgram& program;
|
||||
ShaderStageInputInfo inputInfo;
|
||||
std::array<std::uint32_t, 6> tessVariables {};
|
||||
std::array<std::uint32_t, 7> tessVariables {};
|
||||
std::uint32_t tessInnerVariable = 0;
|
||||
std::uint32_t tessPatchBase = 0;
|
||||
|
||||
SpirvRequirements requirements;
|
||||
std::uint32_t laneCount = 1;
|
||||
|
|
|
|||
|
|
@ -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<bool, 6> used {};
|
||||
std::uint32_t patchBegin = std::numeric_limits<std::uint32_t>::max();
|
||||
std::uint32_t patchEnd = 0u;
|
||||
std::array<bool, 7> 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<std::uint32_t>(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<TessellationAttribute>(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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
Loading…
Reference in New Issue