From 73918d23d5a7c261e4b46342a410fd07b775cc6a Mon Sep 17 00:00:00 2001 From: xbzk Date: Mon, 15 Jun 2026 03:05:11 +0200 Subject: [PATCH] [video_core, maxwell] fixes for homebrew games that use MESA compiler (#4012) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit two main issues: shader_environment.cpp Support Mesa nv50_ir-compiled homebrew fragment shaders Mesa nv50_ir (used by NZP-Switch via libdrm_nouveau) emits Maxwell SASS that differs from NVN in two ways our shader pipeline didn't account for, causing "infinite" loops during shader scanning. How? TryFindSize() previously detected end-of-shader only via NVN's "BRA $-1" self-branch trailer (SELF_BRANCH_A/B). Mesa-compiled shaders end with an unconditional @PT EXIT and emit no trailer, so scanning ran past the shader and looped to MAXIMUM_SIZE. Fix: Added a secondary match against @PT EXIT T (opcode 0xE30, predicate PT, flow T) as a fallback terminator. The mask is estrictive enough to reject predicated EXITs, conditional-flow EXITs, and sched control words, so well-formed NVN shaders see no ehavior change (their single @PT EXIT immediately precedes BRA $-1, and both detections return the same size). load_store_attribute.cpp + attribute.h IPA's is_perspective check only applied the ×position_w correction for IR::IsGeneric() attributes, and used a per-component SPH lookup. Two failure modes followed: 1. Mesa fragment shaders read varyings via legacy attribute slots (ColorFrontDiffuse, FixedFncTexture, FogCoordinate) which are remapped to generic varyings later by ConvertLegacyToGeneric. At IPA-translation time they're still legacy, so IsGeneric() was false and the inject was skipped — but the resulting GLSL/SPIR-V varying was Smooth (perspective-correct), and the SASS still issued its manual perspective dance via MUFU.RCP(ATTR_W)+FMUL, compounding an extra ×clip_w factor. NZP fog (eye_z distance varying) ended up as eye_z⁴×density² instead of eye_z²×density², saturating fog to white everywhere. 2. Even for legacy attrs that should inject, Mesa stores them in two formats selected by the IPA's interpolation_mode field: Pass/Multiply/Constant → attr/w (perspective); inject ×position_w Sc (ScreenLinear) → raw attr; do NOT inject Unconditionally injecting for Sc dims vertex colors by 1/clip_w because there's no SASS multiplier to round-trip it back. Fix: extend the IPA correction path to also fire on IsLegacyAttribute(), gated on interpolation_mode != Sc. While here, the generic-path check is widened from per-component to vector-level (first non-Unused PixelImap, with all-Unused fallthrough treated as Perspective) so it matches the GLSL interpolation qualifier picked by CollectInterpolationInfo — this fixes the same all-zeroed-imap case Mesa exposes for explicit generics. IsLegacyAttribute moved from translate_program.cpp's anonymous namespace into attribute.h next to IsGeneric so IPA can reach it. Tested on NaziZombies:Portable (Switch homebrew): fog now matches real hardware, vertex-colored geometry (hand, gun, decals) renders correctly. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4012 Reviewed-by: Lizzie Reviewed-by: MaranBr --- .../filesystem/fsp/fs_i_filesystem.cpp | 2 +- src/shader_recompiler/frontend/ir/attribute.h | 10 ++++++- .../translate/impl/load_store_attribute.cpp | 27 ++++++++++++++----- .../frontend/maxwell/translate_program.cpp | 8 +----- src/video_core/shader_environment.cpp | 6 +++++ 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp index 0638111ae9..cbc3777da7 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp +++ b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp @@ -151,7 +151,7 @@ Result IFileSystem::GetTotalSpaceSize( Result IFileSystem::GetFileTimeStampRaw( Out out_timestamp, const InLargeData path) { - LOG_WARNING(Service_FS, "(Partial Implementation) called. file={}", path->str); + LOG_DEBUG(Service_FS, "(Partial Implementation) called. file={}", path->str); FileSys::FileTimeStampRaw vfs_timestamp{}; R_TRY(backend->GetFileTimeStampRaw(&vfs_timestamp, FileSys::Path(path->str))); diff --git a/src/shader_recompiler/frontend/ir/attribute.h b/src/shader_recompiler/frontend/ir/attribute.h index 943c8d673f..4adf980949 100644 --- a/src/shader_recompiler/frontend/ir/attribute.h +++ b/src/shader_recompiler/frontend/ir/attribute.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -240,6 +240,14 @@ constexpr size_t NUM_FIXEDFNCTEXTURE = 10; return attribute >= Attribute::Generic0X && attribute <= Attribute::Generic31X; } +[[nodiscard]] inline bool IsLegacyAttribute(Attribute attribute) noexcept { + return (attribute >= Attribute::ColorFrontDiffuseR && + attribute <= Attribute::ColorBackSpecularA) || + attribute == Attribute::FogCoordinate || + (attribute >= Attribute::FixedFncTexture0S && + attribute <= Attribute::FixedFncTexture9Q); +} + [[nodiscard]] inline u32 GenericAttributeIndex(Attribute attribute) { if (!IsGeneric(attribute)) throw InvalidArgument("Attribute is not generic {}", attribute); diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp index e3745ce084..d4990af7a6 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -163,12 +166,24 @@ void TranslatorVisitor::IPA(u64 insn) { const IR::Attribute attribute{ipa.attribute}; IR::F32 value{is_indexed ? ir.GetAttributeIndexed(X(ipa.index_reg)) : ir.GetAttribute(attribute)}; - if (IR::IsGeneric(attribute)) { - const ProgramHeader& sph{env.SPH()}; - const u32 attr_index{IR::GenericAttributeIndex(attribute)}; - const u32 element{static_cast(attribute) % 4}; - const std::array input_map{sph.ps.GenericInputMap(attr_index)}; - const bool is_perspective{input_map[element] == Shader::PixelImap::Perspective}; + const bool is_legacy{IR::IsLegacyAttribute(attribute)}; + if (IR::IsGeneric(attribute) || is_legacy) { + bool is_perspective{is_legacy && + ipa.interpolation_mode != InterpolationMode::Sc}; + if (!is_legacy) { + const ProgramHeader& sph{env.SPH()}; + const u32 attr_index{IR::GenericAttributeIndex(attribute)}; + const std::array input_map{sph.ps.GenericInputMap(attr_index)}; + Shader::PixelImap effective_imap{Shader::PixelImap::Unused}; + for (const Shader::PixelImap component : input_map) { + if (component != Shader::PixelImap::Unused) { + effective_imap = component; + break; + } + } + is_perspective = effective_imap == Shader::PixelImap::Perspective || + effective_imap == Shader::PixelImap::Unused; + } if (is_perspective) { const IR::F32 position_w{ir.GetAttribute(IR::Attribute::PositionW)}; value = ir.FPMul(value, position_w); diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp index f156192c13..705f20850a 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp @@ -132,13 +132,7 @@ void AddNVNStorageBuffers(IR::Program& program) { } } -bool IsLegacyAttribute(IR::Attribute attribute) { - return (attribute >= IR::Attribute::ColorFrontDiffuseR && - attribute <= IR::Attribute::ColorBackSpecularA) || - attribute == IR::Attribute::FogCoordinate || - (attribute >= IR::Attribute::FixedFncTexture0S && - attribute <= IR::Attribute::FixedFncTexture9Q); -} +using IR::IsLegacyAttribute; //rescoped to attribute.h to make it visible in load_store_attribute.cpp IPA std::map GenerateLegacyToGenericMappings( const VaryingState& state, std::queue unused_generics, diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index 29a79810cd..2f21a6f492 100644 --- a/src/video_core/shader_environment.cpp +++ b/src/video_core/shader_environment.cpp @@ -254,6 +254,9 @@ std::optional GenericEnvironment::TryFindSize() { static constexpr u64 SELF_BRANCH_A = 0xE2400FFFFF87000FULL; static constexpr u64 SELF_BRANCH_B = 0xE2400FFFFF07000FULL; + static constexpr u64 MESA_EXIT_MASK = 0xFFF00000000F001FULL; + static constexpr u64 MESA_EXIT_VALUE = (0xE30ULL << 52) | (0x7ULL << 16) | 0xFULL; + code.resize(MAXIMUM_SIZE / INST_SIZE); GPUVAddr guest_addr{program_base + start_address}; @@ -267,6 +270,9 @@ std::optional GenericEnvironment::TryFindSize() { if (inst == SELF_BRANCH_A || inst == SELF_BRANCH_B) { return offset + index; } + if ((inst & MESA_EXIT_MASK) == MESA_EXIT_VALUE) { + return offset + index + INST_SIZE; + } } guest_addr += BLOCK_SIZE; size += BLOCK_SIZE;