aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/shader_cache.cpp
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 03:39:52 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 03:39:52 -0400
commite17fd9cef116a088f080ca502951e500864a58b6 (patch)
treee7ff65f614622515d6a2e4263521909eea89a5d8 /src/video_core/shader_cache.cpp
parentea6c1193fbfa95e586bb6d129dc684163a12cb3e (diff)
parent80c79333fe6fd28a31b65b997f1cbf2987711c91 (diff)
Merge Switch 2 work from local Eden (nx2 at b0dd3fe)
Diffstat (limited to 'src/video_core/shader_cache.cpp')
-rw-r--r--src/video_core/shader_cache.cpp121
1 files changed, 111 insertions, 10 deletions
diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp
index f1aff2810f..dfe80abac3 100644
--- a/src/video_core/shader_cache.cpp
+++ b/src/video_core/shader_cache.cpp
@@ -5,12 +5,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
+#include <cstring>
+#include <string>
+#include <unordered_set>
+#include <fmt/ranges.h>
#include <array>
#include <vector>
#include <boost/container/small_vector.hpp>
#include "common/assert.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"
+#include "shader_recompiler/exception.h"
#include "shader_recompiler/object_pool.h"
#include "video_core/control/channel_state.h"
#include "video_core/dirty_flags.h"
@@ -43,7 +48,7 @@ void ShaderCache::SyncGuestHost() {
ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_)
: device_memory{device_memory_} {}
-bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) {
+bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) try {
auto& dirty{maxwell3d->dirty.flags};
if (!dirty[VideoCommon::Dirty::Shaders]) {
return last_shaders_valid;
@@ -51,6 +56,7 @@ bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) {
dirty[VideoCommon::Dirty::Shaders] = false;
const GPUVAddr base_addr{maxwell3d->regs.program_region.Address()};
+ const bool native = maxwell3d->GetNativeMacroEngine().native_turing;
for (size_t index = 0; index < Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram; ++index) {
if (!maxwell3d->regs.IsShaderConfigEnabled(index)) {
unique_hashes[index] = 0;
@@ -63,7 +69,9 @@ bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) {
unique_hashes[index] = 0;
continue;
}
- const GPUVAddr shader_addr{base_addr + shader_config.offset};
+ const GPUVAddr shader_addr{native
+ ? (static_cast<u64>(shader_config.reserved[0] & 0xff) << 32) | shader_config.reserved[1]
+ : base_addr + shader_config.offset};
const std::optional<VAddr> cpu_shader_addr{gpu_memory->GpuToCpuAddress(shader_addr)};
if (!cpu_shader_addr) {
LOG_ERROR(HW_GPU, "Invalid GPU address for shader {:#016x}", shader_addr);
@@ -71,16 +79,68 @@ bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) {
return false;
}
const ShaderInfo* shader_info{TryGet(*cpu_shader_addr)};
+ if (!shader_info && native && IsRejected(*cpu_shader_addr)) {
+ // A rejected program fails the same way every time it is bound: skip its draws
+ // without rethrowing (two C++ throws each time cost ~1-2% of the GPU thread).
+ maxwell3d->GetNativeMacroEngine().native_execution_failed = true;
+ last_shaders_valid = false;
+ return false;
+ }
if (!shader_info) {
- const u32 start_address{shader_config.offset};
- GraphicsEnvironment env{*maxwell3d, *gpu_memory, program, base_addr, start_address};
- shader_info = MakeShaderInfo(env, *cpu_shader_addr);
+ const u32 start_address{native ? 0 : shader_config.offset};
+ try {
+ // Native programs are lowered while the environment is built.
+ RethrowIfRejected(*cpu_shader_addr);
+ GraphicsEnvironment env{*maxwell3d, *gpu_memory, program, native ? shader_addr : base_addr, start_address};
+ shader_info = MakeShaderInfo(env, *cpu_shader_addr);
+ } catch (const Shader::Exception&) {
+ RememberRejection(*cpu_shader_addr);
+ // Once per program: which stage slot it is bound to, its header and the slot's
+ // registers (to tell how a pipeline with task/mesh programs is set up).
+ static std::unordered_set<GPUVAddr> described;
+ if (native && described.insert(shader_addr).second) {
+ std::array<u32, 5> header{};
+ gpu_memory->ReadBlockUnsafe(shader_addr, header.data(), sizeof(header));
+ std::array<u32, sizeof(shader_config) / sizeof(u32)> config{};
+ std::memcpy(config.data(), &shader_config, sizeof(config));
+ LOG_ERROR(HW_GPU, "Rejected program {:#x} is bound to stage {}: header {:08x}, slot {:08x}",
+ shader_addr, index, fmt::join(header, " "), fmt::join(config, " "));
+ }
+ throw;
+ }
}
shader_infos[index] = shader_info;
unique_hashes[index] = shader_info->unique_hash;
}
last_shaders_valid = true;
return true;
+} catch (const Shader::Exception& error) {
+ auto& native = maxwell3d->GetNativeMacroEngine();
+ if (!native.native_turing) throw;
+ native.native_execution_failed = true;
+ last_shaders_valid = false;
+ // Each rejected program is logged once (the draws that use it keep failing quietly).
+ static std::unordered_set<std::string> logged;
+ if (logged.insert(error.what()).second) {
+ LOG_ERROR(HW_GPU, "Native graphics shader rejected: {}", error.what());
+ }
+ // Once: show what the vertex stage has bound, to find where an unbound bank's data lives.
+ static std::vector<std::string> dumped_errors;
+ if (const std::string what{error.what()};
+ what.find("constant buffer") != std::string::npos && dumped_errors.size() < 12 &&
+ std::count(dumped_errors.begin(), dumped_errors.end(), what) < 3) {
+ dumped_errors.push_back(what);
+ LOG_INFO(HW_GPU, "VS cbuf dump for: {}", what.substr(0, 40));
+ for (u32 slot = 3; slot < 8; ++slot) {
+ const auto& buffer = maxwell3d->state.shader_stages[0].const_buffers[slot];
+ if (!buffer.enabled) continue;
+ std::array<u32, 24> words{};
+ gpu_memory->ReadBlockUnsafe(buffer.address, words.data(), sizeof(words));
+ LOG_INFO(HW_GPU, "VS cbuf {} at {:#x} size {:#x}: {:08x}", slot, buffer.address, buffer.size,
+ fmt::join(words, " "));
+ }
+ }
+ return false;
}
const ShaderInfo* ShaderCache::ComputeShader() {
@@ -95,22 +155,54 @@ const ShaderInfo* ShaderCache::ComputeShader() {
if (const ShaderInfo* const shader = TryGet(*cpu_shader_addr)) {
return shader;
}
- ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start};
- return MakeShaderInfo(env, *cpu_shader_addr);
+ try {
+ RethrowIfRejected(*cpu_shader_addr);
+ ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start};
+ return MakeShaderInfo(env, *cpu_shader_addr);
+ } catch (const Shader::Exception&) {
+ RememberRejection(*cpu_shader_addr);
+ throw;
+ }
+}
+
+void ShaderCache::RethrowIfRejected(VAddr addr) {
+ std::scoped_lock lock{rejected_mutex};
+ if (const auto it = rejected_native.find(addr); it != rejected_native.end()) {
+ std::rethrow_exception(it->second);
+ }
+}
+
+bool ShaderCache::IsRejected(VAddr addr) {
+ std::scoped_lock lock{rejected_mutex};
+ return rejected_native.contains(addr);
+}
+
+void ShaderCache::RememberRejection(VAddr addr) {
+ std::scoped_lock lock{rejected_mutex};
+ rejected_native.emplace(addr, std::current_exception());
+ rejected_lowest.store((std::min)(rejected_lowest.load(std::memory_order_relaxed), addr),
+ std::memory_order_relaxed);
+ rejected_highest.store((std::max)(rejected_highest.load(std::memory_order_relaxed), addr),
+ std::memory_order_relaxed);
}
void ShaderCache::GetGraphicsEnvironments(GraphicsEnvironments& result,
const std::array<u64, NUM_PROGRAMS>& unique_hashes) {
size_t env_index{};
const GPUVAddr base_addr{maxwell3d->regs.program_region.Address()};
+ const bool native = maxwell3d->GetNativeMacroEngine().native_turing;
for (size_t index = 0; index < NUM_PROGRAMS; ++index) {
if (unique_hashes[index] == 0) {
continue;
}
const auto program{static_cast<Tegra::Engines::Maxwell3D::Regs::ShaderType>(index)};
auto& env{result.envs[index]};
- const u32 start_address{maxwell3d->regs.pipelines[index].offset};
- env = GraphicsEnvironment{*maxwell3d, *gpu_memory, program, base_addr, start_address};
+ const auto& config = maxwell3d->regs.pipelines[index];
+ const u32 start_address{native ? 0 : config.offset};
+ const GPUVAddr address = native
+ ? (static_cast<u64>(config.reserved[0] & 0xff) << 32) | config.reserved[1]
+ : base_addr;
+ env = GraphicsEnvironment{*maxwell3d, *gpu_memory, program, address, start_address};
env.SetCachedSize(shader_infos[index]->size_bytes);
result.env_ptrs[env_index++] = &env;
}
@@ -144,6 +236,15 @@ void ShaderCache::Register(std::unique_ptr<ShaderInfo> data, VAddr addr, size_t
void ShaderCache::InvalidatePagesInRegion(VAddr addr, size_t size) {
const VAddr addr_end = addr + size;
+ // Rejected programs are few and in a few places: look at them only for writes that can
+ // reach them (this runs for every inline upload and pre-draw invalidation).
+ if (addr_end > rejected_lowest.load(std::memory_order_relaxed) &&
+ addr <= rejected_highest.load(std::memory_order_relaxed)) {
+ std::scoped_lock lock{rejected_mutex};
+ boost::unordered::erase_if(rejected_native, [&](const auto& rejected) {
+ return rejected.first >= addr && rejected.first < addr_end;
+ });
+ }
const u64 page_end = (addr_end + YUZU_PAGESIZE - 1) >> YUZU_PAGEBITS;
for (u64 page = addr >> YUZU_PAGEBITS; page < page_end; ++page) {
auto it = invalidation_cache.find(page);
@@ -241,7 +342,7 @@ const ShaderInfo* ShaderCache::MakeShaderInfo(GenericEnvironment& env, VAddr cpu
info->unique_hash = env.CalculateHash();
info->size_bytes = env.ReadSizeBytes();
}
- const size_t size_bytes{info->size_bytes};
+ const size_t size_bytes{env.SourceSizeBytes() ? env.SourceSizeBytes() : info->size_bytes};
const ShaderInfo* const result{info.get()};
Register(std::move(info), cpu_addr, size_bytes);
return result;