diff --git a/src/graphics/guest_gpu/gpu_format.cpp b/src/graphics/guest_gpu/gpu_format.cpp index 29b42e0d..5eb99ff0 100644 --- a/src/graphics/guest_gpu/gpu_format.cpp +++ b/src/graphics/guest_gpu/gpu_format.cpp @@ -15,6 +15,7 @@ struct FormatInfo { uint32_t render_target_bytes_per_element; bool sampled_texture; bool uint_texture; + bool sint_texture = false; }; constexpr FormatInfo kFormatInfo[] = { @@ -24,19 +25,19 @@ constexpr FormatInfo kFormatInfo[] = { {BufferFormat::k16UNorm, 2, 0, 2, true, false}, {BufferFormat::k16SNorm, 2, 0, 2, true, false}, {BufferFormat::k16UInt, 2, 0, 2, true, true}, - {BufferFormat::k16SInt, 2, 0, 2, false, false}, + {BufferFormat::k16SInt, 2, 0, 2, true, false, true}, {BufferFormat::k16Float, 2, 0, 2, true, false}, {BufferFormat::k8_8UNorm, 2, 0, 2, true, false}, {BufferFormat::k8_8SNorm, 2, 0, 2, true, false}, {BufferFormat::k8_8UInt, 2, 0, 2, true, true}, - {BufferFormat::k8_8SInt, 2, 0, 2, false, false}, + {BufferFormat::k8_8SInt, 2, 0, 2, true, false, true}, {BufferFormat::k32UInt, 4, 0, 4, true, true}, - {BufferFormat::k32SInt, 4, 0, 4, false, false}, + {BufferFormat::k32SInt, 4, 0, 4, true, false, true}, {BufferFormat::k32Float, 4, 0, 4, true, false}, {BufferFormat::k16_16UNorm, 4, 0, 4, true, false}, {BufferFormat::k16_16SNorm, 4, 0, 4, true, false}, {BufferFormat::k16_16UInt, 4, 0, 4, true, true}, - {BufferFormat::k16_16SInt, 4, 0, 4, false, false}, + {BufferFormat::k16_16SInt, 4, 0, 4, true, false, true}, {BufferFormat::k16_16Float, 4, 0, 4, true, false}, {BufferFormat::k11_11_10UInt, 4, 0, 4, true, true}, {BufferFormat::k11_11_10Float, 4, 0, 4, true, false}, @@ -45,20 +46,20 @@ constexpr FormatInfo kFormatInfo[] = { {BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false}, {BufferFormat::k8_8_8_8SNorm, 4, 0, 4, true, false}, {BufferFormat::k8_8_8_8UInt, 4, 0, 4, true, true}, - {BufferFormat::k8_8_8_8SInt, 4, 0, 4, false, false}, + {BufferFormat::k8_8_8_8SInt, 4, 0, 4, true, false, true}, {BufferFormat::k32_32UInt, 8, 0, 8, true, true}, - {BufferFormat::k32_32SInt, 8, 0, 8, false, false}, + {BufferFormat::k32_32SInt, 8, 0, 8, true, false, true}, {BufferFormat::k32_32Float, 8, 0, 8, true, false}, {BufferFormat::k16_16_16_16UNorm, 8, 0, 8, true, false}, {BufferFormat::k16_16_16_16SNorm, 8, 0, 8, true, false}, {BufferFormat::k16_16_16_16UInt, 8, 0, 8, true, true}, - {BufferFormat::k16_16_16_16SInt, 8, 0, 8, false, false}, + {BufferFormat::k16_16_16_16SInt, 8, 0, 8, true, false, true}, {BufferFormat::k16_16_16_16Float, 8, 0, 8, true, false}, {BufferFormat::k32_32_32UInt, 12, 0, 12, true, true}, - {BufferFormat::k32_32_32SInt, 12, 0, 12, false, false}, + {BufferFormat::k32_32_32SInt, 12, 0, 12, true, false, true}, {BufferFormat::k32_32_32Float, 12, 0, 12, true, false}, {BufferFormat::k32_32_32_32UInt, 16, 0, 16, true, true}, - {BufferFormat::k32_32_32_32SInt, 16, 0, 16, false, false}, + {BufferFormat::k32_32_32_32SInt, 16, 0, 16, true, false, true}, {BufferFormat::k32_32_32_32Float, 16, 0, 16, true, false}, {BufferFormat::k8Srgb, 1, 0, 0, true, false}, {BufferFormat::k8_8Srgb, 2, 0, 0, true, false}, @@ -218,14 +219,15 @@ bool IsFmaskTextureFormat(BufferFormat format) { return format >= BufferFormat::kFmask8_S2_F1 && format <= BufferFormat::kFmask64_S16_F8; } -bool IsSampledTextureFormat(BufferFormat format) { +TextureNumericClass SampledTextureNumericClass(BufferFormat format) { const auto* info = FindFormatInfo(format); - return info != nullptr && info->sampled_texture; -} - -bool IsUintTextureFormat(BufferFormat format) { - const auto* info = FindFormatInfo(format); - return info != nullptr && info->uint_texture; + if (info == nullptr || !info->sampled_texture) { + return TextureNumericClass::Unsupported; + } + if (info->sint_texture) { + return TextureNumericClass::Sint; + } + return info->uint_texture ? TextureNumericClass::Uint : TextureNumericClass::Float; } BufferFormat RemapTextureFormat(BufferFormat format) { diff --git a/src/graphics/guest_gpu/gpu_format.h b/src/graphics/guest_gpu/gpu_format.h index 20d63347..a42177c5 100644 --- a/src/graphics/guest_gpu/gpu_format.h +++ b/src/graphics/guest_gpu/gpu_format.h @@ -37,13 +37,14 @@ struct RenderTargetFormatEncoding { } }; +enum class TextureNumericClass { Unsupported, Float, Uint, Sint }; + RenderTargetFormatEncoding ResolveRenderTargetFormat(ChannelLayout layout, ChannelType type); uint32_t NumBytesPerElement(BufferFormat format); uint32_t BlockCompressedBytesPerBlock(BufferFormat format); uint32_t RenderTargetBytesPerElement(BufferFormat format); bool IsFmaskTextureFormat(BufferFormat format); -bool IsSampledTextureFormat(BufferFormat format); -bool IsUintTextureFormat(BufferFormat format); +TextureNumericClass SampledTextureNumericClass(BufferFormat format); BufferFormat RemapTextureFormat(BufferFormat format); } // namespace Libs::Graphics::Prospero diff --git a/src/graphics/host_gpu/renderer/image/imageView.h b/src/graphics/host_gpu/renderer/image/imageView.h index 32ab5bd8..ddd9e79c 100644 --- a/src/graphics/host_gpu/renderer/image/imageView.h +++ b/src/graphics/host_gpu/renderer/image/imageView.h @@ -103,11 +103,11 @@ SelectSampledDepthView(vk::Format image_format, vk::Format view_format, uint32_t [[nodiscard]] inline bool IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept { - if (resource.kind != ShaderRecompiler::IR::ResourceKind::Image && - resource.kind != ShaderRecompiler::IR::ResourceKind::ImageUint) { + if (!ShaderRecompiler::IR::ImageResourceKindMatches( + resource.kind, ShaderRecompiler::IR::ImageResourceClass::Sampled)) { return false; } - if (resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint && resource.depth_compare) { + if (resource.kind != ShaderRecompiler::IR::ResourceKind::Image && resource.depth_compare) { return false; } return (resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D || diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index 19589226..e8c9c701 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -63,7 +63,14 @@ bool IsSampledImage(BindingKind kind) { case BindingKind::SampledUint2DArray: case BindingKind::SampledUint2DMsaa: case BindingKind::SampledUint2DMsaaArray: - case BindingKind::SampledUint3D: return true; + case BindingKind::SampledUint3D: + case BindingKind::SampledSint1D: + case BindingKind::SampledSint1DArray: + case BindingKind::SampledSint2D: + case BindingKind::SampledSint2DArray: + case BindingKind::SampledSint2DMsaa: + case BindingKind::SampledSint2DMsaaArray: + case BindingKind::SampledSint3D: return true; default: return false; } } @@ -229,8 +236,8 @@ static bool IsSupportedSampledColorResource(const ShaderRecompiler::IR::ImageRes break; default: break; } - const bool sampled_kind = resource.kind == ShaderRecompiler::IR::ResourceKind::Image || - resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint; + const bool sampled_kind = ShaderRecompiler::IR::ImageResourceKindMatches( + resource.kind, ShaderRecompiler::IR::ImageResourceClass::Sampled); return sampled_kind && supported_dimension && resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read && !resource.written && !resource.atomic && !resource.depth_compare; @@ -512,10 +519,13 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource, resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint; const bool raw_sint_storage = format == Prospero::BufferFormat::k32SInt && uint_resource && resource.written && !resource.read && !resource.atomic; + const auto numeric_class = Prospero::SampledTextureNumericClass(format); const bool format_ok = - raw_sint_storage || (Prospero::IsSampledTextureFormat(format) && - uint_resource == Prospero::IsUintTextureFormat(format) && - (!resource.atomic || format == Prospero::BufferFormat::k32UInt)); + raw_sint_storage || + (numeric_class != Prospero::TextureNumericClass::Unsupported && + numeric_class != Prospero::TextureNumericClass::Sint && + uint_resource == (numeric_class == Prospero::TextureNumericClass::Uint) && + (!resource.atomic || format == Prospero::BufferFormat::k32UInt)); if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) { return; } @@ -547,6 +557,9 @@ struct NullImageSpec { }; static NullImageSpec NullTextureSpec(const ShaderRecompiler::IR::ImageResource& resource) { + if (resource.kind == ShaderRecompiler::IR::ResourceKind::ImageSint) { + return {vk::Format::eR32Sint, Prospero::BufferFormat::k32SInt}; + } const bool uint_image = resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint || resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint; return uint_image ? NullImageSpec {vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt} @@ -736,13 +749,25 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR const auto surface_format = TextureGetSurfaceFormatInfo(format); const bool shader_conversion = surface_format.conversion_format != Prospero::BufferFormat::kInvalid; + Prospero::TextureNumericClass resource_numeric_class = + Prospero::TextureNumericClass::Unsupported; + switch (resource.kind) { + case ShaderRecompiler::IR::ResourceKind::Image: + resource_numeric_class = Prospero::TextureNumericClass::Float; + break; + case ShaderRecompiler::IR::ResourceKind::ImageUint: + resource_numeric_class = Prospero::TextureNumericClass::Uint; + break; + case ShaderRecompiler::IR::ResourceKind::ImageSint: + resource_numeric_class = Prospero::TextureNumericClass::Sint; + break; + default: break; + } const bool sampled_numeric_class = - storage || (Prospero::IsSampledTextureFormat(format) && - (resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) == - Prospero::IsUintTextureFormat(format)); + storage || resource_numeric_class == Prospero::SampledTextureNumericClass(format); if (!storage && - (resource.kind == ShaderRecompiler::IR::ResourceKind::Image || - resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) && + ShaderRecompiler::IR::ImageResourceKindMatches( + resource.kind, ShaderRecompiler::IR::ImageResourceClass::Sampled) && !sampled_numeric_class) { EXIT("sampled image numeric class mismatch: kind=%u format=%u addr=0x%016" PRIx64 "\n", static_cast(resource.kind), static_cast(format), address); diff --git a/src/graphics/host_gpu/renderer/renderCompute.cpp b/src/graphics/host_gpu/renderer/renderCompute.cpp index fba45ba1..97b48f67 100644 --- a/src/graphics/host_gpu/renderer/renderCompute.cpp +++ b/src/graphics/host_gpu/renderer/renderCompute.cpp @@ -236,8 +236,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, } const auto sampled_images = std::count_if( program.info.images.begin(), program.info.images.end(), [](const auto& image) { - return image.kind == ShaderRecompiler::IR::ResourceKind::Image || - image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint; + return ShaderRecompiler::IR::ImageResourceKindMatches( + image.kind, ShaderRecompiler::IR::ImageResourceClass::Sampled); }); const bool has_sampler = !program.info.samplers.empty(); static std::atomic dispatch_log_count {0}; @@ -265,8 +265,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, LOGF(" CS texture[%u]: source=%u usage=%s sampled=%s addr=0x%010" PRIx64 " type=%u fmt=%u extent=%ux%u depth=%u levels=%u tile=%u\n", i, image.source, image.written ? "read-write" : "read-only", - (image.kind == ShaderRecompiler::IR::ResourceKind::Image || - image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) + ShaderRecompiler::IR::ImageResourceKindMatches( + image.kind, ShaderRecompiler::IR::ImageResourceClass::Sampled) ? "true" : "false", r.Base40(), static_cast(r.Type()), static_cast(r.Format()), diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index bc040b8d..017af78f 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -199,8 +199,8 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col const auto& ps_resources = ps_input_info.stage.program->info; const auto sampled_images = std::count_if( ps_resources.images.begin(), ps_resources.images.end(), [](const auto& image) { - return image.kind == ShaderRecompiler::IR::ResourceKind::Image || - image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint; + return ShaderRecompiler::IR::ImageResourceKindMatches( + image.kind, ShaderRecompiler::IR::ImageResourceClass::Sampled); }); vk::Extent2D extent = color.image_id ? color.extent : vk::Extent2D {}; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp index dfabff99..8d0af795 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp @@ -163,15 +163,32 @@ uint32_t ImageViewSpatialComponents(ImageViewKind view) { } } -uint32_t ImageViewImageType(EmitterState& state, ImageViewKind view, bool integer) { - const auto component = integer ? TypeU32(state) : TypeF32(state); - return state.builder.Type(OpTypeImage, - {component, ImageSpirvDimension(view), 0, ImageSpirvArrayed(view), - ImageSpirvMultisampled(view), 1, ImageFormatUnknown}); +uint32_t SampledImageScalarType(EmitterState& state, SampledImageClass image_class) { + switch (image_class) { + case SampledImageClass::Float: return TypeF32(state); + case SampledImageClass::Uint: return TypeU32(state); + case SampledImageClass::Sint: return TypeI32(state); + case SampledImageClass::Count: break; + } + return 0; } -uint32_t ImageViewSampledImageType(EmitterState& state, ImageViewKind view, bool integer) { - return state.builder.Type(OpTypeSampledImage, {ImageViewImageType(state, view, integer)}); +uint32_t SampledImageVectorType(EmitterState& state, SampledImageClass image_class, + uint32_t components) { + return state.builder.Type(OpTypeVector, + {SampledImageScalarType(state, image_class), components}); +} + +uint32_t ImageViewImageType(EmitterState& state, ImageViewKind view, + SampledImageClass image_class) { + return state.builder.Type(OpTypeImage, {SampledImageScalarType(state, image_class), + ImageSpirvDimension(view), 0, ImageSpirvArrayed(view), + ImageSpirvMultisampled(view), 1, ImageFormatUnknown}); +} + +uint32_t ImageViewSampledImageType(EmitterState& state, ImageViewKind view, + SampledImageClass image_class) { + return state.builder.Type(OpTypeSampledImage, {ImageViewImageType(state, view, image_class)}); } uint32_t ImageViewSizeType(EmitterState& state, ImageViewKind view) { @@ -218,17 +235,18 @@ uint32_t StorageImageVariable(const EmitterState& state, StorageImageClass image uint32_t LoadSampledImageDescriptor(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc, ImageViewKind view) { (void)use_pc; - const bool integer = mem.kind == IR::ResourceKind::ImageUint; - const auto kind = SampledBindingKind(integer, view); + const auto image_class = SampledImageClassFor(mem.kind); + const auto kind = SampledBindingKind(image_class, view); const auto binding = ResourceForDescriptor(state, kind, mem.resource); - const auto variable = state.sampled_image_variables[SampledImageIndex(integer, view)]; + const auto variable = state.sampled_image_variables[SampledImageIndex(image_class, view)]; const auto pointer_type = state.builder.Type( - OpTypePointer, {StorageClassUniformConstant, ImageViewImageType(state, view, integer)}); + OpTypePointer, {StorageClassUniformConstant, ImageViewImageType(state, view, image_class)}); const auto pointer = DescriptorElementPointer(state, pointer_type, variable, binding.array_index, kind, mem.resource, "sampled image descriptor array was not emitted"); const auto image = state.builder.AllocateId(); - state.builder.AddFunction({OpLoad, ImageViewImageType(state, view, integer), image, pointer}); + state.builder.AddFunction( + {OpLoad, ImageViewImageType(state, view, image_class), image, pointer}); return image; } @@ -252,13 +270,12 @@ uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32 const auto sampler = LoadSamplerDescriptor(state, mem.sampler, use_pc); if (image == 0 || sampler == 0) { ExitDescriptorBindingFailure( - state, SampledBindingKind(mem.kind == IR::ResourceKind::ImageUint, view), mem.resource, + state, SampledBindingKind(SampledImageClassFor(mem.kind), view), mem.resource, "sampled image or sampler descriptor load failed"); } const auto sampled_image = state.builder.AllocateId(); state.builder.AddFunction( - {OpSampledImage, - ImageViewSampledImageType(state, view, mem.kind == IR::ResourceKind::ImageUint), + {OpSampledImage, ImageViewSampledImageType(state, view, SampledImageClassFor(mem.kind)), sampled_image, image, sampler}); return sampled_image; } diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp index bc281777..3bc90792 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp @@ -199,8 +199,27 @@ uint32_t FloatBits(ValueEmitContext& ctx, uint32_t value) { return Unary(ctx.state, OpBitcast, TypeU32(ctx.state), value); } -uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, bool integer, bool dref, - const IR::MemoryInfo& mem, bool gather = false) { +uint32_t SampledComponentBits(ValueEmitContext& ctx, uint32_t value, + SampledImageClass image_class) { + return image_class == SampledImageClass::Uint + ? value + : Unary(ctx.state, OpBitcast, TypeU32(ctx.state), value); +} + +uint32_t SampledComponentZero(EmitterState& state, SampledImageClass image_class) { + switch (image_class) { + case SampledImageClass::Float: return ZeroF32(state); + case SampledImageClass::Uint: return ConstantU32(state, 0); + case SampledImageClass::Sint: return ConstantI32(state, 0); + case SampledImageClass::Count: return 0; + } + return 0; +} + +uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, SampledImageClass image_class, + bool dref, const IR::MemoryInfo& mem, bool gather = false) { + const auto value_class = dref ? SampledImageClass::Float : image_class; + const bool integer = IsIntegerSampledImage(value_class); if (mem.data_bits == 16u) { uint32_t packed[4] = {ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0)}; @@ -209,25 +228,27 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, bool integer, bool const auto component = gather ? index : DmaskComponent(mem.dmask, index); const auto result = ctx.state.builder.AllocateId(); ctx.state.builder.AddFunction({OpCompositeExtract, - integer ? TypeU32(ctx.state) : TypeF32(ctx.state), - result, value, component}); + SampledImageScalarType(ctx.state, value_class), result, + value, component}); return result; }; for (uint32_t word = 0; word < mem.data_dwords; word++) { const auto low_index = word * 2u; const auto high_index = low_index + 1u; const auto low = scalar(low_index); - const auto high = high_index < mem.component_count - ? scalar(high_index) - : (integer ? ConstantU32(ctx.state, 0) : ZeroF32(ctx.state)); + const auto high = high_index < mem.component_count + ? scalar(high_index) + : SampledComponentZero(ctx.state, value_class); if (integer) { - const auto mask = ConstantU32(ctx.state, 0xffffu); - packed[word] = - Binary(ctx.state, OpBitwiseOr, TypeU32(ctx.state), - Binary(ctx.state, OpBitwiseAnd, TypeU32(ctx.state), low, mask), - Binary(ctx.state, OpShiftLeftLogical, TypeU32(ctx.state), - Binary(ctx.state, OpBitwiseAnd, TypeU32(ctx.state), high, mask), - ConstantU32(ctx.state, 16u))); + const auto low_bits = SampledComponentBits(ctx, low, value_class); + const auto high_bits = SampledComponentBits(ctx, high, value_class); + const auto mask = ConstantU32(ctx.state, 0xffffu); + packed[word] = Binary( + ctx.state, OpBitwiseOr, TypeU32(ctx.state), + Binary(ctx.state, OpBitwiseAnd, TypeU32(ctx.state), low_bits, mask), + Binary(ctx.state, OpShiftLeftLogical, TypeU32(ctx.state), + Binary(ctx.state, OpBitwiseAnd, TypeU32(ctx.state), high_bits, mask), + ConstantU32(ctx.state, 16u))); } else { const auto pair = ctx.state.builder.AllocateId(); ctx.state.builder.AddFunction( @@ -250,9 +271,9 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, bool integer, bool } const auto scalar = ctx.state.builder.AllocateId(); ctx.state.builder.AddFunction({OpCompositeExtract, - integer ? TypeU32(ctx.state) : TypeF32(ctx.state), scalar, + SampledImageScalarType(ctx.state, value_class), scalar, value, index}); - component[index] = integer ? scalar : FloatBits(ctx, scalar); + component[index] = SampledComponentBits(ctx, scalar, value_class); } const auto result = ctx.state.builder.AllocateId(); ctx.state.builder.AddFunction({OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result, @@ -363,8 +384,7 @@ Format::BufferFormatInfo ImageConversionFormat(const EmitterState& state, const auto format = state.program.info.images[mem.resource].conversion_format; if (format == Prospero::BufferFormat::kInvalid) return {}; const auto info = Format::GetFormatInfo(format); - EXIT_IF(!Prospero::IsSampledTextureFormat(format) || - !Prospero::IsUintTextureFormat(format) || + EXIT_IF(Prospero::SampledTextureNumericClass(format) != Prospero::TextureNumericClass::Uint || Prospero::RemapTextureFormat(format) == format || info.type != Format::ComponentType::Uint || !info.packed_bitfield || info.byte_size != sizeof(uint32_t) || info.component_count == 0u || @@ -440,7 +460,7 @@ uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uin } uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t pc, - uint32_t coord, bool integer) { + uint32_t coord, SampledImageClass image_class) { auto& state = ctx.state; state.builder.RequireCapability(CapabilityImageQuery); const auto image = LoadSampledImageDescriptor(state, mem, pc, ImageViewKind::Dim1D); @@ -457,8 +477,8 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo& ConstantF32(state, 0x3f000000u))}); const auto sampled = MakeSampledImage(state, mem, pc, ImageViewKind::Dim1D); - const auto vector_type = integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4); - const auto scalar_type = integer ? TypeU32(state) : TypeF32(state); + const auto vector_type = SampledImageVectorType(state, image_class, 4); + const auto scalar_type = SampledImageScalarType(state, image_class); const auto component = ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid ? ImageGatherComponent(mem.dmask) : 0u; @@ -601,9 +621,9 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { return true; } if (op == IR::ValueOpcode::ImageRead) { - const auto view = SampledImageViewKind(state, mem, pc); - const bool integer = mem.kind == IR::ResourceKind::ImageUint; - const auto condition = ctx.Arg(inst, 2); + const auto view = SampledImageViewKind(state, mem, pc); + const auto image_class = SampledImageClassFor(mem.kind); + const auto condition = ctx.Arg(inst, 2); ctx.Define( inst, EmitValueOrDefaultIfCondition( @@ -614,17 +634,15 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { const auto coord = CoordU32(ctx, mem, *address, view); if (ImageSpirvMultisampled(view) != 0u) { state.builder.AddFunction( - {OpImageFetch, - integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4), color, + {OpImageFetch, SampledImageVectorType(state, image_class, 4), color, image, coord, ImageOperandsSampleMask, AddressU32(ctx, mem, *address, ImageViewCoordinateComponents(view))}); } else { state.builder.AddFunction( - {OpImageFetch, - integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4), color, + {OpImageFetch, SampledImageVectorType(state, image_class, 4), color, image, coord, ImageOperandsLodMask, LodU32(ctx, mem, *address, view)}); } - return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), integer, false, + return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), image_class, false, mem); })); return true; @@ -644,10 +662,10 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { return true; } if (op == IR::ValueOpcode::ImageSampleRaw || op == IR::ValueOpcode::ImageGatherRaw) { - const auto view = SampledImageViewKind(state, mem, pc); - const auto layout = Layout(mem, view); - const bool integer = mem.kind == IR::ResourceKind::ImageUint; - const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare); + const auto view = SampledImageViewKind(state, mem, pc); + const auto layout = Layout(mem, view); + const auto image_class = SampledImageClassFor(mem.kind); + const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare); if (dref && state.program.info.images[mem.resource].conversion_format != Prospero::BufferFormat::kInvalid) { ctx.Fail(inst, "uses depth comparison with a packed integer image"); @@ -663,8 +681,8 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { ctx.Fail(inst, "has an unsupported 1D gather variant"); return true; } - const auto sample = EmitOneDimensionalGatherLz(ctx, mem, pc, coord, integer); - ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample), integer, + const auto sample = EmitOneDimensionalGatherLz(ctx, mem, pc, coord, image_class); + ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample), image_class, false, mem, true)); return true; } @@ -685,7 +703,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { : ZeroF32(state)} : std::vector { OpImageGather, - integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4), + SampledImageVectorType(state, image_class, 4), sample, sampled, coord, @@ -702,7 +720,8 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { } state.builder.AddFunction(words); ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample), - integer && !dref, false, mem, true)); + dref ? SampledImageClass::Float : image_class, false, mem, + true)); return true; } const bool explicit_lod = HasFlag(mem, Decoder::ImageSampleFlagDerivative) || @@ -713,7 +732,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { ? (dref ? OpImageSampleDrefExplicitLod : OpImageSampleExplicitLod) : (dref ? OpImageSampleDrefImplicitLod : OpImageSampleImplicitLod); const auto result_type = - dref ? TypeF32(state) : (integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4)); + dref ? TypeF32(state) : SampledImageVectorType(state, image_class, 4); const auto dref_value = dref ? (layout.dref != NoImageComponent ? AddressF32(ctx, mem, *address, layout.dref) : ZeroF32(state)) @@ -754,7 +773,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { if (image.indirect_root != mem.resource) { const auto sample = EmitSample(mem.resource); ctx.Define(inst, ResultVector(ctx, dref ? sample : UnpackImageTexel(ctx, mem, sample), - integer, dref, mem)); + image_class, dref, mem)); return true; } const auto* handle = image_arg.ResolveInstruction(); @@ -847,7 +866,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { state.builder.AddFunction(phi_words); ctx.Define(inst, ResultVector(ctx, dref ? phi_words[2] : UnpackImageTexel(ctx, mem, phi_words[2]), - integer, dref, mem)); + image_class, dref, mem)); return true; } const auto atomic_opcode = ImageAtomicOpcode(op); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index 12e6e9b7..15878594 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -337,6 +337,8 @@ enum class ImageViewKind { constexpr uint32_t SampledImageViewKindCount = static_cast(ImageViewKind::Count); constexpr uint32_t StorageImageViewKindCount = static_cast(ImageViewKind::Dim2DMsaa); +enum class SampledImageClass : uint32_t { Float, Uint, Sint, Count }; + enum class StorageImageClass : uint32_t { FormatlessFloat, FormatlessUint, @@ -369,7 +371,9 @@ struct EmitterState { uint32_t flattened_srt_variable = 0; uint32_t lds_variable = 0; uint32_t scratch_variable = 0; - std::array sampled_image_variables {}; + std::array(SampledImageClass::Count) * SampledImageViewKindCount> + sampled_image_variables {}; std::array(StorageImageClass::Count) * StorageImageViewKindCount> storage_image_variables {}; @@ -471,8 +475,22 @@ struct ImageSampleLayout { uint32_t grad_y = NoImageComponent; }; -constexpr uint32_t SampledImageIndex(bool integer, ImageViewKind view) { - return static_cast(view) + (integer ? SampledImageViewKindCount : 0u); +constexpr SampledImageClass SampledImageClassFor(IR::ResourceKind kind) { + switch (kind) { + case IR::ResourceKind::Image: return SampledImageClass::Float; + case IR::ResourceKind::ImageUint: return SampledImageClass::Uint; + case IR::ResourceKind::ImageSint: return SampledImageClass::Sint; + default: return SampledImageClass::Count; + } +} + +constexpr bool IsIntegerSampledImage(SampledImageClass image_class) { + return image_class == SampledImageClass::Uint || image_class == SampledImageClass::Sint; +} + +constexpr uint32_t SampledImageIndex(SampledImageClass image_class, ImageViewKind view) { + return static_cast(view) + + static_cast(image_class) * SampledImageViewKindCount; } constexpr StorageImageClass StorageImageClassFor(bool uint_image, bool atomic) { @@ -490,8 +508,9 @@ constexpr uint32_t StorageImageIndex(StorageImageClass image_class, ImageViewKin static_cast(image_class) * StorageImageViewKindCount; } -constexpr IR::DescriptorBindingKind SampledBindingKind(bool integer, ImageViewKind view) { - if (integer) { +constexpr IR::DescriptorBindingKind SampledBindingKind(SampledImageClass image_class, + ImageViewKind view) { + if (image_class == SampledImageClass::Uint) { switch (view) { case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::SampledUint1D; case ImageViewKind::Dim1DArray: return IR::DescriptorBindingKind::SampledUint1DArray; @@ -504,6 +523,19 @@ constexpr IR::DescriptorBindingKind SampledBindingKind(bool integer, ImageViewKi default: break; } } + if (image_class == SampledImageClass::Sint) { + switch (view) { + case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::SampledSint1D; + case ImageViewKind::Dim1DArray: return IR::DescriptorBindingKind::SampledSint1DArray; + case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::SampledSint2D; + case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::SampledSint2DArray; + case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::SampledSint3D; + case ImageViewKind::Dim2DMsaa: return IR::DescriptorBindingKind::SampledSint2DMsaa; + case ImageViewKind::Dim2DMsaaArray: + return IR::DescriptorBindingKind::SampledSint2DMsaaArray; + default: break; + } + } switch (view) { case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::Sampled1D; case ImageViewKind::Dim1DArray: return IR::DescriptorBindingKind::Sampled1DArray; @@ -641,9 +673,15 @@ uint32_t ImageViewCoordinateComponents(ImageViewKind view); uint32_t ImageViewSpatialComponents(ImageViewKind view); -uint32_t ImageViewImageType(EmitterState& state, ImageViewKind view, bool integer); +uint32_t SampledImageScalarType(EmitterState& state, SampledImageClass image_class); -uint32_t ImageViewSampledImageType(EmitterState& state, ImageViewKind view, bool integer); +uint32_t SampledImageVectorType(EmitterState& state, SampledImageClass image_class, + uint32_t components); + +uint32_t ImageViewImageType(EmitterState& state, ImageViewKind view, SampledImageClass image_class); + +uint32_t ImageViewSampledImageType(EmitterState& state, ImageViewKind view, + SampledImageClass image_class); uint32_t ImageViewSizeType(EmitterState& state, ImageViewKind view); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index 342d03b8..6010cf26 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -188,14 +188,14 @@ void DefineDescriptorVariables(EmitterState& state) { TypeStorageBufferPointer(state), StorageClassStorageBuffer); } for (uint32_t i = 0; i < state.sampled_image_variables.size(); i++) { - const auto view = static_cast(i % SampledImageViewKindCount); - const bool integer = i >= SampledImageViewKindCount; - const auto kind = SampledBindingKind(integer, view); + const auto view = static_cast(i % SampledImageViewKindCount); + const auto image_class = static_cast(i / SampledImageViewKindCount); + const auto kind = SampledBindingKind(image_class, view); if (DescriptorBinding(state, kind) == nullptr) { continue; } const auto count = ConstantU32(state, DescriptorCount(state, kind)); - const auto image_type = ImageViewImageType(state, view, integer); + const auto image_type = ImageViewImageType(state, view, image_class); const auto array_type = state.builder.Type(OpTypeArray, {image_type, count}); const auto pointer_type = TypePointer(state, StorageClassUniformConstant, array_type); state.sampled_image_variables[i] = @@ -607,11 +607,19 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) { "sampled_uint_2d_array", "sampled_uint_3d", "sampled_uint_2d_msaa", - "sampled_uint_2d_msaa_array"}; + "sampled_uint_2d_msaa_array", + "sampled_sint_1d", + "sampled_sint_1d_array", + "sampled_sint_2d", + "sampled_sint_2d_array", + "sampled_sint_3d", + "sampled_sint_2d_msaa", + "sampled_sint_2d_msaa_array"}; for (uint32_t i = 0; i < state.sampled_image_variables.size(); i++) { const auto view = static_cast(i % SampledImageViewKindCount); + const auto image_class = static_cast(i / SampledImageViewKindCount); Decorate(state.sampled_image_variables[i], SampledNames[i], - SampledBindingKind(i >= SampledImageViewKindCount, view)); + SampledBindingKind(image_class, view)); } constexpr const char* StorageNames[] = {"storage_1d", "storage_1d_array", diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index c09352a2..3aa391a5 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -31,6 +31,7 @@ enum class ResourceKind { Gds, Image, ImageUint, + ImageSint, StorageImage, StorageImageUint, Sampler @@ -45,7 +46,8 @@ enum class ResourceKind { ImageResourceClass resource_class) { switch (resource_class) { case ImageResourceClass::Sampled: - return kind == ResourceKind::Image || kind == ResourceKind::ImageUint; + return kind == ResourceKind::Image || kind == ResourceKind::ImageUint || + kind == ResourceKind::ImageSint; case ImageResourceClass::Storage: return kind == ResourceKind::StorageImage || kind == ResourceKind::StorageImageUint; case ImageResourceClass::StorageUint: return kind == ResourceKind::StorageImageUint; @@ -290,6 +292,13 @@ enum class DescriptorBindingKind { SampledUint2DMsaa, SampledUint2DMsaaArray, SampledUint3D, + SampledSint1D, + SampledSint1DArray, + SampledSint2D, + SampledSint2DArray, + SampledSint2DMsaa, + SampledSint2DMsaaArray, + SampledSint3D, Storage1D, Storage1DArray, Storage2D, @@ -349,6 +358,17 @@ DescriptorBindingForImage(const ImageResource& image) { case Dimension::Dim3D: return Kind::SampledUint3D; default: return std::nullopt; } + case ResourceKind::ImageSint: + switch (image.dimension) { + case Dimension::Dim1D: return Kind::SampledSint1D; + case Dimension::Dim1DArray: return Kind::SampledSint1DArray; + case Dimension::Dim2D: return Kind::SampledSint2D; + case Dimension::Dim2DArray: return Kind::SampledSint2DArray; + case Dimension::Dim2DMsaa: return Kind::SampledSint2DMsaa; + case Dimension::Dim2DMsaaArray: return Kind::SampledSint2DMsaaArray; + case Dimension::Dim3D: return Kind::SampledSint3D; + default: return std::nullopt; + } case ResourceKind::StorageImage: if (image.atomic) { return std::nullopt; diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp index d3da029e..1af9b069 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp @@ -89,6 +89,20 @@ Prospero::BufferFormat ImageConversionFormat(Prospero::BufferFormat format) { : Prospero::BufferFormat::kInvalid; } +Prospero::TextureNumericClass SampledResourceNumericClass(ResourceKind kind) { + switch (kind) { + case ResourceKind::Image: return Prospero::TextureNumericClass::Float; + case ResourceKind::ImageUint: return Prospero::TextureNumericClass::Uint; + case ResourceKind::ImageSint: return Prospero::TextureNumericClass::Sint; + default: return Prospero::TextureNumericClass::Unsupported; + } +} + +bool RequiresPointSampler(const ImageResource& image) { + return image.kind == ResourceKind::ImageSint || + image.conversion_format != Prospero::BufferFormat::kInvalid; +} + bool DescriptorIsCube(const DescriptorValue& descriptor) { return static_cast((descriptor.dwords[3] >> 28u) & 0xfu) == Prospero::ImageType::kCube; @@ -378,9 +392,8 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh DescriptorIsCube(descriptor) != image.cube) { return false; } - if (image.kind == ResourceKind::Image || image.kind == ResourceKind::ImageUint || - image.kind == ResourceKind::StorageImage || - image.kind == ResourceKind::StorageImageUint) { + if (ImageResourceKindMatches(image.kind, ImageResourceClass::Sampled) || + ImageResourceKindMatches(image.kind, ImageResourceClass::Storage)) { const bool storage = image.kind == ResourceKind::StorageImage || image.kind == ResourceKind::StorageImageUint; const auto format = @@ -396,16 +409,27 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh image.shader_swizzle != DescriptorImageSwizzle(descriptor)) { return false; } - const bool raw_sint_storage = storage && format == Prospero::BufferFormat::k32SInt && - image.written && !image.read && !image.atomic; - const bool uint_descriptor = Prospero::IsUintTextureFormat(format) || raw_sint_storage; - const auto uint_program = image.kind == ResourceKind::ImageUint || - image.kind == ResourceKind::StorageImageUint; - if (uint_descriptor != uint_program && !(image.atomic && uint_program)) { - return false; - } - if (image.depth_compare && uint_program) { - return false; + if (storage) { + const bool raw_sint = format == Prospero::BufferFormat::k32SInt && image.written && + !image.read && !image.atomic; + const auto numeric_class = Prospero::SampledTextureNumericClass(format); + if ((!raw_sint && numeric_class == Prospero::TextureNumericClass::Sint) || + numeric_class == Prospero::TextureNumericClass::Unsupported) { + return false; + } + const bool uint_descriptor = + numeric_class == Prospero::TextureNumericClass::Uint || raw_sint; + const bool uint_program = image.kind == ResourceKind::StorageImageUint; + if (uint_descriptor != uint_program && !(image.atomic && uint_program)) { + return false; + } + } else { + const auto numeric_class = SampledResourceNumericClass(image.kind); + if (numeric_class != Prospero::SampledTextureNumericClass(format) || + (image.depth_compare && + numeric_class != Prospero::TextureNumericClass::Float)) { + return false; + } } } } @@ -614,6 +638,7 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) { image.cube = false; switch (image.kind) { case ResourceKind::ImageUint: image.kind = ResourceKind::Image; break; + case ResourceKind::ImageSint: image.kind = ResourceKind::Image; break; case ResourceKind::StorageImageUint: if (!image.atomic) { image.kind = ResourceKind::StorageImage; @@ -649,12 +674,30 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) { } const bool raw_sint_storage = storage && format == Prospero::BufferFormat::k32SInt && image.written && !image.read && !image.atomic; - const bool uint_image = Prospero::IsUintTextureFormat(format) || raw_sint_storage; - if (uint_image) { - switch (image.kind) { - case ResourceKind::Image: image.kind = ResourceKind::ImageUint; break; - case ResourceKind::StorageImage: image.kind = ResourceKind::StorageImageUint; break; - default: break; + const auto numeric_class = Prospero::SampledTextureNumericClass(format); + if (storage) { + if ((!raw_sint_storage && numeric_class == Prospero::TextureNumericClass::Sint) || + numeric_class == Prospero::TextureNumericClass::Unsupported) { + return SpecializationFail( + fmt::format("storage image descriptor {} uses unsupported format {}", i, + static_cast(format))); + } + if (numeric_class == Prospero::TextureNumericClass::Uint || raw_sint_storage) { + image.kind = ResourceKind::StorageImageUint; + } + } else { + switch (numeric_class) { + case Prospero::TextureNumericClass::Uint: + image.kind = ResourceKind::ImageUint; + break; + case Prospero::TextureNumericClass::Sint: + image.kind = ResourceKind::ImageSint; + break; + case Prospero::TextureNumericClass::Float: break; + case Prospero::TextureNumericClass::Unsupported: + return SpecializationFail( + fmt::format("sampled image descriptor {} uses unsupported format {}", i, + static_cast(format))); } } } @@ -723,7 +766,7 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) { fmt::format("sampled resource pair at pc 0x{:08x} is invalid", pair.first_use_pc)); } auto& usage = sampler_usage[pair.sampler]; - if (next.images[pair.image].conversion_format != Prospero::BufferFormat::kInvalid) { + if (RequiresPointSampler(next.images[pair.image])) { usage.point_only = true; } else { usage.native = true; @@ -750,7 +793,7 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) { next_snapshot.samplers.push_back(next_snapshot.samplers[i]); } for (auto& pair: next.sampled_pairs) { - if (next.images[pair.image].conversion_format != Prospero::BufferFormat::kInvalid) { + if (RequiresPointSampler(next.images[pair.image])) { pair.sampler = point_sampler[pair.sampler]; } } @@ -771,8 +814,7 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) { "typed image instruction has invalid resource {}", memory.resource)); } const auto& image = next.images[memory.resource]; - if (image_opcode.needs_sampler && - image.conversion_format != Prospero::BufferFormat::kInvalid && + if (image_opcode.needs_sampler && RequiresPointSampler(image) && memory.sampler < point_sampler.size()) { memory.sampler = point_sampler[memory.sampler]; } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index b38ae6e8..2850deee 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -9531,7 +9531,9 @@ public: test.name, "dispatch", Count(Kind::Sampled2DArray) == 0 && Count(Kind::Sampled3D) == 0 && Count(Kind::SampledUint2DArray) == 0 && - Count(Kind::SampledUint3D) == 0 && Count(Kind::Storage1D) == 0 && + Count(Kind::SampledUint3D) == 0 && + Count(Kind::SampledSint2DArray) == 0 && + Count(Kind::SampledSint3D) == 0 && Count(Kind::Storage1D) == 0 && Count(Kind::Storage1DArray) == 0 && Count(Kind::Storage2DArray) == 0 && Count(Kind::Storage3D) == 0 && Count(Kind::StorageUint1D) == 0 && @@ -9646,7 +9648,10 @@ public: Count(Kind::Sampled2D) + Count(Kind::Sampled2DArray) + Count(Kind::Sampled3D) + Count(Kind::SampledUint1D) + Count(Kind::SampledUint1DArray) + Count(Kind::SampledUint2D) + - Count(Kind::SampledUint2DArray) + Count(Kind::SampledUint3D)); + Count(Kind::SampledUint2DArray) + Count(Kind::SampledUint3D) + + Count(Kind::SampledSint1D) + Count(Kind::SampledSint1DArray) + + Count(Kind::SampledSint2D) + Count(Kind::SampledSint2DArray) + + Count(Kind::SampledSint3D)); add_pool_size( vk::DescriptorType::eStorageImage, Count(Kind::Storage1D) + Count(Kind::Storage1DArray) + @@ -9791,6 +9796,7 @@ public: constexpr std::array sampled_kinds{ Kind::Sampled1D, Kind::Sampled1DArray, Kind::Sampled2D, Kind::SampledUint1D, Kind::SampledUint1DArray, Kind::SampledUint2D, + Kind::SampledSint1D, Kind::SampledSint1DArray, Kind::SampledSint2D, }; for (const auto kind : sampled_kinds) { if (const auto *candidate = Binding(kind); candidate != nullptr) { @@ -10734,8 +10740,8 @@ public: {16, 0}, {8, 16}, {0, 24}}; bool valid = built && Prospero::NumBytesPerElement(format) == 4 && Prospero::BlockCompressedBytesPerBlock(format) == 0 && - Prospero::IsSampledTextureFormat(format) && - Prospero::IsUintTextureFormat(format) && + Prospero::SampledTextureNumericClass(format) == + Prospero::TextureNumericClass::Uint && Prospero::RemapTextureFormat(format) == Prospero::BufferFormat::k32UInt && surface_format.vk_format == vk::Format::eR32Uint && @@ -10756,18 +10762,17 @@ public: "PS5 packed integer texture metadata or Standard64KB mip tail " "changed"); - const auto storage_only_format = Prospero::BufferFormat::k32SInt; - const auto storage_only_surface = - TextureGetSurfaceFormatInfo(storage_only_format); - Require(name, "storage-only surface mapping", - !Prospero::IsSampledTextureFormat(storage_only_format) && - !Prospero::IsUintTextureFormat(storage_only_format) && - Prospero::RemapTextureFormat(storage_only_format) == - storage_only_format && - storage_only_surface.vk_format == vk::Format::eR32Sint && - storage_only_surface.conversion_format == + const auto signed_format = Prospero::BufferFormat::k32SInt; + const auto signed_surface = TextureGetSurfaceFormatInfo(signed_format); + Require(name, "signed sampled surface mapping", + Prospero::SampledTextureNumericClass(signed_format) == + Prospero::TextureNumericClass::Sint && + Prospero::RemapTextureFormat(signed_format) == + signed_format && + signed_surface.vk_format == vk::Format::eR32Sint && + signed_surface.conversion_format == Prospero::BufferFormat::kInvalid, - "storage-only native backing was conflated with sampled support"); + "signed sampled texture class or native backing changed"); } { @@ -11820,7 +11825,10 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) { Has(Kind::Sampled2D) || Has(Kind::Sampled2DArray) || Has(Kind::Sampled3D) || Has(Kind::SampledUint2D) || Has(Kind::SampledUint1D) || Has(Kind::SampledUint1DArray) || - Has(Kind::SampledUint2DArray) || Has(Kind::SampledUint3D); + Has(Kind::SampledUint2DArray) || Has(Kind::SampledUint3D) || + Has(Kind::SampledSint2D) || Has(Kind::SampledSint1D) || + Has(Kind::SampledSint1DArray) || Has(Kind::SampledSint2DArray) || + Has(Kind::SampledSint3D); const bool needs_storage_image = Has(Kind::Storage1D) || Has(Kind::Storage1DArray) || Has(Kind::Storage2D) || Has(Kind::Storage2DArray) || @@ -19740,6 +19748,34 @@ TestCase ImageLoadR32UintUsesIntegerSampledImage() { return test; } +TestCase ImageLoadR32SintUsesSignedSampledImage() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovU32(&code, 20, 2); + AppendVMovU32(&code, 21, 1); + code.push_back(EncodeMimg0(0x00, 0x1)); + code.push_back(EncodeMimg1(0, 20)); + AppendStoreVgpr(&code, 0, 0); + AppendEnd(&code); + + TestCase test; + test.name = "ImageLoadR32SintUsesSignedSampledImage"; + test.code = code; + test.expected = {0xffffff80u}; + test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD, O::BUFFER_STORE_DWORD, + O::S_ENDPGM}; + test.sampled_image_rgba.resize(16); + test.sampled_image_rgba[6] = 0xffffff80u; + test.sampled_image_format = vk::Format::eR32Sint; + test.sampled_image_dwords_per_pixel = 1; + test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32SInt); + test.has_user_data = true; + test.required_spirv = {"sampled_sint_2d", "OpTypeImage %int", + "OpImageFetch %v4int", "OpBitcast %uint"}; + return test; +} + TestCase ImageLoadPackedUintUnpacksAndSwizzles() { using O = ShaderOpcode; @@ -21511,6 +21547,7 @@ std::vector MakeCases() { AddCase(BufferAtomicFMaxContendedWorkgroup); AddCase(ImageLoadVariants); AddCase(ImageLoadR32UintUsesIntegerSampledImage); + AddCase(ImageLoadR32SintUsesSignedSampledImage); AddCase(ImageLoadPackedUintUnpacksAndSwizzles); AddCase(ImageSamplePackedUintConvertsSampleAndGather); AddCase(ImageLoadR128IgnoresAdjacentMaskSgprs); @@ -23611,15 +23648,48 @@ void CheckImageSamplerSpecialization() { sampled_image.kind = ShaderRecompiler::IR::ResourceKind::Image; sampled_image.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D; sampled_image.read = true; - mixed_sampler_program.info.images = {sampled_image, sampled_image}; + mixed_sampler_program.info.images = {sampled_image, sampled_image, + sampled_image}; mixed_sampler_program.info.samplers.push_back({0u, 4u}); - mixed_sampler_program.info.sampled_pairs = {{0u, 0u, 8u}, {1u, 0u, 12u}}; + mixed_sampler_program.info.sampled_pairs = { + {0u, 0u, 8u}, {1u, 0u, 12u}, {2u, 0u, 16u}}; + ShaderRecompiler::IR::MemoryInfo signed_memory; + signed_memory.kind = ShaderRecompiler::IR::ResourceKind::Image; + signed_memory.resource = 2u; + signed_memory.sampler = 0u; + mixed_sampler_program.memory_info.push_back(signed_memory); + mixed_sampler_program.block_storage.push_back( + std::make_unique()); + auto *mixed_sampler_block = mixed_sampler_program.block_storage.back().get(); + mixed_sampler_program.blocks.push_back(mixed_sampler_block); + using ShaderRecompiler::IR::Value; + using ShaderRecompiler::IR::ValueOpcode; + auto &image_handle = mixed_sampler_block->AppendNewInst( + ValueOpcode::GetImageResource, + {Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), + Value(0u), Value(0u)}); + auto &sampler_handle = mixed_sampler_block->AppendNewInst( + ValueOpcode::GetSamplerResource, + {Value(0u), Value(0u), Value(0u), Value(0u)}); + auto &image_address = mixed_sampler_block->AppendNewInst( + ValueOpcode::MakeImageAddress, + {Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), + Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), + Value(0u)}); + const ShaderRecompiler::IR::MemoryFlags signed_memory_flags{0u, 16u}; + uint64_t signed_memory_flag_bits = 0; + std::memcpy(&signed_memory_flag_bits, &signed_memory_flags, + sizeof(signed_memory_flags)); + mixed_sampler_block->AppendNewInst( + ValueOpcode::ImageSampleRaw, + {Value(&image_handle), Value(&sampler_handle), Value(&image_address)}, + signed_memory_flag_bits); ShaderRecompiler::IR::DescriptorValue native_image_descriptor{}; native_image_descriptor.dword_count = 8; native_image_descriptor.dwords[0] = 0x1000u; native_image_descriptor.dwords[1] = - static_cast(Prospero::BufferFormat::k32UInt) << 20u; + static_cast(Prospero::BufferFormat::k32Float) << 20u; native_image_descriptor.dwords[2] = 3u | (3u << 14u); native_image_descriptor.dwords[3] = DstSel(4, 5, 6, 7) | @@ -23628,10 +23698,15 @@ void CheckImageSamplerSpecialization() { packed_image_descriptor.dwords[0] = 0x2000u; packed_image_descriptor.dwords[1] = static_cast(Prospero::BufferFormat::k11_11_10UInt) << 20u; + auto signed_image_descriptor = native_image_descriptor; + signed_image_descriptor.dwords[0] = 0x3000u; + signed_image_descriptor.dwords[1] = + static_cast(Prospero::BufferFormat::k32SInt) << 20u; ShaderRecompiler::IR::ResourceSnapshot mixed_sampler_snapshot; mixed_sampler_snapshot.images = {native_image_descriptor, - packed_image_descriptor}; + packed_image_descriptor, + signed_image_descriptor}; ShaderRecompiler::IR::DescriptorValue sampler_descriptor{}; sampler_descriptor.dword_count = 4; mixed_sampler_snapshot.samplers.push_back(sampler_descriptor); @@ -23643,9 +23718,11 @@ void CheckImageSamplerSpecialization() { mixed_sampler_program.info.samplers[1].force_point_filtering && mixed_sampler_program.info.sampled_pairs[0].sampler == 0u && mixed_sampler_program.info.sampled_pairs[1].sampler == 1u && + mixed_sampler_program.info.sampled_pairs[2].sampler == 1u && + mixed_sampler_program.memory_info[0].sampler == 1u && mixed_sampler_snapshot.samplers.size() == 2u, - "a shared native/bit-packed sampler was not split into point and " - "native variants"); + "a shared float/integer sampler was not split into point and native " + "variants or the signed instruction retained the native sampler"); std::printf("[host] %-32s ok\n", "ImageSpecializationPipelineId"); } @@ -25699,7 +25776,6 @@ int main(int argc, char **argv) { CheckBasicStorageTextureDescriptor(); CheckStorageTextureLinearUploadLayout(); CheckStorageTextureDepthTileUploadLayout(); - CheckImageSamplerSpecialization(); CheckStandard64RenderTargetTileRoundTrip(); CheckStorageTextureVolumeUploadLayout(); CheckStorageTextureVolumeMipRegions(); @@ -25720,6 +25796,7 @@ int main(int argc, char **argv) { CheckShaderRecompilerFatalContracts(); VulkanHarness vulkan; #endif + CheckImageSamplerSpecialization(); CheckClipControlDepthClipState(); CheckReferenceClockScale(); CheckVulkan13FeatureRequirements();