diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/core.cpp | 3 | ||||
| -rw-r--r-- | src/video_core/dma_pusher.cpp | 5 | ||||
| -rw-r--r-- | src/video_core/gpu_split.cpp | 20 | ||||
| -rw-r--r-- | src/video_core/native_qmd.h | 8 | ||||
| -rw-r--r-- | src/video_core/nx2.h | 2 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 25 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_texture_cache.cpp | 22 | ||||
| -rw-r--r-- | src/video_core/texture_cache/texture_cache.h | 1 | ||||
| -rw-r--r-- | src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp | 2 | ||||
| -rw-r--r-- | src/yuzu_cmd/yuzu.cpp | 23 |
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; } |
