shader: decode every PS5 image_gather4 encoding

Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
This commit is contained in:
nmzik
2026-10-27 01:13:46 +01:00
parent 1b70938052
commit ffa3992c1a
7 changed files with 249 additions and 130 deletions
+80 -22
View File
@@ -1723,7 +1723,7 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
const auto uses_image =
std::any_of(test.opcodes.begin(), test.opcodes.end(), [](auto op) {
return op >= ShaderOpcode::IMAGE_GET_RESINFO &&
op <= ShaderOpcode::IMAGE_GATHER4H;
op <= ShaderOpcode::IMAGE_GATHER4;
});
if (uses_image && ((user_data[3] >> 28u) & 0xfu) == 0) {
user_data[3] = static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u;
@@ -14132,7 +14132,7 @@ public:
test.initial.assign(test.expected.size(), 0xdeadbeefu);
test.expected_mip_descriptors = 2u;
test.required_spirv = {"OpImageDrefGather", "OpSwitch"};
test.decoded_counts = {{"IMAGE_GATHER4_C_L", 6u}};
test.decoded_counts = {{"image_gather4_c_l", 6u}};
const auto compiled = CompileCase(test, SubgroupSize());
Require(test.name, "comparison mip resource", compiled.program.info.images.size() == 1u &&
compiled.program.info.images[0].depth_compare,
@@ -22771,13 +22771,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode,
case Opcode::IMAGE_ATOMIC_FMIN:
case Opcode::IMAGE_ATOMIC_FMAX:
case Opcode::IMAGE_SAMPLE:
case Opcode::IMAGE_GATHER4_LZ:
case Opcode::IMAGE_GATHER4_C:
case Opcode::IMAGE_GATHER4_C_L:
case Opcode::IMAGE_GATHER4_C_LZ:
case Opcode::IMAGE_GATHER4_LZ_O:
case Opcode::IMAGE_GATHER4_C_O:
case Opcode::IMAGE_GATHER4_C_LZ_O:
case Opcode::IMAGE_GATHER4:
return CoverageClass::NeedsImageCase;
case Opcode::V_INTERP_P1_F32:
@@ -39002,7 +38996,7 @@ TestCase ImageSamplePackedUintConvertsSampleAndGather() {
test.code = std::move(code);
test.expected = {0x2abu, 0x456u, 0x321u, 0x321u,
0x456u, 0x456u, 0x456u, 0x456u};
test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::IMAGE_GATHER4_LZ,
test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::IMAGE_GATHER4,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.sampled_image_rgba = std::vector<u32>(16, 0xaae2b321u);
test.sampled_image_format = vk::Format::eR32Uint;
@@ -39144,7 +39138,7 @@ template <bool rg, bool snorm = false> TestCase ImageSamplePacked8() {
for (float value : expected) {
test.expected.push_back(std::bit_cast<u32>(value));
}
test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::IMAGE_GATHER4_LZ,
test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::IMAGE_GATHER4,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
return test;
}
@@ -39262,7 +39256,7 @@ TestCase ImageGather2DInstructionWith1DDescriptor() {
test.name = "ImageGather2DInstructionWith1DDescriptor";
test.code = code;
test.expected = {0x40000000u, 0x40400000u, 0x40400000u, 0x40000000u};
test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4_LZ, O::BUFFER_STORE_DWORD,
test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
test.image_width = 4;
test.image_height = 1;
@@ -39531,9 +39525,8 @@ TestCase ImageSampleAndGather() {
0x3f800000u,
0};
test.opcodes = {
O::V_MOV_B32, O::IMAGE_SAMPLE, O::IMAGE_GET_LOD,
O::IMAGE_GATHER4_LZ, O::IMAGE_GATHER4_LZ_O, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
O::V_MOV_B32, O::IMAGE_SAMPLE, O::IMAGE_GET_LOD, O::IMAGE_GATHER4,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.sampled_image_rgba = image;
return test;
}
@@ -39601,7 +39594,7 @@ TestCase ImageGatherExplicitLod() {
TestCase test;
test.name = "ImageGatherExplicitLod";
test.code = std::move(code);
test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4_L, O::IMAGE_SAMPLE,
test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4, O::IMAGE_SAMPLE,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
// A gather's single-mip descriptors must not replace the ordinary sample's
// full mip chain when both instructions use the same guest descriptor.
@@ -39726,7 +39719,7 @@ template <u32 mode> TestCase ImageGatherLodPerLane() {
}
AppendEnd(&code);
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::BUFFER_LOAD_DWORD,
O::IMAGE_GATHER4_L, O::V_ADD_NC_U32, O::BUFFER_STORE_DWORD,
O::IMAGE_GATHER4, O::V_ADD_NC_U32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32_32_32_32Float);
test.user_data[1] |= (3u << 30u) | (mode == 3 ? 768u << 8u : 0u);
@@ -39778,7 +39771,7 @@ TestCase ImageD16GatherPacksHalfPairs() {
test.name = "ImageD16GatherPacksHalfPairs";
test.code = code;
test.expected = {0x38003800u, 0x38003800u};
test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4_LZ, O::BUFFER_STORE_DWORD,
test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
test.sampled_image_rgba = image;
test.decoded_counts = {{"d16=1", 1}};
@@ -40022,6 +40015,63 @@ TestCase ImageSampleA16CompareBiasRdna2AddressOrder() {
return test;
}
TestCase ImageGatherAddressVariants() {
using O = ShaderOpcode;
struct Gather {
u32 opcode;
bool offset;
bool bias;
bool lod_or_clamp;
};
// Plain, LOD clamp, bias, LOD and offset gathers, with and without gradient adjust.
constexpr Gather gathers[] = {
{0x40, false, false, false}, {0x41, false, false, true},
{0x45, false, true, false}, {0x46, false, true, true},
{0x50, true, false, false}, {0x51, true, false, true},
{0x54, true, false, true}, {0x55, true, true, false},
{0x56, true, true, true}, {0xc0, false, false, false},
{0xc1, false, false, true}, {0xc5, false, true, false},
{0xc6, false, true, true}, {0xd0, true, false, false},
{0xd1, true, false, true}, {0xd5, true, true, false},
{0xd6, true, true, true},
};
const auto texel = [](u32 x, u32 y) { return std::bit_cast<u32>(float(1u + x + y * 4u)); };
auto image = MakeRgbaImage(4, 4);
for (u32 y = 0; y < 4u; y++) {
for (u32 x = 0; x < 4u; x++) {
SetRgbaPixel(&image, 4, x, y, texel(x, y), 0, 0, 0);
}
}
TestCase test;
test.name = "ImageGatherAddressVariants";
for (u32 index = 0; index < std::size(gathers); index++) {
const auto &gather = gathers[index];
// Zero bias, clamp and LOD keep the base level; a misplaced offset or bias moves the footprint.
u32 vaddr = 20;
if (gather.offset) AppendVMovU32(&test.code, vaddr++, 0x3fu); // x offset -1
if (gather.bias) AppendVMovU32(&test.code, vaddr++, 0);
AppendVMovLiteral(&test.code, vaddr++, std::bit_cast<u32>(0.6875f));
AppendVMovLiteral(&test.code, vaddr++, std::bit_cast<u32>(0.4375f));
if (gather.lod_or_clamp) AppendVMovU32(&test.code, vaddr++, 0);
test.code.push_back(EncodeMimg0(gather.opcode, 0x1));
test.code.push_back(EncodeMimg1(0, 20));
for (u32 component = 0; component < 4u; component++) {
AppendStoreVgpr(&test.code, component, index * 4u + component);
}
// Footprint x 2..3, y 1..2, or x 1..2 with the offset, in (x0, y1), (x1, y1), (x1, y0),
// (x0, y0) order.
const u32 x0 = gather.offset ? 1u : 2u;
for (const auto [x, y] : {std::pair{x0, 2u}, {x0 + 1u, 2u}, {x0 + 1u, 1u}, {x0, 1u}}) {
test.expected.push_back(texel(x, y));
}
}
AppendEnd(&test.code);
test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
test.sampled_image_rgba = image;
return test;
}
TestCase ImageGatherCompareOpcodes() {
using O = ShaderOpcode;
@@ -40052,15 +40102,21 @@ TestCase ImageGatherCompareOpcodes() {
AppendStoreVgpr(&code, 8, 1);
AppendStoreVgpr(&code, 0, 2);
AppendStoreVgpr(&code, 12, 3);
// The other compare gathers.
constexpr u32 variants[] = {0x49, 0x4c, 0x4d, 0x4e, 0x59, 0x5c, 0x5d, 0x5e,
0xc8, 0xc9, 0xcd, 0xce, 0xd8, 0xd9, 0xdd, 0xde};
for (u32 index = 0; index < std::size(variants); index++) {
code.push_back(EncodeMimg0(variants[index], 0x1));
code.push_back(EncodeMimg1(40, 20));
AppendStoreVgpr(&code, 40, 4u + index);
}
AppendEnd(&code);
TestCase test;
test.name = "ImageGatherCompareOpcodes";
test.code = code;
test.opcodes = {
O::V_MOV_B32, O::IMAGE_GATHER4_C, O::IMAGE_GATHER4_C_LZ,
O::IMAGE_GATHER4_C_O, O::IMAGE_GATHER4_C_LZ_O, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
test.required_spirv = {"OpImageDrefGather", "ConstOffset"};
test.forbidden_spirv = {"OpBitFieldSExtract"};
test.compile_only = true;
@@ -45822,6 +45878,7 @@ std::vector<TestCase> MakeCases() {
AddCase(ImageGetResinfoDmaskWidthHeight);
AddCase(ImageGetResinfoDmaskMipLevels);
AddCase(ImageSampleAndGather);
AddCase(ImageGatherAddressVariants);
AddCase(ImageCubeGradientsPreserveDerivatives);
AddCase(ImageGatherExplicitLod);
AddCase(ImageSampleLodClamp);
@@ -52321,6 +52378,7 @@ int main(int argc, char **argv) {
RunCase(&vulkan, ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu<32>());
RunCase(&vulkan, ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu<64>());
RunCase(&vulkan, ImageSampleAndGather());
RunCase(&vulkan, ImageGatherAddressVariants());
RunCase(nullptr, ImageGatherCompareOpcodes());
return 0;
}
+70 -2
View File
@@ -4715,6 +4715,73 @@ void TestImageAtomicWidthDecoder() {
}
}
void TestImageGatherDecoder() {
using namespace ShaderRecompiler::Decoder;
struct Gather {
uint32_t opcode;
const char *name;
uint32_t address_vgprs;
};
// Every PS5 image_gather4 encoding, with the vaddr VGPR count of its 2D full-float form.
constexpr Gather gathers[] = {
{0x40, "image_gather4", 2}, {0x41, "image_gather4_cl", 3},
{0x44, "image_gather4_l", 3}, {0x45, "image_gather4_b", 3},
{0x46, "image_gather4_b_cl", 4}, {0x47, "image_gather4_lz", 2},
{0x48, "image_gather4_c", 3}, {0x49, "image_gather4_c_cl", 4},
{0x4c, "image_gather4_c_l", 4}, {0x4d, "image_gather4_c_b", 4},
{0x4e, "image_gather4_c_b_cl", 5}, {0x4f, "image_gather4_c_lz", 3},
{0x50, "image_gather4_o", 3}, {0x51, "image_gather4_cl_o", 4},
{0x54, "image_gather4_l_o", 4}, {0x55, "image_gather4_b_o", 4},
{0x56, "image_gather4_b_cl_o", 5}, {0x57, "image_gather4_lz_o", 3},
{0x58, "image_gather4_c_o", 4}, {0x59, "image_gather4_c_cl_o", 5},
{0x5c, "image_gather4_c_l_o", 5}, {0x5d, "image_gather4_c_b_o", 5},
{0x5e, "image_gather4_c_b_cl_o", 6}, {0x5f, "image_gather4_c_lz_o", 4},
{0x61, "image_gather4h", 2}, {0xc0, "image_gather4_a", 2},
{0xc1, "image_gather4_cl_a", 3}, {0xc5, "image_gather4_b_a", 3},
{0xc6, "image_gather4_b_cl_a", 4}, {0xc8, "image_gather4_c_a", 3},
{0xc9, "image_gather4_c_cl_a", 4}, {0xcd, "image_gather4_c_b_a", 4},
{0xce, "image_gather4_c_b_cl_a", 5}, {0xd0, "image_gather4_o_a", 3},
{0xd1, "image_gather4_cl_o_a", 4}, {0xd5, "image_gather4_b_o_a", 4},
{0xd6, "image_gather4_b_cl_o_a", 5}, {0xd8, "image_gather4_c_o_a", 4},
{0xd9, "image_gather4_c_cl_o_a", 5}, {0xdd, "image_gather4_c_b_o_a", 5},
{0xde, "image_gather4_c_b_cl_o_a", 6},
};
const auto expected_flags = [](std::string_view name) {
if (name == "image_gather4h") return uint32_t{ImageSampleFlagGatherHorizontal};
uint32_t flags = 0;
for (size_t at = name.find('_', 6); at != std::string_view::npos;) {
const auto end = name.find('_', at + 1);
const auto modifier = name.substr(at + 1, end - at - 1);
if (modifier == "c") flags |= ImageSampleFlagCompare;
if (modifier == "b") flags |= ImageSampleFlagBias;
if (modifier == "cl") flags |= ImageSampleFlagLodClamp;
if (modifier == "l") flags |= ImageSampleFlagLod;
if (modifier == "lz") flags |= ImageSampleFlagLevelZero;
if (modifier == "o") flags |= ImageSampleFlagOffset;
if (modifier == "a") flags |= ImageSampleFlagAdjust;
at = end;
}
return flags;
};
for (uint32_t opcode = 0; opcode < 0x100u; ++opcode) {
const auto *gather = std::ranges::find(gathers, opcode, &Gather::opcode);
const uint32_t words[] = {EncodeMimg0(opcode, 0x1), EncodeMimg1(8, 0, 1, 0)};
Instruction decoded;
DecodeInstruction(words, 0, decoded);
if (gather == std::end(gathers)) {
Check(decoded.opcode != Opcode::IMAGE_GATHER4,
"a non-gather MIMG encoding decoded as image_gather4");
continue;
}
Check(decoded.opcode == Opcode::IMAGE_GATHER4 && decoded.data_components == 4u &&
decoded.image_sample_flags == expected_flags(gather->name) &&
decoded.image_address_components == gather->address_vgprs &&
InstructionToString(decoded).find(std::string(gather->name) + " ") !=
std::string::npos,
"a PS5 gather encoding lost its opcode, name, modifiers or address count");
}
}
void TestNewShaderDecoderArchitecture() {
using namespace ShaderRecompiler::Decoder;
@@ -5657,7 +5724,7 @@ void TestImageAddressOperands() {
const uint32_t captured[] = {0xf130010au, 0x00a30013u, 0x00161a19u};
Decoder::Instruction gather;
Decoder::DecodeInstruction(captured, 0, gather);
Check(gather.opcode == Decoder::Opcode::IMAGE_GATHER4_C_L &&
Check(gather.opcode == Decoder::Opcode::IMAGE_GATHER4 &&
gather.opcode_id == 0x4cu && gather.word_count == 3u &&
gather.image_dimension == Decoder::ImageDimension::Dim2D &&
gather.dmask == 1u && gather.data_components == 4u &&
@@ -5671,7 +5738,7 @@ void TestImageAddressOperands() {
gather.image_nsa_addr[2] == 22u && !gather.glc && !gather.slc &&
!gather.image_r128,
"captured IMAGE_GATHER4_C_L lost compare/LOD, NSA, or result metadata");
Check(Decoder::InstructionToString(gather).find("IMAGE_GATHER4_C_L") != std::string::npos,
Check(Decoder::InstructionToString(gather).find("image_gather4_c_l") != std::string::npos,
"captured IMAGE_GATHER4_C_L is missing from the decoded dump");
struct Case {
uint32_t opcode;
@@ -17022,6 +17089,7 @@ int main() {
// here.
TestScalarAshrI64Decoder();
TestImageAtomicWidthDecoder();
TestImageGatherDecoder();
TestDsReturningAtomic64Decoder();
TestDsCompareStore32Decoder();
TestDsFloatMinMaxDecoder();