shader: separate formatless storage from image atomics

Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
Co-authored-by: Senaxx <Senaxx@users.noreply.github.com>
This commit is contained in:
nmzik
2026-08-21 01:39:22 +02:00
parent 719e20d470
commit 770bdb9571
11 changed files with 345 additions and 172 deletions
@@ -51,7 +51,12 @@ bool IsStorageImage(BindingKind kind) {
case BindingKind::StorageUint1DArray:
case BindingKind::StorageUint2D:
case BindingKind::StorageUint2DArray:
case BindingKind::StorageUint3D: return true;
case BindingKind::StorageUint3D:
case BindingKind::StorageAtomic1D:
case BindingKind::StorageAtomic1DArray:
case BindingKind::StorageAtomic2D:
case BindingKind::StorageAtomic2DArray:
case BindingKind::StorageAtomic3D: return true;
default: return false;
}
}
@@ -286,10 +286,6 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
LOGF("shaderStorageImageWriteWithoutFormat is not supported\n");
skip_device = true;
}
if (device_features2.features.shaderStorageImageReadWithoutFormat != VK_TRUE) {
LOGF("shaderStorageImageReadWithoutFormat is not supported\n");
skip_device = true;
}
if (device_features2.features.shaderImageGatherExtended != VK_TRUE) {
LOGF("shaderImageGatherExtended is not supported\n");
@@ -570,7 +566,6 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
device_features.depthBounds = VK_TRUE; // unsupported by MoltenVK
#endif
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
device_features.shaderStorageImageReadWithoutFormat = VK_TRUE;
device_features.shaderImageGatherExtended = VK_TRUE;
device_features.independentBlend = VK_TRUE;
device_features.tessellationShader = VK_TRUE;
@@ -134,11 +134,7 @@ ImageViewKind SampledImageViewKind(const EmitterState& state, const IR::MemoryIn
return ImageViewKindFromDimension(mem.image_dimension);
}
ImageViewKind StorageImageViewKind(const EmitterState& state, const IR::MemoryInfo& mem,
bool uint_image, uint32_t use_pc) {
(void)state;
(void)uint_image;
(void)use_pc;
ImageViewKind StorageImageViewKind(const IR::MemoryInfo& mem) {
return ImageViewKindFromDimension(mem.image_dimension);
}
@@ -188,20 +184,36 @@ uint32_t ImageViewSizeType(EmitterState& state, ImageViewKind view) {
}
}
uint32_t StorageImageType(EmitterState& state, bool uint_image, ImageViewKind view) {
const auto component = uint_image ? TypeU32(state) : TypeF32(state);
const auto format = uint_image ? ImageFormatR32ui : ImageFormatUnknown;
uint32_t StorageImageType(EmitterState& state, StorageImageClass image_class, ImageViewKind view) {
uint32_t component = TypeF32(state);
uint32_t format = ImageFormatUnknown;
if (image_class == StorageImageClass::FormatlessUint) {
component = TypeU32(state);
} else if (image_class == StorageImageClass::AtomicUint) {
component = TypeU32(state);
format = ImageFormatR32ui;
}
return state.builder.Type(OpTypeImage, {component, ImageSpirvDimension(view), 0,
ImageSpirvArrayed(view), 0, 2, format});
}
uint32_t StorageImagePointerType(EmitterState& state, bool uint_image, ImageViewKind view) {
uint32_t StorageImagePointerType(EmitterState& state, StorageImageClass image_class,
ImageViewKind view) {
return state.builder.Type(
OpTypePointer, {StorageClassUniformConstant, StorageImageType(state, uint_image, view)});
OpTypePointer, {StorageClassUniformConstant, StorageImageType(state, image_class, view)});
}
uint32_t StorageImageVariable(const EmitterState& state, bool uint_image, ImageViewKind view) {
return state.storage_image_variables[StorageImageIndex(uint_image, view)];
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);
}
uint32_t StorageImageVariable(const EmitterState& state, StorageImageClass image_class,
ImageViewKind view) {
return state.storage_image_variables[StorageImageIndex(image_class, view)];
}
uint32_t LoadSampledImageDescriptor(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc,
@@ -259,46 +271,33 @@ uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32
return MakeSampledImage(state, selected, use_pc, view);
}
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource, bool uint_image,
uint32_t use_pc, ImageViewKind view) {
(void)use_pc;
const auto kind = StorageBindingKind(uint_image, view);
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource, ImageViewKind view) {
const auto image_class = StorageImageClassForResource(state, resource);
const auto kind = StorageBindingKind(image_class, view);
const auto binding = ResourceForDescriptor(state, kind, resource);
const auto ptr_type = StorageImagePointerType(state, uint_image, view);
const auto variable = StorageImageVariable(state, uint_image, view);
const auto ptr_type = StorageImagePointerType(state, image_class, view);
const auto variable = StorageImageVariable(state, image_class, view);
return DescriptorElementPointer(state, ptr_type, variable, binding.array_index, kind, resource,
"storage image descriptor array was not emitted");
}
uint32_t LoadStorageImageDescriptor(EmitterState& state, uint32_t resource, bool uint_image,
uint32_t use_pc, ImageViewKind view) {
const auto pointer = StorageImageDescriptorPointer(state, resource, uint_image, use_pc, view);
if (pointer == 0) {
ExitDescriptorBindingFailure(state, StorageBindingKind(uint_image, view), resource,
"storage image descriptor pointer creation failed");
}
const auto type = StorageImageType(state, uint_image, view);
const auto image = state.builder.AllocateId();
state.builder.AddFunction({OpLoad, type, image, pointer});
return image;
}
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, bool uint_image,
ImageViewKind view, uint32_t mip_lod, uint32_t coord, uint32_t texel) {
if (!uint_image) {
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, ImageViewKind view,
uint32_t mip_lod, uint32_t coord, uint32_t texel) {
const auto image_class = StorageImageClassForResource(state, resource);
if (image_class != StorageImageClass::AtomicUint) {
state.builder.RequireCapability(CapabilityStorageImageWriteWithoutFormat);
}
const auto kind = StorageBindingKind(uint_image, view);
const auto kind = StorageBindingKind(image_class, view);
const auto binding = ResourceForDescriptor(state, kind, resource);
const auto& image = state.program.info.images.at(resource);
const auto LoadAt = [&](uint32_t array_index) {
const auto pointer = DescriptorElementPointer(
state, StorageImagePointerType(state, uint_image, view),
StorageImageVariable(state, uint_image, view), array_index, kind, resource,
state, StorageImagePointerType(state, image_class, view),
StorageImageVariable(state, image_class, view), array_index, kind, resource,
"storage image descriptor array was not emitted");
const auto descriptor = state.builder.AllocateId();
state.builder.AddFunction(
{OpLoad, StorageImageType(state, uint_image, view), descriptor, pointer});
{OpLoad, StorageImageType(state, image_class, view), descriptor, pointer});
return descriptor;
};
if (image.mip_mode != IR::ImageMipMode::DynamicStorage) {
@@ -588,7 +588,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
}
if (op == IR::ValueOpcode::ImageWrite) {
const auto uint_image = mem.kind == IR::ResourceKind::StorageImageUint;
const auto view = StorageImageViewKind(state, mem, uint_image, pc);
const auto view = StorageImageViewKind(mem);
EmitIfCondition(state, ctx.Arg(inst, 3), [&]() {
const auto mip_lod =
state.program.info.images[mem.resource].mip_mode == IR::ImageMipMode::DynamicStorage
@@ -596,7 +596,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
: 0u;
const auto coord = CoordU32(ctx, mem, *address, view);
const auto texel = StoreTexel(ctx, mem, ctx.Arg(inst, 2), uint_image);
EmitStorageImageWrite(state, mem.resource, uint_image, view, mip_lod, coord, texel);
EmitStorageImageWrite(state, mem.resource, view, mip_lod, coord, texel);
});
return true;
}
@@ -793,14 +793,14 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
}
const auto atomic_opcode = ImageAtomicOpcode(op);
if (atomic_opcode != 0u) {
const auto view = StorageImageViewKind(state, mem, true, pc);
const auto view = StorageImageViewKind(mem);
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, true, pc, view),
StorageImageDescriptorPointer(state, mem.resource, view),
CoordU32(ctx, mem, *address, view), ConstantU32(state, 0)});
const auto old = state.builder.AllocateId();
state.builder.AddFunction({atomic_opcode, TypeU32(state), old, pointer,
@@ -42,7 +42,6 @@ enum : uint32_t {
CapabilitySampled1D = 43,
CapabilityImage1D = 44,
CapabilityImageQuery = 50,
CapabilityStorageImageReadWithoutFormat = 55,
CapabilityStorageImageWriteWithoutFormat = 56,
CapabilityGroupNonUniform = 61,
CapabilityGroupNonUniformBallot = 64,
@@ -298,6 +297,27 @@ struct DescriptorResourceBinding {
uint32_t array_index = 0;
};
enum class ImageViewKind {
Dim1D,
Dim1DArray,
Dim2D,
Dim2DArray,
Dim3D,
Dim2DMsaa,
Dim2DMsaaArray,
Count,
};
constexpr uint32_t SampledImageViewKindCount = static_cast<uint32_t>(ImageViewKind::Count);
constexpr uint32_t StorageImageViewKindCount = static_cast<uint32_t>(ImageViewKind::Dim2DMsaa);
enum class StorageImageClass : uint32_t {
FormatlessFloat,
FormatlessUint,
AtomicUint,
Count,
};
struct EmitterState {
EmitterState(const IR::Program& program_, const IR::ResourceSnapshot& resources_,
ShaderStageInputInfo input_info_)
@@ -322,8 +342,10 @@ struct EmitterState {
uint32_t flattened_srt_variable = 0;
uint32_t lds_variable = 0;
uint32_t scratch_variable = 0;
std::array<uint32_t, 14> sampled_image_variables {};
std::array<uint32_t, 10> storage_image_variables {};
std::array<uint32_t, 2u * SampledImageViewKindCount> sampled_image_variables {};
std::array<uint32_t,
static_cast<uint32_t>(StorageImageClass::Count) * StorageImageViewKindCount>
storage_image_variables {};
uint32_t sampler_variable = 0;
uint32_t main_func = 0;
uint32_t entry_label = 0;
@@ -414,26 +436,23 @@ struct ImageSampleLayout {
uint32_t grad_y = NoImageComponent;
};
enum class ImageViewKind {
Dim1D,
Dim1DArray,
Dim2D,
Dim2DArray,
Dim3D,
Dim2DMsaa,
Dim2DMsaaArray,
Count,
};
constexpr uint32_t SampledImageViewKindCount = static_cast<uint32_t>(ImageViewKind::Count);
constexpr uint32_t StorageImageViewKindCount = static_cast<uint32_t>(ImageViewKind::Dim2DMsaa);
constexpr uint32_t SampledImageIndex(bool integer, ImageViewKind view) {
return static_cast<uint32_t>(view) + (integer ? SampledImageViewKindCount : 0u);
}
constexpr uint32_t StorageImageIndex(bool integer, ImageViewKind view) {
return static_cast<uint32_t>(view) + (integer ? StorageImageViewKindCount : 0u);
constexpr StorageImageClass StorageImageClassFor(bool uint_image, bool atomic) {
if (atomic) {
return StorageImageClass::AtomicUint;
}
if (uint_image) {
return StorageImageClass::FormatlessUint;
}
return StorageImageClass::FormatlessFloat;
}
constexpr uint32_t StorageImageIndex(StorageImageClass image_class, ImageViewKind view) {
return static_cast<uint32_t>(view) +
static_cast<uint32_t>(image_class) * StorageImageViewKindCount;
}
constexpr IR::DescriptorBindingKind SampledBindingKind(bool integer, ImageViewKind view) {
@@ -463,24 +482,44 @@ constexpr IR::DescriptorBindingKind SampledBindingKind(bool integer, ImageViewKi
return IR::DescriptorBindingKind::Count;
}
constexpr IR::DescriptorBindingKind StorageBindingKind(bool integer, ImageViewKind view) {
if (integer) {
switch (view) {
case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::StorageUint1D;
case ImageViewKind::Dim1DArray: return IR::DescriptorBindingKind::StorageUint1DArray;
case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::StorageUint2D;
case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::StorageUint2DArray;
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::StorageUint3D;
default: break;
}
}
switch (view) {
case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::Storage1D;
case ImageViewKind::Dim1DArray: return IR::DescriptorBindingKind::Storage1DArray;
case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::Storage2D;
case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::Storage2DArray;
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::Storage3D;
default: break;
constexpr IR::DescriptorBindingKind StorageBindingKind(StorageImageClass image_class,
ImageViewKind view) {
switch (image_class) {
case StorageImageClass::FormatlessFloat:
switch (view) {
case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::Storage1D;
case ImageViewKind::Dim1DArray: return IR::DescriptorBindingKind::Storage1DArray;
case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::Storage2D;
case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::Storage2DArray;
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::Storage3D;
default: break;
}
break;
case StorageImageClass::FormatlessUint:
switch (view) {
case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::StorageUint1D;
case ImageViewKind::Dim1DArray:
return IR::DescriptorBindingKind::StorageUint1DArray;
case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::StorageUint2D;
case ImageViewKind::Dim2DArray:
return IR::DescriptorBindingKind::StorageUint2DArray;
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::StorageUint3D;
default: break;
}
break;
case StorageImageClass::AtomicUint:
switch (view) {
case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::StorageAtomic1D;
case ImageViewKind::Dim1DArray:
return IR::DescriptorBindingKind::StorageAtomic1DArray;
case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::StorageAtomic2D;
case ImageViewKind::Dim2DArray:
return IR::DescriptorBindingKind::StorageAtomic2DArray;
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::StorageAtomic3D;
default: break;
}
break;
case StorageImageClass::Count: break;
}
return IR::DescriptorBindingKind::Count;
}
@@ -575,9 +614,10 @@ uint32_t ImageViewSampledImageType(EmitterState& state, ImageViewKind view, bool
uint32_t ImageViewSizeType(EmitterState& state, ImageViewKind view);
uint32_t StorageImageType(EmitterState& state, bool uint_image, ImageViewKind view);
uint32_t StorageImageType(EmitterState& state, StorageImageClass image_class, ImageViewKind view);
uint32_t StorageImagePointerType(EmitterState& state, bool uint_image, ImageViewKind view);
uint32_t StorageImagePointerType(EmitterState& state, StorageImageClass image_class,
ImageViewKind view);
uint32_t LoadSampledImageDescriptor(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc,
ImageViewKind view);
@@ -589,18 +629,12 @@ uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32
uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc,
ImageViewKind view, uint32_t image_resource);
ImageViewKind StorageImageViewKind(const EmitterState& state, const IR::MemoryInfo& mem,
bool uint_image, uint32_t use_pc);
ImageViewKind StorageImageViewKind(const IR::MemoryInfo& mem);
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource, bool uint_image,
uint32_t use_pc = UINT32_MAX,
ImageViewKind view = ImageViewKind::Dim2D);
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource, ImageViewKind view);
uint32_t LoadStorageImageDescriptor(EmitterState& state, uint32_t resource, bool uint_image,
uint32_t use_pc, ImageViewKind view = ImageViewKind::Dim2D);
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, bool uint_image,
ImageViewKind view, uint32_t mip_lod, uint32_t coord, uint32_t texel);
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, ImageViewKind view,
uint32_t mip_lod, uint32_t coord, uint32_t texel);
uint32_t ExecutionModelForStage(ShaderType stage);
@@ -179,14 +179,14 @@ void DefineDescriptorVariables(EmitterState& state) {
}
}
for (uint32_t i = 0; i < state.storage_image_variables.size(); i++) {
const auto view = static_cast<ImageViewKind>(i % StorageImageViewKindCount);
const bool integer = i >= StorageImageViewKindCount;
const auto kind = StorageBindingKind(integer, view);
const auto view = static_cast<ImageViewKind>(i % StorageImageViewKindCount);
const auto image_class = static_cast<StorageImageClass>(i / StorageImageViewKindCount);
const auto kind = StorageBindingKind(image_class, view);
if (DescriptorBinding(state, kind) == nullptr) {
continue;
}
const auto count = ConstantU32(state, DescriptorCount(state, kind));
const auto image_type = StorageImageType(state, integer, view);
const auto image_type = StorageImageType(state, image_class, view);
const auto array_type = state.builder.Type(OpTypeArray, {image_type, count});
const auto pointer_type = TypePointer(state, StorageClassUniformConstant, array_type);
state.storage_image_variables[i] =
@@ -563,11 +563,17 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) {
"storage_uint_1d_array",
"storage_uint_2d",
"storage_uint_2d_array",
"storage_uint_3d"};
"storage_uint_3d",
"storage_atomic_1d",
"storage_atomic_1d_array",
"storage_atomic_2d",
"storage_atomic_2d_array",
"storage_atomic_3d"};
for (uint32_t i = 0; i < state.storage_image_variables.size(); i++) {
const auto view = static_cast<ImageViewKind>(i % StorageImageViewKindCount);
const auto image_class = static_cast<StorageImageClass>(i / StorageImageViewKindCount);
Decorate(state.storage_image_variables[i], StorageNames[i],
StorageBindingKind(i >= StorageImageViewKindCount, view));
StorageBindingKind(image_class, view));
}
if (state.sampler_variable != 0) {
Decorate(state.sampler_variable, "samplers", IR::DescriptorBindingKind::Samplers);
+33 -5
View File
@@ -325,6 +325,11 @@ enum class DescriptorBindingKind {
StorageUint2D,
StorageUint2DArray,
StorageUint3D,
StorageAtomic1D,
StorageAtomic1DArray,
StorageAtomic2D,
StorageAtomic2DArray,
StorageAtomic3D,
Samplers,
Gds,
AddressMemory,
@@ -362,6 +367,9 @@ DescriptorBindingForImage(const ImageResource& image) {
default: return std::nullopt;
}
case ResourceKind::StorageImage:
if (image.atomic) {
return std::nullopt;
}
switch (image.dimension) {
case Dimension::Dim1D: return Kind::Storage1D;
case Dimension::Dim1DArray: return Kind::Storage1DArray;
@@ -372,11 +380,31 @@ DescriptorBindingForImage(const ImageResource& image) {
}
case ResourceKind::StorageImageUint:
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;
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;
@@ -37,6 +37,11 @@ constexpr std::array ImageBindingKinds = {
DescriptorBindingKind::StorageUint2D,
DescriptorBindingKind::StorageUint2DArray,
DescriptorBindingKind::StorageUint3D,
DescriptorBindingKind::StorageAtomic1D,
DescriptorBindingKind::StorageAtomic1DArray,
DescriptorBindingKind::StorageAtomic2D,
DescriptorBindingKind::StorageAtomic2DArray,
DescriptorBindingKind::StorageAtomic3D,
};
bool CollectUserData(const Program& program, std::vector<uint32_t>& result) {
@@ -81,10 +81,15 @@ 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) {
const bool storage =
memory.kind == ResourceKind::StorageImage || memory.kind == ResourceKind::StorageImageUint;
return storage && memory.image_has_mip ? ImageMipMode::DynamicStorage : ImageMipMode::None;
if (IsStorageImage(memory.kind) && memory.image_has_mip) {
return ImageMipMode::DynamicStorage;
}
return ImageMipMode::None;
}
class Tracker {
@@ -650,9 +655,14 @@ private:
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];
if (image.source == source && image.kind == memory.kind &&
const bool compatible_kind = image.kind == memory.kind || (IsStorageImage(image.kind) &&
IsStorageImage(memory.kind));
if (image.source == source && compatible_kind &&
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;
}
+141 -53
View File
@@ -8781,7 +8781,11 @@ public:
Count(Kind::StorageUint1D) == 0 &&
Count(Kind::StorageUint1DArray) == 0 &&
Count(Kind::StorageUint2DArray) == 0 &&
Count(Kind::StorageUint3D) == 0,
Count(Kind::StorageUint3D) == 0 &&
Count(Kind::StorageAtomic1D) == 0 &&
Count(Kind::StorageAtomic1DArray) == 0 &&
Count(Kind::StorageAtomic2DArray) == 0 &&
Count(Kind::StorageAtomic3D) == 0,
"unsupported array/3D image cases must provide matching Vulkan test "
"views "
"before dispatch");
@@ -8834,6 +8838,11 @@ public:
case Kind::StorageUint2D:
case Kind::StorageUint2DArray:
case Kind::StorageUint3D:
case Kind::StorageAtomic1D:
case Kind::StorageAtomic1DArray:
case Kind::StorageAtomic2D:
case Kind::StorageAtomic2DArray:
case Kind::StorageAtomic3D:
type = vk::DescriptorType::eStorageImage;
break;
case Kind::Samplers:
@@ -8926,7 +8935,10 @@ public:
Count(Kind::Storage2D) + Count(Kind::Storage2DArray) +
Count(Kind::Storage3D) + Count(Kind::StorageUint1D) +
Count(Kind::StorageUint1DArray) + Count(Kind::StorageUint2D) +
Count(Kind::StorageUint2DArray) + Count(Kind::StorageUint3D));
Count(Kind::StorageUint2DArray) + Count(Kind::StorageUint3D) +
Count(Kind::StorageAtomic1D) + Count(Kind::StorageAtomic1DArray) +
Count(Kind::StorageAtomic2D) + Count(Kind::StorageAtomic2DArray) +
Count(Kind::StorageAtomic3D));
add_pool_size(vk::DescriptorType::eSampler, Count(Kind::Samplers));
vk::DescriptorPoolCreateInfo pool_info{};
pool_info.sType = vk::StructureType::eDescriptorPoolCreateInfo;
@@ -8955,6 +8967,7 @@ public:
std::vector<vk::DescriptorImageInfo> sampled_infos;
std::vector<vk::DescriptorImageInfo> storage_infos;
std::vector<vk::DescriptorImageInfo> storage_uint_infos;
std::vector<vk::DescriptorImageInfo> storage_atomic_infos;
std::vector<vk::DescriptorImageInfo> sampler_infos;
Buffer flattened_buffer;
Buffer user_data_buffer;
@@ -9078,45 +9091,34 @@ public:
}
const auto *storage = Binding(Kind::Storage2D);
const auto *storage_uint = Binding(Kind::StorageUint2D);
if (storage != nullptr || storage_uint != nullptr) {
Require(test.name, "dispatch", storage_image != nullptr,
"storage image descriptor requested but no storage image was "
const auto *storage_atomic = Binding(Kind::StorageAtomic2D);
const auto BindStorage =
[&](const ShaderRecompiler::IR::DescriptorBinding *binding,
const Image *image, std::vector<vk::DescriptorImageInfo> *infos) {
if (binding == nullptr) {
return;
}
Require(
test.name, "dispatch", image != nullptr,
"storage image descriptor requested but no matching image was "
"provided");
Require(test.name, "dispatch", storage_image_uint != nullptr,
"uint storage image descriptor requested but no uint storage "
"image was provided");
storage_infos.resize(storage != nullptr ? storage->resources.size() : 0u);
storage_uint_infos.resize(
storage_uint != nullptr ? storage_uint->resources.size() : 0u);
for (auto &info : storage_infos) {
info.imageView = storage_image->view;
info.imageLayout = storage_image->layout;
}
for (auto &info : storage_uint_infos) {
info.imageView = storage_image_uint->view;
info.imageLayout = storage_image_uint->layout;
}
if (storage != nullptr) {
vk::WriteDescriptorSet write{};
write.sType = vk::StructureType::eWriteDescriptorSet;
write.dstSet = descriptor_set;
write.dstBinding = storage->binding;
write.descriptorCount = static_cast<u32>(storage_infos.size());
write.descriptorType = vk::DescriptorType::eStorageImage;
write.pImageInfo = storage_infos.data();
writes.push_back(write);
}
if (storage_uint != nullptr) {
vk::WriteDescriptorSet write{};
write.sType = vk::StructureType::eWriteDescriptorSet;
write.dstSet = descriptor_set;
write.dstBinding = storage_uint->binding;
write.descriptorCount = static_cast<u32>(storage_uint_infos.size());
write.descriptorType = vk::DescriptorType::eStorageImage;
write.pImageInfo = storage_uint_infos.data();
writes.push_back(write);
}
}
infos->resize(binding->resources.size());
for (auto &info : *infos) {
info.imageView = image->view;
info.imageLayout = image->layout;
}
vk::WriteDescriptorSet write{};
write.sType = vk::StructureType::eWriteDescriptorSet;
write.dstSet = descriptor_set;
write.dstBinding = binding->binding;
write.descriptorCount = static_cast<u32>(infos->size());
write.descriptorType = vk::DescriptorType::eStorageImage;
write.pImageInfo = infos->data();
writes.push_back(write);
};
BindStorage(storage, storage_image, &storage_infos);
BindStorage(storage_uint, storage_image_uint, &storage_uint_infos);
BindStorage(storage_atomic, storage_image_uint, &storage_atomic_infos);
const auto *samplers = Binding(Kind::Samplers);
if (samplers != nullptr) {
Require(test.name, "dispatch", sampler != nullptr,
@@ -10582,9 +10584,6 @@ private:
Require("VulkanHarness", "dispatch",
available_features.shaderStorageImageWriteWithoutFormat == true,
"shaderStorageImageWriteWithoutFormat is not supported");
Require("VulkanHarness", "dispatch",
available_features.shaderStorageImageReadWithoutFormat == true,
"shaderStorageImageReadWithoutFormat is not supported");
Require("VulkanHarness", "dispatch",
available_features12.timelineSemaphore == true,
"timeline semaphores are not supported");
@@ -10622,7 +10621,6 @@ private:
device_info.pNext = &device_features13;
vk::PhysicalDeviceFeatures device_features{};
device_features.shaderStorageImageWriteWithoutFormat = true;
device_features.shaderStorageImageReadWithoutFormat = true;
device_features.sampleRateShading = true;
device_info.pEnabledFeatures = &device_features;
constexpr const char *device_extensions[] = {
@@ -10977,7 +10975,10 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) {
Has(Kind::Storage2D) || Has(Kind::Storage2DArray) ||
Has(Kind::Storage3D) || Has(Kind::StorageUint2D) ||
Has(Kind::StorageUint1D) || Has(Kind::StorageUint1DArray) ||
Has(Kind::StorageUint2DArray) || Has(Kind::StorageUint3D);
Has(Kind::StorageUint2DArray) || Has(Kind::StorageUint3D) ||
Has(Kind::StorageAtomic1D) || Has(Kind::StorageAtomic1DArray) ||
Has(Kind::StorageAtomic2D) || Has(Kind::StorageAtomic2DArray) ||
Has(Kind::StorageAtomic3D);
const bool needs_sampler = Has(Kind::Samplers);
const bool needs_gds = Has(Kind::Gds);
if (needs_gds) {
@@ -18632,11 +18633,13 @@ TestCase ImageStoreR32SintUsesRawUintView() {
test.has_user_data = true;
test.storage_image_r32ui = std::vector<u32>(16, 0);
test.expected_storage_image_r32ui = expected_image;
test.required_spirv = {"storage_uint_2d"};
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
"storage_uint_2d"};
test.forbidden_spirv = {"R32ui"};
return test;
}
TestCase ImageStoreR32UintUsesUintStorageImage() {
TestCase ImageStoreR32UintUsesFormatlessStorageImage() {
using O = ShaderOpcode;
std::vector<u32> code;
@@ -18652,7 +18655,7 @@ TestCase ImageStoreR32UintUsesUintStorageImage() {
expected_image[1 * 4 + 2] = 0x12345678u;
TestCase test;
test.name = "ImageStoreR32UintUsesUintStorageImage";
test.name = "ImageStoreR32UintUsesFormatlessStorageImage";
test.code = code;
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::S_ENDPGM};
test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt);
@@ -18660,7 +18663,9 @@ TestCase ImageStoreR32UintUsesUintStorageImage() {
test.storage_image_rgba = MakeRgbaImage(4, 4);
test.storage_image_r32ui = std::vector<u32>(16, 0);
test.expected_storage_image_r32ui = expected_image;
test.required_spirv = {"R32ui", "storage_uint_2d"};
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
"storage_uint_2d"};
test.forbidden_spirv = {"R32ui"};
return test;
}
@@ -18692,7 +18697,9 @@ TestCase ImageStorePackedUintSaturatesChannels() {
test.image_descriptor_swizzle = DstSel(4, 5, 6, 0);
test.storage_image_r32ui = std::vector<u32>(16, 0);
test.expected_storage_image_r32ui = std::move(expected_image);
test.required_spirv = {"R32ui", "OpULessThan", "OpSelect"};
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
"storage_uint_2d", "OpULessThan", "OpSelect"};
test.forbidden_spirv = {"R32ui"};
return test;
}
@@ -18722,6 +18729,9 @@ TestCase ImageStorePackedUintHonorsSparseDmask() {
test.image_descriptor_swizzle = DstSel(4, 5, 6, 0);
test.storage_image_r32ui = std::vector<u32>(16, 0);
test.expected_storage_image_r32ui = std::move(expected_image);
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
"storage_uint_2d"};
test.forbidden_spirv = {"R32ui"};
return test;
}
@@ -18751,6 +18761,82 @@ TestCase ComputeTgSizeSgprUsesWaveMetadata() {
return test;
}
TestCase ImageStoreAndAtomicShareTypedBinding() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 20, 2);
AppendVMovU32(&code, 21, 1);
AppendVMovU32(&code, 22, 0);
AppendVMovU32(&code, 0, 5);
code.push_back(EncodeMimg0(0x08, 0x1));
code.push_back(EncodeMimg1(0, 20));
AppendVMovU32(&code, 0, 3);
code.push_back(EncodeMimg0(0x11, 0x1, 0, true));
code.push_back(EncodeMimg1(0, 20));
AppendStoreVgpr(&code, 0, 0);
AppendEnd(&code);
std::vector<u32> expected_image(16, 0);
expected_image[1 * 4 + 2] = 8;
TestCase test;
test.name = "ImageStoreAndAtomicShareTypedBinding";
test.code = std::move(code);
test.expected = {5};
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::IMAGE_ATOMIC_ADD,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt);
test.has_user_data = true;
test.storage_image_r32ui = std::vector<u32>(16, 0);
test.expected_storage_image_r32ui = std::move(expected_image);
test.required_spirv = {"OpImageWrite", "OpImageTexelPointer", "R32ui",
"storage_atomic_2d"};
test.forbidden_spirv = {"StorageImageReadWithoutFormat",
"StorageImageWriteWithoutFormat", "storage_uint_2d"};
return test;
}
TestCase ImageStoreAndAtomicUseSeparateBindings() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 20, 2);
AppendVMovU32(&code, 21, 1);
AppendVMovU32(&code, 22, 0);
AppendVMovU32(&code, 0, 5);
code.push_back(EncodeMimg0(0x08, 0x1));
code.push_back(EncodeMimg1(0, 20, 0));
AppendVMovU32(&code, 0, 3);
code.push_back(EncodeMimg0(0x11, 0x1, 0, true));
code.push_back(EncodeMimg1(0, 20, 2));
AppendStoreVgpr(&code, 0, 0);
AppendEnd(&code);
auto user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt);
std::copy_n(user_data.begin(), 8, user_data.begin() + 8);
std::vector<u32> expected_image(16, 0);
expected_image[1 * 4 + 2] = 8;
TestCase test;
test.name = "ImageStoreAndAtomicUseSeparateBindings";
test.code = std::move(code);
test.expected = {5};
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::IMAGE_ATOMIC_ADD,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.user_data = user_data;
test.has_user_data = true;
test.storage_image_r32ui = std::vector<u32>(16, 0);
test.expected_storage_image_r32ui = std::move(expected_image);
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
"OpImageWrite",
"OpImageTexelPointer",
"R32ui",
"storage_uint_2d",
"storage_atomic_2d"};
return test;
}
TestCase ImageAtomicVariants() {
using O = ShaderOpcode;
@@ -18778,10 +18864,10 @@ TestCase ImageAtomicVariants() {
O::IMAGE_ATOMIC_UMIN, O::IMAGE_ATOMIC_AND,
O::IMAGE_ATOMIC_OR, O::IMAGE_ATOMIC_XOR,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpImageTexelPointer", "R32ui"};
test.required_spirv = {"OpImageTexelPointer", "R32ui", "storage_atomic_2d"};
test.forbidden_spirv = {"OpTypeSampler", "OpTypeSampledImage",
"StorageImageReadWithoutFormat",
"StorageImageWriteWithoutFormat"};
"StorageImageWriteWithoutFormat", "storage_uint_2d"};
test.storage_image_rgba = MakeRgbaImage(4, 4);
test.storage_image_r32ui = std::vector<u32>(16, 0);
for (u32 i = 0; i < static_cast<u32>(std::size(initial)); i++) {
@@ -19340,10 +19426,12 @@ std::vector<TestCase> MakeCases() {
AddCase(ImageStoreYzwxUsesInverseSwizzle);
AddCase(ImageStoreR32FloatUsesFormatlessStorageImage);
AddCase(ImageStoreR32SintUsesRawUintView);
AddCase(ImageStoreR32UintUsesUintStorageImage);
AddCase(ImageStoreR32UintUsesFormatlessStorageImage);
AddCase(ImageStorePackedUintSaturatesChannels);
AddCase(ImageStorePackedUintHonorsSparseDmask);
AddCase(ComputeTgSizeSgprUsesWaveMetadata);
AddCase(ImageStoreAndAtomicShareTypedBinding);
AddCase(ImageStoreAndAtomicUseSeparateBindings);
AddCase(ImageAtomicVariants);
AddCase(ImageAtomicGlc0DoesNotReturnOldValue);
AddCase(MultipleWorkitemsGlobalId);
+10 -7
View File
@@ -8625,8 +8625,10 @@ void TestDemandDrivenSpirvDeclarations() {
SpirvInstructionOpcodeCount(declaration_binary, 59u) == 2u,
"canonical builder emitted duplicate declarations");
const auto compile = [](std::span<const uint32_t> shader) {
const auto compile = [](std::span<const uint32_t> shader,
Prospero::BufferFormat format) {
auto user_data = ImageTestUserData();
SetImageTestFormat(&user_data, 0, format);
auto options = MakeCompileOptions(ShaderType::Compute);
options.user_data = user_data.data();
ShaderRecompiler::CompileResult result;
@@ -8664,7 +8666,7 @@ void TestDemandDrivenSpirvDeclarations() {
const uint32_t store[] = {EncodeMimg0(0x08, 0xf), EncodeMimg1(0, 0, 0, 20),
EncodeSopp(0x01)};
const auto store_spirv = compile(store);
const auto store_spirv = compile(store, Prospero::BufferFormat::k8UNorm);
Check(SpirvInstructionOpcodeCount(store_spirv, 99u) == 1u,
"storage image fixture did not reach OpImageWrite");
const auto store_metrics = MeasureSpirv(store_spirv);
@@ -8676,7 +8678,7 @@ void TestDemandDrivenSpirvDeclarations() {
const uint32_t atomic[] = {EncodeMimg0(0x11, 0x1, true),
EncodeMimg1(0, 0, 0, 20), EncodeSopp(0x01)};
const auto atomic_spirv = compile(atomic);
const auto atomic_spirv = compile(atomic, Prospero::BufferFormat::k32UInt);
Check(SpirvInstructionOpcodeCount(atomic_spirv, 60u) == 1u,
"image atomic fixture did not reach OpImageTexelPointer");
const auto atomic_metrics = MeasureSpirv(atomic_spirv);
@@ -10170,6 +10172,7 @@ void TestNewShaderRecompilerNativeBindingPlan() {
};
auto user_data = ImageTestUserData();
SetImageTestFormat(&user_data, 6, Prospero::BufferFormat::k32UInt);
auto options = MakeCompileOptions(ShaderType::Compute);
options.dump_ir = true;
options.user_data = user_data.data();
@@ -10185,8 +10188,8 @@ void TestNewShaderRecompilerNativeBindingPlan() {
result.program.bindings, BindingKind::Sampled2D);
const auto *storage = ShaderRecompiler::IR::FindBinding(
result.program.bindings, BindingKind::Storage2D);
const auto *uint_storage = ShaderRecompiler::IR::FindBinding(
result.program.bindings, BindingKind::StorageUint2D);
const auto *atomic_storage = ShaderRecompiler::IR::FindBinding(
result.program.bindings, BindingKind::StorageAtomic2D);
const auto *samplers = ShaderRecompiler::IR::FindBinding(
result.program.bindings, BindingKind::Samplers);
Check(buffers != nullptr && buffers->resources.size() == 2,
@@ -10195,8 +10198,8 @@ void TestNewShaderRecompilerNativeBindingPlan() {
"native binding plan did not allocate the sampled image");
Check(storage != nullptr && storage->resources.size() == 1,
"native binding plan did not allocate the float storage image");
Check(uint_storage != nullptr && uint_storage->resources.size() == 1,
"native binding plan did not allocate the uint storage image");
Check(atomic_storage != nullptr && atomic_storage->resources.size() == 1,
"native binding plan did not allocate the atomic storage image");
Check(samplers != nullptr && samplers->resources.size() == 1,
"native binding plan did not allocate the sampler");
Check(SpirvContainsOpcode(result.spirv, 86),