Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2456,9 +2456,9 @@ void verifyUnnormalizedSamplers() {
require(proven(plain, false), "unnormalized samplers: a normalized S# was flagged");
require(plain.variantId == accepted.variantId && plain.spirv.Words() == accepted.spirv.Words(), "unnormalized samplers: the unnormalized S# compiled another variant");

for (const std::uint32_t mimg : {0xf0900f08u, 0xf0800f08u, 0xf0940f08u, 0xf0840f08u}) {
for (const std::uint32_t mimg : {0xf0900f08u, 0xf0800f08u, 0xf0940f08u, 0xf0840f08u, 0xf0880f08u}) {
const auto result = recompile(program(mimg), userData(unnormalized));
require(proven(result, true), "unnormalized samplers: a compute image_sample_l, image_sample, image_sample_b or image_sample_cl was not flagged");
require(proven(result, true), "unnormalized samplers: a compute image_sample_l, image_sample, image_sample_b, image_sample_cl or image_sample_d was not flagged");
auditSpirv(result.spirv.Words());
}

Expand All @@ -2468,7 +2468,6 @@ void verifyUnnormalizedSamplers() {
reject(0xf0bc0f08u, imageData(unnormalized, Type2D, Format32Float, 0u), "unnormalized guest sampler is used with depth comparison, which is not implemented", "unnormalized samplers: image_sample_c_lz was accepted");
reject(0xf11c0108u, userData(unnormalized), "unnormalized guest sampler is used by a gather, which is not implemented", "unnormalized samplers: image_gather4_lz was accepted");
reject(0xf1800308u, userData(unnormalized), "unnormalized guest sampler is used by image_get_lod, which is not implemented", "unnormalized samplers: image_get_lod was accepted");
reject(0xf0880f08u, userData(unnormalized), "unnormalized guest sampler is used by a sample with derivatives, which is not implemented", "unnormalized samplers: image_sample_d was accepted");
reject(0xf0800f09u, userData(unnormalized), "unnormalized guest sampler is used by an image_sample_*_a variant, which is not implemented", "unnormalized samplers: image_sample_a was accepted");
reject(0xf09c0f18u, imageData(unnormalized, TypeCube, Format8888UNorm, 5u), "unnormalized guest sampler samples a 1D-array, 2D-array, 3D, cube or multisampled image", "unnormalized samplers: a cube T# was accepted");
reject(0xf09c0f10u, imageData(unnormalized, Type3D, Format8888UNorm, 3u), "unnormalized guest sampler samples a 1D-array, 2D-array, 3D, cube or multisampled image", "unnormalized samplers: a 3D T# was accepted");
Expand Down Expand Up @@ -2543,6 +2542,14 @@ void verifyUnnormalizedSamplers() {
require(!masks.empty() && std::ranges::all_of(masks, [](std::uint32_t mask) { return (mask & spv::ImageOperandsOffsetMask) != 0u; }), "unnormalized texel offsets: a normalized computed offset lost its Offset operand");
}

const auto gradient = program(0xf0880f08u);
const auto gradientAddress = nextAddress += 0x1000u;
const auto gradientUnnormalized = recompile(gradient, userData(unnormalized), gradientAddress);
const auto gradientNormalized = recompile(gradient, userData(normalized), gradientAddress);
require(proven(gradientNormalized, false) && gradientNormalized.variantId == gradientUnnormalized.variantId && gradientNormalized.PipelineVariantId() != gradientUnnormalized.PipelineVariantId(), "unnormalized samplers: a normalized image_sample_d did not get its own specialization of the shared artifact");
require(sampleMasks(gradientNormalized.spirv.Words()) == std::vector<std::uint32_t>{spv::ImageOperandsGradMask}, "unnormalized samplers: a normalized image_sample_d lost its derivatives");
require(sampleMasks(gradientUnnormalized.spirv.Words()) == std::vector<std::uint32_t>{spv::ImageOperandsLodMask}, "unnormalized samplers: an unnormalized image_sample_d kept its derivatives");

const std::array<std::uint32_t, 5> pixelCode{0xf0800f08u, 0x00400801u, 0xf800180fu, 0x0b0a0908u, 0xbf810000u};
const auto pixelData = userData(unnormalized);
RecompileRequest pixel{};
Expand All @@ -2556,7 +2563,23 @@ void verifyUnnormalizedSamplers() {
pixel.target.subgroupSize = 64;
pixel.layout.pushConstantSizeBytes = 128;
pixel.useCache = false;
expectFailure([&] { static_cast<void>(Recompile(pixel)); }, "unnormalized guest sampler is used by an implicit-LOD sample, which is not implemented", "unnormalized samplers: a pixel image_sample was accepted");
const auto implicitSamples = [](const std::vector<std::uint32_t>& words) {
std::size_t samples = 0;
for (std::size_t cursor = 5; cursor < words.size();) {
const auto count = words[cursor] >> 16u;
require(count != 0 && count <= words.size() - cursor, "unnormalized samplers: truncated SPIR-V instruction");
if ((words[cursor] & 0xffffu) == spv::OpImageSampleImplicitLod) ++samples;
cursor += count;
}
return samples;
};
const auto pixelUnnormalized = Recompile(pixel);
require(proven(pixelUnnormalized, true), "unnormalized samplers: a pixel image_sample through an unnormalized S# was not flagged");
auditSpirv(pixelUnnormalized.spirv.Words());
const auto normalizedPixelData = userData(normalized);
pixel.context.userData = normalizedPixelData;
const auto pixelNormalized = Recompile(pixel);
require(proven(pixelNormalized, false) && implicitSamples(pixelNormalized.spirv.Words()) == 1u, "unnormalized samplers: a pixel image_sample through a normalized S# is not an implicit-LOD sample");
}

void verifyUnusedUnnormalizedSampler() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ alignas(256) constexpr std::array<std::uint32_t, 18> LevelZeroCode{
0x80000b01, 0xbf810000,
};

alignas(256) constexpr std::array<std::uint32_t, 22> GradientCode{
0x34020086, 0xe0301000, 0x80000601, 0xe0301004, 0x80000701, 0xbf8c3f70, 0x7e0402f6, 0x7e060280,
0x7e080280, 0x7e0a02f6, 0xf0880f08, 0x00610802, 0xbf8c3f70, 0xe0701010, 0x80000801, 0xe0701014,
0x80000901, 0xe0701018, 0x80000a01, 0xe070101c, 0x80000b01, 0xbf810000,
};

alignas(256) constexpr std::array<std::uint32_t, 45> OffsetCode{
0x34020086, 0xe0301008, 0x80000201, 0xe0301000, 0x80000301, 0xe0301004, 0x80000401, 0xbf8c3f70,
0x7e0a02ff, 0x402ccccd, 0x7e2802ff, 0x0000023d, 0xf0dc0f08, 0x00610802, 0xf0d00f08, 0x00610c02,
Expand All @@ -75,6 +81,7 @@ struct Program {

const Program Plain{Code, {"image_sample_lz", "image_sample_l 2.7", "image_sample"}, {Offset::None, Offset::None, Offset::None}};
const Program LevelZero{LevelZeroCode, {"image_sample_lz", "", ""}, {Offset::None, Offset::None, Offset::None}, 1};
const Program Gradient{GradientCode, {"image_sample_d (4, 0, 0, 4)", "", ""}, {Offset::None, Offset::None, Offset::None}, 1};
const Program Offsets{OffsetCode, {"image_sample_lz_o", "image_sample_l_o 2.7", "image_sample_lz_o (-3, 2) NSA"}, {Offset::Lane, Offset::Lane, Offset::Constant}};

alignas(256) constexpr std::array<std::uint32_t, 34> ConstantCode{
Expand Down Expand Up @@ -313,6 +320,8 @@ int main() {
Check(Plain, sampler, 0u, "first level of a 4-level image", coordinates, multiSamples);
Require(multiSamples == singleSamples, std::string(sampler.name) + ": the first level of a 4-level image does not sample like a 1-level image");
Check(Offsets, sampler, 0u, "1-level image", offsetCoordinates, Run(*device, Offsets, single, sampler.words, offsetCoordinates));
Check(Gradient, sampler, 0u, "1-level image", coordinates, Run(*device, Gradient, single, sampler.words, coordinates));
Check(Gradient, sampler, 0u, "first level of a 4-level image", coordinates, Run(*device, Gradient, multi, sampler.words, coordinates));
if (!sampler.atBaseLevel) continue;
const auto levelZeroSingle = Run(*device, LevelZero, single, sampler.words, coordinates);
const auto levelZeroChain = Run(*device, LevelZero, chain, sampler.words, coordinates);
Expand All @@ -324,6 +333,7 @@ int main() {
ExpectFailure(*device, Plain, chain, Samplers[0].words, "single-level", "a 4-level view read by image_sample_l and image_sample");
ExpectFailure(*device, Plain, fromLevelOne, Samplers[0].words, "single-level", "a view starting at mip 1 read by image_sample_l and image_sample");
ExpectFailure(*device, Offsets, chain, Samplers[0].words, "single-level", "a 4-level view read with texel offsets");
ExpectFailure(*device, Gradient, chain, Samplers[0].words, "single-level", "a 4-level view read by image_sample_d");
ExpectFailure(*device, LevelZero, chain, Samplers[2].words, "single-level", "a 4-level view through an S# with a minimum LOD and a LOD bias");
ExpectFailure(*device, Plain, single, {0x00008092u, 0x00fff000u, 0x05100000u, 0u}, "different minification", "unequal minification and magnification filters");
ExpectFailure(*device, Plain, single, {0x00008090u, 0x00fff000u, 0x05500000u, 0u}, "clamp mode 0", "wrap on X");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ inline bool FoldsTexelOffsets(const SamplerResource& sampler) {
return (sampler.uses & SamplerUseOffset) != 0u && (sampler.uses & ~static_cast<std::uint32_t>(SamplerUseExplicitLod | SamplerUseOffset | SamplerUseNonZeroLod)) == 0u && !sampler.depthCompare;
}

inline bool SamplesBaseLevelIfUnnormalized(const SamplerResource& sampler) {
return (sampler.uses & (SamplerUseImplicitLod | SamplerUseGradient)) != 0u && (sampler.uses & ~static_cast<std::uint32_t>(SamplerUseExplicitLod | SamplerUseImplicitLod | SamplerUseGradient | SamplerUseNonZeroLod)) == 0u && !sampler.depthCompare;
}

struct SampledResourcePair {
std::uint32_t image = 0;
std::uint32_t sampler = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ UnnormalizedProof ProveUnnormalized(const ShaderInfo& info, const ResourceSnapsh
continue;
}
const std::uint32_t unsupported = sampler.uses & ~static_cast<std::uint32_t>(SamplerUseExplicitLod | SamplerUseOffset | SamplerUseNonZeroLod);
if (unsupported != 0u) {
if (unsupported != 0u && !SamplesBaseLevelIfUnnormalized(sampler)) {
failUnnormalized(UnnormalizedUseReason(unsupported));
}
if (sampler.depthCompare) {
Expand Down Expand Up @@ -259,6 +259,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
const auto unnormalized = ProveUnnormalized(info, snapshot);
for (std::uint32_t index = 0; index < info.samplers.size(); ++index) {
if (FoldsTexelOffsets(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::FoldOffsetBase + index, unnormalized.samplers[index] ? 1u : 0u});
if (SamplesBaseLevelIfUnnormalized(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::UnnormalizedBaseLevelBase + index, unnormalized.samplers[index] ? 1u : 0u});
}
std::vector<std::uint32_t> compareStates(info.images.size());
for (std::uint32_t index = 0; index < info.images.size(); ++index) {
Expand Down
1 change: 1 addition & 0 deletions core/shader/recompiler/PipelineSpecialization.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ inline constexpr std::uint32_t DescriptorIndexStride = 128u;
inline constexpr std::uint32_t ImageModeBase = 32768u;
inline constexpr std::uint32_t ImageModeStride = 256u;
inline constexpr std::uint32_t FoldOffsetBase = 57344u;
inline constexpr std::uint32_t UnnormalizedBaseLevelBase = 57408u;

inline constexpr std::uint32_t DescriptorIndex(std::uint32_t binding, std::uint32_t element) {
return DescriptorIndexBase + binding * DescriptorIndexStride + element;
Expand Down
23 changes: 16 additions & 7 deletions core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1474,10 +1474,10 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con
operands.insert(operands.end() - ((operandMask & spv::ImageOperandsMinLodMask) != 0u ? 1 : 0), offset);

}
const auto emitSample = [&](std::uint32_t coord, std::uint32_t mask, const std::vector<std::uint32_t>& arguments) {
const auto emitSample = [&](std::uint32_t coord, std::uint32_t mask, const std::vector<std::uint32_t>& arguments, std::uint32_t sampleOpcode) {
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler, access.slot);
const auto sample = state.module.AllocateId();
std::vector<std::uint32_t> words = {opcode, resultType, sample, sampled, coord};
std::vector<std::uint32_t> words = {sampleOpcode, resultType, sample, sampled, coord};
if (setup.dref) {
words.push_back(drefValue);
}
Expand All @@ -1489,10 +1489,19 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con
return sample;
};
std::uint32_t sample = 0;
if (!foldOffset) {
sample = emitSample(setup.coord, operandMask, operands);
const bool baseLevelIfUnnormalized = !setup.dref && SamplesBaseLevelIfUnnormalized(state.program.Info().samplers.at(mem.sampler));
if (baseLevelIfUnnormalized) {
const auto selector = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::UnnormalizedBaseLevelBase + mem.sampler, 0u);
const auto unnormalized = Binary(state, spv::OpINotEqual, TypeBool(state), selector, ConstantU32(state, 0u));
sample = EmitValueIfElse(state, unnormalized, resultType, [&] {
return emitSample(setup.coord, spv::ImageOperandsLodMask, {ZeroF32(state)}, spv::OpImageSampleExplicitLod);
}, [&] {
return emitSample(setup.coord, operandMask, operands, opcode);
});
} else if (!foldOffset) {
sample = emitSample(setup.coord, operandMask, operands, opcode);
} else if (!offsetOperand) {
sample = emitSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands);
sample = emitSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands, opcode);
} else {
const auto selector = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::FoldOffsetBase + mem.sampler, 0u);
const auto folded = Binary(state, spv::OpINotEqual, TypeBool(state), selector, ConstantU32(state, 0u));
Expand All @@ -1502,11 +1511,11 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con
state.module.AddFunction(spv::OpSelectionMerge, merge, spv::SelectionControlMaskNone);
state.module.AddFunction(spv::OpBranchConditional, folded, foldLabel, operandLabel);
EmitLabel(state, foldLabel);
const auto foldSample = emitSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands);
const auto foldSample = emitSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands, opcode);
const auto foldEnd = state.currentLabel;
state.module.AddFunction(spv::OpBranch, merge);
EmitLabel(state, operandLabel);
const auto operandSample = emitSample(setup.coord, operandMask, operands);
const auto operandSample = emitSample(setup.coord, operandMask, operands, opcode);
const auto operandEnd = state.currentLabel;
state.module.AddFunction(spv::OpBranch, merge);
EmitLabel(state, merge);
Expand Down
Loading