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
@@ -185,29 +185,36 @@ uint32_t StorageImagePointerType(EmitterState& state, StorageImageClass image_cl
}
StorageImageClass StorageImageClassForResource(const EmitterState& state, uint32_t resource) {
const auto& image = state.program.info.images.at(resource);
const bool uint_image = image.kind == IR::ResourceKind::StorageImageUint;
EXIT_IF(image.kind != IR::ResourceKind::StorageImage && !uint_image);
EXIT_IF(image.atomic && !uint_image);
return StorageImageClassFor(uint_image, image.atomic);
const auto& image = state.program.info.images.at(resource);
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage);
if (image.atomic) {
EXIT_IF(image.numeric_class != Prospero::TextureNumericClass::Uint);
return StorageImageClass::AtomicUint;
}
switch (image.numeric_class) {
case Prospero::TextureNumericClass::Float: return StorageImageClass::FormatlessFloat;
case Prospero::TextureNumericClass::Uint: return StorageImageClass::FormatlessUint;
case Prospero::TextureNumericClass::Sint:
case Prospero::TextureNumericClass::Unsupported:
EXIT("invalid storage image numeric class");
}
EXIT("invalid storage image numeric class");
}
uint32_t StorageImageVariable(const EmitterState& state, StorageImageClass image_class,
ImageDimension dimension) {
return state.storage_image_variables[StorageImageIndex(image_class, dimension)];
}
uint32_t LoadSampledImageDescriptor(EmitterState& state, const IR::MemoryInfo& mem,
ImageDimension dimension) {
const auto image_class = SampledImageClassFor(mem.kind);
uint32_t LoadSampledImageDescriptor(EmitterState& state, uint32_t resource) {
const auto& image_resource = state.program.info.images.at(resource);
EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
const auto dimension = image_resource.dimension;
const auto image_class = SampledImageClassFor(image_resource.numeric_class);
EXIT_IF(image_class == SampledImageClass::Count);
const auto kind = SampledBindingKind(image_class, dimension);
const auto array_index = ResourceForDescriptor(state, kind, mem.resource);
const auto array_index = ResourceForDescriptor(state, kind, resource);
const auto variable = state.sampled_image_variables[SampledImageIndex(image_class, dimension)];
const auto pointer_type =
state.builder.Type(OpTypePointer, {StorageClassUniformConstant,
ImageViewImageType(state, dimension, image_class)});
const auto pointer =
DescriptorElementPointer(state, pointer_type, variable, array_index, kind, mem.resource,
DescriptorElementPointer(state, pointer_type, variable, array_index, kind, resource,
"sampled image descriptor array was not emitted");
const auto image = state.builder.AllocateId();
state.builder.AddFunction(
@@ -229,43 +236,37 @@ uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler) {
return sampler_id;
}
uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem,
ImageDimension dimension) {
const auto image = LoadSampledImageDescriptor(state, mem, dimension);
const auto sampler = LoadSamplerDescriptor(state, mem.sampler);
if (image == 0 || sampler == 0) {
ExitDescriptorBindingFailure(
state, SampledBindingKind(SampledImageClassFor(mem.kind), dimension), mem.resource,
"sampled image or sampler descriptor load failed");
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler) {
const auto& image_resource = state.program.info.images.at(resource);
const auto dimension = image_resource.dimension;
const auto image_class = SampledImageClassFor(image_resource.numeric_class);
const auto image = LoadSampledImageDescriptor(state, resource);
const auto sampler_id = LoadSamplerDescriptor(state, sampler);
if (image == 0 || sampler_id == 0) {
ExitDescriptorBindingFailure(state, SampledBindingKind(image_class, dimension), resource,
"sampled image or sampler descriptor load failed");
}
const auto sampled_image = state.builder.AllocateId();
state.builder.AddFunction(
{OpSampledImage,
ImageViewSampledImageType(state, dimension, SampledImageClassFor(mem.kind)), sampled_image,
image, sampler});
state.builder.AddFunction({OpSampledImage,
ImageViewSampledImageType(state, dimension, image_class),
sampled_image, image, sampler_id});
return sampled_image;
}
uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, ImageDimension dimension,
uint32_t image_resource) {
auto selected = mem;
selected.resource = image_resource;
return MakeSampledImage(state, selected, dimension);
}
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource,
ImageDimension dimension) {
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource) {
const auto dimension = state.program.info.images.at(resource).dimension;
const auto image_class = StorageImageClassForResource(state, resource);
const auto kind = StorageBindingKind(image_class, dimension);
const auto array_index = ResourceForDescriptor(state, kind, resource);
const auto ptr_type = StorageImagePointerType(state, image_class, dimension);
const auto variable = StorageImageVariable(state, image_class, dimension);
const auto variable = state.storage_image_variables[StorageImageIndex(image_class, dimension)];
return DescriptorElementPointer(state, ptr_type, variable, array_index, kind, resource,
"storage image descriptor array was not emitted");
}
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, ImageDimension dimension,
uint32_t mip_lod, uint32_t coord, uint32_t texel) {
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
uint32_t texel) {
const auto dimension = state.program.info.images.at(resource).dimension;
const auto image_class = StorageImageClassForResource(state, resource);
if (image_class != StorageImageClass::AtomicUint) {
state.builder.RequireCapability(CapabilityStorageImageWriteWithoutFormat);
@@ -276,8 +277,8 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, ImageDimensio
const auto LoadAt = [&](uint32_t array_index) {
const auto pointer = DescriptorElementPointer(
state, StorageImagePointerType(state, image_class, dimension),
StorageImageVariable(state, image_class, dimension), array_index, kind, resource,
"storage image descriptor array was not emitted");
state.storage_image_variables[StorageImageIndex(image_class, dimension)], array_index,
kind, resource, "storage image descriptor array was not emitted");
const auto descriptor = state.builder.AllocateId();
state.builder.AddFunction(
{OpLoad, StorageImageType(state, image_class, dimension), descriptor, pointer});
@@ -145,11 +145,12 @@ uint32_t CubeLayer(EmitterState& state, uint32_t value) {
uint32_t CoordF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
uint32_t first, uint32_t components) {
const bool cube = ctx.state.program.info.images.at(mem.resource).cube;
auto x = AddressF32(ctx, mem, address, first);
if (components == 1u) return x;
auto y = mem.image_address_components > first + 1u ? AddressF32(ctx, mem, address, first + 1u)
: ZeroF32(ctx.state);
if (mem.image_cube) {
if (cube) {
x = CubeAxis(ctx.state, x);
y = CubeAxis(ctx.state, y);
}
@@ -158,7 +159,7 @@ uint32_t CoordF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::In
auto z = mem.image_address_components > first + 2u
? AddressF32(ctx, mem, address, first + 2u)
: ZeroF32(ctx.state);
if (mem.image_cube) z = CubeLayer(ctx.state, z);
if (cube) z = CubeLayer(ctx.state, z);
ctx.state.builder.AddFunction(
{OpCompositeConstruct, TypeF32Vector(ctx.state, 3), result, x, y, z});
} else {
@@ -284,9 +285,9 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, SampledImageClass i
uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
const IR::Inst& address) {
const auto dimension = mem.image_dimension;
const auto dimension = ctx.state.program.info.images.at(mem.resource).dimension;
const auto& info = ImageDimensionInfoFor(dimension);
const auto image = LoadSampledImageDescriptor(ctx.state, mem, dimension);
const auto image = LoadSampledImageDescriptor(ctx.state, mem.resource);
const auto size = ctx.state.builder.AllocateId();
if (info.multisampled != 0u) {
ctx.state.builder.AddFunction(
@@ -464,7 +465,7 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
uint32_t coord, SampledImageClass image_class) {
auto& state = ctx.state;
state.builder.RequireCapability(CapabilityImageQuery);
const auto image = LoadSampledImageDescriptor(state, mem, ImageDimension::Dim1D);
const auto image = LoadSampledImageDescriptor(state, mem.resource);
const auto width = state.builder.AllocateId();
state.builder.AddFunction(
{OpImageQuerySizeLod, TypeU32(state), width, image, ConstantU32(state, 0)});
@@ -476,7 +477,7 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
Binary(state, OpFMul, TypeF32(state), coord, width_f32),
ConstantF32(state, 0x3f000000u))});
const auto sampled = MakeSampledImage(state, mem, ImageDimension::Dim1D);
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
const auto vector_type = SampledImageVectorType(state, image_class, 4);
const auto scalar_type = SampledImageScalarType(state, image_class);
const auto component =
@@ -592,6 +593,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto& mem = ctx.Memory(inst);
const auto image_arg = inst.Arg(0);
ctx.ResourceIndex(image_arg, IR::ValueOpcode::GetImageResource);
const auto& image = state.program.info.images.at(mem.resource);
const auto* address = ctx.ImageAddress(inst.Arg(image_info.needs_sampler ? 2 : 1));
if (address == nullptr) return true;
if (op == IR::ValueOpcode::ImageQueryDimensions) {
@@ -601,8 +603,8 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
}
if (op == IR::ValueOpcode::ImageQueryLod) {
state.builder.RequireCapability(CapabilityImageQuery);
const auto dimension = mem.image_dimension;
const auto sampled = MakeSampledImage(state, mem, dimension);
const auto dimension = image.dimension;
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
const auto lod = state.builder.AllocateId();
state.builder.AddFunction(
{OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
@@ -621,27 +623,27 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
return true;
}
if (op == IR::ValueOpcode::ImageRead) {
const auto dimension = mem.image_dimension;
const auto dimension = image.dimension;
const auto& dimension_info = ImageDimensionInfoFor(dimension);
const auto image_class = SampledImageClassFor(mem.kind);
const auto image_class = SampledImageClassFor(image.numeric_class);
const auto condition = ctx.Arg(inst, 2);
ctx.Define(
inst,
EmitValueOrDefaultIfCondition(
state, condition, TypeU32Vector(state, 4), ConstantU32CompositeZero(state, 4),
[&]() {
const auto image = LoadSampledImageDescriptor(state, mem, dimension);
const auto color = state.builder.AllocateId();
const auto coord = CoordU32(ctx, mem, *address, dimension);
const auto descriptor = LoadSampledImageDescriptor(state, mem.resource);
const auto color = state.builder.AllocateId();
const auto coord = CoordU32(ctx, mem, *address, dimension);
if (dimension_info.multisampled != 0u) {
state.builder.AddFunction(
{OpImageFetch, SampledImageVectorType(state, image_class, 4), color,
image, coord, ImageOperandsSampleMask,
descriptor, coord, ImageOperandsSampleMask,
AddressU32(ctx, mem, *address, dimension_info.coordinate_components)});
} else {
state.builder.AddFunction({OpImageFetch,
SampledImageVectorType(state, image_class, 4),
color, image, coord, ImageOperandsLodMask,
color, descriptor, coord, ImageOperandsLodMask,
LodU32(ctx, mem, *address, dimension)});
}
return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), image_class, false,
@@ -650,8 +652,8 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
return true;
}
if (op == IR::ValueOpcode::ImageWrite) {
const auto uint_image = mem.kind == IR::ResourceKind::StorageImageUint;
const auto dimension = mem.image_dimension;
const bool uint_image = image.numeric_class == Prospero::TextureNumericClass::Uint;
const auto dimension = image.dimension;
EmitIfCondition(state, ctx.Arg(inst, 3), [&]() {
const auto mip_lod =
state.program.info.images[mem.resource].mip_mode == IR::ImageMipMode::DynamicStorage
@@ -659,15 +661,15 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
: 0u;
const auto coord = CoordU32(ctx, mem, *address, dimension);
const auto texel = StoreTexel(ctx, mem, ctx.Arg(inst, 2), uint_image);
EmitStorageImageWrite(state, mem.resource, dimension, mip_lod, coord, texel);
EmitStorageImageWrite(state, mem.resource, mip_lod, coord, texel);
});
return true;
}
if (op == IR::ValueOpcode::ImageSampleRaw || op == IR::ValueOpcode::ImageGatherRaw) {
const auto dimension = mem.image_dimension;
const auto dimension = image.dimension;
const auto& dimension_info = ImageDimensionInfoFor(dimension);
const auto layout = Layout(mem, dimension);
const auto image_class = SampledImageClassFor(mem.kind);
const auto image_class = SampledImageClassFor(image.numeric_class);
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
if (dref && state.program.info.images[mem.resource].conversion_format !=
Prospero::BufferFormat::kInvalid) {
@@ -693,7 +695,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
ctx.Fail(inst, "has an unsupported 1D-array gather");
return true;
}
const auto sampled = MakeSampledImage(state, mem, dimension);
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
const auto sample = state.builder.AllocateId();
std::vector<uint32_t> words =
dref ? std::vector<uint32_t> {OpImageDrefGather,
@@ -759,7 +761,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
operands.push_back(AddressF32(ctx, mem, *address, layout.bias));
}
const auto EmitSample = [&](uint32_t resource) {
const auto sampled = MakeSampledImage(state, mem, dimension, resource);
const auto sampled = MakeSampledImage(state, resource, mem.sampler);
const auto sample = state.builder.AllocateId();
std::vector<uint32_t> words {opcode, result_type, sample, sampled, coord};
if (dref) {
@@ -772,7 +774,6 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
state.builder.AddFunction(words);
return sample;
};
const auto& image = state.program.info.images[mem.resource];
if (image.indirect_root != mem.resource) {
const auto sample = EmitSample(mem.resource);
ctx.Define(inst, ResultVector(ctx, dref ? sample : UnpackImageTexel(ctx, mem, sample),
@@ -874,14 +875,14 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
}
const auto atomic_opcode = ImageAtomicOpcode(op);
if (atomic_opcode != 0u) {
const auto dimension = mem.image_dimension;
const auto dimension = image.dimension;
ctx.Define(inst, EmitValueOrZeroIfCondition(state, ctx.Arg(inst, 3), [&]() {
const auto pointer = state.builder.AllocateId();
const auto pointer_type =
state.builder.Type(OpTypePointer, {StorageClassImage, TypeU32(state)});
state.builder.AddFunction(
{OpImageTexelPointer, pointer_type, pointer,
StorageImageDescriptorPointer(state, mem.resource, dimension),
StorageImageDescriptorPointer(state, mem.resource),
CoordU32(ctx, mem, *address, dimension), ConstantU32(state, 0)});
const auto old = state.builder.AllocateId();
state.builder.AddFunction({atomic_opcode, TypeU32(state), old, pointer,
@@ -482,13 +482,14 @@ struct ImageSampleLayout {
uint32_t grad_y = NoImageComponent;
};
constexpr SampledImageClass SampledImageClassFor(IR::ResourceKind kind) {
switch (kind) {
case IR::ResourceKind::Image: return SampledImageClass::Float;
case IR::ResourceKind::ImageUint: return SampledImageClass::Uint;
case IR::ResourceKind::ImageSint: return SampledImageClass::Sint;
default: return SampledImageClass::Count;
constexpr SampledImageClass SampledImageClassFor(Prospero::TextureNumericClass numeric_class) {
switch (numeric_class) {
case Prospero::TextureNumericClass::Float: return SampledImageClass::Float;
case Prospero::TextureNumericClass::Uint: return SampledImageClass::Uint;
case Prospero::TextureNumericClass::Sint: return SampledImageClass::Sint;
case Prospero::TextureNumericClass::Unsupported: return SampledImageClass::Count;
}
return SampledImageClass::Count;
}
constexpr bool IsIntegerSampledImage(SampledImageClass image_class) {
@@ -500,16 +501,6 @@ inline uint32_t SampledImageIndex(SampledImageClass image_class, ImageDimension
static_cast<uint32_t>(image_class) * SampledImageDimensionCount;
}
constexpr StorageImageClass StorageImageClassFor(bool uint_image, bool atomic) {
if (atomic) {
return StorageImageClass::AtomicUint;
}
if (uint_image) {
return StorageImageClass::FormatlessUint;
}
return StorageImageClass::FormatlessFloat;
}
inline uint32_t StorageImageIndex(StorageImageClass image_class, ImageDimension dimension) {
return ImageDimensionInfoFor(dimension).ordinal +
static_cast<uint32_t>(image_class) * StorageImageDimensionCount;
@@ -667,20 +658,16 @@ uint32_t StorageImageType(EmitterState& state, StorageImageClass image_class,
uint32_t StorageImagePointerType(EmitterState& state, StorageImageClass image_class,
ImageDimension dimension);
uint32_t LoadSampledImageDescriptor(EmitterState& state, const IR::MemoryInfo& mem,
ImageDimension dimension);
uint32_t LoadSampledImageDescriptor(EmitterState& state, uint32_t resource);
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler);
uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, ImageDimension dimension);
uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, ImageDimension dimension,
uint32_t image_resource);
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler);
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource,
ImageDimension dimension);
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource);
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, ImageDimension dimension,
uint32_t mip_lod, uint32_t coord, uint32_t texel);
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
uint32_t texel);
uint32_t ExecutionModelForStage(ShaderType stage);
@@ -84,18 +84,7 @@ ResourceKind MemoryKind(const Decoder::Instruction& decoded) {
case Decoder::Family::MTBUF: return ResourceKind::Buffer;
case Decoder::Family::FLAT: return FlatSegmentResourceKind(decoded.memory_segment);
case Decoder::Family::DS: return decoded.gds ? ResourceKind::Gds : ResourceKind::Lds;
case Decoder::Family::MIMG:
switch (decoded.opcode) {
case Decoder::Opcode::IMAGE_STORE:
case Decoder::Opcode::IMAGE_STORE_MIP: return ResourceKind::StorageImage;
case Decoder::Opcode::IMAGE_ATOMIC_ADD:
case Decoder::Opcode::IMAGE_ATOMIC_UMIN:
case Decoder::Opcode::IMAGE_ATOMIC_UMAX:
case Decoder::Opcode::IMAGE_ATOMIC_AND:
case Decoder::Opcode::IMAGE_ATOMIC_OR:
case Decoder::Opcode::IMAGE_ATOMIC_XOR: return ResourceKind::StorageImageUint;
default: return ResourceKind::Image;
}
case Decoder::Family::MIMG: return ResourceKind::Image;
default: return ResourceKind::None;
}
}
@@ -153,10 +142,6 @@ IR::MemoryInfo MemoryInfoFromDecoded(const Decoder::Instruction& decoded) {
decoded.opcode == Decoder::Opcode::DS_WRITE2ST64_B32) {
memory.data_dwords = 2u;
}
if (memory.kind == ResourceKind::StorageImage ||
memory.kind == ResourceKind::StorageImageUint) {
memory.sampler = 0;
}
return memory;
}
@@ -240,7 +225,10 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
decoded.opcode == Decoder::Opcode::IMAGE_STORE_MIP ||
(decoded.opcode >= Decoder::Opcode::IMAGE_ATOMIC_ADD &&
decoded.opcode <= Decoder::Opcode::IMAGE_ATOMIC_XOR);
return store_or_atomic ? (index == 0u ? decoded.dst : decoded.src0) : decoded.src0;
if (store_or_atomic) {
return index == 0u ? decoded.dst : decoded.src0;
}
return decoded.src0;
}
if (decoded.family == Decoder::Family::DS) {
switch (decoded.opcode) {
@@ -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();