diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-28 02:57:31 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-28 02:57:31 -0400 |
| commit | f7be55dbf1a313382a28bca067960a0fae95227f (patch) | |
| tree | e1848e19ce40078d1b4ea5a8178ab86b6681d473 | |
| parent | 3e48eedecf91f8f8d0917f8f78ce26a3d0482e5c (diff) | |
nx2: title 4's intro cutscene renders correctly
Native shader capture counted CALL.REL.NOINC as a function call, so a program
using it waited for a RET after its EXIT and read on into the following
shaders until a garbage branch target made it fail. NOINC calls push no
return address (the compiler uses them as jumps; the lowering already treats
them so): count them as forward branches.
That shader builds title 4's 3D colour-grading LUT once per scene; with it
rejected the LUT stayed empty and the tonemap turned every frame black. The
intro cutscene now renders lit and graded, with subtitles, through the
bilinear stand-in for the tensor-core upscaler.
Also: rejected captures dump to EDEN_NX2_SHADER_DUMP as rejected-<addr>.bin
and report the offending instruction.
| -rw-r--r-- | src/shader_recompiler/frontend/ampere/program_capture.h | 5 | ||||
| -rw-r--r-- | src/video_core/shader_environment.cpp | 15 |
2 files changed, 17 insertions, 3 deletions
diff --git a/src/shader_recompiler/frontend/ampere/program_capture.h b/src/shader_recompiler/frontend/ampere/program_capture.h index ea7dfb7ef6..f09bf9735d 100644 --- a/src/shader_recompiler/frontend/ampere/program_capture.h +++ b/src/shader_recompiler/frontend/ampere/program_capture.h @@ -19,7 +19,10 @@ public: const s64 target = s64{index} * 16 + 16 + relative; if (target < 0 || target % 16 || target >= MaximumInstructions * 16) return Result::InvalidTarget; - if (opcode == 0x944) { + // CALL.REL.NOINC (bit 86) pushes no return address: the compiler uses it as a jump + // (the lowering treats it as one), so it never pairs with a RET. + const bool noinc_call = opcode == 0x944 && (instruction.words[2] & 0x400000) != 0; + if (opcode == 0x944 && !noinc_call) { last_call_target = std::max(last_call_target, static_cast<u32>(target / 16)); } else { last_forward_target = std::max(last_forward_target, static_cast<u32>(target / 16)); diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index fc2a26a568..5a14211073 100644 --- a/src/video_core/shader_environment.cpp +++ b/src/video_core/shader_environment.cpp @@ -338,8 +338,19 @@ void GenericEnvironment::InitializeNativeCode(u32 source_header_size) { auto& instruction = native.emplace_back(); gpu_memory->ReadBlock(address, &instruction, sizeof(instruction)); const auto status = capture.Observe(instruction, index); - if (status == Shader::Ampere::ProgramCaptureState::Result::InvalidTarget) - throw Shader::RuntimeError("Native shader branch outside capture bounds"); + if (status == Shader::Ampere::ProgramCaptureState::Result::InvalidTarget) { + if (const char* dir = std::getenv("EDEN_NX2_SHADER_DUMP")) { + const auto path = fmt::format("{}/rejected-{:x}.bin", dir, program_base + native_header_size); + if (std::FILE* file = std::fopen(path.c_str(), "wb")) { + std::fwrite(native.data(), sizeof(Instruction), std::min<size_t>(native.size(), 4096), file); + std::fclose(file); + } + } + throw Shader::RuntimeError("Native shader branch outside capture bounds at {:#x} +{:#x} [{:08x} {:08x} {:08x} {:08x}]", + program_base + native_header_size, index * 16, + instruction.words[0], instruction.words[1], + instruction.words[2], instruction.words[3]); + } if (status == Shader::Ampere::ProgramCaptureState::Result::Complete) break; } auto lowered = Shader::Ampere::LowerToMaxwell(native); |
