fix(recompiler): read image address components the instruction omits as 0

An image load whose DIM field encodes fewer coordinates than the resource
(image_load 2d on a 3D texture) was refused. Pad the missing components with
0 so the load reads the first slice.
This commit is contained in:
Gjon Gojcevic 2026-10-07 21:07:06 -04:00
parent df16c4c256
commit 16522d1812
2 changed files with 68 additions and 8 deletions

View File

@ -25,12 +25,15 @@ constexpr std::uint32_t Size = 16;
constexpr std::uint32_t Levels = 5;
constexpr std::uint32_t Format8888UNorm = 56;
constexpr std::uint32_t Type2D = 9;
constexpr std::uint32_t Type3D = 10;
constexpr std::uint32_t Depth = 2;
constexpr std::uint32_t ArrayInput = 0;
constexpr std::uint32_t ArrayResult = 8;
constexpr std::uint32_t PlainInput = 16;
constexpr std::uint32_t PlainResult = 24;
alignas(256) std::array<std::uint32_t, Threads * Words> Buffer{};
alignas(256) std::array<std::uint8_t, 16384> Texels{};
alignas(256) std::array<std::uint8_t, 16384> Volume{};
alignas(256) constexpr std::array<std::uint32_t, 71> Code{
0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301008, 0x80000401, 0xe030100c,
@ -44,6 +47,12 @@ alignas(256) constexpr std::array<std::uint32_t, 71> Code{
0xe0701074, 0x80000d01, 0xe0701078, 0x80000e01, 0xe070107c, 0x80000f01, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 17> VolumeCode{
0x34020087, 0xe030104c, 0x80001301, 0xe0301050, 0x80001401, 0xbf8c3f70, 0xf0001f08, 0x00010c13,
0xe0701070, 0x80000c01, 0xe0701074, 0x80000d01, 0xe0701078, 0x80000e01, 0xe070107c, 0x80000f01,
0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 11> NarrowCode{
0x34020087, 0xe0301040, 0x80001001, 0xe0301044, 0x80001101, 0xbf8c3f70, 0xf0900f00, 0x00610c10,
0xe0701070, 0x80000c01, 0xbf810000,
@ -117,14 +126,49 @@ std::array<std::uint32_t, 8> TextureDescriptor(const void* data) {
};
}
std::array<std::uint32_t, 8> VolumeDescriptor() {
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(Volume.data()));
return {
static_cast<std::uint32_t>(address >> 8u),
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Size - 1u) & 3u) << 30u),
((Size - 1u) >> 2u) | ((Size - 1u) << 14u),
0xfacu | (Type3D << 28u),
Depth - 1u,
0u,
0u,
0u,
};
}
std::uint8_t VolumeSliceMarker(std::uint32_t z) {
return static_cast<std::uint8_t>(z * 100u + 7u);
}
void FillVolume() {
const auto geometry = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(VolumeDescriptor()));
Require(geometry.guestBytes <= Volume.size(), "image address dimension: the volume does not fit the texel storage");
const auto& mip = geometry.mips.at(0);
Volume.fill(0xeeu);
for (std::uint32_t z = 0; z < Depth; ++z) {
for (std::uint32_t y = 0; y < Size; ++y) {
for (std::uint32_t x = 0; x < Size; ++x) {
auto* texel = &Volume[geometry.GuestLayerOffset(z) + mip.tiledOffset + static_cast<std::uint64_t>(y) * mip.pitchBytes + x * 4u];
texel[0] = VolumeSliceMarker(z);
texel[1] = static_cast<std::uint8_t>(x);
texel[2] = static_cast<std::uint8_t>(y);
texel[3] = 255u;
}
}
}
}
std::array<std::uint32_t, 4> SamplerDescriptor() {
return {0u, 0xfffu << 12u, 1u << 26u, 0u};
}
void Run(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code) {
void Run(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, const std::array<std::uint32_t, 8>& texture) {
std::vector<std::uint32_t> userData(16, 0u);
const auto buffer = BufferDescriptor(Buffer.data(), static_cast<std::uint32_t>(Buffer.size() * 4u));
const auto texture = TextureDescriptor(Texels.data());
const auto sampler = SamplerDescriptor();
std::copy(buffer.begin(), buffer.end(), userData.begin());
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
@ -158,10 +202,21 @@ void Check() {
}
}
void CheckVolume() {
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const auto sample = SampleOf(tid);
const std::array<std::uint32_t, 4> expected{VolumeSliceMarker(0), sample.x >> sample.level, sample.y >> sample.level, 255u};
for (std::uint32_t component = 0; component < 4u; ++component) {
const float value = std::bit_cast<float>(Buffer[tid * Words + PlainResult + 4u + component]) * 255.0f;
Require(std::lround(value) == static_cast<long>(expected[component]), "image_load 2d on a 3D texture: thread " + std::to_string(tid) + " component " + std::to_string(component) + " is " + std::to_string(value) + ", expected " + std::to_string(expected[component]));
}
}
}
void RequireRefused(AgcDriver::VulkanDevice& device) {
std::string refusal;
try {
Run(device, NarrowCode);
Run(device, NarrowCode, TextureDescriptor(Texels.data()));
} catch (const std::exception& error) {
refusal = error.what();
}
@ -176,8 +231,11 @@ int main() {
if (!device) return VulkanTestSkipped;
FillInput();
FillTexture();
Run(*device, Code);
Run(*device, Code, TextureDescriptor(Texels.data()));
Check();
FillVolume();
Run(*device, VolumeCode, VolumeDescriptor());
CheckVolume();
RequireRefused(*device);
std::puts("image address dimension tests passed");
return 0;

View File

@ -190,17 +190,19 @@ std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access
std::uint32_t CoordU32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) {
const auto components = RdnaImageDimensionInfoFor(access.image.dimension).coordinateComponents;
if (AddressDimension(access).coordinateComponents < components || access.mem.imageAddressComponents < components) {
const auto encoded = std::min(components, AddressDimension(access).coordinateComponents);
if (access.mem.imageAddressComponents < encoded) {
ctx.Fail(access.inst, "has an image address with too few coordinate components");
}
const auto x = AddressU32(ctx, access, 0);
const auto component = [&](std::uint32_t index) { return index < encoded ? AddressU32(ctx, access, index) : ConstantU32(ctx.state, 0); };
const auto x = component(0);
if (components == 1u) {
return x;
}
const auto y = AddressU32(ctx, access, 1);
const auto y = component(1);
const auto result = ctx.state.module.AllocateId();
if (components == 3u) {
const auto z = AddressU32(ctx, access, 2);
const auto z = component(2);
ctx.state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 3), result, x, y, z);
} else {
ctx.state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 2), result, x, y);