shader: simplify image resource semantics

This commit is contained in:
nmzik
2026-09-02 11:37:39 +04:00
parent 93be8c201b
commit 86e3c90e32
17 changed files with 381 additions and 513 deletions
@@ -450,7 +450,8 @@ void ValidateProgram(const Program& program, bool require_ssa) {
ValueOpcodeName(inst.GetOpcode())));
}
const auto& memory = program.memory_info[memory_index];
if (!ImageResourceKindMatches(memory.kind, image_info.resource_class)) {
if (memory.kind != ResourceKind::Image ||
image_info.resource_class == ImageResourceClass::None) {
return Fail(fmt::format("{} has invalid image-memory metadata",
ValueOpcodeName(inst.GetOpcode())));
}
+83 -100
View File
@@ -4,6 +4,7 @@
#include "common/common.h"
#include "common/stringUtils.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include "graphics/shader/recompiler/ir/Block.h"
@@ -30,10 +31,6 @@ enum class ResourceKind {
Lds,
Gds,
Image,
ImageUint,
ImageSint,
StorageImage,
StorageImageUint,
Sampler
};
@@ -42,20 +39,6 @@ enum class ResourceKind {
kind == ResourceKind::Global || kind == ResourceKind::Scratch;
}
[[nodiscard]] constexpr bool ImageResourceKindMatches(ResourceKind kind,
ImageResourceClass resource_class) {
switch (resource_class) {
case ImageResourceClass::Sampled:
return kind == ResourceKind::Image || kind == ResourceKind::ImageUint ||
kind == ResourceKind::ImageSint;
case ImageResourceClass::Storage:
return kind == ResourceKind::StorageImage || kind == ResourceKind::StorageImageUint;
case ImageResourceClass::StorageUint: return kind == ResourceKind::StorageImageUint;
case ImageResourceClass::None: return false;
}
return false;
}
struct MemoryInfo {
ResourceKind kind = ResourceKind::None;
uint32_t resource = 0;
@@ -80,7 +63,6 @@ struct MemoryInfo {
bool typed = false;
bool formatted = false;
bool image_has_mip = false;
bool image_cube = false;
bool image_r128 = false;
bool glc = false;
bool slc = false;
@@ -140,24 +122,25 @@ constexpr uint32_t ShaderImageIdentitySwizzle = 0x00000facu;
struct ImageResource {
static constexpr uint32_t NoIndirectImage = UINT32_MAX;
uint32_t source = 0;
uint32_t first_use_pc = 0;
ResourceKind kind = ResourceKind::None;
Decoder::ImageDimension dimension = Decoder::ImageDimension::Unknown;
ImageMipMode mip_mode = ImageMipMode::None;
uint32_t mip_count = 1;
Prospero::BufferFormat conversion_format = Prospero::BufferFormat::kInvalid;
uint32_t shader_swizzle = ShaderImageIdentitySwizzle;
bool read = false;
bool written = false;
bool atomic = false;
bool depth_compare = false;
bool cube = false;
bool r128 = false;
uint32_t indirect_root = NoIndirectImage;
uint32_t indirect_mapping_offset = 0;
uint32_t indirect_mapping_capacity = 0;
std::vector<uint32_t> indirect_resources;
uint32_t source = 0;
uint32_t first_use_pc = 0;
ImageResourceClass resource_class = ImageResourceClass::None;
Prospero::TextureNumericClass numeric_class = Prospero::TextureNumericClass::Unsupported;
Decoder::ImageDimension dimension = Decoder::ImageDimension::Unknown;
ImageMipMode mip_mode = ImageMipMode::None;
uint32_t mip_count = 1;
Prospero::BufferFormat conversion_format = Prospero::BufferFormat::kInvalid;
uint32_t shader_swizzle = ShaderImageIdentitySwizzle;
bool read = false;
bool written = false;
bool atomic = false;
bool depth_compare = false;
bool cube = false;
bool r128 = false;
uint32_t indirect_root = NoIndirectImage;
uint32_t indirect_mapping_offset = 0;
uint32_t indirect_mapping_capacity = 0;
std::vector<uint32_t> indirect_resources;
bool operator==(const ImageResource& other) const = default;
};
@@ -335,8 +318,34 @@ DescriptorBindingForImage(const ImageResource& image) {
using Dimension = Decoder::ImageDimension;
using Kind = DescriptorBindingKind;
switch (image.kind) {
case ResourceKind::Image:
switch (image.resource_class) {
case ImageResourceClass::Sampled:
switch (image.numeric_class) {
case Prospero::TextureNumericClass::Float: break;
case Prospero::TextureNumericClass::Uint:
switch (image.dimension) {
case Dimension::Dim1D: return Kind::SampledUint1D;
case Dimension::Dim1DArray: return Kind::SampledUint1DArray;
case Dimension::Dim2D: return Kind::SampledUint2D;
case Dimension::Dim2DArray: return Kind::SampledUint2DArray;
case Dimension::Dim2DMsaa: return Kind::SampledUint2DMsaa;
case Dimension::Dim2DMsaaArray: return Kind::SampledUint2DMsaaArray;
case Dimension::Dim3D: return Kind::SampledUint3D;
default: return std::nullopt;
}
case Prospero::TextureNumericClass::Sint:
switch (image.dimension) {
case Dimension::Dim1D: return Kind::SampledSint1D;
case Dimension::Dim1DArray: return Kind::SampledSint1DArray;
case Dimension::Dim2D: return Kind::SampledSint2D;
case Dimension::Dim2DArray: return Kind::SampledSint2DArray;
case Dimension::Dim2DMsaa: return Kind::SampledSint2DMsaa;
case Dimension::Dim2DMsaaArray: return Kind::SampledSint2DMsaaArray;
case Dimension::Dim3D: return Kind::SampledSint3D;
default: return std::nullopt;
}
case Prospero::TextureNumericClass::Unsupported: return std::nullopt;
}
switch (image.dimension) {
case Dimension::Dim1D: return Kind::Sampled1D;
case Dimension::Dim1DArray: return Kind::Sampled1DArray;
@@ -347,71 +356,45 @@ DescriptorBindingForImage(const ImageResource& image) {
case Dimension::Dim3D: return Kind::Sampled3D;
default: return std::nullopt;
}
case ResourceKind::ImageUint:
switch (image.dimension) {
case Dimension::Dim1D: return Kind::SampledUint1D;
case Dimension::Dim1DArray: return Kind::SampledUint1DArray;
case Dimension::Dim2D: return Kind::SampledUint2D;
case Dimension::Dim2DArray: return Kind::SampledUint2DArray;
case Dimension::Dim2DMsaa: return Kind::SampledUint2DMsaa;
case Dimension::Dim2DMsaaArray: return Kind::SampledUint2DMsaaArray;
case Dimension::Dim3D: return Kind::SampledUint3D;
default: return std::nullopt;
}
case ResourceKind::ImageSint:
switch (image.dimension) {
case Dimension::Dim1D: return Kind::SampledSint1D;
case Dimension::Dim1DArray: return Kind::SampledSint1DArray;
case Dimension::Dim2D: return Kind::SampledSint2D;
case Dimension::Dim2DArray: return Kind::SampledSint2DArray;
case Dimension::Dim2DMsaa: return Kind::SampledSint2DMsaa;
case Dimension::Dim2DMsaaArray: return Kind::SampledSint2DMsaaArray;
case Dimension::Dim3D: return Kind::SampledSint3D;
default: return std::nullopt;
}
case ResourceKind::StorageImage:
case ImageResourceClass::Storage:
if (image.atomic) {
return std::nullopt;
if (image.numeric_class != Prospero::TextureNumericClass::Uint) {
return std::nullopt;
}
switch (image.dimension) {
case Dimension::Dim1D: return Kind::StorageAtomic1D;
case Dimension::Dim1DArray: return Kind::StorageAtomic1DArray;
case Dimension::Dim2D: return Kind::StorageAtomic2D;
case Dimension::Dim2DArray: return Kind::StorageAtomic2DArray;
case Dimension::Dim3D: return Kind::StorageAtomic3D;
default: return std::nullopt;
}
}
switch (image.dimension) {
case Dimension::Dim1D: return Kind::Storage1D;
case Dimension::Dim1DArray: return Kind::Storage1DArray;
case Dimension::Dim2D: return Kind::Storage2D;
case Dimension::Dim2DArray: return Kind::Storage2DArray;
case Dimension::Dim3D: return Kind::Storage3D;
default: return std::nullopt;
switch (image.numeric_class) {
case Prospero::TextureNumericClass::Float:
switch (image.dimension) {
case Dimension::Dim1D: return Kind::Storage1D;
case Dimension::Dim1DArray: return Kind::Storage1DArray;
case Dimension::Dim2D: return Kind::Storage2D;
case Dimension::Dim2DArray: return Kind::Storage2DArray;
case Dimension::Dim3D: return Kind::Storage3D;
default: return std::nullopt;
}
case Prospero::TextureNumericClass::Uint:
switch (image.dimension) {
case Dimension::Dim1D: return Kind::StorageUint1D;
case Dimension::Dim1DArray: return Kind::StorageUint1DArray;
case Dimension::Dim2D: return Kind::StorageUint2D;
case Dimension::Dim2DArray: return Kind::StorageUint2DArray;
case Dimension::Dim3D: return Kind::StorageUint3D;
default: return std::nullopt;
}
case Prospero::TextureNumericClass::Sint:
case Prospero::TextureNumericClass::Unsupported: return std::nullopt;
}
case ResourceKind::StorageImageUint:
switch (image.dimension) {
case Dimension::Dim1D:
if (image.atomic) {
return Kind::StorageAtomic1D;
}
return Kind::StorageUint1D;
case Dimension::Dim1DArray:
if (image.atomic) {
return Kind::StorageAtomic1DArray;
}
return Kind::StorageUint1DArray;
case Dimension::Dim2D:
if (image.atomic) {
return Kind::StorageAtomic2D;
}
return Kind::StorageUint2D;
case Dimension::Dim2DArray:
if (image.atomic) {
return Kind::StorageAtomic2DArray;
}
return Kind::StorageUint2DArray;
case Dimension::Dim3D:
if (image.atomic) {
return Kind::StorageAtomic3D;
}
return Kind::StorageUint3D;
default: return std::nullopt;
}
default: return std::nullopt;
case ImageResourceClass::None: return std::nullopt;
}
return std::nullopt;
}
struct DescriptorBinding {
@@ -210,7 +210,7 @@ ImageOpcodeInfo ImageOpcodeInfoOf(ValueOpcode opcode) {
case ValueOpcode::ImageAtomicAnd32:
case ValueOpcode::ImageAtomicOr32:
case ValueOpcode::ImageAtomicXor32:
return {ImageAccess::Atomic, ImageResourceClass::StorageUint, false};
return {ImageAccess::Atomic, ImageResourceClass::Storage, false};
default: return {};
}
}
@@ -20,7 +20,7 @@ enum class BufferAccess { None, Read, Write, Atomic };
enum class SharedAccess { None, Read, Write, Atomic, Append, Consume };
enum class AddressAccess { None, Read, Write };
enum class ImageAccess { None, Read, Write, Atomic };
enum class ImageResourceClass { None, Sampled, Storage, StorageUint };
enum class ImageResourceClass { None, Sampled, Storage };
struct AddressOpcodeInfo {
AddressAccess access = AddressAccess::None;
@@ -89,17 +89,8 @@ Prospero::BufferFormat ImageConversionFormat(Prospero::BufferFormat format) {
: Prospero::BufferFormat::kInvalid;
}
Prospero::TextureNumericClass SampledResourceNumericClass(ResourceKind kind) {
switch (kind) {
case ResourceKind::Image: return Prospero::TextureNumericClass::Float;
case ResourceKind::ImageUint: return Prospero::TextureNumericClass::Uint;
case ResourceKind::ImageSint: return Prospero::TextureNumericClass::Sint;
default: return Prospero::TextureNumericClass::Unsupported;
}
}
bool RequiresPointSampler(const ImageResource& image) {
return image.kind == ResourceKind::ImageSint ||
return image.numeric_class == Prospero::TextureNumericClass::Sint ||
image.conversion_format != Prospero::BufferFormat::kInvalid;
}
@@ -365,7 +356,8 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
if (image.indirect_root != ImageResource::NoIndirectImage) {
const auto root = image.indirect_root;
if (root >= program.info.images.size() ||
program.info.images[root].kind != image.kind ||
program.info.images[root].resource_class != image.resource_class ||
program.info.images[root].numeric_class != image.numeric_class ||
program.info.images[root].dimension != image.dimension ||
program.info.images[root].mip_mode != image.mip_mode ||
program.info.images[root].mip_count != image.mip_count ||
@@ -376,13 +368,11 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
}
continue;
}
bool canonical_kind =
image.kind == ResourceKind::Image || image.kind == ResourceKind::StorageImage;
if (image.atomic) {
canonical_kind = image.kind == ResourceKind::StorageImageUint;
}
const auto canonical_numeric = image.atomic ? Prospero::TextureNumericClass::Uint
: Prospero::TextureNumericClass::Float;
if (image.dimension != Decoder::ImageDimension::Dim2D || image.cube ||
!canonical_kind) {
image.resource_class == ImageResourceClass::None ||
image.numeric_class != canonical_numeric) {
return false;
}
continue;
@@ -392,45 +382,39 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
DescriptorIsCube(descriptor) != image.cube) {
return false;
}
if (ImageResourceKindMatches(image.kind, ImageResourceClass::Sampled) ||
ImageResourceKindMatches(image.kind, ImageResourceClass::Storage)) {
const bool storage = image.kind == ResourceKind::StorageImage ||
image.kind == ResourceKind::StorageImageUint;
const auto format =
static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
if (image.atomic && format != Prospero::BufferFormat::k32UInt) {
if (image.resource_class == ImageResourceClass::None) {
return false;
}
const bool storage = image.resource_class == ImageResourceClass::Storage;
const auto format =
static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
if (image.atomic && format != Prospero::BufferFormat::k32UInt) {
return false;
}
const auto conversion_format = ImageConversionFormat(format);
if (image.conversion_format != conversion_format) {
return false;
}
if ((storage || conversion_format != Prospero::BufferFormat::kInvalid) &&
image.shader_swizzle != DescriptorImageSwizzle(descriptor)) {
return false;
}
auto numeric_class = Prospero::SampledTextureNumericClass(format);
if (storage) {
const bool raw_sint = format == Prospero::BufferFormat::k32SInt && image.written &&
!image.read && !image.atomic;
if ((!raw_sint && numeric_class == Prospero::TextureNumericClass::Sint) ||
numeric_class == Prospero::TextureNumericClass::Unsupported) {
return false;
}
const auto conversion_format = ImageConversionFormat(format);
if (image.conversion_format != conversion_format) {
return false;
}
if ((storage || conversion_format != Prospero::BufferFormat::kInvalid) &&
image.shader_swizzle != DescriptorImageSwizzle(descriptor)) {
return false;
}
if (storage) {
const bool raw_sint = format == Prospero::BufferFormat::k32SInt && image.written &&
!image.read && !image.atomic;
const auto numeric_class = Prospero::SampledTextureNumericClass(format);
if ((!raw_sint && numeric_class == Prospero::TextureNumericClass::Sint) ||
numeric_class == Prospero::TextureNumericClass::Unsupported) {
return false;
}
const bool uint_descriptor =
numeric_class == Prospero::TextureNumericClass::Uint || raw_sint;
const bool uint_program = image.kind == ResourceKind::StorageImageUint;
if (uint_descriptor != uint_program && !(image.atomic && uint_program)) {
return false;
}
} else {
const auto numeric_class = SampledResourceNumericClass(image.kind);
if (numeric_class != Prospero::SampledTextureNumericClass(format) ||
(image.depth_compare &&
numeric_class != Prospero::TextureNumericClass::Float)) {
return false;
}
if (raw_sint) {
numeric_class = Prospero::TextureNumericClass::Uint;
}
} else if (image.depth_compare && numeric_class != Prospero::TextureNumericClass::Float) {
return false;
}
if (image.numeric_class != numeric_class) {
return false;
}
}
return true;
@@ -628,24 +612,20 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
for (uint32_t i = 0; i < next.images.size(); i++) {
const auto& descriptor = next_snapshot.images[i];
auto& image = next.images[i];
if (image.resource_class == ImageResourceClass::None ||
(image.atomic && image.resource_class != ImageResourceClass::Storage)) {
return SpecializationFail(fmt::format("image resource {} has an invalid class", i));
}
image.mip_count = StorageMipCount(image, descriptor);
if (image.mip_count == 0u) {
return SpecializationFail(
fmt::format("storage image descriptor {} has an invalid mip range", i));
}
if (NullImageDescriptor(descriptor)) {
image.dimension = Decoder::ImageDimension::Dim2D;
image.cube = false;
switch (image.kind) {
case ResourceKind::ImageUint: image.kind = ResourceKind::Image; break;
case ResourceKind::ImageSint: image.kind = ResourceKind::Image; break;
case ResourceKind::StorageImageUint:
if (!image.atomic) {
image.kind = ResourceKind::StorageImage;
}
break;
default: break;
}
image.numeric_class = image.atomic ? Prospero::TextureNumericClass::Uint
: Prospero::TextureNumericClass::Float;
image.dimension = Decoder::ImageDimension::Dim2D;
image.cube = false;
continue;
}
const auto descriptor_dimension = DescriptorDimension(descriptor, image.dimension);
@@ -666,39 +646,30 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
fmt::format("atomic image descriptor {} uses unsupported format {}", i,
static_cast<uint32_t>(format)));
}
const bool storage = image.kind == ResourceKind::StorageImage ||
image.kind == ResourceKind::StorageImageUint;
const bool storage = image.resource_class == ImageResourceClass::Storage;
image.conversion_format = ImageConversionFormat(format);
if (storage || image.conversion_format != Prospero::BufferFormat::kInvalid) {
image.shader_swizzle = DescriptorImageSwizzle(descriptor);
}
const bool raw_sint_storage = storage && format == Prospero::BufferFormat::k32SInt &&
image.written && !image.read && !image.atomic;
const auto numeric_class = Prospero::SampledTextureNumericClass(format);
image.numeric_class = Prospero::SampledTextureNumericClass(format);
if (storage) {
if ((!raw_sint_storage && numeric_class == Prospero::TextureNumericClass::Sint) ||
numeric_class == Prospero::TextureNumericClass::Unsupported) {
if ((!raw_sint_storage && image.numeric_class == Prospero::TextureNumericClass::Sint) ||
image.numeric_class == Prospero::TextureNumericClass::Unsupported) {
return SpecializationFail(
fmt::format("storage image descriptor {} uses unsupported format {}", i,
static_cast<uint32_t>(format)));
}
if (numeric_class == Prospero::TextureNumericClass::Uint || raw_sint_storage) {
image.kind = ResourceKind::StorageImageUint;
}
} else {
switch (numeric_class) {
case Prospero::TextureNumericClass::Uint:
image.kind = ResourceKind::ImageUint;
break;
case Prospero::TextureNumericClass::Sint:
image.kind = ResourceKind::ImageSint;
break;
case Prospero::TextureNumericClass::Float: break;
case Prospero::TextureNumericClass::Unsupported:
return SpecializationFail(
fmt::format("sampled image descriptor {} uses unsupported format {}", i,
static_cast<uint32_t>(format)));
if (raw_sint_storage) {
image.numeric_class = Prospero::TextureNumericClass::Uint;
}
} else if (image.numeric_class == Prospero::TextureNumericClass::Unsupported ||
(image.depth_compare &&
image.numeric_class != Prospero::TextureNumericClass::Float)) {
return SpecializationFail(
fmt::format("sampled image descriptor {} uses unsupported format {}", i,
static_cast<uint32_t>(format)));
}
}
for (uint32_t root_index = 0; root_index < next.images.size(); root_index++) {
@@ -733,14 +704,17 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
continue;
}
if (NullImageDescriptor(next_snapshot.images[candidate])) {
image.kind = image_class.kind;
image.resource_class = image_class.resource_class;
image.numeric_class = image_class.numeric_class;
image.dimension = image_class.dimension;
image.mip_count = image_class.mip_count;
image.conversion_format = image_class.conversion_format;
image.shader_swizzle = image_class.shader_swizzle;
image.cube = image_class.cube;
}
if (image.kind != image_class.kind || image.dimension != image_class.dimension ||
if (image.resource_class != image_class.resource_class ||
image.numeric_class != image_class.numeric_class ||
image.dimension != image_class.dimension ||
image.mip_mode != image_class.mip_mode ||
image.mip_count != image_class.mip_count ||
image.conversion_format != image_class.conversion_format ||
@@ -824,9 +798,6 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
fmt::format("indirect image table at pc 0x{:08x} is used by unsupported {}",
inst.Flags<MemoryFlags>().pc, ValueOpcodeName(inst.GetOpcode())));
}
memory.kind = image.kind;
memory.image_dimension = image.dimension;
memory.image_cube = image.cube;
}
}
program.info = std::move(next);
@@ -80,17 +80,6 @@ uint32_t ByteExtent(const MemoryInfo& memory) {
return end > UINT32_MAX ? UINT32_MAX : static_cast<uint32_t>(end);
}
bool IsStorageImage(ResourceKind kind) {
return kind == ResourceKind::StorageImage || kind == ResourceKind::StorageImageUint;
}
ImageMipMode MipMode(const MemoryInfo& memory) {
if (IsStorageImage(memory.kind) && memory.image_has_mip) {
return ImageMipMode::DynamicStorage;
}
return ImageMipMode::None;
}
class Tracker {
public:
explicit Tracker(Program& program): m_program(program), m_info(program.info) {
@@ -526,18 +515,16 @@ private:
}
uint32_t AddImage(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) {
const auto mip = MipMode(memory);
const auto resource_class = ImageOpcodeInfoOf(op).resource_class;
const auto mip = resource_class == ImageResourceClass::Storage && memory.image_has_mip
? ImageMipMode::DynamicStorage
: ImageMipMode::None;
const bool depth = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0;
for (uint32_t i = 0; i < m_info.images.size(); i++) {
auto& image = m_info.images[i];
const bool compatible_kind = image.kind == memory.kind || (IsStorageImage(image.kind) &&
IsStorageImage(memory.kind));
if (image.source == source && compatible_kind &&
auto& image = m_info.images[i];
if (image.source == source && image.resource_class == resource_class &&
image.dimension == memory.image_dimension && image.mip_mode == mip &&
image.depth_compare == depth && image.r128 == memory.image_r128) {
if (memory.kind == ResourceKind::StorageImageUint) {
image.kind = ResourceKind::StorageImageUint;
}
Merge(image, op, pc);
return i;
}
@@ -546,13 +533,13 @@ private:
return UINT32_MAX;
}
ImageResource image;
image.source = source;
image.first_use_pc = pc;
image.kind = memory.kind;
image.dimension = memory.image_dimension;
image.mip_mode = mip;
image.depth_compare = depth;
image.r128 = memory.image_r128;
image.source = source;
image.first_use_pc = pc;
image.resource_class = resource_class;
image.dimension = memory.image_dimension;
image.mip_mode = mip;
image.depth_compare = depth;
image.r128 = memory.image_r128;
Merge(image, op, pc);
m_info.images.push_back(image);
return static_cast<uint32_t>(m_info.images.size() - 1);
@@ -681,7 +668,8 @@ private:
return;
}
if (!ImageResourceKindMatches(memory.kind, image_info.resource_class)) {
if (memory.kind != ResourceKind::Image ||
image_info.resource_class == ImageResourceClass::None) {
Fail(flags.pc, "image operation has invalid resource kind");
}
handle = inst.Arg(0).Resolve().TryInstruction();