VUID-vkCmdDrawIndexed-format-07753 (FLOAT/SINT/UINT mistmatches)

This commit is contained in:
xbzk
2026-02-07 21:45:43 -03:00
parent 42451f0c3d
commit 3ee14ed99a
20 changed files with 332 additions and 57 deletions
@@ -206,10 +206,16 @@ std::string_view FormatStorage(ImageFormat format) {
return "S16";
case ImageFormat::R32_UINT:
return "U32";
case ImageFormat::R32_SINT:
return "S32";
case ImageFormat::R32G32_UINT:
return "U32X2";
case ImageFormat::R32G32_SINT:
return "S32X2";
case ImageFormat::R32G32B32A32_UINT:
return "U32X4";
case ImageFormat::R32G32B32A32_SINT:
return "S32X4";
}
throw InvalidArgument("Invalid image format {}", format);
}
@@ -148,10 +148,16 @@ std::string_view ImageFormatString(ImageFormat format) {
return ",r16i";
case ImageFormat::R32_UINT:
return ",r32ui";
case ImageFormat::R32_SINT:
return ",r32i";
case ImageFormat::R32G32_UINT:
return ",rg32ui";
case ImageFormat::R32G32_SINT:
return ",rg32i";
case ImageFormat::R32G32B32A32_UINT:
return ",rgba32ui";
case ImageFormat::R32G32B32A32_SINT:
return ",rgba32i";
default:
throw NotImplementedException("Image format: {}", format);
}
@@ -205,7 +205,7 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
if (def.count > 1) {
throw NotImplementedException("Indirect texture sample");
}
return ctx.OpLoad(ctx.image_buffer_type, def.id);
return ctx.OpLoad(def.image_type, def.id);
} else {
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
if (def.count > 1) {
@@ -215,16 +215,22 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
}
}
std::pair<Id, bool> Image(EmitContext& ctx, const IR::Value& index, IR::TextureInstInfo info) {
struct ImageInfo {
Id image;
bool is_integer;
bool is_signed;
};
ImageInfo Image(EmitContext& ctx, const IR::Value& index, IR::TextureInstInfo info) {
if (!index.IsImmediate() || index.U32() != 0) {
throw NotImplementedException("Indirect image indexing");
}
if (info.type == TextureType::Buffer) {
const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)};
return {ctx.OpLoad(def.image_type, def.id), def.is_integer};
return {ctx.OpLoad(def.image_type, def.id), def.is_integer, def.is_signed};
} else {
const ImageDefinition def{ctx.images.at(info.descriptor_index)};
return {ctx.OpLoad(def.image_type, def.id), def.is_integer};
return {ctx.OpLoad(def.image_type, def.id), def.is_integer, def.is_signed};
}
}
@@ -550,8 +556,25 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c
lod = Id{};
}
const ImageOperands operands(lod, ms);
return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4],
TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span());
bool is_integer = false;
bool is_signed = false;
Id result_type{ctx.F32[4]};
if (info.type == TextureType::Buffer) {
const TextureBufferDefinition& def{ctx.texture_buffers.at(info.descriptor_index)};
is_integer = def.is_integer;
is_signed = def.is_signed;
if (is_integer) {
result_type = is_signed ? ctx.S32[4] : ctx.U32[4];
}
}
Id fetched = Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst,
result_type, TextureImage(ctx, info, index), coords,
operands.MaskOptional(), operands.Span());
if (is_integer) {
// IR expects F32x4 from ImageFetch; bitcast integer results to float vector.
fetched = ctx.OpBitcast(ctx.F32[4], fetched);
}
return fetched;
}
Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod,
@@ -612,21 +635,25 @@ Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id co
LOG_WARNING(Shader_SPIRV, "Typeless image read not supported by host");
return ctx.ConstantNull(ctx.U32[4]);
}
const auto [image, is_integer] = Image(ctx, index, info);
const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]};
const auto [image, is_integer, is_signed] = Image(ctx, index, info);
const Id result_type{is_integer ? (is_signed ? ctx.S32[4] : ctx.U32[4]) : ctx.F32[4]};
Id color{Emit(&EmitContext::OpImageSparseRead, &EmitContext::OpImageRead, ctx, inst,
result_type, image, coords, std::nullopt, std::span<const Id>{})};
if (!is_integer) {
color = ctx.OpBitcast(ctx.U32[4], color);
} else if (is_signed) {
color = ctx.OpBitcast(ctx.U32[4], color);
}
return color;
}
void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id color) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const auto [image, is_integer] = Image(ctx, index, info);
const auto [image, is_integer, is_signed] = Image(ctx, index, info);
if (!is_integer) {
color = ctx.OpBitcast(ctx.F32[4], color);
} else if (is_signed) {
color = ctx.OpBitcast(ctx.S32[4], color);
}
ctx.OpImageWrite(image, coords, color);
}
@@ -7,13 +7,18 @@
namespace Shader::Backend::SPIRV {
namespace {
Id Image(EmitContext& ctx, IR::TextureInstInfo info) {
struct ImageInfo {
Id id;
bool is_signed;
};
ImageInfo Image(EmitContext& ctx, IR::TextureInstInfo info) {
if (info.type == TextureType::Buffer) {
const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)};
return def.id;
return {def.id, def.is_signed};
} else {
const ImageDefinition def{ctx.images.at(info.descriptor_index)};
return def.id;
return {def.id, def.is_signed};
}
}
@@ -24,42 +29,66 @@ std::pair<Id, Id> AtomicArgs(EmitContext& ctx) {
}
Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id value,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id), bool value_signed) {
if (!index.IsImmediate() || index.U32() != 0) {
// TODO: handle layers
throw NotImplementedException("Image indexing");
}
const auto info{inst->Flags<IR::TextureInstInfo>()};
const Id image{Image(ctx, info)};
const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, image, coords, ctx.Const(0U))};
const auto image_info{Image(ctx, info)};
Id pointer_type{image_info.is_signed ? ctx.image_s32 : ctx.image_u32};
if (!Sirit::ValidId(pointer_type)) {
const Id element_type{image_info.is_signed ? ctx.S32[1] : ctx.U32[1]};
pointer_type = ctx.TypePointer(spv::StorageClass::Image, element_type);
}
const Id image{image_info.id};
const Id pointer{ctx.OpImageTexelPointer(pointer_type, image, coords, ctx.Const(0U))};
const auto [scope, semantics]{AtomicArgs(ctx)};
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value);
const Id result_type{image_info.is_signed ? ctx.S32[1] : ctx.U32[1]};
// Ensure value type matches result_type's pointee type
Id cast_value{value};
if (image_info.is_signed) {
// Result type is signed s32, ensure value is also s32
cast_value = ctx.OpBitcast(ctx.S32[1], value);
} else {
// Result type is unsigned u32, ensure value is also u32
cast_value = ctx.OpBitcast(ctx.U32[1], value);
}
Id result{(ctx.*atomic_func)(result_type, pointer, scope, semantics, cast_value)};
// Convert result back to u32 for IR compatibility
if (image_info.is_signed) {
result = ctx.OpBitcast(ctx.U32[1], result);
}
return result;
}
} // Anonymous namespace
Id EmitImageAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id value) {
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicIAdd);
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicIAdd, false);
}
Id EmitImageAtomicSMin32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id value) {
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicSMin);
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicSMin, true);
}
Id EmitImageAtomicUMin32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id value) {
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicUMin);
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicUMin, false);
}
Id EmitImageAtomicSMax32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id value) {
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicSMax);
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicSMax, true);
}
Id EmitImageAtomicUMax32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id value) {
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicUMax);
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicUMax, false);
}
Id EmitImageAtomicInc32(EmitContext&, IR::Inst*, const IR::Value&, Id, Id) {
@@ -74,22 +103,22 @@ Id EmitImageAtomicDec32(EmitContext&, IR::Inst*, const IR::Value&, Id, Id) {
Id EmitImageAtomicAnd32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id value) {
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicAnd);
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicAnd, false);
}
Id EmitImageAtomicOr32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id value) {
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicOr);
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicOr, false);
}
Id EmitImageAtomicXor32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id value) {
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicXor);
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicXor, false);
}
Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id value) {
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicExchange);
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicExchange, false);
}
Id EmitBindlessImageAtomicIAdd32(EmitContext&) {
@@ -69,10 +69,16 @@ spv::ImageFormat GetImageFormat(ImageFormat format) {
return spv::ImageFormat::R16i;
case ImageFormat::R32_UINT:
return spv::ImageFormat::R32ui;
case ImageFormat::R32_SINT:
return spv::ImageFormat::R32i;
case ImageFormat::R32G32_UINT:
return spv::ImageFormat::Rg32ui;
case ImageFormat::R32G32_SINT:
return spv::ImageFormat::Rg32i;
case ImageFormat::R32G32B32A32_UINT:
return spv::ImageFormat::Rgba32ui;
case ImageFormat::R32G32B32A32_SINT:
return spv::ImageFormat::Rgba32i;
}
throw InvalidArgument("Invalid image format {}", format);
}
@@ -1311,20 +1317,25 @@ void EmitContext::DefineTextureBuffers(const Info& info, u32& binding) {
return;
}
const spv::ImageFormat format{spv::ImageFormat::Unknown};
image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format);
const Id type{TypePointer(spv::StorageClass::UniformConstant, image_buffer_type)};
texture_buffers.reserve(info.texture_buffer_descriptors.size());
for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) {
if (desc.count != 1) {
throw NotImplementedException("Array of texture buffers");
}
// Use the correct sampled type based on the descriptor's data format
const Id sampled_type{desc.is_integer ? (desc.is_signed ? S32[1] : U32[1]) : F32[1]};
image_buffer_type = TypeImage(sampled_type, spv::Dim::Buffer, 0U, false, false, 1, format);
const Id type{TypePointer(spv::StorageClass::UniformConstant, image_buffer_type)};
const Id id{AddGlobalVariable(type, spv::StorageClass::UniformConstant)};
Decorate(id, spv::Decoration::Binding, binding);
Decorate(id, spv::Decoration::DescriptorSet, 0U);
Name(id, NameOf(stage, desc, "texbuf"));
texture_buffers.push_back({
.id = id,
.image_type = image_buffer_type,
.is_integer = desc.is_integer,
.is_signed = desc.is_signed,
.count = desc.count,
});
if (profile.supported_spirv >= 0x00010400) {
@@ -1341,7 +1352,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
throw NotImplementedException("Array of image buffers");
}
const spv::ImageFormat format{GetImageFormat(desc.format)};
const Id sampled_type{desc.is_integer ? U32[1] : F32[1]};
const Id sampled_type{desc.is_integer ? (desc.is_signed ? S32[1] : U32[1]) : F32[1]};
const Id image_type{
TypeImage(sampled_type, spv::Dim::Buffer, false, false, false, 2, format)};
const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)};
@@ -1357,6 +1368,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
.image_type = image_type,
.count = desc.count,
.is_integer = desc.is_integer,
.is_signed = desc.is_signed,
});
if (profile.supported_spirv >= 0x00010400) {
interfaces.push_back(id);
@@ -1393,6 +1405,9 @@ void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_in
if (info.uses_atomic_image_u32) {
image_u32 = TypePointer(spv::StorageClass::Image, U32[1]);
}
if (info.uses_atomic_s32_min || info.uses_atomic_s32_max) {
image_s32 = TypePointer(spv::StorageClass::Image, S32[1]);
}
}
void EmitContext::DefineImages(const Info& info, u32& binding, u32& scaling_index) {
@@ -1401,7 +1416,7 @@ void EmitContext::DefineImages(const Info& info, u32& binding, u32& scaling_inde
if (desc.count != 1) {
throw NotImplementedException("Array of images");
}
const Id sampled_type{desc.is_integer ? U32[1] : F32[1]};
const Id sampled_type{desc.is_integer ? (desc.is_signed ? S32[1] : U32[1]) : F32[1]};
const Id image_type{ImageType(*this, desc, sampled_type)};
const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)};
const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)};
@@ -1417,6 +1432,7 @@ void EmitContext::DefineImages(const Info& info, u32& binding, u32& scaling_inde
.image_type = image_type,
.count = desc.count,
.is_integer = desc.is_integer,
.is_signed = desc.is_signed,
});
if (profile.supported_spirv >= 0x00010400) {
interfaces.push_back(id);
@@ -45,6 +45,9 @@ struct TextureDefinition {
struct TextureBufferDefinition {
Id id;
Id image_type; // Stores the correct buffer image type (F32 or U32 based on descriptor)
bool is_integer;
bool is_signed;
u32 count;
};
@@ -53,6 +56,7 @@ struct ImageBufferDefinition {
Id image_type;
u32 count;
bool is_integer;
bool is_signed;
};
struct ImageDefinition {
@@ -60,6 +64,7 @@ struct ImageDefinition {
Id image_type;
u32 count;
bool is_integer;
bool is_signed;
};
struct UniformDefinitions {
@@ -250,6 +255,7 @@ public:
Id image_buffer_type{};
Id image_u32{};
Id image_s32{};
std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{};
std::array<StorageDefinitions, Info::MAX_SSBOS> ssbos{};
+74 -2
View File
@@ -204,6 +204,65 @@ static inline bool IsTexturePixelFormatIntegerCached(Environment& env,
return env.IsTexturePixelFormatInteger(GetTextureHandleCached(env, cbuf));
}
static inline bool IsTexturePixelFormatSignedCached(Environment& env,
const ConstBufferAddr& cbuf) {
switch (ReadTexturePixelFormatCached(env, cbuf)) {
case TexturePixelFormat::A8B8G8R8_SINT:
case TexturePixelFormat::R8_SINT:
case TexturePixelFormat::R8G8_SINT:
case TexturePixelFormat::R16_SINT:
case TexturePixelFormat::R16G16_SINT:
case TexturePixelFormat::R16G16B16A16_SINT:
case TexturePixelFormat::R32_SINT:
case TexturePixelFormat::R32G32_SINT:
case TexturePixelFormat::R32G32B32A32_SINT:
return true;
default:
return false;
}
}
static inline bool IsImageFormatSigned(ImageFormat format) {
switch (format) {
case ImageFormat::R8_SINT:
case ImageFormat::R16_SINT:
case ImageFormat::R32_SINT:
case ImageFormat::R32G32_SINT:
case ImageFormat::R32G32B32A32_SINT:
return true;
default:
return false;
}
}
static inline std::optional<ImageFormat> BufferImageFormatFromPixelFormat(
TexturePixelFormat pixel_format) {
switch (pixel_format) {
case TexturePixelFormat::R8_UINT:
return ImageFormat::R8_UINT;
case TexturePixelFormat::R8_SINT:
return ImageFormat::R8_SINT;
case TexturePixelFormat::R16_UINT:
return ImageFormat::R16_UINT;
case TexturePixelFormat::R16_SINT:
return ImageFormat::R16_SINT;
case TexturePixelFormat::R32_UINT:
return ImageFormat::R32_UINT;
case TexturePixelFormat::R32_SINT:
return ImageFormat::R32_SINT;
case TexturePixelFormat::R32G32_UINT:
return ImageFormat::R32G32_UINT;
case TexturePixelFormat::R32G32_SINT:
return ImageFormat::R32G32_SINT;
case TexturePixelFormat::R32G32B32A32_UINT:
return ImageFormat::R32G32B32A32_UINT;
case TexturePixelFormat::R32G32B32A32_SINT:
return ImageFormat::R32G32B32A32_SINT;
default:
return std::nullopt;
}
}
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env);
static inline std::optional<ConstBufferAddr> TrackCached(const IR::Value& v, Environment& env) {
@@ -652,12 +711,21 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
const bool is_written{inst->GetOpcode() != IR::Opcode::ImageRead};
const bool is_read{inst->GetOpcode() != IR::Opcode::ImageWrite};
const bool is_integer{IsTexturePixelFormatIntegerCached(env, cbuf)};
ImageFormat image_format = flags.image_format;
if (flags.type == TextureType::Buffer) {
const auto pixel_format = ReadTexturePixelFormatCached(env, cbuf);
if (const auto mapped = BufferImageFormatFromPixelFormat(pixel_format)) {
image_format = *mapped;
}
}
const bool is_signed{IsImageFormatSigned(image_format)};
if (flags.type == TextureType::Buffer) {
index = descriptors.Add(ImageBufferDescriptor{
.format = flags.image_format,
.format = image_format,
.is_written = is_written,
.is_read = is_read,
.is_integer = is_integer,
.is_signed = is_signed,
.cbuf_index = cbuf.index,
.cbuf_offset = cbuf.offset,
.count = cbuf.count,
@@ -666,22 +734,26 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
} else {
index = descriptors.Add(ImageDescriptor{
.type = flags.type,
.format = flags.image_format,
.format = image_format,
.is_written = is_written,
.is_read = is_read,
.is_integer = is_integer,
.is_signed = is_signed,
.cbuf_index = cbuf.index,
.cbuf_offset = cbuf.offset,
.count = cbuf.count,
.size_shift = DESCRIPTOR_SIZE_SHIFT,
});
}
flags.image_format.Assign(image_format);
break;
}
default:
if (flags.type == TextureType::Buffer) {
index = descriptors.Add(TextureBufferDescriptor{
.has_secondary = cbuf.has_secondary,
.is_integer = IsTexturePixelFormatIntegerCached(env, cbuf),
.is_signed = IsTexturePixelFormatSignedCached(env, cbuf),
.cbuf_index = cbuf.index,
.cbuf_offset = cbuf.offset,
.shift_left = cbuf.shift_left,
+7
View File
@@ -150,8 +150,11 @@ enum class ImageFormat : u32 {
R16_UINT,
R16_SINT,
R32_UINT,
R32_SINT,
R32G32_UINT,
R32G32_SINT,
R32G32B32A32_UINT,
R32G32B32A32_SINT,
};
enum class Interpolation {
@@ -178,6 +181,8 @@ struct StorageBufferDescriptor {
struct TextureBufferDescriptor {
bool has_secondary;
bool is_integer; // True if data is SINT/UINT (from R_type in TIC), false if FLOAT
bool is_signed; // True if integer data is signed
u32 cbuf_index;
u32 cbuf_offset;
u32 shift_left;
@@ -196,6 +201,7 @@ struct ImageBufferDescriptor {
bool is_written;
bool is_read;
bool is_integer;
bool is_signed;
u32 cbuf_index;
u32 cbuf_offset;
u32 count;
@@ -229,6 +235,7 @@ struct ImageDescriptor {
bool is_written;
bool is_read;
bool is_integer;
bool is_signed;
u32 cbuf_index;
u32 cbuf_offset;
u32 count;