From fba374442cdd7470ee46f39aa2bed7395ce7d6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Mon, 27 Apr 2026 17:07:05 +0200 Subject: [PATCH] file_sys: apply case-insensitive search to mods_path on GNU/Linux and macOS (#4312) (#4310) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The case-insensitive fallback search() in GetHostPath is only invoked for patch_path and host_path, so mods whose file or folder capitalization does not exactly match the guest path are silently bypassed even when the files are present. Mirror the existing search(patch_path) pass for mods_path, placed first to preserve mod > patch > base precedence. Co-authored-by: Matías Buzzo --- .../spirv/emit_spirv_floating_point.cpp | 12 +----- .../backend/spirv/emit_spirv_instructions.h | 4 +- .../frontend/translate/vector_alu.cpp | 16 +++++++- src/shader_recompiler/ir/ir_emitter.cpp | 14 ++----- src/shader_recompiler/ir/ir_emitter.h | 4 +- src/shader_recompiler/ir/opcodes.inc | 4 +- tests/gcn/test_gcn_instructions.cpp | 40 +++++++++++++++++++ 7 files changed, 66 insertions(+), 28 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp index 66d400c7d..6f78fdd4b 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp @@ -39,11 +39,7 @@ Id EmitFPFma64(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c) { return Decorate(ctx, inst, ctx.OpFma(ctx.F64[1], a, b, c)); } -Id EmitFPMax32(EmitContext& ctx, Id a, Id b, bool is_legacy) { - if (is_legacy) { - return ctx.OpNMax(ctx.F32[1], a, b); - } - +Id EmitFPMax32(EmitContext& ctx, Id a, Id b) { return ctx.OpFMax(ctx.F32[1], a, b); } @@ -51,11 +47,7 @@ Id EmitFPMax64(EmitContext& ctx, Id a, Id b) { return ctx.OpFMax(ctx.F64[1], a, b); } -Id EmitFPMin32(EmitContext& ctx, Id a, Id b, bool is_legacy) { - if (is_legacy) { - return ctx.OpNMin(ctx.F32[1], a, b); - } - +Id EmitFPMin32(EmitContext& ctx, Id a, Id b) { return ctx.OpFMin(ctx.F32[1], a, b); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index da84e253c..080f6dd92 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -237,9 +237,9 @@ Id EmitFPAdd64(EmitContext& ctx, IR::Inst* inst, Id a, Id b); Id EmitFPSub32(EmitContext& ctx, IR::Inst* inst, Id a, Id b); Id EmitFPFma32(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c); Id EmitFPFma64(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c); -Id EmitFPMax32(EmitContext& ctx, Id a, Id b, bool is_legacy = false); +Id EmitFPMax32(EmitContext& ctx, Id a, Id b); Id EmitFPMax64(EmitContext& ctx, Id a, Id b); -Id EmitFPMin32(EmitContext& ctx, Id a, Id b, bool is_legacy = false); +Id EmitFPMin32(EmitContext& ctx, Id a, Id b); Id EmitFPMin64(EmitContext& ctx, Id a, Id b); Id EmitFPMinTri32(EmitContext& ctx, Id a, Id b, Id c); Id EmitFPMaxTri32(EmitContext& ctx, Id a, Id b, Id c); diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 7427d4b9e..17cccf90b 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -527,13 +527,25 @@ void Translator::V_MUL_I32_I24(const GcnInst& inst, bool is_signed) { void Translator::V_MIN_F32(const GcnInst& inst, bool is_legacy) { const IR::F32 src0{GetSrc(inst.src[0])}; const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPMin(src0, src1, is_legacy)); + + const IR::F32 fpmin = ir.FPMin(src0, src1); + const IR::F32 result = + is_legacy + ? IR::F32{ir.Select(ir.LogicalOr(ir.FPIsNan(src0), ir.FPIsNan(src1)), src1, fpmin)} + : fpmin; + SetDst(inst.dst[0], result); } void Translator::V_MAX_F32(const GcnInst& inst, bool is_legacy) { const IR::F32 src0{GetSrc(inst.src[0])}; const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPMax(src0, src1, is_legacy)); + + const IR::F32 fpmax = ir.FPMax(src0, src1); + const IR::F32 result = + is_legacy + ? IR::F32{ir.Select(ir.LogicalOr(ir.FPIsNan(src0), ir.FPIsNan(src1)), src1, fpmax)} + : fpmax; + SetDst(inst.dst[0], result); } void Translator::V_MIN_I32(const GcnInst& inst) { diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index e45bcc15e..8431daf02 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -1386,35 +1386,29 @@ U1 IREmitter::FPUnordered(const F32F64& lhs, const F32F64& rhs) { return LogicalOr(FPIsNan(lhs), FPIsNan(rhs)); } -F32F64 IREmitter::FPMax(const F32F64& lhs, const F32F64& rhs, bool is_legacy) { +F32F64 IREmitter::FPMax(const F32F64& lhs, const F32F64& rhs) { if (lhs.Type() != rhs.Type()) { UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type()); } switch (lhs.Type()) { case Type::F32: - return Inst(Opcode::FPMax32, lhs, rhs, is_legacy); + return Inst(Opcode::FPMax32, lhs, rhs); case Type::F64: - if (is_legacy) { - UNREACHABLE_MSG("F64 cannot be used with LEGACY ops"); - } return Inst(Opcode::FPMax64, lhs, rhs); default: ThrowInvalidType(lhs.Type()); } } -F32F64 IREmitter::FPMin(const F32F64& lhs, const F32F64& rhs, bool is_legacy) { +F32F64 IREmitter::FPMin(const F32F64& lhs, const F32F64& rhs) { if (lhs.Type() != rhs.Type()) { UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type()); } switch (lhs.Type()) { case Type::F32: - return Inst(Opcode::FPMin32, lhs, rhs, is_legacy); + return Inst(Opcode::FPMin32, lhs, rhs); case Type::F64: - if (is_legacy) { - UNREACHABLE_MSG("F64 cannot be used with LEGACY ops"); - } return Inst(Opcode::FPMin64, lhs, rhs); default: ThrowInvalidType(lhs.Type()); diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index a7e45c69f..17a58bcb5 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -259,8 +259,8 @@ public: [[nodiscard]] U1 FPCmpClass32(const F32& value, const U32& op); [[nodiscard]] U1 FPOrdered(const F32F64& lhs, const F32F64& rhs); [[nodiscard]] U1 FPUnordered(const F32F64& lhs, const F32F64& rhs); - [[nodiscard]] F32F64 FPMax(const F32F64& lhs, const F32F64& rhs, bool is_legacy = false); - [[nodiscard]] F32F64 FPMin(const F32F64& lhs, const F32F64& rhs, bool is_legacy = false); + [[nodiscard]] F32F64 FPMax(const F32F64& lhs, const F32F64& rhs); + [[nodiscard]] F32F64 FPMin(const F32F64& lhs, const F32F64& rhs); [[nodiscard]] F32F64 FPMinTri(const F32F64& a, const F32F64& b, const F32F64& c); [[nodiscard]] F32F64 FPMaxTri(const F32F64& a, const F32F64& b, const F32F64& c); [[nodiscard]] F32F64 FPMedTri(const F32F64& a, const F32F64& b, const F32F64& c); diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index ebbc702b7..302d95c2f 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -236,9 +236,9 @@ OPCODE(FPAdd64, F64, F64, OPCODE(FPSub32, F32, F32, F32, ) OPCODE(FPFma32, F32, F32, F32, F32, ) OPCODE(FPFma64, F64, F64, F64, F64, ) -OPCODE(FPMax32, F32, F32, F32, U1, ) +OPCODE(FPMax32, F32, F32, F32, ) OPCODE(FPMax64, F64, F64, F64, ) -OPCODE(FPMin32, F32, F32, F32, U1, ) +OPCODE(FPMin32, F32, F32, F32, ) OPCODE(FPMin64, F64, F64, F64, ) OPCODE(FPMinTri32, F32, F32, F32, F32, ) OPCODE(FPMaxTri32, F32, F32, F32, F32, ) diff --git a/tests/gcn/test_gcn_instructions.cpp b/tests/gcn/test_gcn_instructions.cpp index d88c8d6a5..e39f36a99 100644 --- a/tests/gcn/test_gcn_instructions.cpp +++ b/tests/gcn/test_gcn_instructions.cpp @@ -121,3 +121,43 @@ TEST_F(GcnTest, sub_f16) { EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, F16x2{half(-1.0f)}); //confirmed with neo } + +TEST_F(GcnTest, mul_legacy_nan) { + auto runner = gcn_test::Runner::instance().value(); + + auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MUL_LEGACY_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get()); + auto result = runner->run(spirv, std::array{u32(0), u32(0x7fc00000)}); + + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(*result, 0); +} + +TEST_F(GcnTest, mul_nan) { + auto runner = gcn_test::Runner::instance().value(); + + auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MUL_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get()); + auto result = runner->run(spirv, std::array{u32(0), u32(0x7fc00000)}); + + EXPECT_TRUE(result.has_value()); + EXPECT_TRUE(std::isnan(*result)); +} + +TEST_F(GcnTest, min_legacy_nan) { + auto runner = gcn_test::Runner::instance().value(); + + auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MIN_LEGACY_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get()); + auto result = runner->run(spirv, std::array{u32(0), u32(0x7fc00000)}); + + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(*result, 0x7fc00000); +} + +TEST_F(GcnTest, min_nan) { + auto runner = gcn_test::Runner::instance().value(); + + auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MIN_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get()); + auto result = runner->run(spirv, std::array{u32(0), u32(0x7fc00000)}); + + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(*result, 0); +}