aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/engines/kepler_compute.cpp
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-11-02 02:31:57 -0500
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-11-02 02:31:57 -0500
commit82e34d410ff6fdffa4916c90faa05c676a5d65e4 (patch)
tree15a8fdcbf41f6f873b4843fa7565e1c57da498b3 /src/video_core/engines/kepler_compute.cpp
parent9c55e70d790a4cc9507d80dbb9a9d6224175574e (diff)
nx2: decode sRGB in the upscale fallback when the program does
Splatoon Raiders' upscaler program ends by decoding its result from sRGB to linear (HSETP2 against 0.04045, HMUL2 by 2.4 between MUFU.LG2 and MUFU.EX2) before its surface store. The bilinear copy that stands in for it while HMMA isn't emulated wrote the sRGB input as-is, so the later passes encoded it a second time: outdoor scenes were washed out, with a pale sky and a white haze. When the program carries those two immediates, the copy now decodes like it, through a new copy operation of the blit helper (a fragment shader with the piecewise sRGB decode). Programs without them, such as Kirby Air Riders' upscaler, keep the plain copy. The OpenGL backend ignores the operation.
Diffstat (limited to 'src/video_core/engines/kepler_compute.cpp')
-rw-r--r--src/video_core/engines/kepler_compute.cpp26
1 files changed, 25 insertions, 1 deletions
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index b8a6567907..e4f527cd58 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -665,8 +665,32 @@ bool KeplerCompute::UpscaleInsteadOfNativeProgram() {
result.addr_lower = static_cast<u32>(tic.Address());
return result;
};
+ // Title 4's program ends by decoding its result from sRGB to linear (the 0.04045 threshold
+ // and the 2.4 exponent as half-precision immediates of HSETP2 and HMUL2). The copy of its
+ // sRGB input left the frame gamma-encoded twice, washed out: decode in the copy when the
+ // program does.
+ static std::unordered_map<GPUVAddr, bool> decodes_srgb;
+ const GPUVAddr program = regs.code_loc.Address();
+ const auto [known, inserted] = decodes_srgb.try_emplace(program, false);
+ if (inserted) {
+ bool threshold = false, exponent = false;
+ std::array<u32, 0x400> block{};
+ for (u64 offset = 0; offset < 0x20000 && !(threshold && exponent); offset += sizeof(block)) {
+ if (!memory_manager.IsFullyMappedRange(program + offset, sizeof(block))) break;
+ memory_manager.ReadBlock(program + offset, block.data(), sizeof(block));
+ for (size_t word = 0; word < block.size(); word += 4) {
+ const u32 opcode = block[word] & 0xfff;
+ threshold |= opcode == 0x434 && block[word + 1] == 0x292d292d;
+ exponent |= opcode == 0x832 && block[word + 1] == 0x40cc40cc;
+ }
+ }
+ known->second = threshold && exponent;
+ LOG_INFO(HW_GPU, "Upscale fallback for program {:#x}: {}", program,
+ known->second ? "decodes sRGB like the program" : "plain copy");
+ }
const Fermi2D::Config config{
- .operation = Fermi2D::Operation::SrcCopy,
+ .operation = known->second ? Fermi2D::Operation::SrcCopySrgbToLinear
+ : Fermi2D::Operation::SrcCopy,
.filter = Fermi2D::Filter::Bilinear,
.must_accelerate = true,
.dst_x0 = 0,