Merge pull request #1418 from gjonn/feat/agc-unnormalized-implicit-lod

feat(agc): sample the base level through unnormalized samplers
This commit is contained in:
Adrià 2026-10-13 11:49:07 +02:00 committed by GitHub
commit 8bcd97cd5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 60 additions and 12 deletions

View File

@ -2634,9 +2634,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());
}
@ -2646,7 +2646,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");
@ -2721,6 +2720,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{};
@ -2734,7 +2741,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() {

View File

@ -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,
@ -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{
@ -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);
@ -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");

View File

@ -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;

View File

@ -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) {
@ -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) {

View File

@ -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;

View File

@ -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);
}
@ -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));
@ -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);