summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/scripts/windows/upload.ps16
-rw-r--r--CMakeLists.txt1
-rw-r--r--CMakeModules/CopyYuzuFFmpegDeps.cmake2
-rw-r--r--CMakeModules/CopyYuzuQt5Deps.cmake2
-rw-r--r--CMakeModules/CopyYuzuSDLDeps.cmake2
-rw-r--r--externals/CMakeLists.txt42
-rw-r--r--externals/ffmpeg/CMakeLists.txt5
-rw-r--r--externals/glad/CMakeLists.txt2
-rw-r--r--externals/libusb/CMakeLists.txt2
-rw-r--r--externals/opus/CMakeLists.txt2
-rw-r--r--src/audio_core/sink/sink_stream.cpp3
-rw-r--r--src/core/core.cpp3
-rw-r--r--src/core/file_sys/romfs.cpp3
-rw-r--r--src/core/file_sys/vfs_concat.cpp161
-rw-r--r--src/core/file_sys/vfs_concat.h28
-rw-r--r--src/video_core/buffer_cache/buffer_cache.cpp4
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h291
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h141
-rw-r--r--src/video_core/host1x/codecs/codec.cpp93
-rw-r--r--src/video_core/host1x/codecs/codec.h8
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp9
-rw-r--r--src/yuzu/CMakeLists.txt6
-rw-r--r--src/yuzu/game_list.cpp4
-rw-r--r--src/yuzu/game_list.h1
-rw-r--r--src/yuzu/main.cpp20
-rw-r--r--src/yuzu/main.h1
27 files changed, 527 insertions, 317 deletions
diff --git a/.ci/scripts/windows/upload.ps1 b/.ci/scripts/windows/upload.ps1
index 21abcd752..492763420 100644
--- a/.ci/scripts/windows/upload.ps1
+++ b/.ci/scripts/windows/upload.ps1
@@ -26,7 +26,11 @@ $env:BUILD_ZIP = $MSVC_BUILD_ZIP
26$env:BUILD_SYMBOLS = $MSVC_BUILD_PDB 26$env:BUILD_SYMBOLS = $MSVC_BUILD_PDB
27$env:BUILD_UPDATE = $MSVC_SEVENZIP 27$env:BUILD_UPDATE = $MSVC_SEVENZIP
28 28
29$BUILD_DIR = ".\build\bin\Release" 29if (Test-Path -Path ".\build\bin\Release") {
30 $BUILD_DIR = ".\build\bin\Release"
31} else {
32 $BUILD_DIR = ".\build\bin\"
33}
30 34
31# Cleanup unneeded data in submodules 35# Cleanup unneeded data in submodules
32git submodule foreach git clean -fxd 36git submodule foreach git clean -fxd
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 561eaafb2..7276ac9dd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -453,6 +453,7 @@ endif()
453# List of all FFmpeg components required 453# List of all FFmpeg components required
454set(FFmpeg_COMPONENTS 454set(FFmpeg_COMPONENTS
455 avcodec 455 avcodec
456 avfilter
456 avutil 457 avutil
457 swscale) 458 swscale)
458 459
diff --git a/CMakeModules/CopyYuzuFFmpegDeps.cmake b/CMakeModules/CopyYuzuFFmpegDeps.cmake
index c6231737e..7aaa073ee 100644
--- a/CMakeModules/CopyYuzuFFmpegDeps.cmake
+++ b/CMakeModules/CopyYuzuFFmpegDeps.cmake
@@ -3,7 +3,7 @@
3 3
4function(copy_yuzu_FFmpeg_deps target_dir) 4function(copy_yuzu_FFmpeg_deps target_dir)
5 include(WindowsCopyFiles) 5 include(WindowsCopyFiles)
6 set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/") 6 set(DLL_DEST "$<TARGET_FILE_DIR:${target_dir}>/")
7 file(READ "${FFmpeg_PATH}/requirements.txt" FFmpeg_REQUIRED_DLLS) 7 file(READ "${FFmpeg_PATH}/requirements.txt" FFmpeg_REQUIRED_DLLS)
8 string(STRIP "${FFmpeg_REQUIRED_DLLS}" FFmpeg_REQUIRED_DLLS) 8 string(STRIP "${FFmpeg_REQUIRED_DLLS}" FFmpeg_REQUIRED_DLLS)
9 windows_copy_files(${target_dir} ${FFmpeg_DLL_DIR} ${DLL_DEST} ${FFmpeg_REQUIRED_DLLS}) 9 windows_copy_files(${target_dir} ${FFmpeg_DLL_DIR} ${DLL_DEST} ${FFmpeg_REQUIRED_DLLS})
diff --git a/CMakeModules/CopyYuzuQt5Deps.cmake b/CMakeModules/CopyYuzuQt5Deps.cmake
index ab56de444..b3a65c347 100644
--- a/CMakeModules/CopyYuzuQt5Deps.cmake
+++ b/CMakeModules/CopyYuzuQt5Deps.cmake
@@ -4,7 +4,7 @@
4function(copy_yuzu_Qt5_deps target_dir) 4function(copy_yuzu_Qt5_deps target_dir)
5 include(WindowsCopyFiles) 5 include(WindowsCopyFiles)
6 if (MSVC) 6 if (MSVC)
7 set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/") 7 set(DLL_DEST "$<TARGET_FILE_DIR:${target_dir}>/")
8 set(Qt5_DLL_DIR "${Qt5_DIR}/../../../bin") 8 set(Qt5_DLL_DIR "${Qt5_DIR}/../../../bin")
9 else() 9 else()
10 set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/") 10 set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/")
diff --git a/CMakeModules/CopyYuzuSDLDeps.cmake b/CMakeModules/CopyYuzuSDLDeps.cmake
index 7ffdd8a1d..464eed5e9 100644
--- a/CMakeModules/CopyYuzuSDLDeps.cmake
+++ b/CMakeModules/CopyYuzuSDLDeps.cmake
@@ -3,6 +3,6 @@
3 3
4function(copy_yuzu_SDL_deps target_dir) 4function(copy_yuzu_SDL_deps target_dir)
5 include(WindowsCopyFiles) 5 include(WindowsCopyFiles)
6 set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/") 6 set(DLL_DEST "$<TARGET_FILE_DIR:${target_dir}>/")
7 windows_copy_files(${target_dir} ${SDL2_DLL_DIR} ${DLL_DEST} SDL2.dll) 7 windows_copy_files(${target_dir} ${SDL2_DLL_DIR} ${DLL_DEST} SDL2.dll)
8endfunction(copy_yuzu_SDL_deps) 8endfunction(copy_yuzu_SDL_deps)
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index e59eeb489..d78d10147 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -8,15 +8,21 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
8# Disable tests in all externals supporting the standard option name 8# Disable tests in all externals supporting the standard option name
9set(BUILD_TESTING OFF) 9set(BUILD_TESTING OFF)
10 10
11# Build only static externals
12set(BUILD_SHARED_LIBS OFF)
13
14# Skip install rules for all externals
15set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON)
16
11# xbyak 17# xbyak
12if ((ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) AND NOT TARGET xbyak::xbyak) 18if ((ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) AND NOT TARGET xbyak::xbyak)
13 add_subdirectory(xbyak EXCLUDE_FROM_ALL) 19 add_subdirectory(xbyak)
14endif() 20endif()
15 21
16# Dynarmic 22# Dynarmic
17if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) AND NOT TARGET dynarmic::dynarmic) 23if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) AND NOT TARGET dynarmic::dynarmic)
18 set(DYNARMIC_IGNORE_ASSERTS ON) 24 set(DYNARMIC_IGNORE_ASSERTS ON)
19 add_subdirectory(dynarmic EXCLUDE_FROM_ALL) 25 add_subdirectory(dynarmic)
20 add_library(dynarmic::dynarmic ALIAS dynarmic) 26 add_library(dynarmic::dynarmic ALIAS dynarmic)
21endif() 27endif()
22 28
@@ -34,7 +40,7 @@ if (NOT TARGET inih::INIReader)
34endif() 40endif()
35 41
36# mbedtls 42# mbedtls
37add_subdirectory(mbedtls EXCLUDE_FROM_ALL) 43add_subdirectory(mbedtls)
38target_include_directories(mbedtls PUBLIC ./mbedtls/include) 44target_include_directories(mbedtls PUBLIC ./mbedtls/include)
39 45
40# MicroProfile 46# MicroProfile
@@ -48,7 +54,7 @@ endif()
48 54
49# libusb 55# libusb
50if (ENABLE_LIBUSB AND NOT TARGET libusb::usb) 56if (ENABLE_LIBUSB AND NOT TARGET libusb::usb)
51 add_subdirectory(libusb EXCLUDE_FROM_ALL) 57 add_subdirectory(libusb)
52endif() 58endif()
53 59
54# SDL2 60# SDL2
@@ -67,18 +73,16 @@ if (YUZU_USE_EXTERNAL_SDL2)
67 73
68 set(HIDAPI ON) 74 set(HIDAPI ON)
69 endif() 75 endif()
70 set(SDL_STATIC ON)
71 set(SDL_SHARED OFF)
72 if (APPLE) 76 if (APPLE)
73 set(SDL_FILE ON) 77 set(SDL_FILE ON)
74 endif() 78 endif()
75 79
76 add_subdirectory(SDL EXCLUDE_FROM_ALL) 80 add_subdirectory(SDL)
77endif() 81endif()
78 82
79# ENet 83# ENet
80if (NOT TARGET enet::enet) 84if (NOT TARGET enet::enet)
81 add_subdirectory(enet EXCLUDE_FROM_ALL) 85 add_subdirectory(enet)
82 target_include_directories(enet INTERFACE ./enet/include) 86 target_include_directories(enet INTERFACE ./enet/include)
83 add_library(enet::enet ALIAS enet) 87 add_library(enet::enet ALIAS enet)
84endif() 88endif()
@@ -86,24 +90,26 @@ endif()
86# Cubeb 90# Cubeb
87if (ENABLE_CUBEB AND NOT TARGET cubeb::cubeb) 91if (ENABLE_CUBEB AND NOT TARGET cubeb::cubeb)
88 set(BUILD_TESTS OFF) 92 set(BUILD_TESTS OFF)
89 add_subdirectory(cubeb EXCLUDE_FROM_ALL) 93 set(BUILD_TOOLS OFF)
94 add_subdirectory(cubeb)
90 add_library(cubeb::cubeb ALIAS cubeb) 95 add_library(cubeb::cubeb ALIAS cubeb)
91endif() 96endif()
92 97
93# DiscordRPC 98# DiscordRPC
94if (USE_DISCORD_PRESENCE AND NOT TARGET DiscordRPC::discord-rpc) 99if (USE_DISCORD_PRESENCE AND NOT TARGET DiscordRPC::discord-rpc)
95 add_subdirectory(discord-rpc EXCLUDE_FROM_ALL) 100 set(BUILD_EXAMPLES OFF)
101 add_subdirectory(discord-rpc)
96 target_include_directories(discord-rpc INTERFACE ./discord-rpc/include) 102 target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
97 add_library(DiscordRPC::discord-rpc ALIAS discord-rpc) 103 add_library(DiscordRPC::discord-rpc ALIAS discord-rpc)
98endif() 104endif()
99 105
100# Sirit 106# Sirit
101add_subdirectory(sirit EXCLUDE_FROM_ALL) 107add_subdirectory(sirit)
102 108
103# httplib 109# httplib
104if (ENABLE_WEB_SERVICE AND NOT TARGET httplib::httplib) 110if (ENABLE_WEB_SERVICE AND NOT TARGET httplib::httplib)
105 set(HTTPLIB_REQUIRE_OPENSSL ON) 111 set(HTTPLIB_REQUIRE_OPENSSL ON)
106 add_subdirectory(cpp-httplib EXCLUDE_FROM_ALL) 112 add_subdirectory(cpp-httplib)
107endif() 113endif()
108 114
109# cpp-jwt 115# cpp-jwt
@@ -111,12 +117,12 @@ if (ENABLE_WEB_SERVICE AND NOT TARGET cpp-jwt::cpp-jwt)
111 set(CPP_JWT_BUILD_EXAMPLES OFF) 117 set(CPP_JWT_BUILD_EXAMPLES OFF)
112 set(CPP_JWT_BUILD_TESTS OFF) 118 set(CPP_JWT_BUILD_TESTS OFF)
113 set(CPP_JWT_USE_VENDORED_NLOHMANN_JSON OFF) 119 set(CPP_JWT_USE_VENDORED_NLOHMANN_JSON OFF)
114 add_subdirectory(cpp-jwt EXCLUDE_FROM_ALL) 120 add_subdirectory(cpp-jwt)
115endif() 121endif()
116 122
117# Opus 123# Opus
118if (NOT TARGET Opus::opus) 124if (NOT TARGET Opus::opus)
119 add_subdirectory(opus EXCLUDE_FROM_ALL) 125 add_subdirectory(opus)
120endif() 126endif()
121 127
122# FFMpeg 128# FFMpeg
@@ -130,16 +136,14 @@ endif()
130 136
131# Vulkan-Headers 137# Vulkan-Headers
132if (YUZU_USE_EXTERNAL_VULKAN_HEADERS) 138if (YUZU_USE_EXTERNAL_VULKAN_HEADERS)
133 add_subdirectory(Vulkan-Headers EXCLUDE_FROM_ALL) 139 add_subdirectory(Vulkan-Headers)
134endif() 140endif()
135 141
136if (NOT TARGET LLVM::Demangle) 142if (NOT TARGET LLVM::Demangle)
137 add_library(demangle STATIC) 143 add_library(demangle demangle/ItaniumDemangle.cpp)
138 target_include_directories(demangle PUBLIC ./demangle) 144 target_include_directories(demangle PUBLIC ./demangle)
139 target_sources(demangle PRIVATE demangle/ItaniumDemangle.cpp)
140 add_library(LLVM::Demangle ALIAS demangle) 145 add_library(LLVM::Demangle ALIAS demangle)
141endif() 146endif()
142 147
143add_library(stb STATIC) 148add_library(stb stb/stb_dxt.cpp)
144target_include_directories(stb PUBLIC ./stb) 149target_include_directories(stb PUBLIC ./stb)
145target_sources(stb PRIVATE stb/stb_dxt.cpp)
diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt
index 0baac8e17..03fad0778 100644
--- a/externals/ffmpeg/CMakeLists.txt
+++ b/externals/ffmpeg/CMakeLists.txt
@@ -131,7 +131,6 @@ if (NOT WIN32)
131 COMMAND 131 COMMAND
132 /bin/bash ${FFmpeg_PREFIX}/configure 132 /bin/bash ${FFmpeg_PREFIX}/configure
133 --disable-avdevice 133 --disable-avdevice
134 --disable-avfilter
135 --disable-avformat 134 --disable-avformat
136 --disable-doc 135 --disable-doc
137 --disable-everything 136 --disable-everything
@@ -143,6 +142,7 @@ if (NOT WIN32)
143 --enable-decoder=h264 142 --enable-decoder=h264
144 --enable-decoder=vp8 143 --enable-decoder=vp8
145 --enable-decoder=vp9 144 --enable-decoder=vp9
145 --enable-filter=yadif
146 --cc="${FFmpeg_CC}" 146 --cc="${FFmpeg_CC}"
147 --cxx="${FFmpeg_CXX}" 147 --cxx="${FFmpeg_CXX}"
148 ${FFmpeg_HWACCEL_FLAGS} 148 ${FFmpeg_HWACCEL_FLAGS}
@@ -199,7 +199,7 @@ if (NOT WIN32)
199 endif() 199 endif()
200else(WIN32) 200else(WIN32)
201 # Use yuzu FFmpeg binaries 201 # Use yuzu FFmpeg binaries
202 set(FFmpeg_EXT_NAME "ffmpeg-4.4") 202 set(FFmpeg_EXT_NAME "ffmpeg-5.1.3")
203 set(FFmpeg_PATH "${CMAKE_BINARY_DIR}/externals/${FFmpeg_EXT_NAME}") 203 set(FFmpeg_PATH "${CMAKE_BINARY_DIR}/externals/${FFmpeg_EXT_NAME}")
204 download_bundled_external("ffmpeg/" ${FFmpeg_EXT_NAME} "") 204 download_bundled_external("ffmpeg/" ${FFmpeg_EXT_NAME} "")
205 set(FFmpeg_FOUND YES) 205 set(FFmpeg_FOUND YES)
@@ -210,6 +210,7 @@ else(WIN32)
210 set(FFmpeg_LIBRARIES 210 set(FFmpeg_LIBRARIES
211 ${FFmpeg_LIBRARY_DIR}/swscale.lib 211 ${FFmpeg_LIBRARY_DIR}/swscale.lib
212 ${FFmpeg_LIBRARY_DIR}/avcodec.lib 212 ${FFmpeg_LIBRARY_DIR}/avcodec.lib
213 ${FFmpeg_LIBRARY_DIR}/avfilter.lib
213 ${FFmpeg_LIBRARY_DIR}/avutil.lib 214 ${FFmpeg_LIBRARY_DIR}/avutil.lib
214 CACHE PATH "Paths to FFmpeg libraries" FORCE) 215 CACHE PATH "Paths to FFmpeg libraries" FORCE)
215 # exported variables 216 # exported variables
diff --git a/externals/glad/CMakeLists.txt b/externals/glad/CMakeLists.txt
index 3dfcac2fd..0c8e285a4 100644
--- a/externals/glad/CMakeLists.txt
+++ b/externals/glad/CMakeLists.txt
@@ -1,7 +1,7 @@
1# SPDX-FileCopyrightText: 2015 Yuri Kunde Schlesner <yuriks@yuriks.net> 1# SPDX-FileCopyrightText: 2015 Yuri Kunde Schlesner <yuriks@yuriks.net>
2# SPDX-License-Identifier: GPL-2.0-or-later 2# SPDX-License-Identifier: GPL-2.0-or-later
3 3
4add_library(glad STATIC 4add_library(glad
5 src/glad.c 5 src/glad.c
6 include/KHR/khrplatform.h 6 include/KHR/khrplatform.h
7 include/glad/glad.h 7 include/glad/glad.h
diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt
index 6317ea807..6757b59da 100644
--- a/externals/libusb/CMakeLists.txt
+++ b/externals/libusb/CMakeLists.txt
@@ -122,7 +122,7 @@ else() # MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
122 add_compile_options(/utf-8) 122 add_compile_options(/utf-8)
123 endif() 123 endif()
124 124
125 add_library(usb STATIC EXCLUDE_FROM_ALL 125 add_library(usb
126 libusb/libusb/core.c 126 libusb/libusb/core.c
127 libusb/libusb/core.c 127 libusb/libusb/core.c
128 libusb/libusb/descriptor.c 128 libusb/libusb/descriptor.c
diff --git a/externals/opus/CMakeLists.txt b/externals/opus/CMakeLists.txt
index 410ff7c08..d9a03423d 100644
--- a/externals/opus/CMakeLists.txt
+++ b/externals/opus/CMakeLists.txt
@@ -23,7 +23,7 @@ else()
23 endif() 23 endif()
24endif() 24endif()
25 25
26add_library(opus STATIC 26add_library(opus
27 # CELT sources 27 # CELT sources
28 opus/celt/bands.c 28 opus/celt/bands.c
29 opus/celt/celt.c 29 opus/celt/celt.c
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
index 9bbb54162..2331aaff9 100644
--- a/src/audio_core/sink/sink_stream.cpp
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -273,6 +273,9 @@ void SinkStream::WaitFreeSpace() {
273 std::unique_lock lk{release_mutex}; 273 std::unique_lock lk{release_mutex};
274 release_cv.wait_for(lk, std::chrono::milliseconds(5), 274 release_cv.wait_for(lk, std::chrono::milliseconds(5),
275 [this]() { return queued_buffers < max_queue_size; }); 275 [this]() { return queued_buffers < max_queue_size; });
276 if (queued_buffers > max_queue_size + 3) {
277 release_cv.wait(lk, [this]() { return queued_buffers < max_queue_size; });
278 }
276} 279}
277 280
278} // namespace AudioCore::Sink 281} // namespace AudioCore::Sink
diff --git a/src/core/core.cpp b/src/core/core.cpp
index b5f62690e..4406ae30e 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -117,8 +117,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
117 return nullptr; 117 return nullptr;
118 } 118 }
119 119
120 return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(std::move(concat), 120 return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
121 dir->GetName());
122 } 121 }
123 122
124 if (Common::FS::IsDir(path)) { 123 if (Common::FS::IsDir(path)) {
diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp
index ddcfe5980..fb5683a6b 100644
--- a/src/core/file_sys/romfs.cpp
+++ b/src/core/file_sys/romfs.cpp
@@ -140,7 +140,8 @@ VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) {
140 return nullptr; 140 return nullptr;
141 141
142 RomFSBuildContext ctx{dir, ext}; 142 RomFSBuildContext ctx{dir, ext};
143 return ConcatenatedVfsFile::MakeConcatenatedFile(0, ctx.Build(), dir->GetName()); 143 auto file_map = ctx.Build();
144 return ConcatenatedVfsFile::MakeConcatenatedFile(0, file_map, dir->GetName());
144} 145}
145 146
146} // namespace FileSys 147} // namespace FileSys
diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp
index d23623aa0..853b893a1 100644
--- a/src/core/file_sys/vfs_concat.cpp
+++ b/src/core/file_sys/vfs_concat.cpp
@@ -10,84 +10,105 @@
10 10
11namespace FileSys { 11namespace FileSys {
12 12
13static bool VerifyConcatenationMapContinuity(const std::multimap<u64, VirtualFile>& map) { 13ConcatenatedVfsFile::ConcatenatedVfsFile(ConcatenationMap&& concatenation_map_, std::string&& name_)
14 const auto last_valid = --map.end(); 14 : concatenation_map(std::move(concatenation_map_)), name(std::move(name_)) {
15 for (auto iter = map.begin(); iter != last_valid;) { 15 DEBUG_ASSERT(this->VerifyContinuity());
16 const auto old = iter++; 16}
17 if (old->first + old->second->GetSize() != iter->first) { 17
18bool ConcatenatedVfsFile::VerifyContinuity() const {
19 u64 last_offset = 0;
20 for (auto& entry : concatenation_map) {
21 if (entry.offset != last_offset) {
18 return false; 22 return false;
19 } 23 }
20 }
21
22 return map.begin()->first == 0;
23}
24 24
25ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name_) 25 last_offset = entry.offset + entry.file->GetSize();
26 : name(std::move(name_)) {
27 std::size_t next_offset = 0;
28 for (const auto& file : files_) {
29 files.emplace(next_offset, file);
30 next_offset += file->GetSize();
31 } 26 }
32}
33 27
34ConcatenatedVfsFile::ConcatenatedVfsFile(std::multimap<u64, VirtualFile> files_, std::string name_) 28 return true;
35 : files(std::move(files_)), name(std::move(name_)) {
36 ASSERT(VerifyConcatenationMapContinuity(files));
37} 29}
38 30
39ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; 31ConcatenatedVfsFile::~ConcatenatedVfsFile() = default;
40 32
41VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::vector<VirtualFile> files, 33VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(const std::vector<VirtualFile>& files,
42 std::string name) { 34 std::string&& name) {
43 if (files.empty()) 35 // Fold trivial cases.
36 if (files.empty()) {
44 return nullptr; 37 return nullptr;
45 if (files.size() == 1) 38 }
46 return files[0]; 39 if (files.size() == 1) {
40 return files.front();
41 }
42
43 // Make the concatenation map from the input.
44 std::vector<ConcatenationEntry> concatenation_map;
45 concatenation_map.reserve(files.size());
46 u64 last_offset = 0;
47
48 for (auto& file : files) {
49 concatenation_map.emplace_back(ConcatenationEntry{
50 .offset = last_offset,
51 .file = file,
52 });
53
54 last_offset += file->GetSize();
55 }
47 56
48 return VirtualFile(new ConcatenatedVfsFile(std::move(files), std::move(name))); 57 return VirtualFile(new ConcatenatedVfsFile(std::move(concatenation_map), std::move(name)));
49} 58}
50 59
51VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte, 60VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
52 std::multimap<u64, VirtualFile> files, 61 const std::multimap<u64, VirtualFile>& files,
53 std::string name) { 62 std::string&& name) {
54 if (files.empty()) 63 // Fold trivial cases.
64 if (files.empty()) {
55 return nullptr; 65 return nullptr;
56 if (files.size() == 1) 66 }
67 if (files.size() == 1) {
57 return files.begin()->second; 68 return files.begin()->second;
69 }
58 70
59 const auto last_valid = --files.end(); 71 // Make the concatenation map from the input.
60 for (auto iter = files.begin(); iter != last_valid;) { 72 std::vector<ConcatenationEntry> concatenation_map;
61 const auto old = iter++; 73
62 if (old->first + old->second->GetSize() != iter->first) { 74 concatenation_map.reserve(files.size());
63 files.emplace(old->first + old->second->GetSize(), 75 u64 last_offset = 0;
64 std::make_shared<StaticVfsFile>(filler_byte, iter->first - old->first - 76
65 old->second->GetSize())); 77 // Iteration of a multimap is ordered, so offset will be strictly non-decreasing.
78 for (auto& [offset, file] : files) {
79 if (offset > last_offset) {
80 concatenation_map.emplace_back(ConcatenationEntry{
81 .offset = last_offset,
82 .file = std::make_shared<StaticVfsFile>(filler_byte, offset - last_offset),
83 });
66 } 84 }
67 }
68 85
69 // Ensure the map starts at offset 0 (start of file), otherwise pad to fill. 86 concatenation_map.emplace_back(ConcatenationEntry{
70 if (files.begin()->first != 0) 87 .offset = offset,
71 files.emplace(0, std::make_shared<StaticVfsFile>(filler_byte, files.begin()->first)); 88 .file = file,
89 });
90
91 last_offset = offset + file->GetSize();
92 }
72 93
73 return VirtualFile(new ConcatenatedVfsFile(std::move(files), std::move(name))); 94 return VirtualFile(new ConcatenatedVfsFile(std::move(concatenation_map), std::move(name)));
74} 95}
75 96
76std::string ConcatenatedVfsFile::GetName() const { 97std::string ConcatenatedVfsFile::GetName() const {
77 if (files.empty()) { 98 if (concatenation_map.empty()) {
78 return ""; 99 return "";
79 } 100 }
80 if (!name.empty()) { 101 if (!name.empty()) {
81 return name; 102 return name;
82 } 103 }
83 return files.begin()->second->GetName(); 104 return concatenation_map.front().file->GetName();
84} 105}
85 106
86std::size_t ConcatenatedVfsFile::GetSize() const { 107std::size_t ConcatenatedVfsFile::GetSize() const {
87 if (files.empty()) { 108 if (concatenation_map.empty()) {
88 return 0; 109 return 0;
89 } 110 }
90 return files.rbegin()->first + files.rbegin()->second->GetSize(); 111 return concatenation_map.back().offset + concatenation_map.back().file->GetSize();
91} 112}
92 113
93bool ConcatenatedVfsFile::Resize(std::size_t new_size) { 114bool ConcatenatedVfsFile::Resize(std::size_t new_size) {
@@ -95,10 +116,10 @@ bool ConcatenatedVfsFile::Resize(std::size_t new_size) {
95} 116}
96 117
97VirtualDir ConcatenatedVfsFile::GetContainingDirectory() const { 118VirtualDir ConcatenatedVfsFile::GetContainingDirectory() const {
98 if (files.empty()) { 119 if (concatenation_map.empty()) {
99 return nullptr; 120 return nullptr;
100 } 121 }
101 return files.begin()->second->GetContainingDirectory(); 122 return concatenation_map.front().file->GetContainingDirectory();
102} 123}
103 124
104bool ConcatenatedVfsFile::IsWritable() const { 125bool ConcatenatedVfsFile::IsWritable() const {
@@ -110,25 +131,45 @@ bool ConcatenatedVfsFile::IsReadable() const {
110} 131}
111 132
112std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { 133std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
113 auto entry = --files.end(); 134 const ConcatenationEntry key{
114 for (auto iter = files.begin(); iter != files.end(); ++iter) { 135 .offset = offset,
115 if (iter->first > offset) { 136 .file = nullptr,
116 entry = --iter; 137 };
138
139 // Read nothing if the map is empty.
140 if (concatenation_map.empty()) {
141 return 0;
142 }
143
144 // Binary search to find the iterator to the first position we can check.
145 // It must exist, since we are not empty and are comparing unsigned integers.
146 auto it = std::prev(std::upper_bound(concatenation_map.begin(), concatenation_map.end(), key));
147 u64 cur_length = length;
148 u64 cur_offset = offset;
149
150 while (cur_length > 0 && it != concatenation_map.end()) {
151 // Check if we can read the file at this position.
152 const auto& file = it->file;
153 const u64 file_offset = it->offset;
154 const u64 file_size = file->GetSize();
155
156 if (cur_offset >= file_offset + file_size) {
157 // Entirely out of bounds read.
117 break; 158 break;
118 } 159 }
119 }
120 160
121 if (entry->first + entry->second->GetSize() <= offset) 161 // Read the file at this position.
122 return 0; 162 const u64 intended_read_size = std::min<u64>(cur_length, file_size);
163 const u64 actual_read_size =
164 file->Read(data + (cur_offset - offset), intended_read_size, cur_offset - file_offset);
123 165
124 const auto read_in = 166 // Update tracking.
125 std::min<u64>(entry->first + entry->second->GetSize() - offset, entry->second->GetSize()); 167 cur_offset += actual_read_size;
126 if (length > read_in) { 168 cur_length -= actual_read_size;
127 return entry->second->Read(data, read_in, offset - entry->first) + 169 it++;
128 Read(data + read_in, length - read_in, offset + read_in);
129 } 170 }
130 171
131 return entry->second->Read(data, std::min<u64>(read_in, length), offset - entry->first); 172 return cur_offset - offset;
132} 173}
133 174
134std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { 175std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h
index 9be0261b6..6b329d545 100644
--- a/src/core/file_sys/vfs_concat.h
+++ b/src/core/file_sys/vfs_concat.h
@@ -3,6 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <compare>
6#include <map> 7#include <map>
7#include <memory> 8#include <memory>
8#include "core/file_sys/vfs.h" 9#include "core/file_sys/vfs.h"
@@ -12,19 +13,33 @@ namespace FileSys {
12// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently 13// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
13// read-only. 14// read-only.
14class ConcatenatedVfsFile : public VfsFile { 15class ConcatenatedVfsFile : public VfsFile {
15 explicit ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name_); 16private:
16 explicit ConcatenatedVfsFile(std::multimap<u64, VirtualFile> files, std::string name_); 17 struct ConcatenationEntry {
18 u64 offset;
19 VirtualFile file;
20
21 auto operator<=>(const ConcatenationEntry& other) const {
22 return this->offset <=> other.offset;
23 }
24 };
25 using ConcatenationMap = std::vector<ConcatenationEntry>;
26
27 explicit ConcatenatedVfsFile(std::vector<ConcatenationEntry>&& concatenation_map,
28 std::string&& name);
29 bool VerifyContinuity() const;
17 30
18public: 31public:
19 ~ConcatenatedVfsFile() override; 32 ~ConcatenatedVfsFile() override;
20 33
21 /// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases. 34 /// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
22 static VirtualFile MakeConcatenatedFile(std::vector<VirtualFile> files, std::string name); 35 static VirtualFile MakeConcatenatedFile(const std::vector<VirtualFile>& files,
36 std::string&& name);
23 37
24 /// Convenience function that turns a map of offsets to files into a concatenated file, filling 38 /// Convenience function that turns a map of offsets to files into a concatenated file, filling
25 /// gaps with a given filler byte. 39 /// gaps with a given filler byte.
26 static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::multimap<u64, VirtualFile> files, 40 static VirtualFile MakeConcatenatedFile(u8 filler_byte,
27 std::string name); 41 const std::multimap<u64, VirtualFile>& files,
42 std::string&& name);
28 43
29 std::string GetName() const override; 44 std::string GetName() const override;
30 std::size_t GetSize() const override; 45 std::size_t GetSize() const override;
@@ -37,8 +52,7 @@ public:
37 bool Rename(std::string_view new_name) override; 52 bool Rename(std::string_view new_name) override;
38 53
39private: 54private:
40 // Maps starting offset to file -- more efficient. 55 ConcatenationMap concatenation_map;
41 std::multimap<u64, VirtualFile> files;
42 std::string name; 56 std::string name;
43}; 57};
44 58
diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp
index 40db243d2..4b4f7061b 100644
--- a/src/video_core/buffer_cache/buffer_cache.cpp
+++ b/src/video_core/buffer_cache/buffer_cache.cpp
@@ -2,6 +2,8 @@
2// SPDX-License-Identifier: GPL-3.0-or-later 2// SPDX-License-Identifier: GPL-3.0-or-later
3 3
4#include "common/microprofile.h" 4#include "common/microprofile.h"
5#include "video_core/buffer_cache/buffer_cache_base.h"
6#include "video_core/control/channel_state_cache.inc"
5 7
6namespace VideoCommon { 8namespace VideoCommon {
7 9
@@ -9,4 +11,6 @@ MICROPROFILE_DEFINE(GPU_PrepareBuffers, "GPU", "Prepare buffers", MP_RGB(224, 12
9MICROPROFILE_DEFINE(GPU_BindUploadBuffers, "GPU", "Bind and upload buffers", MP_RGB(224, 128, 128)); 11MICROPROFILE_DEFINE(GPU_BindUploadBuffers, "GPU", "Bind and upload buffers", MP_RGB(224, 128, 128));
10MICROPROFILE_DEFINE(GPU_DownloadMemory, "GPU", "Download buffers", MP_RGB(224, 128, 128)); 12MICROPROFILE_DEFINE(GPU_DownloadMemory, "GPU", "Download buffers", MP_RGB(224, 128, 128));
11 13
14template class VideoCommon::ChannelSetupCaches<VideoCommon::BufferCacheChannelInfo>;
15
12} // namespace VideoCommon 16} // namespace VideoCommon
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 65494097b..c336be707 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -64,17 +64,22 @@ void BufferCache<P>::RunGarbageCollector() {
64template <class P> 64template <class P>
65void BufferCache<P>::TickFrame() { 65void BufferCache<P>::TickFrame() {
66 // Calculate hits and shots and move hit bits to the right 66 // Calculate hits and shots and move hit bits to the right
67 const u32 hits = std::reduce(uniform_cache_hits.begin(), uniform_cache_hits.end()); 67
68 const u32 shots = std::reduce(uniform_cache_shots.begin(), uniform_cache_shots.end()); 68 const u32 hits = std::reduce(channel_state->uniform_cache_hits.begin(),
69 std::copy_n(uniform_cache_hits.begin(), uniform_cache_hits.size() - 1, 69 channel_state->uniform_cache_hits.end());
70 uniform_cache_hits.begin() + 1); 70 const u32 shots = std::reduce(channel_state->uniform_cache_shots.begin(),
71 std::copy_n(uniform_cache_shots.begin(), uniform_cache_shots.size() - 1, 71 channel_state->uniform_cache_shots.end());
72 uniform_cache_shots.begin() + 1); 72 std::copy_n(channel_state->uniform_cache_hits.begin(),
73 uniform_cache_hits[0] = 0; 73 channel_state->uniform_cache_hits.size() - 1,
74 uniform_cache_shots[0] = 0; 74 channel_state->uniform_cache_hits.begin() + 1);
75 std::copy_n(channel_state->uniform_cache_shots.begin(),
76 channel_state->uniform_cache_shots.size() - 1,
77 channel_state->uniform_cache_shots.begin() + 1);
78 channel_state->uniform_cache_hits[0] = 0;
79 channel_state->uniform_cache_shots[0] = 0;
75 80
76 const bool skip_preferred = hits * 256 < shots * 251; 81 const bool skip_preferred = hits * 256 < shots * 251;
77 uniform_buffer_skip_cache_size = skip_preferred ? DEFAULT_SKIP_CACHE_SIZE : 0; 82 channel_state->uniform_buffer_skip_cache_size = skip_preferred ? DEFAULT_SKIP_CACHE_SIZE : 0;
78 83
79 // If we can obtain the memory info, use it instead of the estimate. 84 // If we can obtain the memory info, use it instead of the estimate.
80 if (runtime.CanReportMemoryUsage()) { 85 if (runtime.CanReportMemoryUsage()) {
@@ -164,10 +169,10 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
164 BufferId buffer_a; 169 BufferId buffer_a;
165 BufferId buffer_b; 170 BufferId buffer_b;
166 do { 171 do {
167 has_deleted_buffers = false; 172 channel_state->has_deleted_buffers = false;
168 buffer_a = FindBuffer(*cpu_src_address, static_cast<u32>(amount)); 173 buffer_a = FindBuffer(*cpu_src_address, static_cast<u32>(amount));
169 buffer_b = FindBuffer(*cpu_dest_address, static_cast<u32>(amount)); 174 buffer_b = FindBuffer(*cpu_dest_address, static_cast<u32>(amount));
170 } while (has_deleted_buffers); 175 } while (channel_state->has_deleted_buffers);
171 auto& src_buffer = slot_buffers[buffer_a]; 176 auto& src_buffer = slot_buffers[buffer_a];
172 auto& dest_buffer = slot_buffers[buffer_b]; 177 auto& dest_buffer = slot_buffers[buffer_b];
173 SynchronizeBuffer(src_buffer, *cpu_src_address, static_cast<u32>(amount)); 178 SynchronizeBuffer(src_buffer, *cpu_src_address, static_cast<u32>(amount));
@@ -272,30 +277,30 @@ void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr
272 .size = size, 277 .size = size,
273 .buffer_id = BufferId{}, 278 .buffer_id = BufferId{},
274 }; 279 };
275 uniform_buffers[stage][index] = binding; 280 channel_state->uniform_buffers[stage][index] = binding;
276} 281}
277 282
278template <class P> 283template <class P>
279void BufferCache<P>::DisableGraphicsUniformBuffer(size_t stage, u32 index) { 284void BufferCache<P>::DisableGraphicsUniformBuffer(size_t stage, u32 index) {
280 uniform_buffers[stage][index] = NULL_BINDING; 285 channel_state->uniform_buffers[stage][index] = NULL_BINDING;
281} 286}
282 287
283template <class P> 288template <class P>
284void BufferCache<P>::UpdateGraphicsBuffers(bool is_indexed) { 289void BufferCache<P>::UpdateGraphicsBuffers(bool is_indexed) {
285 MICROPROFILE_SCOPE(GPU_PrepareBuffers); 290 MICROPROFILE_SCOPE(GPU_PrepareBuffers);
286 do { 291 do {
287 has_deleted_buffers = false; 292 channel_state->has_deleted_buffers = false;
288 DoUpdateGraphicsBuffers(is_indexed); 293 DoUpdateGraphicsBuffers(is_indexed);
289 } while (has_deleted_buffers); 294 } while (channel_state->has_deleted_buffers);
290} 295}
291 296
292template <class P> 297template <class P>
293void BufferCache<P>::UpdateComputeBuffers() { 298void BufferCache<P>::UpdateComputeBuffers() {
294 MICROPROFILE_SCOPE(GPU_PrepareBuffers); 299 MICROPROFILE_SCOPE(GPU_PrepareBuffers);
295 do { 300 do {
296 has_deleted_buffers = false; 301 channel_state->has_deleted_buffers = false;
297 DoUpdateComputeBuffers(); 302 DoUpdateComputeBuffers();
298 } while (has_deleted_buffers); 303 } while (channel_state->has_deleted_buffers);
299} 304}
300 305
301template <class P> 306template <class P>
@@ -338,98 +343,102 @@ template <class P>
338void BufferCache<P>::SetUniformBuffersState(const std::array<u32, NUM_STAGES>& mask, 343void BufferCache<P>::SetUniformBuffersState(const std::array<u32, NUM_STAGES>& mask,
339 const UniformBufferSizes* sizes) { 344 const UniformBufferSizes* sizes) {
340 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { 345 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
341 if (enabled_uniform_buffer_masks != mask) { 346 if (channel_state->enabled_uniform_buffer_masks != mask) {
342 if constexpr (IS_OPENGL) { 347 if constexpr (IS_OPENGL) {
343 fast_bound_uniform_buffers.fill(0); 348 channel_state->fast_bound_uniform_buffers.fill(0);
344 } 349 }
345 dirty_uniform_buffers.fill(~u32{0}); 350 channel_state->dirty_uniform_buffers.fill(~u32{0});
346 uniform_buffer_binding_sizes.fill({}); 351 channel_state->uniform_buffer_binding_sizes.fill({});
347 } 352 }
348 } 353 }
349 enabled_uniform_buffer_masks = mask; 354 channel_state->enabled_uniform_buffer_masks = mask;
350 uniform_buffer_sizes = sizes; 355 channel_state->uniform_buffer_sizes = sizes;
351} 356}
352 357
353template <class P> 358template <class P>
354void BufferCache<P>::SetComputeUniformBufferState(u32 mask, 359void BufferCache<P>::SetComputeUniformBufferState(u32 mask,
355 const ComputeUniformBufferSizes* sizes) { 360 const ComputeUniformBufferSizes* sizes) {
356 enabled_compute_uniform_buffer_mask = mask; 361 channel_state->enabled_compute_uniform_buffer_mask = mask;
357 compute_uniform_buffer_sizes = sizes; 362 channel_state->compute_uniform_buffer_sizes = sizes;
358} 363}
359 364
360template <class P> 365template <class P>
361void BufferCache<P>::UnbindGraphicsStorageBuffers(size_t stage) { 366void BufferCache<P>::UnbindGraphicsStorageBuffers(size_t stage) {
362 enabled_storage_buffers[stage] = 0; 367 channel_state->enabled_storage_buffers[stage] = 0;
363 written_storage_buffers[stage] = 0; 368 channel_state->written_storage_buffers[stage] = 0;
364} 369}
365 370
366template <class P> 371template <class P>
367void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, 372void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index,
368 u32 cbuf_offset, bool is_written) { 373 u32 cbuf_offset, bool is_written) {
369 enabled_storage_buffers[stage] |= 1U << ssbo_index; 374 channel_state->enabled_storage_buffers[stage] |= 1U << ssbo_index;
370 written_storage_buffers[stage] |= (is_written ? 1U : 0U) << ssbo_index; 375 channel_state->written_storage_buffers[stage] |= (is_written ? 1U : 0U) << ssbo_index;
371 376
372 const auto& cbufs = maxwell3d->state.shader_stages[stage]; 377 const auto& cbufs = maxwell3d->state.shader_stages[stage];
373 const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset; 378 const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
374 storage_buffers[stage][ssbo_index] = StorageBufferBinding(ssbo_addr, cbuf_index, is_written); 379 channel_state->storage_buffers[stage][ssbo_index] =
380 StorageBufferBinding(ssbo_addr, cbuf_index, is_written);
375} 381}
376 382
377template <class P> 383template <class P>
378void BufferCache<P>::UnbindGraphicsTextureBuffers(size_t stage) { 384void BufferCache<P>::UnbindGraphicsTextureBuffers(size_t stage) {
379 enabled_texture_buffers[stage] = 0; 385 channel_state->enabled_texture_buffers[stage] = 0;
380 written_texture_buffers[stage] = 0; 386 channel_state->written_texture_buffers[stage] = 0;
381 image_texture_buffers[stage] = 0; 387 channel_state->image_texture_buffers[stage] = 0;
382} 388}
383 389
384template <class P> 390template <class P>
385void BufferCache<P>::BindGraphicsTextureBuffer(size_t stage, size_t tbo_index, GPUVAddr gpu_addr, 391void BufferCache<P>::BindGraphicsTextureBuffer(size_t stage, size_t tbo_index, GPUVAddr gpu_addr,
386 u32 size, PixelFormat format, bool is_written, 392 u32 size, PixelFormat format, bool is_written,
387 bool is_image) { 393 bool is_image) {
388 enabled_texture_buffers[stage] |= 1U << tbo_index; 394 channel_state->enabled_texture_buffers[stage] |= 1U << tbo_index;
389 written_texture_buffers[stage] |= (is_written ? 1U : 0U) << tbo_index; 395 channel_state->written_texture_buffers[stage] |= (is_written ? 1U : 0U) << tbo_index;
390 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 396 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
391 image_texture_buffers[stage] |= (is_image ? 1U : 0U) << tbo_index; 397 channel_state->image_texture_buffers[stage] |= (is_image ? 1U : 0U) << tbo_index;
392 } 398 }
393 texture_buffers[stage][tbo_index] = GetTextureBufferBinding(gpu_addr, size, format); 399 channel_state->texture_buffers[stage][tbo_index] =
400 GetTextureBufferBinding(gpu_addr, size, format);
394} 401}
395 402
396template <class P> 403template <class P>
397void BufferCache<P>::UnbindComputeStorageBuffers() { 404void BufferCache<P>::UnbindComputeStorageBuffers() {
398 enabled_compute_storage_buffers = 0; 405 channel_state->enabled_compute_storage_buffers = 0;
399 written_compute_storage_buffers = 0; 406 channel_state->written_compute_storage_buffers = 0;
400 image_compute_texture_buffers = 0; 407 channel_state->image_compute_texture_buffers = 0;
401} 408}
402 409
403template <class P> 410template <class P>
404void BufferCache<P>::BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset, 411void BufferCache<P>::BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
405 bool is_written) { 412 bool is_written) {
406 enabled_compute_storage_buffers |= 1U << ssbo_index; 413 channel_state->enabled_compute_storage_buffers |= 1U << ssbo_index;
407 written_compute_storage_buffers |= (is_written ? 1U : 0U) << ssbo_index; 414 channel_state->written_compute_storage_buffers |= (is_written ? 1U : 0U) << ssbo_index;
408 415
409 const auto& launch_desc = kepler_compute->launch_description; 416 const auto& launch_desc = kepler_compute->launch_description;
410 ASSERT(((launch_desc.const_buffer_enable_mask >> cbuf_index) & 1) != 0); 417 ASSERT(((launch_desc.const_buffer_enable_mask >> cbuf_index) & 1) != 0);
411 418
412 const auto& cbufs = launch_desc.const_buffer_config; 419 const auto& cbufs = launch_desc.const_buffer_config;
413 const GPUVAddr ssbo_addr = cbufs[cbuf_index].Address() + cbuf_offset; 420 const GPUVAddr ssbo_addr = cbufs[cbuf_index].Address() + cbuf_offset;
414 compute_storage_buffers[ssbo_index] = StorageBufferBinding(ssbo_addr, cbuf_index, is_written); 421 channel_state->compute_storage_buffers[ssbo_index] =
422 StorageBufferBinding(ssbo_addr, cbuf_index, is_written);
415} 423}
416 424
417template <class P> 425template <class P>
418void BufferCache<P>::UnbindComputeTextureBuffers() { 426void BufferCache<P>::UnbindComputeTextureBuffers() {
419 enabled_compute_texture_buffers = 0; 427 channel_state->enabled_compute_texture_buffers = 0;
420 written_compute_texture_buffers = 0; 428 channel_state->written_compute_texture_buffers = 0;
421 image_compute_texture_buffers = 0; 429 channel_state->image_compute_texture_buffers = 0;
422} 430}
423 431
424template <class P> 432template <class P>
425void BufferCache<P>::BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_addr, u32 size, 433void BufferCache<P>::BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_addr, u32 size,
426 PixelFormat format, bool is_written, bool is_image) { 434 PixelFormat format, bool is_written, bool is_image) {
427 enabled_compute_texture_buffers |= 1U << tbo_index; 435 channel_state->enabled_compute_texture_buffers |= 1U << tbo_index;
428 written_compute_texture_buffers |= (is_written ? 1U : 0U) << tbo_index; 436 channel_state->written_compute_texture_buffers |= (is_written ? 1U : 0U) << tbo_index;
429 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 437 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
430 image_compute_texture_buffers |= (is_image ? 1U : 0U) << tbo_index; 438 channel_state->image_compute_texture_buffers |= (is_image ? 1U : 0U) << tbo_index;
431 } 439 }
432 compute_texture_buffers[tbo_index] = GetTextureBufferBinding(gpu_addr, size, format); 440 channel_state->compute_texture_buffers[tbo_index] =
441 GetTextureBufferBinding(gpu_addr, size, format);
433} 442}
434 443
435template <class P> 444template <class P>
@@ -672,10 +681,10 @@ bool BufferCache<P>::IsRegionCpuModified(VAddr addr, size_t size) {
672 681
673template <class P> 682template <class P>
674void BufferCache<P>::BindHostIndexBuffer() { 683void BufferCache<P>::BindHostIndexBuffer() {
675 Buffer& buffer = slot_buffers[index_buffer.buffer_id]; 684 Buffer& buffer = slot_buffers[channel_state->index_buffer.buffer_id];
676 TouchBuffer(buffer, index_buffer.buffer_id); 685 TouchBuffer(buffer, channel_state->index_buffer.buffer_id);
677 const u32 offset = buffer.Offset(index_buffer.cpu_addr); 686 const u32 offset = buffer.Offset(channel_state->index_buffer.cpu_addr);
678 const u32 size = index_buffer.size; 687 const u32 size = channel_state->index_buffer.size;
679 const auto& draw_state = maxwell3d->draw_manager->GetDrawState(); 688 const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
680 if (!draw_state.inline_index_draw_indexes.empty()) [[unlikely]] { 689 if (!draw_state.inline_index_draw_indexes.empty()) [[unlikely]] {
681 if constexpr (USE_MEMORY_MAPS) { 690 if constexpr (USE_MEMORY_MAPS) {
@@ -689,7 +698,7 @@ void BufferCache<P>::BindHostIndexBuffer() {
689 buffer.ImmediateUpload(0, draw_state.inline_index_draw_indexes); 698 buffer.ImmediateUpload(0, draw_state.inline_index_draw_indexes);
690 } 699 }
691 } else { 700 } else {
692 SynchronizeBuffer(buffer, index_buffer.cpu_addr, size); 701 SynchronizeBuffer(buffer, channel_state->index_buffer.cpu_addr, size);
693 } 702 }
694 if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) { 703 if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
695 const u32 new_offset = 704 const u32 new_offset =
@@ -706,7 +715,7 @@ template <class P>
706void BufferCache<P>::BindHostVertexBuffers() { 715void BufferCache<P>::BindHostVertexBuffers() {
707 auto& flags = maxwell3d->dirty.flags; 716 auto& flags = maxwell3d->dirty.flags;
708 for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { 717 for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
709 const Binding& binding = vertex_buffers[index]; 718 const Binding& binding = channel_state->vertex_buffers[index];
710 Buffer& buffer = slot_buffers[binding.buffer_id]; 719 Buffer& buffer = slot_buffers[binding.buffer_id];
711 TouchBuffer(buffer, binding.buffer_id); 720 TouchBuffer(buffer, binding.buffer_id);
712 SynchronizeBuffer(buffer, binding.cpu_addr, binding.size); 721 SynchronizeBuffer(buffer, binding.cpu_addr, binding.size);
@@ -729,19 +738,19 @@ void BufferCache<P>::BindHostDrawIndirectBuffers() {
729 SynchronizeBuffer(buffer, binding.cpu_addr, binding.size); 738 SynchronizeBuffer(buffer, binding.cpu_addr, binding.size);
730 }; 739 };
731 if (current_draw_indirect->include_count) { 740 if (current_draw_indirect->include_count) {
732 bind_buffer(count_buffer_binding); 741 bind_buffer(channel_state->count_buffer_binding);
733 } 742 }
734 bind_buffer(indirect_buffer_binding); 743 bind_buffer(channel_state->indirect_buffer_binding);
735} 744}
736 745
737template <class P> 746template <class P>
738void BufferCache<P>::BindHostGraphicsUniformBuffers(size_t stage) { 747void BufferCache<P>::BindHostGraphicsUniformBuffers(size_t stage) {
739 u32 dirty = ~0U; 748 u32 dirty = ~0U;
740 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { 749 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
741 dirty = std::exchange(dirty_uniform_buffers[stage], 0); 750 dirty = std::exchange(channel_state->dirty_uniform_buffers[stage], 0);
742 } 751 }
743 u32 binding_index = 0; 752 u32 binding_index = 0;
744 ForEachEnabledBit(enabled_uniform_buffer_masks[stage], [&](u32 index) { 753 ForEachEnabledBit(channel_state->enabled_uniform_buffer_masks[stage], [&](u32 index) {
745 const bool needs_bind = ((dirty >> index) & 1) != 0; 754 const bool needs_bind = ((dirty >> index) & 1) != 0;
746 BindHostGraphicsUniformBuffer(stage, index, binding_index, needs_bind); 755 BindHostGraphicsUniformBuffer(stage, index, binding_index, needs_bind);
747 if constexpr (NEEDS_BIND_UNIFORM_INDEX) { 756 if constexpr (NEEDS_BIND_UNIFORM_INDEX) {
@@ -753,13 +762,13 @@ void BufferCache<P>::BindHostGraphicsUniformBuffers(size_t stage) {
753template <class P> 762template <class P>
754void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index, 763void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index,
755 bool needs_bind) { 764 bool needs_bind) {
756 const Binding& binding = uniform_buffers[stage][index]; 765 const Binding& binding = channel_state->uniform_buffers[stage][index];
757 const VAddr cpu_addr = binding.cpu_addr; 766 const VAddr cpu_addr = binding.cpu_addr;
758 const u32 size = std::min(binding.size, (*uniform_buffer_sizes)[stage][index]); 767 const u32 size = std::min(binding.size, (*channel_state->uniform_buffer_sizes)[stage][index]);
759 Buffer& buffer = slot_buffers[binding.buffer_id]; 768 Buffer& buffer = slot_buffers[binding.buffer_id];
760 TouchBuffer(buffer, binding.buffer_id); 769 TouchBuffer(buffer, binding.buffer_id);
761 const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID && 770 const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID &&
762 size <= uniform_buffer_skip_cache_size && 771 size <= channel_state->uniform_buffer_skip_cache_size &&
763 !memory_tracker.IsRegionGpuModified(cpu_addr, size); 772 !memory_tracker.IsRegionGpuModified(cpu_addr, size);
764 if (use_fast_buffer) { 773 if (use_fast_buffer) {
765 if constexpr (IS_OPENGL) { 774 if constexpr (IS_OPENGL) {
@@ -767,11 +776,11 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
767 // Fast path for Nvidia 776 // Fast path for Nvidia
768 const bool should_fast_bind = 777 const bool should_fast_bind =
769 !HasFastUniformBufferBound(stage, binding_index) || 778 !HasFastUniformBufferBound(stage, binding_index) ||
770 uniform_buffer_binding_sizes[stage][binding_index] != size; 779 channel_state->uniform_buffer_binding_sizes[stage][binding_index] != size;
771 if (should_fast_bind) { 780 if (should_fast_bind) {
772 // We only have to bind when the currently bound buffer is not the fast version 781 // We only have to bind when the currently bound buffer is not the fast version
773 fast_bound_uniform_buffers[stage] |= 1U << binding_index; 782 channel_state->fast_bound_uniform_buffers[stage] |= 1U << binding_index;
774 uniform_buffer_binding_sizes[stage][binding_index] = size; 783 channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size;
775 runtime.BindFastUniformBuffer(stage, binding_index, size); 784 runtime.BindFastUniformBuffer(stage, binding_index, size);
776 } 785 }
777 const auto span = ImmediateBufferWithData(cpu_addr, size); 786 const auto span = ImmediateBufferWithData(cpu_addr, size);
@@ -780,8 +789,8 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
780 } 789 }
781 } 790 }
782 if constexpr (IS_OPENGL) { 791 if constexpr (IS_OPENGL) {
783 fast_bound_uniform_buffers[stage] |= 1U << binding_index; 792 channel_state->fast_bound_uniform_buffers[stage] |= 1U << binding_index;
784 uniform_buffer_binding_sizes[stage][binding_index] = size; 793 channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size;
785 } 794 }
786 // Stream buffer path to avoid stalling on non-Nvidia drivers or Vulkan 795 // Stream buffer path to avoid stalling on non-Nvidia drivers or Vulkan
787 const std::span<u8> span = runtime.BindMappedUniformBuffer(stage, binding_index, size); 796 const std::span<u8> span = runtime.BindMappedUniformBuffer(stage, binding_index, size);
@@ -791,15 +800,15 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
791 // Classic cached path 800 // Classic cached path
792 const bool sync_cached = SynchronizeBuffer(buffer, cpu_addr, size); 801 const bool sync_cached = SynchronizeBuffer(buffer, cpu_addr, size);
793 if (sync_cached) { 802 if (sync_cached) {
794 ++uniform_cache_hits[0]; 803 ++channel_state->uniform_cache_hits[0];
795 } 804 }
796 ++uniform_cache_shots[0]; 805 ++channel_state->uniform_cache_shots[0];
797 806
798 // Skip binding if it's not needed and if the bound buffer is not the fast version 807 // Skip binding if it's not needed and if the bound buffer is not the fast version
799 // This exists to avoid instances where the fast buffer is bound and a GPU write happens 808 // This exists to avoid instances where the fast buffer is bound and a GPU write happens
800 needs_bind |= HasFastUniformBufferBound(stage, binding_index); 809 needs_bind |= HasFastUniformBufferBound(stage, binding_index);
801 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { 810 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
802 needs_bind |= uniform_buffer_binding_sizes[stage][binding_index] != size; 811 needs_bind |= channel_state->uniform_buffer_binding_sizes[stage][binding_index] != size;
803 } 812 }
804 if (!needs_bind) { 813 if (!needs_bind) {
805 return; 814 return;
@@ -807,14 +816,14 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
807 const u32 offset = buffer.Offset(cpu_addr); 816 const u32 offset = buffer.Offset(cpu_addr);
808 if constexpr (IS_OPENGL) { 817 if constexpr (IS_OPENGL) {
809 // Fast buffer will be unbound 818 // Fast buffer will be unbound
810 fast_bound_uniform_buffers[stage] &= ~(1U << binding_index); 819 channel_state->fast_bound_uniform_buffers[stage] &= ~(1U << binding_index);
811 820
812 // Mark the index as dirty if offset doesn't match 821 // Mark the index as dirty if offset doesn't match
813 const bool is_copy_bind = offset != 0 && !runtime.SupportsNonZeroUniformOffset(); 822 const bool is_copy_bind = offset != 0 && !runtime.SupportsNonZeroUniformOffset();
814 dirty_uniform_buffers[stage] |= (is_copy_bind ? 1U : 0U) << index; 823 channel_state->dirty_uniform_buffers[stage] |= (is_copy_bind ? 1U : 0U) << index;
815 } 824 }
816 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { 825 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
817 uniform_buffer_binding_sizes[stage][binding_index] = size; 826 channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size;
818 } 827 }
819 if constexpr (NEEDS_BIND_UNIFORM_INDEX) { 828 if constexpr (NEEDS_BIND_UNIFORM_INDEX) {
820 runtime.BindUniformBuffer(stage, binding_index, buffer, offset, size); 829 runtime.BindUniformBuffer(stage, binding_index, buffer, offset, size);
@@ -826,15 +835,15 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
826template <class P> 835template <class P>
827void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) { 836void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
828 u32 binding_index = 0; 837 u32 binding_index = 0;
829 ForEachEnabledBit(enabled_storage_buffers[stage], [&](u32 index) { 838 ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
830 const Binding& binding = storage_buffers[stage][index]; 839 const Binding& binding = channel_state->storage_buffers[stage][index];
831 Buffer& buffer = slot_buffers[binding.buffer_id]; 840 Buffer& buffer = slot_buffers[binding.buffer_id];
832 TouchBuffer(buffer, binding.buffer_id); 841 TouchBuffer(buffer, binding.buffer_id);
833 const u32 size = binding.size; 842 const u32 size = binding.size;
834 SynchronizeBuffer(buffer, binding.cpu_addr, size); 843 SynchronizeBuffer(buffer, binding.cpu_addr, size);
835 844
836 const u32 offset = buffer.Offset(binding.cpu_addr); 845 const u32 offset = buffer.Offset(binding.cpu_addr);
837 const bool is_written = ((written_storage_buffers[stage] >> index) & 1) != 0; 846 const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
838 if constexpr (NEEDS_BIND_STORAGE_INDEX) { 847 if constexpr (NEEDS_BIND_STORAGE_INDEX) {
839 runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written); 848 runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written);
840 ++binding_index; 849 ++binding_index;
@@ -846,8 +855,8 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
846 855
847template <class P> 856template <class P>
848void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) { 857void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
849 ForEachEnabledBit(enabled_texture_buffers[stage], [&](u32 index) { 858 ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
850 const TextureBufferBinding& binding = texture_buffers[stage][index]; 859 const TextureBufferBinding& binding = channel_state->texture_buffers[stage][index];
851 Buffer& buffer = slot_buffers[binding.buffer_id]; 860 Buffer& buffer = slot_buffers[binding.buffer_id];
852 const u32 size = binding.size; 861 const u32 size = binding.size;
853 SynchronizeBuffer(buffer, binding.cpu_addr, size); 862 SynchronizeBuffer(buffer, binding.cpu_addr, size);
@@ -855,7 +864,7 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
855 const u32 offset = buffer.Offset(binding.cpu_addr); 864 const u32 offset = buffer.Offset(binding.cpu_addr);
856 const PixelFormat format = binding.format; 865 const PixelFormat format = binding.format;
857 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 866 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
858 if (((image_texture_buffers[stage] >> index) & 1) != 0) { 867 if (((channel_state->image_texture_buffers[stage] >> index) & 1) != 0) {
859 runtime.BindImageBuffer(buffer, offset, size, format); 868 runtime.BindImageBuffer(buffer, offset, size, format);
860 } else { 869 } else {
861 runtime.BindTextureBuffer(buffer, offset, size, format); 870 runtime.BindTextureBuffer(buffer, offset, size, format);
@@ -872,7 +881,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
872 return; 881 return;
873 } 882 }
874 for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) { 883 for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
875 const Binding& binding = transform_feedback_buffers[index]; 884 const Binding& binding = channel_state->transform_feedback_buffers[index];
876 Buffer& buffer = slot_buffers[binding.buffer_id]; 885 Buffer& buffer = slot_buffers[binding.buffer_id];
877 TouchBuffer(buffer, binding.buffer_id); 886 TouchBuffer(buffer, binding.buffer_id);
878 const u32 size = binding.size; 887 const u32 size = binding.size;
@@ -887,15 +896,16 @@ template <class P>
887void BufferCache<P>::BindHostComputeUniformBuffers() { 896void BufferCache<P>::BindHostComputeUniformBuffers() {
888 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { 897 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
889 // Mark all uniform buffers as dirty 898 // Mark all uniform buffers as dirty
890 dirty_uniform_buffers.fill(~u32{0}); 899 channel_state->dirty_uniform_buffers.fill(~u32{0});
891 fast_bound_uniform_buffers.fill(0); 900 channel_state->fast_bound_uniform_buffers.fill(0);
892 } 901 }
893 u32 binding_index = 0; 902 u32 binding_index = 0;
894 ForEachEnabledBit(enabled_compute_uniform_buffer_mask, [&](u32 index) { 903 ForEachEnabledBit(channel_state->enabled_compute_uniform_buffer_mask, [&](u32 index) {
895 const Binding& binding = compute_uniform_buffers[index]; 904 const Binding& binding = channel_state->compute_uniform_buffers[index];
896 Buffer& buffer = slot_buffers[binding.buffer_id]; 905 Buffer& buffer = slot_buffers[binding.buffer_id];
897 TouchBuffer(buffer, binding.buffer_id); 906 TouchBuffer(buffer, binding.buffer_id);
898 const u32 size = std::min(binding.size, (*compute_uniform_buffer_sizes)[index]); 907 const u32 size =
908 std::min(binding.size, (*channel_state->compute_uniform_buffer_sizes)[index]);
899 SynchronizeBuffer(buffer, binding.cpu_addr, size); 909 SynchronizeBuffer(buffer, binding.cpu_addr, size);
900 910
901 const u32 offset = buffer.Offset(binding.cpu_addr); 911 const u32 offset = buffer.Offset(binding.cpu_addr);
@@ -911,15 +921,16 @@ void BufferCache<P>::BindHostComputeUniformBuffers() {
911template <class P> 921template <class P>
912void BufferCache<P>::BindHostComputeStorageBuffers() { 922void BufferCache<P>::BindHostComputeStorageBuffers() {
913 u32 binding_index = 0; 923 u32 binding_index = 0;
914 ForEachEnabledBit(enabled_compute_storage_buffers, [&](u32 index) { 924 ForEachEnabledBit(channel_state->enabled_compute_storage_buffers, [&](u32 index) {
915 const Binding& binding = compute_storage_buffers[index]; 925 const Binding& binding = channel_state->compute_storage_buffers[index];
916 Buffer& buffer = slot_buffers[binding.buffer_id]; 926 Buffer& buffer = slot_buffers[binding.buffer_id];
917 TouchBuffer(buffer, binding.buffer_id); 927 TouchBuffer(buffer, binding.buffer_id);
918 const u32 size = binding.size; 928 const u32 size = binding.size;
919 SynchronizeBuffer(buffer, binding.cpu_addr, size); 929 SynchronizeBuffer(buffer, binding.cpu_addr, size);
920 930
921 const u32 offset = buffer.Offset(binding.cpu_addr); 931 const u32 offset = buffer.Offset(binding.cpu_addr);
922 const bool is_written = ((written_compute_storage_buffers >> index) & 1) != 0; 932 const bool is_written =
933 ((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
923 if constexpr (NEEDS_BIND_STORAGE_INDEX) { 934 if constexpr (NEEDS_BIND_STORAGE_INDEX) {
924 runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written); 935 runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written);
925 ++binding_index; 936 ++binding_index;
@@ -931,8 +942,8 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
931 942
932template <class P> 943template <class P>
933void BufferCache<P>::BindHostComputeTextureBuffers() { 944void BufferCache<P>::BindHostComputeTextureBuffers() {
934 ForEachEnabledBit(enabled_compute_texture_buffers, [&](u32 index) { 945 ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
935 const TextureBufferBinding& binding = compute_texture_buffers[index]; 946 const TextureBufferBinding& binding = channel_state->compute_texture_buffers[index];
936 Buffer& buffer = slot_buffers[binding.buffer_id]; 947 Buffer& buffer = slot_buffers[binding.buffer_id];
937 const u32 size = binding.size; 948 const u32 size = binding.size;
938 SynchronizeBuffer(buffer, binding.cpu_addr, size); 949 SynchronizeBuffer(buffer, binding.cpu_addr, size);
@@ -940,7 +951,7 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
940 const u32 offset = buffer.Offset(binding.cpu_addr); 951 const u32 offset = buffer.Offset(binding.cpu_addr);
941 const PixelFormat format = binding.format; 952 const PixelFormat format = binding.format;
942 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 953 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
943 if (((image_compute_texture_buffers >> index) & 1) != 0) { 954 if (((channel_state->image_compute_texture_buffers >> index) & 1) != 0) {
944 runtime.BindImageBuffer(buffer, offset, size, format); 955 runtime.BindImageBuffer(buffer, offset, size, format);
945 } else { 956 } else {
946 runtime.BindTextureBuffer(buffer, offset, size, format); 957 runtime.BindTextureBuffer(buffer, offset, size, format);
@@ -954,7 +965,7 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
954template <class P> 965template <class P>
955void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) { 966void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
956 do { 967 do {
957 has_deleted_buffers = false; 968 channel_state->has_deleted_buffers = false;
958 if (is_indexed) { 969 if (is_indexed) {
959 UpdateIndexBuffer(); 970 UpdateIndexBuffer();
960 } 971 }
@@ -968,7 +979,7 @@ void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
968 if (current_draw_indirect) { 979 if (current_draw_indirect) {
969 UpdateDrawIndirect(); 980 UpdateDrawIndirect();
970 } 981 }
971 } while (has_deleted_buffers); 982 } while (channel_state->has_deleted_buffers);
972} 983}
973 984
974template <class P> 985template <class P>
@@ -999,7 +1010,7 @@ void BufferCache<P>::UpdateIndexBuffer() {
999 slot_buffers.erase(inline_buffer_id); 1010 slot_buffers.erase(inline_buffer_id);
1000 inline_buffer_id = CreateBuffer(0, buffer_size); 1011 inline_buffer_id = CreateBuffer(0, buffer_size);
1001 } 1012 }
1002 index_buffer = Binding{ 1013 channel_state->index_buffer = Binding{
1003 .cpu_addr = 0, 1014 .cpu_addr = 0,
1004 .size = inline_index_size, 1015 .size = inline_index_size,
1005 .buffer_id = inline_buffer_id, 1016 .buffer_id = inline_buffer_id,
@@ -1015,10 +1026,10 @@ void BufferCache<P>::UpdateIndexBuffer() {
1015 (index_buffer_ref.count + index_buffer_ref.first) * index_buffer_ref.FormatSizeInBytes(); 1026 (index_buffer_ref.count + index_buffer_ref.first) * index_buffer_ref.FormatSizeInBytes();
1016 const u32 size = std::min(address_size, draw_size); 1027 const u32 size = std::min(address_size, draw_size);
1017 if (size == 0 || !cpu_addr) { 1028 if (size == 0 || !cpu_addr) {
1018 index_buffer = NULL_BINDING; 1029 channel_state->index_buffer = NULL_BINDING;
1019 return; 1030 return;
1020 } 1031 }
1021 index_buffer = Binding{ 1032 channel_state->index_buffer = Binding{
1022 .cpu_addr = *cpu_addr, 1033 .cpu_addr = *cpu_addr,
1023 .size = size, 1034 .size = size,
1024 .buffer_id = FindBuffer(*cpu_addr, size), 1035 .buffer_id = FindBuffer(*cpu_addr, size),
@@ -1051,13 +1062,13 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) {
1051 const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); 1062 const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
1052 u32 size = address_size; // TODO: Analyze stride and number of vertices 1063 u32 size = address_size; // TODO: Analyze stride and number of vertices
1053 if (array.enable == 0 || size == 0 || !cpu_addr) { 1064 if (array.enable == 0 || size == 0 || !cpu_addr) {
1054 vertex_buffers[index] = NULL_BINDING; 1065 channel_state->vertex_buffers[index] = NULL_BINDING;
1055 return; 1066 return;
1056 } 1067 }
1057 if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end)) { 1068 if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end)) {
1058 size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size)); 1069 size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
1059 } 1070 }
1060 vertex_buffers[index] = Binding{ 1071 channel_state->vertex_buffers[index] = Binding{
1061 .cpu_addr = *cpu_addr, 1072 .cpu_addr = *cpu_addr,
1062 .size = size, 1073 .size = size,
1063 .buffer_id = FindBuffer(*cpu_addr, size), 1074 .buffer_id = FindBuffer(*cpu_addr, size),
@@ -1079,23 +1090,24 @@ void BufferCache<P>::UpdateDrawIndirect() {
1079 }; 1090 };
1080 }; 1091 };
1081 if (current_draw_indirect->include_count) { 1092 if (current_draw_indirect->include_count) {
1082 update(current_draw_indirect->count_start_address, sizeof(u32), count_buffer_binding); 1093 update(current_draw_indirect->count_start_address, sizeof(u32),
1094 channel_state->count_buffer_binding);
1083 } 1095 }
1084 update(current_draw_indirect->indirect_start_address, current_draw_indirect->buffer_size, 1096 update(current_draw_indirect->indirect_start_address, current_draw_indirect->buffer_size,
1085 indirect_buffer_binding); 1097 channel_state->indirect_buffer_binding);
1086} 1098}
1087 1099
1088template <class P> 1100template <class P>
1089void BufferCache<P>::UpdateUniformBuffers(size_t stage) { 1101void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
1090 ForEachEnabledBit(enabled_uniform_buffer_masks[stage], [&](u32 index) { 1102 ForEachEnabledBit(channel_state->enabled_uniform_buffer_masks[stage], [&](u32 index) {
1091 Binding& binding = uniform_buffers[stage][index]; 1103 Binding& binding = channel_state->uniform_buffers[stage][index];
1092 if (binding.buffer_id) { 1104 if (binding.buffer_id) {
1093 // Already updated 1105 // Already updated
1094 return; 1106 return;
1095 } 1107 }
1096 // Mark as dirty 1108 // Mark as dirty
1097 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { 1109 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
1098 dirty_uniform_buffers[stage] |= 1U << index; 1110 channel_state->dirty_uniform_buffers[stage] |= 1U << index;
1099 } 1111 }
1100 // Resolve buffer 1112 // Resolve buffer
1101 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1113 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
@@ -1104,10 +1116,10 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
1104 1116
1105template <class P> 1117template <class P>
1106void BufferCache<P>::UpdateStorageBuffers(size_t stage) { 1118void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
1107 const u32 written_mask = written_storage_buffers[stage]; 1119 const u32 written_mask = channel_state->written_storage_buffers[stage];
1108 ForEachEnabledBit(enabled_storage_buffers[stage], [&](u32 index) { 1120 ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
1109 // Resolve buffer 1121 // Resolve buffer
1110 Binding& binding = storage_buffers[stage][index]; 1122 Binding& binding = channel_state->storage_buffers[stage][index];
1111 const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1123 const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1112 binding.buffer_id = buffer_id; 1124 binding.buffer_id = buffer_id;
1113 // Mark buffer as written if needed 1125 // Mark buffer as written if needed
@@ -1119,11 +1131,11 @@ void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
1119 1131
1120template <class P> 1132template <class P>
1121void BufferCache<P>::UpdateTextureBuffers(size_t stage) { 1133void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
1122 ForEachEnabledBit(enabled_texture_buffers[stage], [&](u32 index) { 1134 ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
1123 Binding& binding = texture_buffers[stage][index]; 1135 Binding& binding = channel_state->texture_buffers[stage][index];
1124 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1136 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1125 // Mark buffer as written if needed 1137 // Mark buffer as written if needed
1126 if (((written_texture_buffers[stage] >> index) & 1) != 0) { 1138 if (((channel_state->written_texture_buffers[stage] >> index) & 1) != 0) {
1127 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size); 1139 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1128 } 1140 }
1129 }); 1141 });
@@ -1146,11 +1158,11 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
1146 const u32 size = binding.size; 1158 const u32 size = binding.size;
1147 const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr); 1159 const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
1148 if (binding.enable == 0 || size == 0 || !cpu_addr) { 1160 if (binding.enable == 0 || size == 0 || !cpu_addr) {
1149 transform_feedback_buffers[index] = NULL_BINDING; 1161 channel_state->transform_feedback_buffers[index] = NULL_BINDING;
1150 return; 1162 return;
1151 } 1163 }
1152 const BufferId buffer_id = FindBuffer(*cpu_addr, size); 1164 const BufferId buffer_id = FindBuffer(*cpu_addr, size);
1153 transform_feedback_buffers[index] = Binding{ 1165 channel_state->transform_feedback_buffers[index] = Binding{
1154 .cpu_addr = *cpu_addr, 1166 .cpu_addr = *cpu_addr,
1155 .size = size, 1167 .size = size,
1156 .buffer_id = buffer_id, 1168 .buffer_id = buffer_id,
@@ -1160,8 +1172,8 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
1160 1172
1161template <class P> 1173template <class P>
1162void BufferCache<P>::UpdateComputeUniformBuffers() { 1174void BufferCache<P>::UpdateComputeUniformBuffers() {
1163 ForEachEnabledBit(enabled_compute_uniform_buffer_mask, [&](u32 index) { 1175 ForEachEnabledBit(channel_state->enabled_compute_uniform_buffer_mask, [&](u32 index) {
1164 Binding& binding = compute_uniform_buffers[index]; 1176 Binding& binding = channel_state->compute_uniform_buffers[index];
1165 binding = NULL_BINDING; 1177 binding = NULL_BINDING;
1166 const auto& launch_desc = kepler_compute->launch_description; 1178 const auto& launch_desc = kepler_compute->launch_description;
1167 if (((launch_desc.const_buffer_enable_mask >> index) & 1) != 0) { 1179 if (((launch_desc.const_buffer_enable_mask >> index) & 1) != 0) {
@@ -1178,12 +1190,12 @@ void BufferCache<P>::UpdateComputeUniformBuffers() {
1178 1190
1179template <class P> 1191template <class P>
1180void BufferCache<P>::UpdateComputeStorageBuffers() { 1192void BufferCache<P>::UpdateComputeStorageBuffers() {
1181 ForEachEnabledBit(enabled_compute_storage_buffers, [&](u32 index) { 1193 ForEachEnabledBit(channel_state->enabled_compute_storage_buffers, [&](u32 index) {
1182 // Resolve buffer 1194 // Resolve buffer
1183 Binding& binding = compute_storage_buffers[index]; 1195 Binding& binding = channel_state->compute_storage_buffers[index];
1184 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1196 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1185 // Mark as written if needed 1197 // Mark as written if needed
1186 if (((written_compute_storage_buffers >> index) & 1) != 0) { 1198 if (((channel_state->written_compute_storage_buffers >> index) & 1) != 0) {
1187 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size); 1199 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1188 } 1200 }
1189 }); 1201 });
@@ -1191,11 +1203,11 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
1191 1203
1192template <class P> 1204template <class P>
1193void BufferCache<P>::UpdateComputeTextureBuffers() { 1205void BufferCache<P>::UpdateComputeTextureBuffers() {
1194 ForEachEnabledBit(enabled_compute_texture_buffers, [&](u32 index) { 1206 ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
1195 Binding& binding = compute_texture_buffers[index]; 1207 Binding& binding = channel_state->compute_texture_buffers[index];
1196 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1208 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1197 // Mark as written if needed 1209 // Mark as written if needed
1198 if (((written_compute_texture_buffers >> index) & 1) != 0) { 1210 if (((channel_state->written_compute_texture_buffers >> index) & 1) != 0) {
1199 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size); 1211 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1200 } 1212 }
1201 }); 1213 });
@@ -1610,13 +1622,13 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
1610 const auto replace = [scalar_replace](std::span<Binding> bindings) { 1622 const auto replace = [scalar_replace](std::span<Binding> bindings) {
1611 std::ranges::for_each(bindings, scalar_replace); 1623 std::ranges::for_each(bindings, scalar_replace);
1612 }; 1624 };
1613 scalar_replace(index_buffer); 1625 scalar_replace(channel_state->index_buffer);
1614 replace(vertex_buffers); 1626 replace(channel_state->vertex_buffers);
1615 std::ranges::for_each(uniform_buffers, replace); 1627 std::ranges::for_each(channel_state->uniform_buffers, replace);
1616 std::ranges::for_each(storage_buffers, replace); 1628 std::ranges::for_each(channel_state->storage_buffers, replace);
1617 replace(transform_feedback_buffers); 1629 replace(channel_state->transform_feedback_buffers);
1618 replace(compute_uniform_buffers); 1630 replace(channel_state->compute_uniform_buffers);
1619 replace(compute_storage_buffers); 1631 replace(channel_state->compute_storage_buffers);
1620 1632
1621 // Mark the whole buffer as CPU written to stop tracking CPU writes 1633 // Mark the whole buffer as CPU written to stop tracking CPU writes
1622 if (!do_not_mark) { 1634 if (!do_not_mark) {
@@ -1634,8 +1646,8 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
1634template <class P> 1646template <class P>
1635void BufferCache<P>::NotifyBufferDeletion() { 1647void BufferCache<P>::NotifyBufferDeletion() {
1636 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { 1648 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
1637 dirty_uniform_buffers.fill(~u32{0}); 1649 channel_state->dirty_uniform_buffers.fill(~u32{0});
1638 uniform_buffer_binding_sizes.fill({}); 1650 channel_state->uniform_buffer_binding_sizes.fill({});
1639 } 1651 }
1640 auto& flags = maxwell3d->dirty.flags; 1652 auto& flags = maxwell3d->dirty.flags;
1641 flags[Dirty::IndexBuffer] = true; 1653 flags[Dirty::IndexBuffer] = true;
@@ -1643,13 +1655,12 @@ void BufferCache<P>::NotifyBufferDeletion() {
1643 for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { 1655 for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
1644 flags[Dirty::VertexBuffer0 + index] = true; 1656 flags[Dirty::VertexBuffer0 + index] = true;
1645 } 1657 }
1646 has_deleted_buffers = true; 1658 channel_state->has_deleted_buffers = true;
1647} 1659}
1648 1660
1649template <class P> 1661template <class P>
1650typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr, 1662Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
1651 u32 cbuf_index, 1663 bool is_written) const {
1652 bool is_written) const {
1653 const GPUVAddr gpu_addr = gpu_memory->Read<u64>(ssbo_addr); 1664 const GPUVAddr gpu_addr = gpu_memory->Read<u64>(ssbo_addr);
1654 const auto size = [&]() { 1665 const auto size = [&]() {
1655 const bool is_nvn_cbuf = cbuf_index == 0; 1666 const bool is_nvn_cbuf = cbuf_index == 0;
@@ -1681,8 +1692,8 @@ typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr s
1681} 1692}
1682 1693
1683template <class P> 1694template <class P>
1684typename BufferCache<P>::TextureBufferBinding BufferCache<P>::GetTextureBufferBinding( 1695TextureBufferBinding BufferCache<P>::GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size,
1685 GPUVAddr gpu_addr, u32 size, PixelFormat format) { 1696 PixelFormat format) {
1686 const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr); 1697 const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
1687 TextureBufferBinding binding; 1698 TextureBufferBinding binding;
1688 if (!cpu_addr || size == 0) { 1699 if (!cpu_addr || size == 0) {
@@ -1721,7 +1732,7 @@ std::span<u8> BufferCache<P>::ImmediateBuffer(size_t wanted_capacity) {
1721template <class P> 1732template <class P>
1722bool BufferCache<P>::HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept { 1733bool BufferCache<P>::HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept {
1723 if constexpr (IS_OPENGL) { 1734 if constexpr (IS_OPENGL) {
1724 return ((fast_bound_uniform_buffers[stage] >> binding_index) & 1) != 0; 1735 return ((channel_state->fast_bound_uniform_buffers[stage] >> binding_index) & 1) != 0;
1725 } else { 1736 } else {
1726 // Only OpenGL has fast uniform buffers 1737 // Only OpenGL has fast uniform buffers
1727 return false; 1738 return false;
@@ -1730,14 +1741,14 @@ bool BufferCache<P>::HasFastUniformBufferBound(size_t stage, u32 binding_index)
1730 1741
1731template <class P> 1742template <class P>
1732std::pair<typename BufferCache<P>::Buffer*, u32> BufferCache<P>::GetDrawIndirectCount() { 1743std::pair<typename BufferCache<P>::Buffer*, u32> BufferCache<P>::GetDrawIndirectCount() {
1733 auto& buffer = slot_buffers[count_buffer_binding.buffer_id]; 1744 auto& buffer = slot_buffers[channel_state->count_buffer_binding.buffer_id];
1734 return std::make_pair(&buffer, buffer.Offset(count_buffer_binding.cpu_addr)); 1745 return std::make_pair(&buffer, buffer.Offset(channel_state->count_buffer_binding.cpu_addr));
1735} 1746}
1736 1747
1737template <class P> 1748template <class P>
1738std::pair<typename BufferCache<P>::Buffer*, u32> BufferCache<P>::GetDrawIndirectBuffer() { 1749std::pair<typename BufferCache<P>::Buffer*, u32> BufferCache<P>::GetDrawIndirectBuffer() {
1739 auto& buffer = slot_buffers[indirect_buffer_binding.buffer_id]; 1750 auto& buffer = slot_buffers[channel_state->indirect_buffer_binding.buffer_id];
1740 return std::make_pair(&buffer, buffer.Offset(indirect_buffer_binding.cpu_addr)); 1751 return std::make_pair(&buffer, buffer.Offset(channel_state->indirect_buffer_binding.cpu_addr));
1741} 1752}
1742 1753
1743} // namespace VideoCommon 1754} // namespace VideoCommon
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index ac00d4d9d..c689fe06b 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -86,8 +86,78 @@ enum class ObtainBufferOperation : u32 {
86 MarkQuery = 3, 86 MarkQuery = 3,
87}; 87};
88 88
89template <typename P> 89static constexpr BufferId NULL_BUFFER_ID{0};
90class BufferCache : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> { 90static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
91
92struct Binding {
93 VAddr cpu_addr{};
94 u32 size{};
95 BufferId buffer_id;
96};
97
98struct TextureBufferBinding : Binding {
99 PixelFormat format;
100};
101
102static constexpr Binding NULL_BINDING{
103 .cpu_addr = 0,
104 .size = 0,
105 .buffer_id = NULL_BUFFER_ID,
106};
107
108class BufferCacheChannelInfo : public ChannelInfo {
109public:
110 BufferCacheChannelInfo() = delete;
111 BufferCacheChannelInfo(Tegra::Control::ChannelState& state) noexcept : ChannelInfo(state) {}
112 BufferCacheChannelInfo(const BufferCacheChannelInfo& state) = delete;
113 BufferCacheChannelInfo& operator=(const BufferCacheChannelInfo&) = delete;
114
115 Binding index_buffer;
116 std::array<Binding, NUM_VERTEX_BUFFERS> vertex_buffers;
117 std::array<std::array<Binding, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES> uniform_buffers;
118 std::array<std::array<Binding, NUM_STORAGE_BUFFERS>, NUM_STAGES> storage_buffers;
119 std::array<std::array<TextureBufferBinding, NUM_TEXTURE_BUFFERS>, NUM_STAGES> texture_buffers;
120 std::array<Binding, NUM_TRANSFORM_FEEDBACK_BUFFERS> transform_feedback_buffers;
121 Binding count_buffer_binding;
122 Binding indirect_buffer_binding;
123
124 std::array<Binding, NUM_COMPUTE_UNIFORM_BUFFERS> compute_uniform_buffers;
125 std::array<Binding, NUM_STORAGE_BUFFERS> compute_storage_buffers;
126 std::array<TextureBufferBinding, NUM_TEXTURE_BUFFERS> compute_texture_buffers;
127
128 std::array<u32, NUM_STAGES> enabled_uniform_buffer_masks{};
129 u32 enabled_compute_uniform_buffer_mask = 0;
130
131 const UniformBufferSizes* uniform_buffer_sizes{};
132 const ComputeUniformBufferSizes* compute_uniform_buffer_sizes{};
133
134 std::array<u32, NUM_STAGES> enabled_storage_buffers{};
135 std::array<u32, NUM_STAGES> written_storage_buffers{};
136 u32 enabled_compute_storage_buffers = 0;
137 u32 written_compute_storage_buffers = 0;
138
139 std::array<u32, NUM_STAGES> enabled_texture_buffers{};
140 std::array<u32, NUM_STAGES> written_texture_buffers{};
141 std::array<u32, NUM_STAGES> image_texture_buffers{};
142 u32 enabled_compute_texture_buffers = 0;
143 u32 written_compute_texture_buffers = 0;
144 u32 image_compute_texture_buffers = 0;
145
146 std::array<u32, 16> uniform_cache_hits{};
147 std::array<u32, 16> uniform_cache_shots{};
148
149 u32 uniform_buffer_skip_cache_size = DEFAULT_SKIP_CACHE_SIZE;
150
151 bool has_deleted_buffers = false;
152
153 std::array<u32, NUM_STAGES> dirty_uniform_buffers{};
154 std::array<u32, NUM_STAGES> fast_bound_uniform_buffers{};
155 std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES>
156 uniform_buffer_binding_sizes{};
157};
158
159template <class P>
160class BufferCache : public VideoCommon::ChannelSetupCaches<BufferCacheChannelInfo> {
91 // Page size for caching purposes. 161 // Page size for caching purposes.
92 // This is unrelated to the CPU page size and it can be changed as it seems optimal. 162 // This is unrelated to the CPU page size and it can be changed as it seems optimal.
93 static constexpr u32 CACHING_PAGEBITS = 16; 163 static constexpr u32 CACHING_PAGEBITS = 16;
@@ -104,8 +174,6 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelI
104 static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS; 174 static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
105 static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = P::IMPLEMENTS_ASYNC_DOWNLOADS; 175 static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = P::IMPLEMENTS_ASYNC_DOWNLOADS;
106 176
107 static constexpr BufferId NULL_BUFFER_ID{0};
108
109 static constexpr s64 DEFAULT_EXPECTED_MEMORY = 512_MiB; 177 static constexpr s64 DEFAULT_EXPECTED_MEMORY = 512_MiB;
110 static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB; 178 static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB;
111 static constexpr s64 TARGET_THRESHOLD = 4_GiB; 179 static constexpr s64 TARGET_THRESHOLD = 4_GiB;
@@ -149,8 +217,6 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelI
149 using OverlapSection = boost::icl::inter_section<int>; 217 using OverlapSection = boost::icl::inter_section<int>;
150 using OverlapCounter = boost::icl::split_interval_map<VAddr, int>; 218 using OverlapCounter = boost::icl::split_interval_map<VAddr, int>;
151 219
152 struct Empty {};
153
154 struct OverlapResult { 220 struct OverlapResult {
155 std::vector<BufferId> ids; 221 std::vector<BufferId> ids;
156 VAddr begin; 222 VAddr begin;
@@ -158,25 +224,7 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelI
158 bool has_stream_leap = false; 224 bool has_stream_leap = false;
159 }; 225 };
160 226
161 struct Binding {
162 VAddr cpu_addr{};
163 u32 size{};
164 BufferId buffer_id;
165 };
166
167 struct TextureBufferBinding : Binding {
168 PixelFormat format;
169 };
170
171 static constexpr Binding NULL_BINDING{
172 .cpu_addr = 0,
173 .size = 0,
174 .buffer_id = NULL_BUFFER_ID,
175 };
176
177public: 227public:
178 static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
179
180 explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_, 228 explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
181 Core::Memory::Memory& cpu_memory_, Runtime& runtime_); 229 Core::Memory::Memory& cpu_memory_, Runtime& runtime_);
182 230
@@ -496,51 +544,6 @@ private:
496 544
497 u32 last_index_count = 0; 545 u32 last_index_count = 0;
498 546
499 Binding index_buffer;
500 std::array<Binding, NUM_VERTEX_BUFFERS> vertex_buffers;
501 std::array<std::array<Binding, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES> uniform_buffers;
502 std::array<std::array<Binding, NUM_STORAGE_BUFFERS>, NUM_STAGES> storage_buffers;
503 std::array<std::array<TextureBufferBinding, NUM_TEXTURE_BUFFERS>, NUM_STAGES> texture_buffers;
504 std::array<Binding, NUM_TRANSFORM_FEEDBACK_BUFFERS> transform_feedback_buffers;
505 Binding count_buffer_binding;
506 Binding indirect_buffer_binding;
507
508 std::array<Binding, NUM_COMPUTE_UNIFORM_BUFFERS> compute_uniform_buffers;
509 std::array<Binding, NUM_STORAGE_BUFFERS> compute_storage_buffers;
510 std::array<TextureBufferBinding, NUM_TEXTURE_BUFFERS> compute_texture_buffers;
511
512 std::array<u32, NUM_STAGES> enabled_uniform_buffer_masks{};
513 u32 enabled_compute_uniform_buffer_mask = 0;
514
515 const UniformBufferSizes* uniform_buffer_sizes{};
516 const ComputeUniformBufferSizes* compute_uniform_buffer_sizes{};
517
518 std::array<u32, NUM_STAGES> enabled_storage_buffers{};
519 std::array<u32, NUM_STAGES> written_storage_buffers{};
520 u32 enabled_compute_storage_buffers = 0;
521 u32 written_compute_storage_buffers = 0;
522
523 std::array<u32, NUM_STAGES> enabled_texture_buffers{};
524 std::array<u32, NUM_STAGES> written_texture_buffers{};
525 std::array<u32, NUM_STAGES> image_texture_buffers{};
526 u32 enabled_compute_texture_buffers = 0;
527 u32 written_compute_texture_buffers = 0;
528 u32 image_compute_texture_buffers = 0;
529
530 std::array<u32, 16> uniform_cache_hits{};
531 std::array<u32, 16> uniform_cache_shots{};
532
533 u32 uniform_buffer_skip_cache_size = DEFAULT_SKIP_CACHE_SIZE;
534
535 bool has_deleted_buffers = false;
536
537 std::conditional_t<HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS, std::array<u32, NUM_STAGES>, Empty>
538 dirty_uniform_buffers{};
539 std::conditional_t<IS_OPENGL, std::array<u32, NUM_STAGES>, Empty> fast_bound_uniform_buffers{};
540 std::conditional_t<HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS,
541 std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES>, Empty>
542 uniform_buffer_binding_sizes{};
543
544 MemoryTracker memory_tracker; 547 MemoryTracker memory_tracker;
545 IntervalSet uncommitted_ranges; 548 IntervalSet uncommitted_ranges;
546 IntervalSet common_ranges; 549 IntervalSet common_ranges;
diff --git a/src/video_core/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp
index 3e9022dce..cd6a3a9b8 100644
--- a/src/video_core/host1x/codecs/codec.cpp
+++ b/src/video_core/host1x/codecs/codec.cpp
@@ -5,6 +5,7 @@
5#include <fstream> 5#include <fstream>
6#include <vector> 6#include <vector>
7#include "common/assert.h" 7#include "common/assert.h"
8#include "common/scope_exit.h"
8#include "common/settings.h" 9#include "common/settings.h"
9#include "video_core/host1x/codecs/codec.h" 10#include "video_core/host1x/codecs/codec.h"
10#include "video_core/host1x/codecs/h264.h" 11#include "video_core/host1x/codecs/h264.h"
@@ -14,6 +15,8 @@
14#include "video_core/memory_manager.h" 15#include "video_core/memory_manager.h"
15 16
16extern "C" { 17extern "C" {
18#include <libavfilter/buffersink.h>
19#include <libavfilter/buffersrc.h>
17#include <libavutil/opt.h> 20#include <libavutil/opt.h>
18#ifdef LIBVA_FOUND 21#ifdef LIBVA_FOUND
19// for querying VAAPI driver information 22// for querying VAAPI driver information
@@ -85,6 +88,10 @@ Codec::~Codec() {
85 // Free libav memory 88 // Free libav memory
86 avcodec_free_context(&av_codec_ctx); 89 avcodec_free_context(&av_codec_ctx);
87 av_buffer_unref(&av_gpu_decoder); 90 av_buffer_unref(&av_gpu_decoder);
91
92 if (filters_initialized) {
93 avfilter_graph_free(&av_filter_graph);
94 }
88} 95}
89 96
90bool Codec::CreateGpuAvDevice() { 97bool Codec::CreateGpuAvDevice() {
@@ -167,6 +174,62 @@ void Codec::InitializeGpuDecoder() {
167 av_codec_ctx->get_format = GetGpuFormat; 174 av_codec_ctx->get_format = GetGpuFormat;
168} 175}
169 176
177void Codec::InitializeAvFilters(AVFrame* frame) {
178 const AVFilter* buffer_src = avfilter_get_by_name("buffer");
179 const AVFilter* buffer_sink = avfilter_get_by_name("buffersink");
180 AVFilterInOut* inputs = avfilter_inout_alloc();
181 AVFilterInOut* outputs = avfilter_inout_alloc();
182 SCOPE_EXIT({
183 avfilter_inout_free(&inputs);
184 avfilter_inout_free(&outputs);
185 });
186
187 // Don't know how to get the accurate time_base but it doesn't matter for yadif filter
188 // so just use 1/1 to make buffer filter happy
189 std::string args = fmt::format("video_size={}x{}:pix_fmt={}:time_base=1/1", frame->width,
190 frame->height, frame->format);
191
192 av_filter_graph = avfilter_graph_alloc();
193 int ret = avfilter_graph_create_filter(&av_filter_src_ctx, buffer_src, "in", args.c_str(),
194 nullptr, av_filter_graph);
195 if (ret < 0) {
196 LOG_ERROR(Service_NVDRV, "avfilter_graph_create_filter source error: {}", ret);
197 return;
198 }
199
200 ret = avfilter_graph_create_filter(&av_filter_sink_ctx, buffer_sink, "out", nullptr, nullptr,
201 av_filter_graph);
202 if (ret < 0) {
203 LOG_ERROR(Service_NVDRV, "avfilter_graph_create_filter sink error: {}", ret);
204 return;
205 }
206
207 inputs->name = av_strdup("out");
208 inputs->filter_ctx = av_filter_sink_ctx;
209 inputs->pad_idx = 0;
210 inputs->next = nullptr;
211
212 outputs->name = av_strdup("in");
213 outputs->filter_ctx = av_filter_src_ctx;
214 outputs->pad_idx = 0;
215 outputs->next = nullptr;
216
217 const char* description = "yadif=1:-1:0";
218 ret = avfilter_graph_parse_ptr(av_filter_graph, description, &inputs, &outputs, nullptr);
219 if (ret < 0) {
220 LOG_ERROR(Service_NVDRV, "avfilter_graph_parse_ptr error: {}", ret);
221 return;
222 }
223
224 ret = avfilter_graph_config(av_filter_graph, nullptr);
225 if (ret < 0) {
226 LOG_ERROR(Service_NVDRV, "avfilter_graph_config error: {}", ret);
227 return;
228 }
229
230 filters_initialized = true;
231}
232
170void Codec::Initialize() { 233void Codec::Initialize() {
171 const AVCodecID codec = [&] { 234 const AVCodecID codec = [&] {
172 switch (current_codec) { 235 switch (current_codec) {
@@ -271,8 +334,34 @@ void Codec::Decode() {
271 UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format); 334 UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format);
272 return; 335 return;
273 } 336 }
274 av_frames.push(std::move(final_frame)); 337 if (!final_frame->interlaced_frame) {
275 if (av_frames.size() > 10) { 338 av_frames.push(std::move(final_frame));
339 } else {
340 if (!filters_initialized) {
341 InitializeAvFilters(final_frame.get());
342 }
343 if (const int ret = av_buffersrc_add_frame_flags(av_filter_src_ctx, final_frame.get(),
344 AV_BUFFERSRC_FLAG_KEEP_REF);
345 ret) {
346 LOG_DEBUG(Service_NVDRV, "av_buffersrc_add_frame_flags error {}", ret);
347 return;
348 }
349 while (true) {
350 auto filter_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
351
352 int ret = av_buffersink_get_frame(av_filter_sink_ctx, filter_frame.get());
353
354 if (ret == AVERROR(EAGAIN) || ret == AVERROR(AVERROR_EOF))
355 break;
356 if (ret < 0) {
357 LOG_DEBUG(Service_NVDRV, "av_buffersink_get_frame error {}", ret);
358 return;
359 }
360
361 av_frames.push(std::move(filter_frame));
362 }
363 }
364 while (av_frames.size() > 10) {
276 LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame"); 365 LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame");
277 av_frames.pop(); 366 av_frames.pop();
278 } 367 }
diff --git a/src/video_core/host1x/codecs/codec.h b/src/video_core/host1x/codecs/codec.h
index 0d45fb7fe..06fe00a4b 100644
--- a/src/video_core/host1x/codecs/codec.h
+++ b/src/video_core/host1x/codecs/codec.h
@@ -15,6 +15,7 @@ extern "C" {
15#pragma GCC diagnostic ignored "-Wconversion" 15#pragma GCC diagnostic ignored "-Wconversion"
16#endif 16#endif
17#include <libavcodec/avcodec.h> 17#include <libavcodec/avcodec.h>
18#include <libavfilter/avfilter.h>
18#if defined(__GNUC__) || defined(__clang__) 19#if defined(__GNUC__) || defined(__clang__)
19#pragma GCC diagnostic pop 20#pragma GCC diagnostic pop
20#endif 21#endif
@@ -61,17 +62,24 @@ public:
61private: 62private:
62 void InitializeAvCodecContext(); 63 void InitializeAvCodecContext();
63 64
65 void InitializeAvFilters(AVFrame* frame);
66
64 void InitializeGpuDecoder(); 67 void InitializeGpuDecoder();
65 68
66 bool CreateGpuAvDevice(); 69 bool CreateGpuAvDevice();
67 70
68 bool initialized{}; 71 bool initialized{};
72 bool filters_initialized{};
69 Host1x::NvdecCommon::VideoCodec current_codec{Host1x::NvdecCommon::VideoCodec::None}; 73 Host1x::NvdecCommon::VideoCodec current_codec{Host1x::NvdecCommon::VideoCodec::None};
70 74
71 const AVCodec* av_codec{nullptr}; 75 const AVCodec* av_codec{nullptr};
72 AVCodecContext* av_codec_ctx{nullptr}; 76 AVCodecContext* av_codec_ctx{nullptr};
73 AVBufferRef* av_gpu_decoder{nullptr}; 77 AVBufferRef* av_gpu_decoder{nullptr};
74 78
79 AVFilterContext* av_filter_src_ctx{nullptr};
80 AVFilterContext* av_filter_sink_ctx{nullptr};
81 AVFilterGraph* av_filter_graph{nullptr};
82
75 Host1x::Host1x& host1x; 83 Host1x::Host1x& host1x;
76 const Host1x::NvdecCommon::NvdecRegisters& state; 84 const Host1x::NvdecCommon::NvdecRegisters& state;
77 std::unique_ptr<Decoder::H264> h264_decoder; 85 std::unique_ptr<Decoder::H264> h264_decoder;
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 6af4ae793..6d3bda192 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -117,7 +117,7 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_)
117 for (auto& stage_uniforms : fast_uniforms) { 117 for (auto& stage_uniforms : fast_uniforms) {
118 for (OGLBuffer& buffer : stage_uniforms) { 118 for (OGLBuffer& buffer : stage_uniforms) {
119 buffer.Create(); 119 buffer.Create();
120 glNamedBufferData(buffer.handle, BufferCache::DEFAULT_SKIP_CACHE_SIZE, nullptr, 120 glNamedBufferData(buffer.handle, VideoCommon::DEFAULT_SKIP_CACHE_SIZE, nullptr,
121 GL_STREAM_DRAW); 121 GL_STREAM_DRAW);
122 } 122 }
123 } 123 }
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 1e0823836..56d0ff869 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -439,6 +439,11 @@ OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_form
439 return GL_R32UI; 439 return GL_R32UI;
440} 440}
441 441
442[[nodiscard]] bool IsAstcRecompressionEnabled() {
443 return Settings::values.astc_recompression.GetValue() !=
444 Settings::AstcRecompression::Uncompressed;
445}
446
442[[nodiscard]] GLenum SelectAstcFormat(PixelFormat format, bool is_srgb) { 447[[nodiscard]] GLenum SelectAstcFormat(PixelFormat format, bool is_srgb) {
443 switch (Settings::values.astc_recompression.GetValue()) { 448 switch (Settings::values.astc_recompression.GetValue()) {
444 case Settings::AstcRecompression::Bc1: 449 case Settings::AstcRecompression::Bc1:
@@ -760,7 +765,7 @@ Image::Image(TextureCacheRuntime& runtime_, const VideoCommon::ImageInfo& info_,
760 gl_format = GL_RGBA; 765 gl_format = GL_RGBA;
761 gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; 766 gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
762 767
763 if (IsPixelFormatASTC(info.format)) { 768 if (IsPixelFormatASTC(info.format) && IsAstcRecompressionEnabled()) {
764 gl_internal_format = SelectAstcFormat(info.format, is_srgb); 769 gl_internal_format = SelectAstcFormat(info.format, is_srgb);
765 gl_format = GL_NONE; 770 gl_format = GL_NONE;
766 } 771 }
@@ -1155,7 +1160,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
1155 const bool is_srgb = IsPixelFormatSRGB(info.format); 1160 const bool is_srgb = IsPixelFormatSRGB(info.format);
1156 internal_format = is_srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8; 1161 internal_format = is_srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8;
1157 1162
1158 if (IsPixelFormatASTC(info.format)) { 1163 if (IsPixelFormatASTC(info.format) && IsAstcRecompressionEnabled()) {
1159 internal_format = SelectAstcFormat(info.format, is_srgb); 1164 internal_format = SelectAstcFormat(info.format, is_srgb);
1160 } 1165 }
1161 } else { 1166 } else {
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 2d7b9ab65..84d9ca796 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -378,11 +378,7 @@ if(UNIX AND NOT APPLE)
378endif() 378endif()
379 379
380if (WIN32 AND QT_VERSION VERSION_GREATER_EQUAL 6) 380if (WIN32 AND QT_VERSION VERSION_GREATER_EQUAL 6)
381 if (MSVC AND NOT ${CMAKE_GENERATOR} STREQUAL "Ninja") 381 set(YUZU_EXE_DIR "$<TARGET_FILE_DIR:yuzu>")
382 set(YUZU_EXE_DIR "${CMAKE_BINARY_DIR}/bin/$<CONFIG>")
383 else()
384 set(YUZU_EXE_DIR "${CMAKE_BINARY_DIR}/bin")
385 endif()
386 add_custom_command(TARGET yuzu POST_BUILD COMMAND ${WINDEPLOYQT_EXECUTABLE} "${YUZU_EXE_DIR}/yuzu.exe" --dir "${YUZU_EXE_DIR}" --libdir "${YUZU_EXE_DIR}" --plugindir "${YUZU_EXE_DIR}/plugins" --no-compiler-runtime --no-opengl-sw --no-system-d3d-compiler --no-translations --verbose 0) 382 add_custom_command(TARGET yuzu POST_BUILD COMMAND ${WINDEPLOYQT_EXECUTABLE} "${YUZU_EXE_DIR}/yuzu.exe" --dir "${YUZU_EXE_DIR}" --libdir "${YUZU_EXE_DIR}" --plugindir "${YUZU_EXE_DIR}/plugins" --no-compiler-runtime --no-opengl-sw --no-system-d3d-compiler --no-translations --verbose 0)
387endif() 383endif()
388 384
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index c21828b1d..465084fea 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -544,6 +544,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
544 QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); 544 QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update"));
545 QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); 545 QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC"));
546 QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); 546 QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
547 QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage"));
547 QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache")); 548 QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache"));
548 QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache")); 549 QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache"));
549 remove_menu->addSeparator(); 550 remove_menu->addSeparator();
@@ -614,6 +615,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
614 connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { 615 connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
615 emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); 616 emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path);
616 }); 617 });
618 connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] {
619 emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path);
620 });
617 connect(dump_romfs, &QAction::triggered, [this, program_id, path]() { 621 connect(dump_romfs, &QAction::triggered, [this, program_id, path]() {
618 emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::Normal); 622 emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::Normal);
619 }); 623 });
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 64e5af4c1..6c2f75e53 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -45,6 +45,7 @@ enum class GameListRemoveTarget {
45 VkShaderCache, 45 VkShaderCache,
46 AllShaderCache, 46 AllShaderCache,
47 CustomConfiguration, 47 CustomConfiguration,
48 CacheStorage,
48}; 49};
49 50
50enum class DumpRomFSTarget { 51enum class DumpRomFSTarget {
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 4489f43af..25cfef6d5 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -2323,6 +2323,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
2323 return tr("Delete All Transferable Shader Caches?"); 2323 return tr("Delete All Transferable Shader Caches?");
2324 case GameListRemoveTarget::CustomConfiguration: 2324 case GameListRemoveTarget::CustomConfiguration:
2325 return tr("Remove Custom Game Configuration?"); 2325 return tr("Remove Custom Game Configuration?");
2326 case GameListRemoveTarget::CacheStorage:
2327 return tr("Remove Cache Storage?");
2326 default: 2328 default:
2327 return QString{}; 2329 return QString{};
2328 } 2330 }
@@ -2346,6 +2348,9 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
2346 case GameListRemoveTarget::CustomConfiguration: 2348 case GameListRemoveTarget::CustomConfiguration:
2347 RemoveCustomConfiguration(program_id, game_path); 2349 RemoveCustomConfiguration(program_id, game_path);
2348 break; 2350 break;
2351 case GameListRemoveTarget::CacheStorage:
2352 RemoveCacheStorage(program_id);
2353 break;
2349 } 2354 }
2350} 2355}
2351 2356
@@ -2435,6 +2440,21 @@ void GMainWindow::RemoveCustomConfiguration(u64 program_id, const std::string& g
2435 } 2440 }
2436} 2441}
2437 2442
2443void GMainWindow::RemoveCacheStorage(u64 program_id) {
2444 const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir);
2445 auto vfs_nand_dir =
2446 vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
2447
2448 const auto cache_storage_path = FileSys::SaveDataFactory::GetFullPath(
2449 *system, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
2450 FileSys::SaveDataType::CacheStorage, 0 /* program_id */, {}, 0);
2451
2452 const auto path = Common::FS::ConcatPathSafe(nand_dir, cache_storage_path);
2453
2454 // Not an error if it wasn't cleared.
2455 Common::FS::RemoveDirRecursively(path);
2456}
2457
2438void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path, 2458void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path,
2439 DumpRomFSTarget target) { 2459 DumpRomFSTarget target) {
2440 const auto failed = [this] { 2460 const auto failed = [this] {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 17631a2d9..6bb70972f 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -370,6 +370,7 @@ private:
370 void RemoveVulkanDriverPipelineCache(u64 program_id); 370 void RemoveVulkanDriverPipelineCache(u64 program_id);
371 void RemoveAllTransferableShaderCaches(u64 program_id); 371 void RemoveAllTransferableShaderCaches(u64 program_id);
372 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); 372 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
373 void RemoveCacheStorage(u64 program_id);
373 std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); 374 std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
374 InstallResult InstallNSPXCI(const QString& filename); 375 InstallResult InstallNSPXCI(const QString& filename);
375 InstallResult InstallNCA(const QString& filename); 376 InstallResult InstallNCA(const QString& filename);