#include "graphics/shader/recompiler/frontend/decode/ImageOps.h" #include "graphics/shader/recompiler/frontend/decode/OpcodeTable.h" #include namespace Libs::Graphics::ShaderRecompiler::Decoder { namespace { struct MimgSampleInfo { uint32_t encoding = 0; const char* name = nullptr; uint32_t flags = 0; uint32_t address_components = 3; }; struct MimgGatherInfo { uint32_t encoding = 0; const char* name = nullptr; Opcode decoded = Opcode::UNSUPPORTED; uint32_t flags = 0; uint32_t address_components = 3; }; struct MimgAtomicInfo { uint32_t encoding = 0; const char* name = nullptr; Opcode decoded = Opcode::UNSUPPORTED; }; constexpr ImageDimension DecodeImageDimension(uint32_t dim) { switch (dim) { case 0u: return ImageDimension::Dim1D; case 1u: return ImageDimension::Dim2D; case 2u: return ImageDimension::Dim3D; case 3u: return ImageDimension::Dim2DArray; case 4u: return ImageDimension::Dim1DArray; case 5u: return ImageDimension::Dim2DArray; case 6u: return ImageDimension::Dim2DMsaa; case 7u: return ImageDimension::Dim2DMsaaArray; default: return ImageDimension::Unknown; } } constexpr uint32_t ImageCoordComponents(ImageDimension dimension) { switch (dimension) { case ImageDimension::Dim1D: return 1u; case ImageDimension::Dim1DArray: return 2u; case ImageDimension::Dim2DMsaa: case ImageDimension::Dim3D: case ImageDimension::Dim2DArray: return 3u; case ImageDimension::Dim2DMsaaArray: return 4u; default: return 2u; } } constexpr uint32_t ImageGradientComponents(ImageDimension dimension) { switch (dimension) { case ImageDimension::Dim1D: case ImageDimension::Dim1DArray: return 1u; case ImageDimension::Dim3D: return 3u; default: return 2u; } } constexpr uint32_t ImageSampleAddressComponents(uint32_t flags, ImageDimension dimension) { const auto coord_components = ImageCoordComponents(dimension); uint32_t components = coord_components; if ((flags & ImageSampleFlagOffset) != 0) { components++; } if ((flags & ImageSampleFlagCompare) != 0) { components++; } if ((flags & ImageSampleFlagBias) != 0) { components++; } if ((flags & ImageSampleFlagLod) != 0) { components++; } if ((flags & ImageSampleFlagDerivative) != 0) { components += ImageGradientComponents(dimension) * 2u; } return components; } constexpr MimgSampleInfo SampleInfo(uint32_t opcode, const char* name, uint32_t flags) { return {opcode, name, flags, ImageSampleAddressComponents(flags, ImageDimension::Dim2D)}; } constexpr MimgSampleInfo MIMG_SAMPLE_OPCODE_LIST[] = { SampleInfo(0x20u, "image_sample", 0), SampleInfo(0x21u, "image_sample_cl", ImageSampleFlagLodClamp), SampleInfo(0x22u, "image_sample_d", ImageSampleFlagDerivative), SampleInfo(0x23u, "image_sample_d_cl", ImageSampleFlagDerivative | ImageSampleFlagLodClamp), SampleInfo(0x24u, "image_sample_l", ImageSampleFlagLod), SampleInfo(0x25u, "image_sample_b", ImageSampleFlagBias), SampleInfo(0x26u, "image_sample_b_cl", ImageSampleFlagBias | ImageSampleFlagLodClamp), SampleInfo(0x27u, "image_sample_lz", ImageSampleFlagLevelZero), SampleInfo(0x28u, "image_sample_c", ImageSampleFlagCompare), SampleInfo(0x29u, "image_sample_c_cl", ImageSampleFlagCompare | ImageSampleFlagLodClamp), SampleInfo(0x2au, "image_sample_c_d", ImageSampleFlagCompare | ImageSampleFlagDerivative), SampleInfo(0x2bu, "image_sample_c_d_cl", ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagLodClamp), SampleInfo(0x2cu, "image_sample_c_l", ImageSampleFlagCompare | ImageSampleFlagLod), SampleInfo(0x2du, "image_sample_c_b", ImageSampleFlagCompare | ImageSampleFlagBias), SampleInfo(0x2eu, "image_sample_c_b_cl", ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp), SampleInfo(0x2fu, "image_sample_c_lz", ImageSampleFlagCompare | ImageSampleFlagLevelZero), SampleInfo(0x30u, "image_sample_o", ImageSampleFlagOffset), SampleInfo(0x31u, "image_sample_cl_o", ImageSampleFlagLodClamp | ImageSampleFlagOffset), SampleInfo(0x32u, "image_sample_d_o", ImageSampleFlagDerivative | ImageSampleFlagOffset), SampleInfo(0x33u, "image_sample_d_cl_o", ImageSampleFlagDerivative | ImageSampleFlagLodClamp | ImageSampleFlagOffset), SampleInfo(0x34u, "image_sample_l_o", ImageSampleFlagLod | ImageSampleFlagOffset), SampleInfo(0x35u, "image_sample_b_o", ImageSampleFlagBias | ImageSampleFlagOffset), SampleInfo(0x36u, "image_sample_b_cl_o", ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset), SampleInfo(0x37u, "image_sample_lz_o", ImageSampleFlagLevelZero | ImageSampleFlagOffset), SampleInfo(0x38u, "image_sample_c_o", ImageSampleFlagCompare | ImageSampleFlagOffset), SampleInfo(0x39u, "image_sample_c_cl_o", ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset), SampleInfo(0x3au, "image_sample_c_d_o", ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagOffset), SampleInfo(0x3bu, "image_sample_c_d_cl_o", ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagLodClamp | ImageSampleFlagOffset), SampleInfo(0x3cu, "image_sample_c_l_o", ImageSampleFlagCompare | ImageSampleFlagLod | ImageSampleFlagOffset), SampleInfo(0x3du, "image_sample_c_b_o", ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset), SampleInfo(0x3eu, "image_sample_c_b_cl_o", ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset), SampleInfo(0x3fu, "image_sample_c_lz_o", ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset), SampleInfo(0x68u, "image_sample_cd", ImageSampleFlagDerivative | ImageSampleFlagCd), SampleInfo(0x69u, "image_sample_cd_cl", ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagLodClamp), SampleInfo(0x6au, "image_sample_c_cd", ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd), SampleInfo(0x6bu, "image_sample_c_cd_cl", ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagLodClamp), SampleInfo(0x6cu, "image_sample_cd_o", ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagOffset), SampleInfo(0x6du, "image_sample_cd_cl_o", ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagLodClamp | ImageSampleFlagOffset), SampleInfo(0x6eu, "image_sample_c_cd_o", ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagOffset), SampleInfo(0x6fu, "image_sample_c_cd_cl_o", ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagLodClamp | ImageSampleFlagOffset), SampleInfo(0xa0u, "image_sample_a", ImageSampleFlagAdjust), SampleInfo(0xa1u, "image_sample_cl_a", ImageSampleFlagLodClamp | ImageSampleFlagAdjust), SampleInfo(0xa5u, "image_sample_b_a", ImageSampleFlagBias | ImageSampleFlagAdjust), SampleInfo(0xa6u, "image_sample_b_cl_a", ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagAdjust), SampleInfo(0xa8u, "image_sample_c_a", ImageSampleFlagCompare | ImageSampleFlagAdjust), SampleInfo(0xa9u, "image_sample_c_cl_a", ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagAdjust), SampleInfo(0xadu, "image_sample_c_b_a", ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagAdjust), SampleInfo(0xaeu, "image_sample_c_b_cl_a", ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagAdjust), SampleInfo(0xb0u, "image_sample_o_a", ImageSampleFlagOffset | ImageSampleFlagAdjust), SampleInfo(0xb1u, "image_sample_cl_o_a", ImageSampleFlagLodClamp | ImageSampleFlagOffset | ImageSampleFlagAdjust), SampleInfo(0xb5u, "image_sample_b_o_a", ImageSampleFlagBias | ImageSampleFlagOffset | ImageSampleFlagAdjust), SampleInfo(0xb6u, "image_sample_b_cl_o_a", ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset | ImageSampleFlagAdjust), SampleInfo(0xb8u, "image_sample_c_o_a", ImageSampleFlagCompare | ImageSampleFlagOffset | ImageSampleFlagAdjust), SampleInfo(0xb9u, "image_sample_c_cl_o_a", ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset | ImageSampleFlagAdjust), SampleInfo(0xbdu, "image_sample_c_b_o_a", ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset | ImageSampleFlagAdjust), SampleInfo(0xbeu, "image_sample_c_b_cl_o_a", ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset | ImageSampleFlagAdjust), }; constexpr MimgGatherInfo MIMG_GATHER_OPCODE_LIST[] = { {0x47u, "image_gather4_lz", Opcode::IMAGE_GATHER4_LZ, ImageSampleFlagLevelZero, 2u}, {0x48u, "image_gather4_c", Opcode::IMAGE_GATHER4_C, ImageSampleFlagCompare, 3u}, {0x4fu, "image_gather4_c_lz", Opcode::IMAGE_GATHER4_C_LZ, ImageSampleFlagCompare | ImageSampleFlagLevelZero, 3u}, {0x57u, "image_gather4_lz_o", Opcode::IMAGE_GATHER4_LZ_O, ImageSampleFlagLevelZero | ImageSampleFlagOffset, 3u}, {0x58u, "image_gather4_c_o", Opcode::IMAGE_GATHER4_C_O, ImageSampleFlagCompare | ImageSampleFlagOffset, 4u}, {0x5fu, "image_gather4_c_lz_o", Opcode::IMAGE_GATHER4_C_LZ_O, ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset, 4u}, {0x61u, "image_gather4h", Opcode::IMAGE_GATHER4H, ImageSampleFlagGatherHorizontal, 2u}, }; constexpr MimgAtomicInfo MIMG_ATOMIC_OPCODE_LIST[] = { {0x0fu, "image_atomic_swap", Opcode::IMAGE_ATOMIC_SWAP}, {0x11u, "image_atomic_add", Opcode::IMAGE_ATOMIC_ADD}, {0x15u, "image_atomic_umin", Opcode::IMAGE_ATOMIC_UMIN}, {0x17u, "image_atomic_umax", Opcode::IMAGE_ATOMIC_UMAX}, {0x18u, "image_atomic_and", Opcode::IMAGE_ATOMIC_AND}, {0x19u, "image_atomic_or", Opcode::IMAGE_ATOMIC_OR}, {0x1au, "image_atomic_xor", Opcode::IMAGE_ATOMIC_XOR}, }; constexpr auto MIMG_SAMPLE_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_SAMPLE_OPCODE_LIST); constexpr auto MIMG_GATHER_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_GATHER_OPCODE_LIST); constexpr auto MIMG_ATOMIC_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_ATOMIC_OPCODE_LIST); const MimgSampleInfo* LookupSample(uint32_t opcode) { return Detail::FindOpcode(MIMG_SAMPLE_OPS, opcode); } const MimgGatherInfo* LookupGather(uint32_t opcode) { return Detail::FindOpcode(MIMG_GATHER_OPS, opcode); } const MimgAtomicInfo* LookupAtomic(uint32_t opcode) { return Detail::FindOpcode(MIMG_ATOMIC_OPS, opcode); } Opcode DecodeMimgOpcode(uint32_t opcode, const MimgSampleInfo* sample, const MimgGatherInfo* gather, const MimgAtomicInfo* atomic) { if (sample != nullptr) { return Opcode::IMAGE_SAMPLE; } if (gather != nullptr) { return gather->decoded; } if (atomic != nullptr) { return atomic->decoded; } switch (opcode) { case 0x00u: return Opcode::IMAGE_LOAD; case 0x01u: return Opcode::IMAGE_LOAD_MIP; case 0x08u: return Opcode::IMAGE_STORE; case 0x09u: return Opcode::IMAGE_STORE_MIP; case 0x0eu: return Opcode::IMAGE_GET_RESINFO; case 0x60u: return Opcode::IMAGE_GET_LOD; default: return Opcode::UNSUPPORTED; } } uint32_t DecodeMimgSampleFlags(const MimgSampleInfo* sample, const MimgGatherInfo* gather) { if (sample != nullptr) { return sample->flags; } if (gather != nullptr) { return gather->flags; } return 0; } uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension, const MimgSampleInfo* sample, const MimgGatherInfo* gather, const MimgAtomicInfo* atomic) { if (sample != nullptr) { return ImageSampleAddressComponents(sample->flags, dimension); } if (gather != nullptr) { return ImageSampleAddressComponents(gather->flags, dimension); } if (atomic != nullptr) { return ImageCoordComponents(dimension); } switch (opcode) { case 0x0eu: return 1u; case 0x01u: case 0x09u: return ImageCoordComponents(dimension) + 1u; case 0x00u: case 0x08u: case 0x60u: return ImageCoordComponents(dimension); default: return 0; } } uint32_t CountDmaskComponents(uint32_t dmask) { uint32_t count = 0; for (uint32_t i = 0; i < 4u; i++) { count += (dmask >> i) & 1u; } return count != 0 ? count : 1u; } bool IsSingleDmaskBit(uint32_t dmask) { return dmask != 0u && (dmask & (dmask - 1u)) == 0u; } } // namespace ImageAddressComponent ImageAddressComponentLayout(uint32_t flags, uint32_t component) { const auto width = [flags](uint32_t index) { if ((flags & ImageSampleFlagA16) == 0u) return 32u; uint32_t cursor = 0; if ((flags & ImageSampleFlagOffset) != 0u) { if (index == cursor++) return 32u; } if ((flags & ImageSampleFlagBias) != 0u) { if (index == cursor++) return 16u; } if ((flags & ImageSampleFlagCompare) != 0u && index == cursor) return 32u; return 16u; }; uint32_t offset = 0; for (uint32_t index = 0; index < component; index++) { const auto bits = width(index); if (bits == 32u) offset = (offset + 31u) & ~31u; offset += bits; } const auto bits = width(component); if (bits == 32u) offset = (offset + 31u) & ~31u; return {offset, bits}; } uint32_t ImageAddressDwordCount(uint32_t flags, uint32_t components) { if (components == 0u) return 0u; const auto last = ImageAddressComponentLayout(flags, components - 1u); return (last.bit_offset + last.bit_width + 31u) / 32u; } void DecodeMimg(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst) { const uint32_t word0 = code[word_index]; const uint32_t word1 = code[word_index + 1u]; const uint32_t opcode = ((word0 >> 18u) & 0x7fu) | ((word0 & 1u) << 7u); const uint32_t nsa_dwords = (word0 >> 1u) & 0x3u; const auto dimension = DecodeImageDimension((word0 >> 3u) & 0x7u); const uint32_t word_count = 2u + nsa_dwords; const uint32_t vdata = (word1 >> 8u) & 0xffu; const uint32_t vaddr = word1 & 0xffu; const uint32_t srsrc = (word1 >> 16u) & 0x1fu; const uint32_t ssamp = (word1 >> 21u) & 0x1fu; const bool r128 = ((word0 >> 15u) & 0x1u) != 0u; const bool a16 = ((word1 >> 30u) & 0x1u) != 0u; const bool d16 = ((word1 >> 31u) & 0x1u) != 0u; const auto* sample = LookupSample(opcode); const auto* gather = LookupGather(opcode); const auto* atomic = LookupAtomic(opcode); inst.pc = pc; inst.word = word0; inst.word_count = word_count; inst.family = Family::MIMG; inst.opcode_id = opcode; inst.opcode = DecodeMimgOpcode(opcode, sample, gather, atomic); inst.dmask = (word0 >> 8u) & 0xfu; inst.data_components = gather != nullptr ? 4u : CountDmaskComponents(inst.dmask); inst.data_bits = d16 ? 16u : 32u; inst.data_dwords = d16 ? (inst.data_components + 1u) / 2u : inst.data_components; inst.glc = ((word0 >> 13u) & 1u) != 0; inst.slc = ((word0 >> 25u) & 1u) != 0; inst.image_sample_flags = DecodeMimgSampleFlags(sample, gather); if (a16) { inst.image_sample_flags |= ImageSampleFlagA16; } inst.image_dimension = dimension; inst.image_r128 = r128; inst.image_nsa_dwords = nsa_dwords; for (uint32_t i = 0; i < nsa_dwords * 4u; i++) { inst.image_nsa_addr[i] = (code[word_index + 2u + i / 4u] >> ((i % 4u) * 8u)) & 0xffu; } inst.image_address_components = DecodeMimgAddressComponents(opcode, dimension, sample, gather, atomic); SetRawWords(inst, code, word_index, word_count); if (inst.opcode == Opcode::UNSUPPORTED) { SetUnsupported(inst, Family::MIMG, opcode, "MIMG opcode is not implemented"); } if (gather != nullptr && !IsSingleDmaskBit(inst.dmask)) { SetUnsupported(inst, Family::MIMG, opcode, "MIMG image gather requires exactly one dmask bit"); } const bool supports_d16 = sample != nullptr || gather != nullptr || opcode == 0x00u || opcode == 0x01u || opcode == 0x08u || opcode == 0x09u; if (d16 && !supports_d16) { SetUnsupported(inst, Family::MIMG, opcode, "MIMG opcode does not support D16 data"); } DecodeVectorGpr(vdata, inst.dst); DecodeVectorGpr(vaddr, inst.src0); DecodeScalarSource(srsrc * 4u, pc, inst.src1); DecodeScalarSource(ssamp * 4u, pc, inst.src2); inst.src_count = 3; } const char* MimgSampleOpcodeName(uint32_t opcode) { const auto* sample = LookupSample(opcode); return sample != nullptr ? sample->name : nullptr; } } // namespace Libs::Graphics::ShaderRecompiler::Decoder