diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 313a1deb30..ba23fb8d34 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -409,7 +409,7 @@ void SetupTransformFeedbackCapabilities(EmitContext& ctx, Id main_func) { } void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ctx) { - if (info.uses_sampled_1d) { + if (info.uses_sampled_1d || info.uses_image_1d) { ctx.AddCapability(spv::Capability::Sampled1D); } if (info.uses_sparse_residency) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 945cdb42bc..59e96ae06d 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "shader_recompiler/backend/spirv/emit_spirv.h" #include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" @@ -185,6 +186,84 @@ private: spv::ImageOperandsMask mask{}; }; + +Id SampledVectorType(EmitContext& ctx, TextureComponentType component_type) { + switch (component_type) { + case TextureComponentType::Float: + return ctx.F32[4]; + case TextureComponentType::Sint: + return ctx.S32[4]; + case TextureComponentType::Uint: + return ctx.U32[4]; + } + throw LogicError("Unhandled texture component type {}", static_cast(component_type)); +} + +bool ExpectsFloatResult(const IR::Inst* inst) { + switch (inst->Type()) { + case IR::Type::F32: + case IR::Type::F32x2: + case IR::Type::F32x3: + case IR::Type::F32x4: + return true; + default: + return false; + } +} + +Id MakeFloatVector(EmitContext& ctx, float value) { + const Id scalar{ctx.Const(value)}; + return ctx.ConstantComposite(ctx.F32[4], scalar, scalar, scalar, scalar); +} + +Id NormalizeUnsignedSample(EmitContext& ctx, u32 component_bits, Id value) { + if (component_bits == 0) { + return value; + } + const double max_value = std::exp2(static_cast(component_bits)) - 1.0; + if (!(max_value > 0.0)) { + return value; + } + const float inv_max = static_cast(1.0 / max_value); + return ctx.OpFMul(ctx.F32[4], value, MakeFloatVector(ctx, inv_max)); +} + +Id NormalizeSignedSample(EmitContext& ctx, u32 component_bits, Id value) { + if (component_bits == 0) { + return value; + } + const double positive_max = component_bits > 0 ? std::exp2(static_cast(component_bits - 1)) - 1.0 : 0.0; + if (!(positive_max > 0.0)) { + return ctx.OpFClamp(ctx.F32[4], value, MakeFloatVector(ctx, -1.0f), MakeFloatVector(ctx, 1.0f)); + } + const float inv_pos = static_cast(1.0 / positive_max); + const Id scaled{ctx.OpFMul(ctx.F32[4], value, MakeFloatVector(ctx, inv_pos))}; + return ctx.OpFClamp(ctx.F32[4], scaled, MakeFloatVector(ctx, -1.0f), MakeFloatVector(ctx, 1.0f)); +} + +Id ConvertSampleToExpectedType(EmitContext& ctx, const IR::Inst* inst, + const TextureDefinition* texture_def, Id value) { + if (!texture_def || texture_def->component_type == TextureComponentType::Float) { + return value; + } + if (!ExpectsFloatResult(inst)) { + return value; + } + switch (texture_def->component_type) { + case TextureComponentType::Sint: { + const Id as_float{ctx.OpConvertSToF(ctx.F32[4], value)}; + return NormalizeSignedSample(ctx, texture_def->component_bit_size, as_float); + } + case TextureComponentType::Uint: { + const Id as_float{ctx.OpConvertUToF(ctx.F32[4], value)}; + return NormalizeUnsignedSample(ctx, texture_def->component_bit_size, as_float); + } + case TextureComponentType::Float: + break; + } + return value; +} + Id Texture(EmitContext& ctx, IR::TextureInstInfo info, [[maybe_unused]] const IR::Value& index) { const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; if (def.count > 1) { @@ -449,31 +528,39 @@ Id EmitBoundImageWrite(EmitContext&) { Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id bias_lc, const IR::Value& offset) { const auto info{inst->Flags()}; + const TextureDefinition* texture_def = + info.type == TextureType::Buffer ? nullptr : &ctx.textures.at(info.descriptor_index); + const Id result_type = + texture_def ? SampledVectorType(ctx, texture_def->component_type) : ctx.F32[4]; + Id sample{}; if (ctx.stage == Stage::Fragment) { const ImageOperands operands(ctx, info.has_bias != 0, false, info.has_lod_clamp != 0, bias_lc, offset); - return Emit(&EmitContext::OpImageSparseSampleImplicitLod, - &EmitContext::OpImageSampleImplicitLod, ctx, inst, ctx.F32[4], - Texture(ctx, info, index), coords, operands.MaskOptional(), operands.Span()); + sample = Emit(&EmitContext::OpImageSparseSampleImplicitLod, + &EmitContext::OpImageSampleImplicitLod, ctx, inst, result_type, + Texture(ctx, info, index), coords, operands.MaskOptional(), operands.Span()); } else { - // We can't use implicit lods on non-fragment stages on SPIR-V. Maxwell hardware behaves as - // if the lod was explicitly zero. This may change on Turing with implicit compute - // derivatives const Id lod{ctx.Const(0.0f)}; const ImageOperands operands(ctx, false, true, info.has_lod_clamp != 0, lod, offset); - return Emit(&EmitContext::OpImageSparseSampleExplicitLod, - &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], - Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); + sample = Emit(&EmitContext::OpImageSparseSampleExplicitLod, + &EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type, + Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); } + return ConvertSampleToExpectedType(ctx, inst, texture_def, sample); } Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id lod, const IR::Value& offset) { const auto info{inst->Flags()}; + const TextureDefinition* texture_def = + info.type == TextureType::Buffer ? nullptr : &ctx.textures.at(info.descriptor_index); + const Id result_type = + texture_def ? SampledVectorType(ctx, texture_def->component_type) : ctx.F32[4]; const ImageOperands operands(ctx, false, true, false, lod, offset); - return Emit(&EmitContext::OpImageSparseSampleExplicitLod, - &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], - Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); + const Id sample = Emit(&EmitContext::OpImageSparseSampleExplicitLod, + &EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type, + Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); + return ConvertSampleToExpectedType(ctx, inst, texture_def, sample); } Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, @@ -509,13 +596,19 @@ Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Va Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, const IR::Value& offset, const IR::Value& offset2) { const auto info{inst->Flags()}; + const TextureDefinition* texture_def = + info.type == TextureType::Buffer ? nullptr : &ctx.textures.at(info.descriptor_index); + const Id result_type = + texture_def ? SampledVectorType(ctx, texture_def->component_type) : ctx.F32[4]; const ImageOperands operands(ctx, offset, offset2); if (ctx.profile.need_gather_subpixel_offset) { coords = ImageGatherSubpixelOffset(ctx, info, TextureImage(ctx, info, index), coords); } - return Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst, - ctx.F32[4], Texture(ctx, info, index), coords, ctx.Const(info.gather_component), - operands.MaskOptional(), operands.Span()); + const Id sample = Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst, + result_type, Texture(ctx, info, index), coords, + ctx.Const(info.gather_component), operands.MaskOptional(), + operands.Span()); + return ConvertSampleToExpectedType(ctx, inst, texture_def, sample); } Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, @@ -538,12 +631,17 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c lod = Id{}; } if (Sirit::ValidId(ms)) { - // This image is multisampled, lod must be implicit lod = Id{}; } + const TextureDefinition* texture_def = + info.type == TextureType::Buffer ? nullptr : &ctx.textures.at(info.descriptor_index); + const Id result_type = + texture_def ? SampledVectorType(ctx, texture_def->component_type) : ctx.F32[4]; 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()); + const Id sample = Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, + result_type, TextureImage(ctx, info, index), coords, + operands.MaskOptional(), operands.Span()); + return ConvertSampleToExpectedType(ctx, inst, texture_def, sample); } Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod, @@ -588,14 +686,19 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id derivatives, const IR::Value& offset, Id lod_clamp) { const auto info{inst->Flags()}; + const TextureDefinition* texture_def = + info.type == TextureType::Buffer ? nullptr : &ctx.textures.at(info.descriptor_index); + const Id result_type = + texture_def ? SampledVectorType(ctx, texture_def->component_type) : ctx.F32[4]; const auto operands = info.num_derivatives == 3 ? ImageOperands(ctx, info.has_lod_clamp != 0, derivatives, ctx.Def(offset), {}, lod_clamp) : ImageOperands(ctx, info.has_lod_clamp != 0, derivatives, info.num_derivatives, offset, lod_clamp); - return Emit(&EmitContext::OpImageSparseSampleExplicitLod, - &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], - Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); + const Id sample = Emit(&EmitContext::OpImageSparseSampleExplicitLod, + &EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type, + Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); + return ConvertSampleToExpectedType(ctx, inst, texture_def, sample); } Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 4c3e101433..3b7094eb6f 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -28,9 +28,23 @@ enum class Operation { FPMax, }; +Id ComponentTypeId(EmitContext& ctx, TextureComponentType component_type) { + switch (component_type) { + case TextureComponentType::Float: + return ctx.F32[1]; + case TextureComponentType::Sint: + return ctx.S32[1]; + case TextureComponentType::Uint: + return ctx.U32[1]; + } + throw LogicError("Unhandled texture component type {}", static_cast(component_type)); +} + Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) { const spv::ImageFormat format{spv::ImageFormat::Unknown}; - const Id type{ctx.F32[1]}; + const TextureComponentType component_type = desc.is_depth ? TextureComponentType::Float + : desc.component_type; + const Id type{ComponentTypeId(ctx, component_type)}; const bool depth{desc.is_depth}; const bool ms{desc.is_multisample}; switch (desc.type) { @@ -1374,6 +1388,8 @@ void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_in .image_type = image_type, .count = desc.count, .is_multisample = desc.is_multisample, + .component_type = desc.component_type, + .component_bit_size = desc.component_bit_size, }); if (profile.supported_spirv >= 0x00010400) { interfaces.push_back(id); @@ -1417,6 +1433,12 @@ void EmitContext::DefineInputs(const IR::Program& program) { const Info& info{program.info}; const VaryingState loads{info.loads.mask | info.passthrough.mask}; + const auto decorate_flat_if_fragment = [this](Id id) { + if (stage == Stage::Fragment) { + Decorate(id, spv::Decoration::Flat); + } + }; + if (info.uses_workgroup_id) { workgroup_id = DefineInput(*this, U32[3], false, spv::BuiltIn::WorkgroupId); } @@ -1432,16 +1454,22 @@ void EmitContext::DefineInputs(const IR::Program& program) { } if (info.uses_sample_id) { sample_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SampleId); + decorate_flat_if_fragment(sample_id); } if (info.uses_is_helper_invocation) { is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation); } if (info.uses_subgroup_mask) { subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR); + decorate_flat_if_fragment(subgroup_mask_eq); subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR); + decorate_flat_if_fragment(subgroup_mask_lt); subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR); + decorate_flat_if_fragment(subgroup_mask_le); subgroup_mask_gt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGtMaskKHR); + decorate_flat_if_fragment(subgroup_mask_gt); subgroup_mask_ge = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGeMaskKHR); + decorate_flat_if_fragment(subgroup_mask_ge); } if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || (profile.warp_size_potentially_larger_than_guest && @@ -1461,6 +1489,7 @@ void EmitContext::DefineInputs(const IR::Program& program) { } if (loads[IR::Attribute::PrimitiveId]) { primitive_id = DefineInput(*this, U32[1], false, spv::BuiltIn::PrimitiveId); + decorate_flat_if_fragment(primitive_id); } if (loads[IR::Attribute::Layer]) { AddCapability(spv::Capability::Geometry); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 66cdb1d3db..0606f51f65 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -38,6 +38,8 @@ struct TextureDefinition { Id image_type; u32 count; bool is_multisample; + TextureComponentType component_type{TextureComponentType::Float}; + u32 component_bit_size{}; }; struct TextureBufferDefinition { diff --git a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp index 2bfa3227a8..8bb5cc4b33 100644 --- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp +++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp @@ -14,6 +14,10 @@ namespace Shader::Optimization { namespace { +constexpr bool IsOneDimensional(TextureType type) { + return type == TextureType::Color1D || type == TextureType::ColorArray1D; +} + void AddConstantBufferDescriptor(Info& info, u32 index, u32 count) { if (count != 1) { throw NotImplementedException("Constant buffer descriptor indexing"); @@ -548,7 +552,7 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::ImageQueryDimensions: case IR::Opcode::ImageGradient: { const TextureType type{inst.Flags().type}; - info.uses_sampled_1d |= type == TextureType::Color1D || type == TextureType::ColorArray1D; + info.uses_sampled_1d |= IsOneDimensional(type); info.uses_sparse_residency |= inst.GetAssociatedPseudoOperation(IR::Opcode::GetSparseFromOp) != nullptr; break; @@ -560,7 +564,7 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::ImageQueryLod: { const auto flags{inst.Flags()}; const TextureType type{flags.type}; - info.uses_sampled_1d |= type == TextureType::Color1D || type == TextureType::ColorArray1D; + info.uses_sampled_1d |= IsOneDimensional(type); info.uses_shadow_lod |= flags.is_depth != 0; info.uses_sparse_residency |= inst.GetAssociatedPseudoOperation(IR::Opcode::GetSparseFromOp) != nullptr; @@ -569,6 +573,7 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::ImageRead: { const auto flags{inst.Flags()}; info.uses_typeless_image_reads |= flags.image_format == ImageFormat::Typeless; + info.uses_image_1d |= IsOneDimensional(flags.type); info.uses_sparse_residency |= inst.GetAssociatedPseudoOperation(IR::Opcode::GetSparseFromOp) != nullptr; break; @@ -576,6 +581,7 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::ImageWrite: { const auto flags{inst.Flags()}; info.uses_typeless_image_writes |= flags.image_format == ImageFormat::Typeless; + info.uses_image_1d |= IsOneDimensional(flags.type); info.uses_image_buffers |= flags.type == TextureType::Buffer; break; } @@ -761,9 +767,12 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::ImageAtomicAnd32: case IR::Opcode::ImageAtomicOr32: case IR::Opcode::ImageAtomicXor32: - case IR::Opcode::ImageAtomicExchange32: + case IR::Opcode::ImageAtomicExchange32: { + const auto flags{inst.Flags()}; info.uses_atomic_image_u32 = true; + info.uses_image_1d |= IsOneDimensional(flags.type); break; + } default: break; } diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index 7ff1961172..5d0056ccbd 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -19,6 +19,7 @@ #include "shader_recompiler/host_translate_info.h" #include "shader_recompiler/ir_opt/passes.h" #include "shader_recompiler/shader_info.h" +#include "video_core/surface.h" namespace Shader::Optimization { namespace { @@ -248,11 +249,19 @@ bool IsTextureInstruction(const IR::Inst& inst) { } static inline TexturePixelFormat ReadTexturePixelFormatCached(Environment& env, const ConstBufferAddr& cbuf) { - return env.ReadTexturePixelFormat(GetTextureHandleCached(env, cbuf)); + const u32 handle = GetTextureHandleCached(env, cbuf); + if (handle == 0) { + return TexturePixelFormat::A8B8G8R8_UNORM; + } + return env.ReadTexturePixelFormat(handle); } static inline bool IsTexturePixelFormatIntegerCached(Environment& env, const ConstBufferAddr& cbuf) { - return env.IsTexturePixelFormatInteger(GetTextureHandleCached(env, cbuf)); + const u32 handle = GetTextureHandleCached(env, cbuf); + if (handle == 0) { + return false; + } + return env.IsTexturePixelFormatInteger(handle); } @@ -524,6 +533,8 @@ public: const u32 index{Add(texture_descriptors, desc, [&desc](const auto& existing) { return desc.type == existing.type && desc.is_depth == existing.is_depth && desc.has_secondary == existing.has_secondary && + desc.component_type == existing.component_type && + desc.component_bit_size == existing.component_bit_size && desc.cbuf_index == existing.cbuf_index && desc.cbuf_offset == existing.cbuf_offset && desc.shift_left == existing.shift_left && @@ -598,6 +609,35 @@ bool IsPixelFormatSNorm(TexturePixelFormat pixel_format) { } } +TextureComponentType PixelFormatComponentType(TexturePixelFormat pixel_format, bool is_integer) { + if (!is_integer) { + return TextureComponentType::Float; + } + + switch (pixel_format) { + case TexturePixelFormat::A8B8G8R8_SINT: + case TexturePixelFormat::R8_SINT: + case TexturePixelFormat::R16G16B16A16_SINT: + case TexturePixelFormat::R32G32B32A32_SINT: + case TexturePixelFormat::R32G32_SINT: + case TexturePixelFormat::R16_SINT: + case TexturePixelFormat::R16G16_SINT: + case TexturePixelFormat::R8G8_SINT: + case TexturePixelFormat::R32_SINT: + return TextureComponentType::Sint; + default: + return TextureComponentType::Uint; + } +} + +u8 PixelFormatIntegerComponentBits(TexturePixelFormat pixel_format, bool is_integer) { + if (!is_integer) { + return 0; + } + return static_cast(VideoCore::Surface::PixelComponentSizeBitsInteger( + static_cast(pixel_format))); +} + void PatchTexelFetch(IR::Block& block, IR::Inst& inst, TexturePixelFormat pixel_format) { const auto it{IR::Block::InstructionList::s_iterator_to(inst)}; IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; @@ -698,6 +738,8 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo default: break; } + const TexturePixelFormat pixel_format{ReadTexturePixelFormatCached(env, cbuf)}; + const bool is_integer{IsTexturePixelFormatIntegerCached(env, cbuf)}; u32 index; switch (inst->GetOpcode()) { case IR::Opcode::ImageRead: @@ -718,7 +760,6 @@ 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)}; if (flags.type == TextureType::Buffer) { index = descriptors.Add(ImageBufferDescriptor{ .format = flags.image_format, @@ -764,6 +805,8 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo .is_depth = flags.is_depth != 0, .is_multisample = is_multisample, .has_secondary = cbuf.has_secondary, + .component_type = PixelFormatComponentType(pixel_format, is_integer), + .component_bit_size = PixelFormatIntegerComponentBits(pixel_format, is_integer), .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 ed13e68209..d1de3be7fb 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -35,6 +38,12 @@ enum class TextureType : u32 { }; constexpr u32 NUM_TEXTURE_TYPES = 9; +enum class TextureComponentType : u32 { + Float, + Sint, + Uint, +}; + enum class TexturePixelFormat { A8B8G8R8_UNORM, A8B8G8R8_SNORM, @@ -174,7 +183,9 @@ struct StorageBufferDescriptor { }; struct TextureBufferDescriptor { - bool has_secondary; + TextureComponentType component_type{TextureComponentType::Float}; + u8 component_bit_size{}; + bool has_secondary{}; u32 cbuf_index; u32 cbuf_offset; u32 shift_left; @@ -207,6 +218,8 @@ struct TextureDescriptor { bool is_depth; bool is_multisample; bool has_secondary; + TextureComponentType component_type{TextureComponentType::Float}; + u8 component_bit_size{}; u32 cbuf_index; u32 cbuf_offset; u32 shift_left; diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index 910e07a606..25f848f0cd 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h @@ -191,10 +191,19 @@ inline void PushImageDescriptors(TextureCache& texture_cache, ImageView& image_view{texture_cache.GetImageView(image_view_id)}; const VkImageView vk_image_view{image_view.Handle(desc.type)}; const Sampler& sampler{texture_cache.GetSampler(sampler_id)}; - const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && - !image_view.SupportsAnisotropy()}; - const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() - : sampler.Handle()}; + const bool needs_linear_fallback = sampler.RequiresLinearFiltering() && + !image_view.SupportsLinearFiltering(); + const bool needs_aniso_fallback = sampler.HasAddedAnisotropy() && + !image_view.SupportsAnisotropy(); + if (!image_view.SupportsLinearFiltering()) { + ASSERT_MSG(!sampler.RequiresLinearFiltering() || needs_linear_fallback, + "Linear filtering sampler bound to unsupported image view"); + } + // Prefer degrading to nearest sampling when the view lacks linear support. + const VkSampler vk_sampler = needs_linear_fallback + ? sampler.HandleWithoutLinearFiltering() + : (needs_aniso_fallback ? sampler.HandleWithDefaultAnisotropy() + : sampler.Handle()); guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler); rescaling.PushTexture(texture_cache.IsRescaling(image_view)); } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 7327c90c12..f1089c0d13 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2061,6 +2061,8 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI }, .subresourceRange = MakeSubresourceRange(aspect_mask, info.range), }; + supports_linear_filtering = device->IsFormatSupported( + create_info.format, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT, FormatType::Optimal); const auto create = [&](TextureType tex_type, std::optional num_layers) { VkImageViewCreateInfo ci{create_info}; ci.viewType = ImageViewType(tex_type); @@ -2111,10 +2113,13 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageInfo& info, const VideoCommon::ImageViewInfo& view_info, GPUVAddr gpu_addr_) : VideoCommon::ImageViewBase{info, view_info, gpu_addr_}, - buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {} + buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} { + supports_linear_filtering = true; +} ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageViewParams& params) : VideoCommon::ImageViewBase{params}, device{&runtime.device} { + supports_linear_filtering = true; if (device->HasNullDescriptor()) { return; } @@ -2175,6 +2180,7 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type, if (image_format == Shader::ImageFormat::Typeless) { return Handle(texture_type); } + const bool is_signed{image_format == Shader::ImageFormat::R8_SINT || image_format == Shader::ImageFormat::R16_SINT}; if (!storage_views) { @@ -2244,15 +2250,23 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t } // Some games have samplers with garbage. Sanitize them here. const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); + const VkFilter mag_filter = MaxwellToVK::Sampler::Filter(tsc.mag_filter); + const VkFilter min_filter = MaxwellToVK::Sampler::Filter(tsc.min_filter); + const VkSamplerMipmapMode mipmap_mode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter); + const f32 min_lod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(); + const f32 max_lod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(); + requires_linear_filtering = mag_filter == VK_FILTER_LINEAR || min_filter == VK_FILTER_LINEAR || + mipmap_mode == VK_SAMPLER_MIPMAP_MODE_LINEAR || max_anisotropy > 1.0f; - const auto create_sampler = [&](const f32 anisotropy) { + const auto create_sampler = [&](VkFilter mag, VkFilter min, VkSamplerMipmapMode mip, + f32 anisotropy) { return device.GetLogical().CreateSampler(VkSamplerCreateInfo{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = pnext, .flags = 0, - .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), - .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), - .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), + .magFilter = mag, + .minFilter = min, + .mipmapMode = mip, .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), @@ -2261,19 +2275,25 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t .maxAnisotropy = anisotropy, .compareEnable = tsc.depth_compare_enabled, .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), - .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), - .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), + .minLod = min_lod, + .maxLod = max_lod, .borderColor = arbitrary_borders ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT : ConvertBorderColor(color), .unnormalizedCoordinates = VK_FALSE, }); }; - sampler = create_sampler(max_anisotropy); + sampler = create_sampler(mag_filter, min_filter, mipmap_mode, max_anisotropy); const f32 max_anisotropy_default = static_cast(1U << tsc.max_anisotropy); if (max_anisotropy > max_anisotropy_default) { - sampler_default_anisotropy = create_sampler(max_anisotropy_default); + sampler_default_anisotropy = create_sampler(mag_filter, min_filter, mipmap_mode, + max_anisotropy_default); + } + + if (requires_linear_filtering) { + sampler_no_linear = create_sampler(VK_FILTER_NEAREST, VK_FILTER_NEAREST, + VK_SAMPLER_MIPMAP_MODE_NEAREST, 1.0f); } } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index cd11cc8fc7..4a13531096 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -238,6 +238,10 @@ public: [[nodiscard]] bool IsRescaled() const noexcept; + [[nodiscard]] bool SupportsLinearFiltering() const noexcept { + return supports_linear_filtering; + } + [[nodiscard]] VkImageView Handle(Shader::TextureType texture_type) const noexcept { return *image_views[static_cast(texture_type)]; } @@ -278,6 +282,7 @@ private: vk::ImageView depth_view; vk::ImageView stencil_view; vk::ImageView color_view; + bool supports_linear_filtering{}; vk::Image null_image; VkImage image_handle = VK_NULL_HANDLE; VkImageView render_target = VK_NULL_HANDLE; @@ -296,16 +301,26 @@ public: } [[nodiscard]] VkSampler HandleWithDefaultAnisotropy() const noexcept { - return *sampler_default_anisotropy; + return sampler_default_anisotropy ? *sampler_default_anisotropy : *sampler; + } + + [[nodiscard]] VkSampler HandleWithoutLinearFiltering() const noexcept { + return sampler_no_linear ? *sampler_no_linear : *sampler; } [[nodiscard]] bool HasAddedAnisotropy() const noexcept { return static_cast(sampler_default_anisotropy); } + [[nodiscard]] bool RequiresLinearFiltering() const noexcept { + return requires_linear_filtering; + } + private: vk::Sampler sampler; vk::Sampler sampler_default_anisotropy; + vk::Sampler sampler_no_linear; + bool requires_linear_filtering{}; }; class Framebuffer {