Merge pull request #1417 from gjonn/feat/agc-fold-constant-swizzle-samples

feat(agc): fold samples of views whose channels all select constants
This commit is contained in:
Adrià 2026-10-10 03:01:21 +02:00 committed by GitHub
commit 5a014b888a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 117 additions and 6 deletions

View File

@ -1564,6 +1564,11 @@ void verifyUnusedUnnormalizedSampler() {
auto selected = info;
selected.images[0].indirectRoot = 0u;
expectFailure([&] { static_cast<void>(populate(selected)); }, "unnormalized guest sampler samples an image selected at run time, which is not implemented", "unnormalized samplers: an image table root was accepted");
snapshot.images[0].dwords[3] = 0xa0000041u;
const auto constant = populate(info);
require(constant.size() == 1u && constant[0].samplerUnnormalized == std::vector<bool>{true}, "unnormalized samplers: a 3D view whose channels select constants was bound or refused");
expectFailure([&] { static_cast<void>(populate(selected)); }, "unnormalized guest sampler samples an image selected at run time, which is not implemented", "unnormalized samplers: an image table root whose channels select constants was accepted");
snapshot.images[0].dwords[3] = 0x90000facu;
auto compared = info;
compared.samplers[0].depthCompare = true;
expectFailure([&] { static_cast<void>(populate(compared)); }, "unnormalized guest sampler is used with depth comparison, which is not implemented", "unnormalized samplers: a depth-compare S# without live uses was accepted");

View File

@ -28,7 +28,12 @@ constexpr std::int32_t Width = 13;
constexpr std::int32_t Height = 7;
constexpr std::uint32_t StorageLevels = 4;
constexpr std::uint32_t Format8888UNorm = 56;
constexpr std::uint32_t Format32UInt = 20;
constexpr std::uint32_t Format32SInt = 21;
constexpr std::uint32_t Type1D = 8;
constexpr std::uint32_t Type2D = 9;
constexpr std::uint32_t Type3D = 10;
constexpr std::uint32_t OneZeroOneZero = 0x041u;
constexpr std::uint8_t OtherLevelTexel = 0x40u;
alignas(256) std::array<std::uint32_t, Threads * Words> Buffer{};
alignas(256) std::array<std::uint8_t, 16384> SingleLevel{};
@ -44,6 +49,14 @@ alignas(256) constexpr std::array<std::uint32_t, 40> Code{
constexpr std::array<const char*, 3> Instructions{"image_sample_lz", "image_sample_l 2.7", "image_sample"};
alignas(256) constexpr std::array<std::uint32_t, 34> ConstantCode{
0x34020086, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xbf8c3f70, 0xf09c0608, 0x00610802,
0xf11c0208, 0x00610a02, 0xf11c0408, 0x00610e02, 0xbf8c3f70, 0xe0701010, 0x80000801, 0xe0701014,
0x80000901, 0xe0701018, 0x80000a01, 0xe070101c, 0x80000b01, 0xe0701020, 0x80000c01, 0xe0701024,
0x80000d01, 0xe0701028, 0x80000e01, 0xe070102c, 0x80000f01, 0xe0701030, 0x80001001, 0xe0701034,
0x80001101, 0xbf810000,
};
struct SamplerCase {
const char* name;
std::array<std::uint32_t, 4> words;
@ -107,6 +120,12 @@ void FillTexture(std::span<std::uint8_t> texels, std::uint32_t levels) {
}
}
std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
@ -128,7 +147,7 @@ std::array<std::uint32_t, 8> TextureDescriptor(const void* data, std::uint32_t l
using Samples = std::vector<std::array<std::uint32_t, Results>>;
Samples Run(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& texture, const std::array<std::uint32_t, 4>& sampler, const std::vector<Coordinate>& coordinates) {
Samples Run(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& texture, const std::array<std::uint32_t, 4>& sampler, const std::vector<Coordinate>& coordinates, std::span<const std::uint32_t> code = Code) {
Samples samples;
for (std::size_t first = 0; first < coordinates.size(); first += Threads) {
Buffer.fill(0xdeadbeefu);
@ -142,7 +161,6 @@ Samples Run(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>&
std::copy(buffer.begin(), buffer.end(), userData.begin());
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
std::copy(sampler.begin(), sampler.end(), userData.begin() + 12);
const std::span<const std::uint32_t> code(Code);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
@ -231,6 +249,43 @@ int main() {
ExpectFailure(*device, TextureDescriptor(MultiLevel.data(), 2u, 2u), Samplers[0].words, "single-level", "a 3-level view");
ExpectFailure(*device, single, {0x00008092u, 0x00fff000u, 0x05100000u, 0u}, "different minification", "unequal minification and magnification filters");
ExpectFailure(*device, single, {0x00008090u, 0x00fff000u, 0x05500000u, 0u}, "clamp mode 0", "wrap on X");
const auto constantView = [&](std::uint32_t type, std::uint32_t format, std::uint32_t swizzle) {
auto descriptor = TextureDescriptor(SingleLevel.data(), 0u, 0u);
descriptor[1] = (descriptor[1] & ~(0x1ffu << 20u)) | (format << 20u);
descriptor[3] = swizzle | (type << 28u);
return descriptor;
};
for (const auto& sampler : Samplers) {
for (const auto& sample : Run(*device, constantView(Type3D, Format8888UNorm, OneZeroOneZero), sampler.words, coordinates)) {
for (std::uint32_t index = 0; index < Results; ++index) {
const std::uint32_t expected = index % 2u == 0u ? 0x3f800000u : 0u;
Require(sample[index] == expected, std::string(sampler.name) + ": a 3D view whose channels select (1, 0, 1, 0) returned " + Hex(sample[index]) + " for result " + std::to_string(index));
}
}
}
struct ConstantCase {
const char* name;
std::array<std::uint32_t, 8> texture;
std::uint32_t one;
};
const std::array<ConstantCase, 4> constants{{
{"a 3D UNORM view selecting (1, 0, 1, 0)", constantView(Type3D, Format8888UNorm, OneZeroOneZero), 0x3f800000u},
{"a 32_UINT view selecting (1, 0, 1, 0)", constantView(Type2D, Format32UInt, OneZeroOneZero), 1u},
{"a 32_SINT view selecting (1, 0, 1, 0)", constantView(Type2D, Format32SInt, OneZeroOneZero), 1u},
{"a 1D view selecting (0, 0, 0, 0)", constantView(Type1D, Format8888UNorm, 0u), 0u},
}};
const std::array<const char*, 3> constantInstructions{"image_sample_lz dmask 0x6", "image_gather4_lz dmask 0x2", "image_gather4_lz dmask 0x4"};
for (const auto& constant : constants) {
auto normalized = Samplers[0].words;
normalized[0] &= ~0x8000u;
for (const auto& sample : Run(*device, constant.texture, normalized, coordinates, ConstantCode)) {
const std::array<std::uint32_t, 10> expected{0u, constant.one, 0u, 0u, 0u, 0u, constant.one, constant.one, constant.one, constant.one};
for (std::uint32_t index = 0; index < expected.size(); ++index) {
const auto instruction = constantInstructions[index < 2u ? 0u : index < 6u ? 1u : 2u];
Require(sample[index] == expected[index], std::string(instruction) + ", " + constant.name + ": result " + std::to_string(index) + " is " + Hex(sample[index]) + ", expected " + Hex(expected[index]));
}
}
}
std::puts("image sample unnormalized tests passed");
return 0;
} catch (const std::exception& error) {

View File

@ -83,6 +83,8 @@ struct ImageResource {
bool packed = false;
bool fmaskCompatible = true;
bool depthBitsCompatible = true;
bool constantSwizzle = false;
bool constantSwizzleCompatible = true;
std::uint32_t byElements = 0;
std::uint32_t byComponents = 0;
IrBufferFormat packedFormat = IrBufferFormat::Invalid;

View File

@ -187,6 +187,9 @@ UnnormalizedProof ProveUnnormalized(const ShaderInfo& info, const ResourceSnapsh
if (image.indirectRoot != ImageResource::NoIndirectImage) {
failUnnormalized("samples an image selected at run time");
}
if (image.constantSwizzle) {
continue;
}
if ((image.dimension != RdnaImageDimension::Dim1D && image.dimension != RdnaImageDimension::Dim2D) || image.cube) {
failUnnormalized("samples a 1D-array, 2D-array, 3D, cube or multisampled image");
}
@ -295,7 +298,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
for (std::uint32_t component = 0; component < 4u; ++component) plan.specialization.push_back({PipelineSpecialization::ExportBase + target * 4u + component, (exportMappings[target] >> (component * 2u)) & 3u});
}
std::vector<std::uint32_t> samplerModes(info.samplers.size(), 0u);
const auto samplerMode = [](const ImageResource& image) { return image.numericClass == IrTextureNumericClass::Sint || image.conversionFormat != IrBufferFormat::Invalid || image.depthBits ? 2u : 1u; };
const auto samplerMode = [](const ImageResource& image) { return !image.constantSwizzle && (image.numericClass == IrTextureNumericClass::Sint || image.conversionFormat != IrBufferFormat::Invalid || image.depthBits) ? 2u : 1u; };
for (const auto& pair : info.sampledPairs) {
const auto& image = info.images.at(pair.image);
if (image.indirectRoot == ImageResource::NoIndirectImage) samplerModes.at(pair.sampler) |= samplerMode(info.runtimeImageModes.at(pair.image).at(imageModes[pair.image]));
@ -343,7 +346,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
previous = resource;
{
const auto word = snapshot.images.at(resource).dwords[3];
active = DescriptorBindingForImage(mode) == logical.kind && mode.packedFormat != IrBufferFormat::Fmask8_S2_F1 && (image.mipMode != ImageMipMode::DynamicStorage || mip <= ((word >> 16u) & 0xfu) - ((word >> 12u) & 0xfu));
active = DescriptorBindingForImage(mode) == logical.kind && mode.packedFormat != IrBufferFormat::Fmask8_S2_F1 && !mode.constantSwizzle && (image.mipMode != ImageMipMode::DynamicStorage || mip <= ((word >> 16u) & 0xfu) - ((word >> 12u) & 0xfu));
}
} else if (samplerHeap) {
active = (samplerModes.at(resource) & (1u << (element & 1u))) != 0u;

View File

@ -703,6 +703,21 @@ std::vector<ImageResource> ResourceMaterializer::RuntimeImageModes(const ImageRe
modes.push_back(mode);
}
}
if (image.constantSwizzleCompatible && !storage && !image.depthCompare && !image.packed && image.indirectRoot == ImageResource::NoIndirectImage) {
for (const auto numeric : {IrTextureNumericClass::Float, IrTextureNumericClass::Uint, IrTextureNumericClass::Sint}) {
auto mode = image;
mode.numericClass = numeric;
mode.conversionFormat = IrBufferFormat::Invalid;
mode.packedFormat = IrBufferFormat::Invalid;
mode.depthBits = false;
mode.depthUnorm16 = false;
mode.cube = false;
mode.mipCount = 1u;
mode.shaderSwizzle = ShaderImageIdentitySwizzle;
mode.constantSwizzle = true;
modes.push_back(mode);
}
}
if (modes.empty()) throw std::runtime_error("image instruction has no supported runtime modes");
return modes;
}
@ -727,6 +742,11 @@ std::uint32_t ResourceMaterializer::RuntimeImageMode(const ImageResource& image,
}
}
if (decoded.mipCount > (image.mipMode == ImageMipMode::DynamicStorage ? RuntimeAbi::StorageHeapCapacity : 1u)) throw std::runtime_error("runtime storage image mip capacity exceeded");
if ((descriptorImageSwizzle(descriptor) & 06666u) == 0u && !decoded.fmask && !decoded.depthBits && decoded.conversionFormat == IrBufferFormat::Invalid && !decoded.srgbDecode) {
for (std::uint32_t index = 0u; index < modes.size(); ++index) {
if (modes[index].constantSwizzle && modes[index].numericClass == decoded.numericClass) return index;
}
}
for (std::uint32_t index = 0u; index < modes.size(); ++index) {
const auto& mode = modes[index];
if (((mode.emulatedCompare & EmulatedCompare::Enabled) != 0u) != emulated) continue;

View File

@ -1061,6 +1061,7 @@ private:
image.srgbDecodeCompatible = image.srgbDecodeCompatible && !ImageOpcodeInfoOf(op).needsSampler;
image.fmaskCompatible = image.fmaskCompatible && op == IrOpcode::ImageRead && memory.dataBits == 32u;
image.depthBitsCompatible = image.depthBitsCompatible && memory.dataBits == 32u;
image.constantSwizzleCompatible = image.constantSwizzleCompatible && (op == IrOpcode::ImageSampleRaw || op == IrOpcode::ImageGatherRaw);
if ((memory.imageSampleFlags & RdnaImageSampleFlagCompare) != 0u) {
constexpr auto unsupported = RdnaImageSampleFlagLod | RdnaImageSampleFlagDerivative;
if (op == IrOpcode::ImageGatherRaw || (memory.imageSampleFlags & unsupported) != 0u) image.emulatedCompare |= EmulatedCompare::Unsupported;

View File

@ -474,6 +474,8 @@ void encodeInfo(Writer& writer, const CompiledShaderInfo& compiled) {
out.Value(image.packed);
out.Value(image.fmaskCompatible);
out.Value(image.depthBitsCompatible);
out.Value(image.constantSwizzle);
out.Value(image.constantSwizzleCompatible);
out.Value(image.byElements);
out.Value(image.byComponents);
out.Value(image.packedFormat);
@ -550,7 +552,7 @@ void decodeInfo(Reader& reader, CompiledShaderInfo& compiled) {
in.Value(buffer.scalar);
in.Value(buffer.typedAlignment);
});
reader.List(info.images, 72, [](Reader& in, ImageResource& image) {
reader.List(info.images, 74, [](Reader& in, ImageResource& image) {
in.Value(image.source);
in.Value(image.firstUsePc);
in.Value(image.resourceClass);
@ -575,6 +577,8 @@ void decodeInfo(Reader& reader, CompiledShaderInfo& compiled) {
in.Value(image.packed);
in.Value(image.fmaskCompatible);
in.Value(image.depthBitsCompatible);
in.Value(image.constantSwizzle);
in.Value(image.constantSwizzleCompatible);
in.Value(image.byElements);
in.Value(image.byComponents);
in.Value(image.packedFormat);

View File

@ -12,7 +12,7 @@
namespace ShaderRecompiler::ShaderDiskCache {
inline constexpr std::uint32_t FormatVersion = 21;
inline constexpr std::uint32_t FormatVersion = 22;
enum class LoadStatus {
Loaded,

View File

@ -1470,7 +1470,28 @@ TableSelection TableSlot(SpirvValueEmitContext& ctx, const IrValue& inst, const
return EmitIndirectImageSelector(ctx, image, key);
}
void EmitConstantSwizzleSample(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) {
auto& state = ctx.state;
const auto numericClass = access.image.numericClass;
const bool gather = access.inst.Opcode() == IrOpcode::ImageGatherRaw;
const auto scalarType = ImageScalarType(state, numericClass);
const auto one = numericClass == IrTextureNumericClass::Float ? ConstantF32(state, 0x3f800000u) : numericClass == IrTextureNumericClass::Sint ? ConstantI32(state, 1) : ConstantU32(state, 1u);
const auto zero = SampledComponentZero(state, numericClass);
std::uint32_t channels[4] = {};
for (std::uint32_t channel = 0; channel < 4u; channel++) {
const auto selector = RuntimeImageSwizzle(state, access.mem.resource, gather ? ImageGatherComponent(EffectiveDmask(access.mem)) : channel);
channels[channel] = Select(state, scalarType, Binary(state, spv::OpIEqual, TypeBool(state), selector, ConstantU32(state, 1u)), one, zero);
}
const auto value = state.module.AllocateId();
state.module.AddFunction(spv::OpCompositeConstruct, ImageVectorType(state, numericClass, 4), value, channels[0], channels[1], channels[2], channels[3]);
ctx.Define(access.inst, ResultVector(ctx, access, value, numericClass, false, gather));
}
void EmitSamplingOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) {
if (access.image.constantSwizzle) {
EmitConstantSwizzleSample(ctx, access);
return;
}
if (access.image.srgbDecode) {
ctx.Fail(access.inst, "samples or gathers an sRGB image the device cannot sample, which is not implemented");
}