From 3ee14ed99a17e6c8fb85eff19d030609e1665870 Mon Sep 17 00:00:00 2001 From: xbzk Date: Sat, 7 Feb 2026 21:45:43 -0300 Subject: [PATCH] VUID-vkCmdDrawIndexed-format-07753 (FLOAT/SINT/UINT mistmatches) --- .../backend/glasm/emit_glasm_image.cpp | 6 ++ .../backend/glsl/glsl_emit_context.cpp | 6 ++ .../backend/spirv/emit_spirv_image.cpp | 45 ++++++++--- .../backend/spirv/emit_spirv_image_atomic.cpp | 61 +++++++++++---- .../backend/spirv/spirv_emit_context.cpp | 24 +++++- .../backend/spirv/spirv_emit_context.h | 6 ++ src/shader_recompiler/ir_opt/texture_pass.cpp | 76 ++++++++++++++++++- src/shader_recompiler/shader_info.h | 7 ++ src/video_core/buffer_cache/buffer_cache.h | 28 ++++--- .../buffer_cache/buffer_cache_base.h | 10 ++- .../renderer_opengl/gl_buffer_cache.cpp | 6 +- .../renderer_opengl/gl_buffer_cache.h | 5 +- .../renderer_opengl/gl_compute_pipeline.cpp | 3 +- .../renderer_opengl/gl_graphics_pipeline.cpp | 3 +- .../renderer_opengl/gl_texture_cache.cpp | 6 ++ .../renderer_vulkan/vk_buffer_cache.cpp | 45 ++++++++++- .../renderer_vulkan/vk_buffer_cache.h | 9 ++- .../renderer_vulkan/vk_compute_pipeline.cpp | 3 +- .../renderer_vulkan/vk_graphics_pipeline.cpp | 3 +- .../renderer_vulkan/vk_texture_cache.cpp | 37 ++++++++- 20 files changed, 332 insertions(+), 57 deletions(-) diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp index 64e7bad75e..a7f52677fa 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp +++ b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp @@ -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); } diff --git a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp index a2df6159fb..10d3b94a90 100644 --- a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp +++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp @@ -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); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index c07a778958..0be4fa3e46 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -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 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{})}; 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()}; - 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); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp index dde0f6e9ca..aed2051121 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp @@ -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 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()}; - 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&) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 2a606f833b..c145e2fbac 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -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); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 80ac79f2d6..4f94911f65 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -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 cbufs{}; std::array ssbos{}; diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index 20b8591072..c12591b2a5 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -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 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 Track(const IR::Value& value, Environment& env); static inline std::optional 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, diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index dfacc06802..c913ca95a1 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h @@ -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; diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index b368ffea05..fdb5d1c92e 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -458,14 +458,14 @@ void BufferCache

::UnbindGraphicsTextureBuffers(size_t stage) { template void BufferCache

::BindGraphicsTextureBuffer(size_t stage, size_t tbo_index, GPUVAddr gpu_addr, u32 size, PixelFormat format, bool is_written, - bool is_image) { + bool is_image, bool is_integer, bool is_signed) { channel_state->enabled_texture_buffers[stage] |= 1U << tbo_index; channel_state->written_texture_buffers[stage] |= (is_written ? 1U : 0U) << tbo_index; if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { channel_state->image_texture_buffers[stage] |= (is_image ? 1U : 0U) << tbo_index; } channel_state->texture_buffers[stage][tbo_index] = - GetTextureBufferBinding(gpu_addr, size, format); + GetTextureBufferBinding(gpu_addr, size, format, is_integer, is_signed); } template @@ -532,7 +532,8 @@ void BufferCache

::UnbindComputeTextureBuffers() { template void BufferCache

::BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_addr, u32 size, - PixelFormat format, bool is_written, bool is_image) { + PixelFormat format, bool is_written, bool is_image, + bool is_integer, bool is_signed) { if (tbo_index >= channel_state->compute_texture_buffers.size()) [[unlikely]] { LOG_ERROR(HW_GPU, "Texture buffer index {} exceeds maximum texture buffer count", tbo_index); @@ -544,7 +545,7 @@ void BufferCache

::BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_add channel_state->image_compute_texture_buffers |= (is_image ? 1U : 0U) << tbo_index; } channel_state->compute_texture_buffers[tbo_index] = - GetTextureBufferBinding(gpu_addr, size, format); + GetTextureBufferBinding(gpu_addr, size, format, is_integer, is_signed); } template @@ -955,15 +956,17 @@ void BufferCache

::BindHostGraphicsTextureBuffers(size_t stage) { const u32 offset = buffer.Offset(binding.device_addr); const PixelFormat format = binding.format; + const bool is_integer = binding.is_integer; + const bool is_signed = binding.is_signed; buffer.MarkUsage(offset, size); if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { if (((channel_state->image_texture_buffers[stage] >> index) & 1) != 0) { runtime.BindImageBuffer(buffer, offset, size, format); } else { - runtime.BindTextureBuffer(buffer, offset, size, format); + runtime.BindTextureBuffer(buffer, offset, size, format, is_integer, is_signed); } } else { - runtime.BindTextureBuffer(buffer, offset, size, format); + runtime.BindTextureBuffer(buffer, offset, size, format, is_integer, is_signed); } }); } @@ -1090,15 +1093,17 @@ void BufferCache

::BindHostComputeTextureBuffers() { const u32 offset = buffer.Offset(binding.device_addr); const PixelFormat format = binding.format; + const bool is_integer = binding.is_integer; + const bool is_signed = binding.is_signed; buffer.MarkUsage(offset, size); if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { if (((channel_state->image_compute_texture_buffers >> index) & 1) != 0) { runtime.BindImageBuffer(buffer, offset, size, format); } else { - runtime.BindTextureBuffer(buffer, offset, size, format); + runtime.BindTextureBuffer(buffer, offset, size, format, is_integer, is_signed); } } else { - runtime.BindTextureBuffer(buffer, offset, size, format); + runtime.BindTextureBuffer(buffer, offset, size, format, is_integer, is_signed); } }); } @@ -1833,7 +1838,8 @@ Binding BufferCache

::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index, template TextureBufferBinding BufferCache

::GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size, - PixelFormat format) { + PixelFormat format, bool is_integer, + bool is_signed) { const std::optional device_addr = gpu_memory->GpuToCpuAddress(gpu_addr); TextureBufferBinding binding; if (!device_addr || size == 0) { @@ -1841,11 +1847,15 @@ TextureBufferBinding BufferCache

::GetTextureBufferBinding(GPUVAddr gpu_addr, binding.size = 0; binding.buffer_id = NULL_BUFFER_ID; binding.format = PixelFormat::Invalid; + binding.is_integer = false; + binding.is_signed = false; } else { binding.device_addr = *device_addr; binding.size = size; binding.buffer_id = BufferId{}; binding.format = format; + binding.is_integer = is_integer; + binding.is_signed = is_signed; } return binding; } diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 3c0bc81758..9a31df9c2c 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -84,6 +84,8 @@ struct Binding { struct TextureBufferBinding : Binding { PixelFormat format; + bool is_integer{}; // True if data is SINT/UINT, false if FLOAT + bool is_signed{}; // True if integer data is signed }; static constexpr Binding NULL_BINDING{ @@ -251,7 +253,8 @@ public: void UnbindGraphicsTextureBuffers(size_t stage); void BindGraphicsTextureBuffer(size_t stage, size_t tbo_index, GPUVAddr gpu_addr, u32 size, - PixelFormat format, bool is_written, bool is_image); + PixelFormat format, bool is_written, bool is_image, + bool is_integer, bool is_signed); void UnbindComputeStorageBuffers(); @@ -261,7 +264,7 @@ public: void UnbindComputeTextureBuffers(); void BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_addr, u32 size, PixelFormat format, - bool is_written, bool is_image); + bool is_written, bool is_image, bool is_integer, bool is_signed); [[nodiscard]] std::pair ObtainBuffer(GPUVAddr gpu_addr, u32 size, ObtainBufferSynchronize sync_info, @@ -445,7 +448,8 @@ private: bool is_written) const; [[nodiscard]] TextureBufferBinding GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size, - PixelFormat format); + PixelFormat format, bool is_integer, + bool is_signed); [[nodiscard]] std::span ImmediateBufferWithData(DAddr device_addr, size_t size); diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index 59829b667f..5f4085f178 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -88,7 +88,7 @@ void Buffer::MakeResident(GLenum access) noexcept { glMakeNamedBufferResidentNV(buffer.handle, access); } -GLuint Buffer::View(u32 offset, u32 size, PixelFormat format) { +GLuint Buffer::View(u32 offset, u32 size, PixelFormat format, bool is_integer, bool is_signed) { const auto it{std::ranges::find_if(views, [offset, size, format](const BufferView& view) { return offset == view.offset && size == view.size && format == view.format; })}; @@ -370,8 +370,8 @@ void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings< } void BufferCacheRuntime::BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, - PixelFormat format) { - *texture_handles++ = buffer.View(offset, size, format); + PixelFormat format, bool is_integer, bool is_signed) { + *texture_handles++ = buffer.View(offset, size, format, is_integer, is_signed); } void BufferCacheRuntime::BindImageBuffer(Buffer& buffer, u32 offset, u32 size, PixelFormat format) { diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index a0acfc48c1..a52c54f505 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -34,7 +34,8 @@ public: void MarkUsage(u64 offset, u64 size) {} - [[nodiscard]] GLuint View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format); + [[nodiscard]] GLuint View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format, + bool is_integer = false, bool is_signed = false); [[nodiscard]] GLuint64EXT HostGpuAddr() const noexcept { return address; @@ -118,7 +119,7 @@ public: void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, - VideoCore::Surface::PixelFormat format); + VideoCore::Surface::PixelFormat format, bool is_integer, bool is_signed); void BindImageBuffer(Buffer& buffer, u32 offset, u32 size, VideoCore::Surface::PixelFormat format); diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp index 661fbef2b0..5d612beaf3 100644 --- a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp @@ -177,7 +177,8 @@ void ComputePipeline::Configure() { ImageView& image_view{texture_cache.GetImageView(views[texbuf_index].id)}; buffer_cache.BindComputeTextureBuffer(texbuf_index, image_view.GpuAddr(), image_view.BufferSize(), image_view.format, - is_written, is_image); + is_written, is_image, desc.is_integer, + desc.is_signed); ++texbuf_index; } }}; diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp index 2abbd0de78..9e5e741be2 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp @@ -397,7 +397,8 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)}; buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(), image_view.BufferSize(), image_view.format, - is_written, is_image); + is_written, is_image, desc.is_integer, + desc.is_signed); ++index; ++texture_buffer_it; } diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 958988f27e..368bd5af92 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -433,10 +433,16 @@ OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_form return GL_R16I; case Shader::ImageFormat::R32_UINT: return GL_R32UI; + case Shader::ImageFormat::R32_SINT: + return GL_R32I; case Shader::ImageFormat::R32G32_UINT: return GL_RG32UI; + case Shader::ImageFormat::R32G32_SINT: + return GL_RG32I; case Shader::ImageFormat::R32G32B32A32_UINT: return GL_RGBA32UI; + case Shader::ImageFormat::R32G32B32A32_SINT: + return GL_RGBA32I; } ASSERT_MSG(false, "Invalid image format={}", format); return GL_R32UI; diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index b10031de10..bdb70ea007 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -83,6 +83,7 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo } // Anonymous namespace Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_params) + : VideoCommon::BufferBase(null_params), tracker{4096} { : VideoCommon::BufferBase(null_params), tracker{4096} { if (runtime.device.HasNullDescriptor()) { return; @@ -93,6 +94,7 @@ Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_p } Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_) + : VideoCommon::BufferBase(cpu_addr_, size_bytes_), device{&runtime.device}, : VideoCommon::BufferBase(cpu_addr_, size_bytes_), device{&runtime.device}, buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())}, tracker{SizeBytes()} { if (runtime.device.HasDebuggingToolAttached()) { @@ -100,7 +102,46 @@ Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_) } } -VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format) { +VkFormat SelectTexelBufferFormat(VkFormat float_format, bool is_integer, bool is_signed) { + // If the buffer stores integer data but Vulkan reports float format, + // we need to map to appropriate integer formats for type compatibility + if (!is_integer) { + // Non-integer buffer, use the original float format + return float_format; + } + + // Integer buffer: map float formats to signed/unsigned equivalents + if (is_signed) { + // Signed integer + switch (float_format) { + case VK_FORMAT_R32_SFLOAT: + return VK_FORMAT_R32_SINT; + case VK_FORMAT_R32G32_SFLOAT: + return VK_FORMAT_R32G32_SINT; + case VK_FORMAT_R32G32B32A32_SFLOAT: + return VK_FORMAT_R32G32B32A32_SINT; + default: + // For non-float formats, use as-is + return float_format; + } + } else { + // Unsigned integer + switch (float_format) { + case VK_FORMAT_R32_SFLOAT: + return VK_FORMAT_R32_UINT; + case VK_FORMAT_R32G32_SFLOAT: + return VK_FORMAT_R32G32_UINT; + case VK_FORMAT_R32G32B32A32_SFLOAT: + return VK_FORMAT_R32G32B32A32_UINT; + default: + // For non-float formats, use as-is + return float_format; + } + } +} + +VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format, + bool is_integer, bool is_signed) { if (!device) { // Null buffer supported, return a null descriptor return VK_NULL_HANDLE; @@ -119,6 +160,8 @@ VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat .offset = offset, .size = size, .format = format, + .is_integer = is_integer, + .is_signed = is_signed, .handle = device->GetLogical().CreateBufferView({ .sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO, .pNext = nullptr, diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index b73fcd162b..83c9e6dea5 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -33,7 +33,8 @@ public: explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params); explicit Buffer(BufferCacheRuntime& runtime, VAddr cpu_addr_, u64 size_bytes_); - [[nodiscard]] VkBufferView View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format); + [[nodiscard]] VkBufferView View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format, + bool is_integer = false, bool is_signed = false); [[nodiscard]] VkBuffer Handle() const noexcept { return *buffer; @@ -60,6 +61,8 @@ private: u32 offset; u32 size; VideoCore::Surface::PixelFormat format; + bool is_integer; + bool is_signed; vk::BufferView handle; }; @@ -149,8 +152,8 @@ public: } void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, - VideoCore::Surface::PixelFormat format) { - guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format)); + VideoCore::Surface::PixelFormat format, bool is_integer, bool is_signed) { + guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format, is_integer, is_signed)); } bool ShouldLimitDynamicStorageBuffers() const { diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 96a9fe59e7..e95084b92b 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -194,7 +194,8 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, ImageView& image_view = texture_cache.GetImageView(views[index].id); buffer_cache.BindComputeTextureBuffer(index, image_view.GpuAddr(), image_view.BufferSize(), image_view.format, - is_written, is_image); + is_written, is_image, desc.is_integer, + desc.is_signed); ++index; } }}; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index d36553da4a..1b4953be5e 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -422,7 +422,8 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)}; buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(), image_view.BufferSize(), image_view.format, - is_written, is_image); + is_written, is_image, desc.is_integer, + desc.is_signed); ++index; ++texture_buffer_it; } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 0069f10f9c..1d8b2a3f5f 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -184,6 +184,29 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { return allocator.CreateImage(image_ci); } +[[nodiscard]] VkFormat ConvertUintToUnormFormat(VkFormat format) { + // Convert UINT formats to UNORM equivalents for sampling compatibility + // Shaders expect FLOAT component types for samplers, not UINT + switch (format) { + case VK_FORMAT_A8B8G8R8_UINT_PACK32: + return VK_FORMAT_A8B8G8R8_UNORM_PACK32; + case VK_FORMAT_A2B10G10R10_UINT_PACK32: + return VK_FORMAT_A2B10G10R10_UNORM_PACK32; + case VK_FORMAT_R8_UINT: + return VK_FORMAT_R8_UNORM; + case VK_FORMAT_R16_UINT: + return VK_FORMAT_R16_UNORM; + case VK_FORMAT_R8G8_UINT: + return VK_FORMAT_R8G8_UNORM; + case VK_FORMAT_R16G16_UINT: + return VK_FORMAT_R16G16_UNORM; + case VK_FORMAT_R16G16B16A16_UINT: + return VK_FORMAT_R16G16B16A16_UNORM; + default: + return format; + } +} + [[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image, VkFormat format) { static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{ @@ -692,10 +715,16 @@ void TryTransformSwizzleIfNeeded(PixelFormat format, std::array