aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/engines/kepler_compute.cpp
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-28 01:55:10 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-28 01:55:10 -0400
commit585e457172693730fe82f34a2ce513ac7cdc948d (patch)
treee4d5716861d2e133fae7251a33cb11452d6d1862 /src/video_core/engines/kepler_compute.cpp
parent869c6e4b33a6432166597f6f48812d44b3f5de02 (diff)
nx2: stand in for title 4's tensor-core upscaler and its blit constants
- When a compute program is rejected for tensor-core math, blit its input image (handle at cbuf0+0x90) to its output (cbuf0+0xa0) with bilinear filtering. Title 4 renders at 2560x1440 and upscales to 3840x2160 with a DLSS-style network; this keeps the 4K output fed. EDEN_NX2_TENSOR=stub keeps the program with HMMA as D = C (for experiments). - BlitConstantsPass: vertex shaders reading bank 24 (lowered to 17) get the console-provided blit block synthesized from the render area (texel size, size, UV scale 1, rectangle 0..W-1,H-1). - Debug: EDEN_NX2_CB24_TRACE, upscaler resource description and render target sampling at the fallback. The scene behind the intro dialogue is still black: the tonemap output that feeds the upscaler has no colour although earlier targets do.
Diffstat (limited to 'src/video_core/engines/kepler_compute.cpp')
-rw-r--r--src/video_core/engines/kepler_compute.cpp136
1 files changed, 136 insertions, 0 deletions
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index f49dc26f82..136fcb9dda 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -8,10 +8,12 @@
#include <bitset>
#include <cstdlib>
#include <fstream>
+#include <optional>
#include "common/assert.h"
#include "common/logging.h"
#include "shader_recompiler/exception.h"
#include "core/core.h"
+#include "video_core/engines/fermi_2d.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
@@ -396,11 +398,145 @@ bool KeplerCompute::DispatchNativeCompute(const NativeQmdScheduler::Descriptor&
rasterizer->DispatchCompute();
} catch (const Shader::Exception& error) {
LOG_ERROR(HW_GPU, "Native compute shader rejected: {}", error.what());
+ if (std::string_view{error.what()}.find("matrix multiply") != std::string_view::npos) {
+ DescribeNativeResources(qmd);
+ // The console runs this on tensor cores; stand in with a plain upscale of its input.
+ return UpscaleInsteadOfNativeProgram();
+ }
return false;
}
return true;
}
+// Logs the images a rejected program's handles (constant bank 0, bytes 0x80-0x11c) point to, so a
+// substitute (e.g. an upscale blit for a tensor-core pass) can find its inputs and output.
+void KeplerCompute::DescribeNativeResources(const NativeQmdScheduler::Descriptor& qmd) {
+ static u32 described{};
+ if (described++ >= 2) return;
+ const auto& cbuf = launch_description.const_buffer_config[0];
+ const GPUVAddr cbuf_address = (GPUVAddr{cbuf.address_high} << 32) | cbuf.address_low;
+ LOG_INFO(HW_GPU, "Native resources: cbuf0 {:#x} size {:#x}, grid {}x{}x{}, block {}x{}x{}, tic pool {:#x}",
+ cbuf_address, cbuf.size.Value(), qmd[12], qmd[13] & 0xffff, qmd[14] & 0xffff,
+ launch_description.block_dim_x.Value(), launch_description.block_dim_y.Value(),
+ launch_description.block_dim_z.Value(), regs.tic.Address());
+ for (u32 offset = 0x80; offset < 0x120; offset += 4) {
+ const u32 handle = memory_manager.Read<u32>(cbuf_address + offset);
+ const u32 tic_index = handle & 0xfffff;
+ if (handle == 0 || tic_index > regs.tic.limit) continue;
+ const auto tic = GetTICEntry(tic_index);
+ LOG_INFO(HW_GPU, " handle @{:#x} = {:#x}: tic {} address {:#x} {}x{} format {:#x} types {}/{}/{}/{}",
+ offset, handle, tic_index, tic.Address(), tic.Width(), tic.Height(),
+ static_cast<u32>(tic.format.Value()), static_cast<u32>(tic.r_type.Value()),
+ static_cast<u32>(tic.g_type.Value()), static_cast<u32>(tic.b_type.Value()),
+ static_cast<u32>(tic.a_type.Value()));
+ }
+}
+
+// Title 4's upscaler reads the tonemapped frame through the texture handle at cbuf0+0x90 and
+// writes the output resolution image through the surface handle at cbuf0+0xa0 (from its
+// TLD/SUST operands). Blit one to the other with bilinear filtering when both are plain 2D images
+// of the same format and the output is larger.
+bool KeplerCompute::UpscaleInsteadOfNativeProgram() {
+ const auto& cbuf = launch_description.const_buffer_config[0];
+ const GPUVAddr cbuf_address = (GPUVAddr{cbuf.address_high} << 32) | cbuf.address_low;
+ const auto image = [&](u32 offset) -> std::optional<Texture::TICEntry> {
+ const u32 tic_index = memory_manager.Read<u32>(cbuf_address + offset) & 0xfffff;
+ if (tic_index > regs.tic.limit) return std::nullopt;
+ return GetTICEntry(tic_index);
+ };
+ const auto input = image(0x90), output = image(0xa0);
+ if (!input || !output || input->format != output->format || input->Address() == 0 ||
+ output->Address() == 0 || output->Width() <= input->Width() || !input->IsBlockLinear() ||
+ !output->IsBlockLinear())
+ return false;
+ using RTFormat = Tegra::RenderTargetFormat;
+ RTFormat format{};
+ switch (input->format) {
+ case Texture::TextureFormat::A2B10G10R10: format = RTFormat::A2B10G10R10_UNORM; break;
+ case Texture::TextureFormat::A8B8G8R8: format = RTFormat::A8B8G8R8_UNORM; break;
+ case Texture::TextureFormat::B10G11R11: format = RTFormat::B10G11R11_FLOAT; break;
+ case Texture::TextureFormat::R16G16B16A16: format = RTFormat::R16G16B16A16_FLOAT; break;
+ default: return false;
+ }
+ const auto surface = [&](const Texture::TICEntry& tic) {
+ Fermi2D::Surface result{};
+ result.format = format;
+ result.linear = Fermi2D::MemoryLayout::BlockLinear;
+ result.block_width.Assign(tic.block_width);
+ result.block_height.Assign(tic.block_height);
+ result.block_depth.Assign(tic.block_depth);
+ result.depth = 1;
+ result.width = tic.Width();
+ result.height = tic.Height();
+ result.addr_upper = static_cast<u32>(tic.Address() >> 32);
+ result.addr_lower = static_cast<u32>(tic.Address());
+ return result;
+ };
+ const Fermi2D::Config config{
+ .operation = Fermi2D::Operation::SrcCopy,
+ .filter = Fermi2D::Filter::Bilinear,
+ .must_accelerate = true,
+ .dst_x0 = 0,
+ .dst_y0 = 0,
+ .dst_x1 = static_cast<s32>(output->Width()),
+ .dst_y1 = static_cast<s32>(output->Height()),
+ .src_x0 = 0,
+ .src_y0 = 0,
+ .src_x1 = static_cast<s32>(input->Width()),
+ .src_y1 = static_cast<s32>(input->Height()),
+ };
+ // Debug: does the input hold anything? Download it once and count non-zero words.
+ if (static u32 sampled{}; sampled++ == 20) {
+ const auto sample = [&](const Texture::TICEntry& tic) {
+ const u64 size = u64{tic.Width()} * tic.Height() * 4;
+ if (const auto device = memory_manager.GpuToCpuAddress(tic.Address())) {
+ rasterizer->FlushRegion(*device, size, VideoCommon::CacheType::All);
+ }
+ u32 nonzero{}, samples{};
+ u64 sum{};
+ for (u64 offset = 0; offset < size; offset += 4096 + 4) {
+ const u32 word = memory_manager.Read<u32>(tic.Address() + offset);
+ ++samples;
+ nonzero += word != 0;
+ sum += (word & 0x3ff) + ((word >> 10) & 0x3ff) + ((word >> 20) & 0x3ff);
+ }
+ LOG_INFO(HW_GPU, "Upscale fallback image {:#x}: {}/{} sampled words non-zero, mean channel {}",
+ tic.Address(), nonzero, samples, samples ? sum / (samples * 3) : 0);
+ };
+ sample(*input);
+ sample(*output);
+ // Every 2560x1440 target the in-game passes draw to (from EDEN_NX2_RT_TRACE).
+ for (const GPUVAddr address : {0x8116a03000ULL, 0x8117ae5000ULL, 0x8114b62000ULL,
+ 0x8115a63000ULL, 0x8117904000ULL, 0x8114a02000ULL}) {
+ Texture::TICEntry fake = *input;
+ const u64 size = 2560ULL * 1440 * 4;
+ if (const auto device = memory_manager.GpuToCpuAddress(address)) {
+ rasterizer->FlushRegion(*device, size, VideoCommon::CacheType::All);
+ }
+ u32 nonzero{}, samples{}, distinct{};
+ u32 last{};
+ for (u64 offset = 0; offset < size; offset += 4096 + 4) {
+ const u32 word = memory_manager.Read<u32>(address + offset);
+ ++samples;
+ nonzero += word != 0;
+ distinct += word != last;
+ last = word;
+ }
+ LOG_INFO(HW_GPU, "Upscale fallback target {:#x}: {}/{} non-zero, {} changes", address, nonzero,
+ samples, distinct);
+ (void)fake;
+ }
+ }
+ const bool done = rasterizer->AccelerateSurfaceCopy(surface(*input), surface(*output), config);
+ static u32 logged{};
+ if (logged++ < 4) {
+ LOG_INFO(HW_GPU, "Upscale fallback {:#x} {}x{} -> {:#x} {}x{}: {}", input->Address(),
+ input->Width(), input->Height(), output->Address(), output->Width(),
+ output->Height(), done ? "blitted" : "not accelerated");
+ }
+ return done;
+}
+
void KeplerCompute::ProcessLaunch() {
const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address();
memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description,