From 12a9e9bd7c468eca91ed5a1c7850cddb46f822eb Mon Sep 17 00:00:00 2001 From: JPikachu Date: Mon, 2 Mar 2026 20:55:37 +0000 Subject: [PATCH] [shader_recompiler] Add workaround for AMD shader bug in LM3 Fixes red, green and blue lines artifact on AMD GPUs in Luigi's Mansion 3. This removes the old global legacy rescaling toggle. Instead it automatically detects the specific fragment shaders causing the issue (due to F32/U32 bitcasts on position attributes). The legacy rescaling workaround is now applied exclusively to these broken shaders, keeping the default scaling math intact for everything else. --- src/common/settings.h | 3 -- src/qt_common/config/shared_translation.cpp | 6 --- .../frontend/maxwell/translate_program.cpp | 2 +- src/shader_recompiler/ir_opt/passes.h | 1 + .../ir_opt/rescaling_pass.cpp | 46 +++++++++++++++---- 5 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/common/settings.h b/src/common/settings.h index 7ea4136576..dd3cd14a02 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -557,9 +557,6 @@ struct Values { SwitchableSetting fix_bloom_effects{linkage, false, "fix_bloom_effects", Category::RendererHacks}; - SwitchableSetting rescale_hack{linkage, false, "rescale_hack", - Category::RendererHacks}; - SwitchableSetting use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders", Category::RendererHacks}; diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index f49c43ee2a..9d33fa6979 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -356,12 +356,6 @@ std::unique_ptr InitializeTranslations(QObject* parent) tr("Fix bloom effects"), tr("Removes bloom in Burnout.")); - INSERT(Settings, - rescale_hack, - tr("Enable Legacy Rescale Pass"), - tr("May fix rescale issues in some games by relying on behavior from the previous implementation.\n" - "Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3.")); - // Renderer (Extensions) INSERT(Settings, dyna_state, tr("Extended Dynamic State"), tr("Controls the number of features that can be used in Extended Dynamic State.\n" diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp index f156192c13..c89c918d61 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp @@ -304,7 +304,7 @@ IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPoolGetOpcode() == IR::Opcode::GetAttribute) { const IR::Attribute attr{bitcast_inst->Arg(0).Attribute()}; - switch (attr) { - case IR::Attribute::PositionX: - case IR::Attribute::PositionY: + if (attr >= IR::Attribute::PositionX && attr <= IR::Attribute::PositionW) { bitcast_inst->SetFlags(0xDEADBEEF); must_patch_outside = true; - break; - default: - break; } } if (must_patch_outside) { const auto it{IR::Block::InstructionList::s_iterator_to(inst)}; IR::IREmitter ir{block, it}; - if (Settings::values.rescale_hack.GetValue()) { + + if (needs_hack) { const IR::F32 new_inst{&*block.PrependNewInst(it, inst)}; const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())}; const IR::Value converted{ir.FPMul(new_inst, up_factor)}; @@ -347,12 +343,44 @@ void Visit(const IR::Program& program, IR::Block& block, IR::Inst& inst) { } } // Anonymous namespace +bool FragmentShaderNeedsRescalingPass(const IR::Program& program) { + if (program.stage != Stage::Fragment) return false; + + for (const IR::Block* block : program.post_order_blocks) { + for (const IR::Inst& inst : block->Instructions()) { + const auto op = inst.GetOpcode(); + if (op != IR::Opcode::ShuffleIndex && op != IR::Opcode::ShuffleUp && + op != IR::Opcode::ShuffleDown && op != IR::Opcode::ShuffleButterfly) { + continue; + } + + if (inst.Arg(0).IsImmediate()) continue; + const IR::Inst* arg_inst = inst.Arg(0).InstRecursive(); + + if (arg_inst->GetOpcode() != IR::Opcode::BitCastU32F32 || arg_inst->Arg(0).IsImmediate()) continue; + const IR::Inst* bitcast_inst = arg_inst->Arg(0).InstRecursive(); + + if (bitcast_inst->GetOpcode() == IR::Opcode::GetAttribute) { + const auto attr = bitcast_inst->Arg(0).Attribute(); + if (attr >= IR::Attribute::PositionX && attr <= IR::Attribute::PositionW) { + return true; + } + } + } + } + return false; +} + void RescalingPass(IR::Program& program) { const bool is_fragment_shader{program.stage == Stage::Fragment}; + const bool needs_hack{FragmentShaderNeedsRescalingPass(program)}; + if (needs_hack) { + LOG_WARNING(Shader, "F32/U32 bitcast detected. Applying rescaling workaround."); + } if (is_fragment_shader) { for (IR::Block* const block : program.post_order_blocks) { for (IR::Inst& inst : block->Instructions()) { - VisitMark(*block, inst); + VisitMark(*block, inst, needs_hack); } } }