aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/core.cpp3
-rw-r--r--src/video_core/dma_pusher.cpp5
-rw-r--r--src/video_core/gpu_split.cpp20
-rw-r--r--src/video_core/native_qmd.h8
-rw-r--r--src/video_core/nx2.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp25
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp22
-rw-r--r--src/video_core/texture_cache/texture_cache.h1
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp2
-rw-r--r--src/yuzu_cmd/yuzu.cpp23
10 files changed, 98 insertions, 13 deletions
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 3728ccf8f7..64e1d8de06 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -496,6 +496,9 @@ struct System::Impl {
}
PerfStatsResults GetAndResetPerfStats() {
+ if (!perf_stats) {
+ return {};
+ }
return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs());
}
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp
index 62f7e6bfdb..88d571ae5f 100644
--- a/src/video_core/dma_pusher.cpp
+++ b/src/video_core/dma_pusher.cpp
@@ -151,7 +151,10 @@ bool DmaPusher::Step() {
}
}
// A resumed entry is read again: the words after the acquire may be new by now.
- const bool use_safe = Settings::IsDMALevelDefault() ? Settings::IsGPULevelHigh() : Settings::IsDMALevelSafe();
+ // A safe read flushes the caches, which belong to the split's back thread while it runs
+ // (the split only starts with unsafe reads; the settings can change mid-game).
+ const bool use_safe = !VideoCommon::GpuSplit::Get().Active() &&
+ (Settings::IsDMALevelDefault() ? Settings::IsGPULevelHigh() : Settings::IsDMALevelSafe());
if (use_safe) {
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::SafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
const std::span<const CommandHeader> commands{headers.data(), headers.size()};
diff --git a/src/video_core/gpu_split.cpp b/src/video_core/gpu_split.cpp
index f913ca82ce..ac0d3f25f1 100644
--- a/src/video_core/gpu_split.cpp
+++ b/src/video_core/gpu_split.cpp
@@ -34,12 +34,14 @@ void Pause() {
_mm_pause();
#elif defined(__x86_64__) || defined(__i386__)
__builtin_ia32_pause();
+#elif defined(__aarch64__)
+ asm volatile("yield");
#else
std::this_thread::yield();
#endif
}
-std::atomic<u32> g_back_thread_id{};
+[[maybe_unused]] std::atomic<u32> g_back_thread_id{};
std::atomic<bool> g_back_busy{};
} // Anonymous namespace
@@ -88,15 +90,23 @@ void GpuSplit::MaybeStart(Core::System& system_) {
return value != nullptr ? value[0] : '2';
}();
const bool wanted = mode == '1' || mode == '2';
- if (active || !wanted || !Tegra::g_nx2_ampere.load(std::memory_order_relaxed)) {
+ if (!wanted || !Tegra::g_nx2_ampere.load(std::memory_order_relaxed)) {
return;
}
// The front reads command lists straight from guest memory; a read that flushes the caches
- // first (safe DMA, accurate GPU) would touch back-owned state.
+ // first (safe DMA, accurate GPU) would touch back-owned state. The settings can change
+ // mid-game: hand the engines back to the front (and start again when they allow it).
const bool safe_reads = Settings::IsDMALevelDefault() ? Settings::IsGPULevelHigh()
: Settings::IsDMALevelSafe();
- if (safe_reads || Settings::IsGPULevelHigh() ||
- Settings::values.renderer_backend.GetValue() != Settings::RendererBackend::Vulkan) {
+ const bool allowed = !safe_reads && !Settings::IsGPULevelHigh() &&
+ Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan;
+ if (active && !allowed) {
+ Drain();
+ Stop();
+ LOG_INFO(HW_GPU, "GPU thread split: stopped, the accuracy settings need the caches on the front");
+ return;
+ }
+ if (active || !allowed) {
return;
}
system = &system_;
diff --git a/src/video_core/native_qmd.h b/src/video_core/native_qmd.h
index 15c59cb7c6..924267e406 100644
--- a/src/video_core/native_qmd.h
+++ b/src/video_core/native_qmd.h
@@ -32,7 +32,13 @@ public:
pending[0] = {address, argument & 15, ((argument >> 14) & 0x3ff) + 1};
for (std::size_t i = 0; i < count; ++i) {
const auto request = pending[i];
- if (states.size() >= 4096 && !states.contains(request.address)) return "QMD cache limit";
+ if (states.size() >= 4096 && !states.contains(request.address)) {
+ // Finished QMDs with nothing deferred are only history, drop them instead of stalling.
+ std::erase_if(states, [](const auto& entry) {
+ return entry.second.completed && entry.second.deferred == 0;
+ });
+ if (states.size() >= 4096) return "QMD cache limit";
+ }
auto& state = states[request.address];
if (request.action == 3 || request.action == 2) {
if (request.action == 3 || !state.loaded) {
diff --git a/src/video_core/nx2.h b/src/video_core/nx2.h
index 1a3bae93ec..4b0d3d5f62 100644
--- a/src/video_core/nx2.h
+++ b/src/video_core/nx2.h
@@ -96,7 +96,7 @@ inline void Nx2BackSparseWrite(u64 gpu_address, u64 size) {
// Which blocks of each sparse region have been written (got memory), for the texture cache: an
// image over blocks nobody wrote is all zeros and needs no device memory. Its own lock, taken last:
-// the texture cache asks with its lock held, and nx2:gpu updates it holding none of the GPU's.
+// the texture cache asks with its lock held, and nx2:gpu updates it holding only its region mutex.
struct Nx2SparseRegionState {
u64 size;
u64 block_size;
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 17f57983e3..2c64f3375b 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -1089,7 +1089,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
const VkDriverIdKHR driver_id = device.GetDriverID();
const bool needs_shared_mem_clamp =
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
- driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
+ driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MOLTENVK;
const u32 max_shared_memory = device.GetMaxComputeSharedMemorySize();
if (needs_shared_mem_clamp && program.shared_memory_size > max_shared_memory) {
LOG_WARNING(Render_Vulkan,
@@ -1138,7 +1138,11 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
void PipelineCache::SerializeVulkanPipelineCache(const std::filesystem::path& filename,
const vk::PipelineCache& pipeline_cache,
u32 cache_version) try {
- std::ofstream file(filename, std::ios::binary);
+ // Written beside the cache and renamed over it: the emulator stopped mid-write left the cache
+ // empty, and every later boot failed to read it.
+ auto temporary = filename;
+ temporary += ".tmp";
+ std::ofstream file(temporary, std::ios::binary);
file.exceptions(std::ifstream::failbit);
if (!file.is_open()) {
LOG_ERROR(Common_Filesystem, "Failed to open Vulkan driver pipeline cache file {}",
@@ -1156,6 +1160,15 @@ void PipelineCache::SerializeVulkanPipelineCache(const std::filesystem::path& fi
pipeline_cache.Read(&cache_size, cache_data.data());
}
file.write(cache_data.data(), cache_size);
+ file.close();
+ std::error_code error;
+ std::filesystem::rename(temporary, filename, error);
+ if (error) {
+ LOG_ERROR(Common_Filesystem, "Failed to replace Vulkan driver pipeline cache file {}: {}",
+ Common::FS::PathToUTF8String(filename), error.message());
+ std::filesystem::remove(temporary, error);
+ return;
+ }
LOG_INFO(Render_Vulkan, "Vulkan driver pipelines cached at: {}",
Common::FS::PathToUTF8String(filename));
@@ -1190,6 +1203,14 @@ vk::PipelineCache PipelineCache::LoadVulkanPipelineCache(const std::filesystem::
std::array<char, 8> magic_number;
u32 cache_version;
+ // A file cut short (the emulator stopped while writing it) would throw from the reads
+ // below, and macOS's libc++ throws an ios_base::failure the handler doesn't match.
+ if (static_cast<size_t>(end) < magic_number.size() + sizeof(cache_version)) {
+ file.close();
+ LOG_ERROR(Common_Filesystem, "Truncated Vulkan driver pipeline cache file, deleting it");
+ Common::FS::RemoveFile(filename);
+ return create_pipeline_cache(0, nullptr);
+ }
file.read(magic_number.data(), magic_number.size())
.read(reinterpret_cast<char*>(&cache_version), sizeof(cache_version));
if (magic_number != VULKAN_CACHE_MAGIC_NUMBER || cache_version != expected_cache_version) {
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index e192d8d7b1..d5136a2abe 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -2635,6 +2635,20 @@ std::vector<u32> Image::BindLayers(std::span<const u32> layers) {
const VkDeviceSize cap = SparseLayersCap(device);
std::vector<VkSparseImageMemoryBind> image_binds;
std::vector<VkSparseMemoryBind> opaque_binds;
+ const bool tail_was_bound = s.tail_memory != VK_NULL_HANDLE;
+ std::vector<u32> fresh; // Layers given memory by this call
+ // A failed bind leaves the layers as they were, so the next call tries them again.
+ const auto roll_back = [&] {
+ for (const u32 layer : fresh) {
+ s.allocator->FreeSparseMemory(s.layer_memory[layer]);
+ s.layer_memory[layer] = VK_NULL_HANDLE;
+ s.bound_bytes -= s.layer_bytes;
+ }
+ if (s.tail_memory != VK_NULL_HANDLE && !tail_was_bound) {
+ s.allocator->FreeSparseMemory(s.tail_memory);
+ s.tail_memory = VK_NULL_HANDLE;
+ }
+ };
for (const u32 layer : layers) {
if (s.layer_memory[layer] != VK_NULL_HANDLE) {
bound.push_back(layer);
@@ -2662,6 +2676,7 @@ std::vector<u32> Image::BindLayers(std::span<const u32> layers) {
}
s.layer_memory[layer] = allocation;
s.bound_bytes += s.layer_bytes;
+ fresh.push_back(layer);
for (u32 level = 0; level < tail_lod; ++level) {
const u32 width = (std::max)(s.width >> level, 1U);
const u32 height = (std::max)(s.height >> level, 1U);
@@ -2708,6 +2723,12 @@ std::vector<u32> Image::BindLayers(std::span<const u32> layers) {
.memoryOffset = offset,
.flags = 0,
});
+ } else {
+ LOG_WARNING(Render_Vulkan, "Sparse layered image {:#x}: out of device memory for the "
+ "mip tail",
+ gpu_addr);
+ roll_back();
+ return {};
}
}
if (image_binds.empty() && opaque_binds.empty()) {
@@ -2752,6 +2773,7 @@ std::vector<u32> Image::BindLayers(std::span<const u32> layers) {
if (result != VK_SUCCESS) {
LOG_ERROR(Render_Vulkan, "Sparse layered image {:#x}: binding failed ({})", gpu_addr,
static_cast<int>(result));
+ roll_back();
return {};
}
fence.Wait();
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 3de82d2eb4..63abffed0f 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -2999,6 +2999,7 @@ void TextureCache<P>::PrepareImage(ImageId image_id, bool is_modification, bool
if (True(image.flags & ImageFlagBits::SparseLayers)) {
std::vector<u32> layers(static_cast<size_t>(image.info.resources.layers));
std::iota(layers.begin(), layers.end(), 0U);
+ // Layers over the image's memory cap stay unbacked (logged once).
image.BindLayers(layers);
}
}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp
index afbe67716f..08e0db10da 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp
@@ -73,7 +73,7 @@ EmuWindow_SDL3_VK::EmuWindow_SDL3_VK(InputCommon::InputSubsystem* input_subsyste
} else if (SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER,
nullptr) != nullptr) {
window_info.type = Core::Frontend::WindowSystemType::Cocoa;
- window_info.render_surface = SDL_Metal_CreateView(render_window);
+ window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(render_window));
} else if (void* android_window =
SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER,
nullptr);
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 3d9f50d4e5..22abe6a759 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <algorithm>
#include <atomic>
#include <iterator>
#include <fstream>
@@ -56,6 +57,10 @@
#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h"
#include "yuzu_cmd/nx2_sampler.h"
+#if defined(__unix__) || defined(__APPLE__)
+#include <sys/resource.h>
+#endif
+
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
#include <windows.h>
@@ -262,6 +267,12 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
// Raise the CRT's open-file limit like the Qt frontend: loose RomFS folders keep thousands of
// files open through the VFS cache.
_setmaxstdio(8192);
+#elif defined(__unix__) || defined(__APPLE__)
+ // Same for the open file limit (macOS shells default to 256).
+ if (struct rlimit rl; getrlimit(RLIMIT_NOFILE, &rl) == 0) {
+ rl.rlim_cur = std::min<rlim_t>(8192, rl.rlim_max);
+ setrlimit(RLIMIT_NOFILE, &rl);
+ }
#endif
Common::Log::Initialize();
@@ -480,9 +491,17 @@ extern "C" SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
}
extern "C" void SDL_AppQuit(void *appstate, SDL_AppResult result) {
SdlState *state = (SdlState *)appstate;
+ if (!state) {
+ return;
+ }
state->system.DetachDebugger();
- void(state->system.Pause());
- state->system.ShutdownMainProcess();
+ // SDL calls this even when SDL_AppInit never powered the system on (no ROM given, --help or
+ // --version): the kernel is uninitialised then, and Pause()/ShutdownMainProcess() would
+ // dereference it.
+ if (state->system.IsPoweredOn()) {
+ void(state->system.Pause());
+ state->system.ShutdownMainProcess();
+ }
delete state;
}