summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--CMakeModules/FindSimpleIni.cmake19
-rw-r--r--externals/CMakeLists.txt8
-rw-r--r--externals/gamemode/CMakeLists.txt11
-rw-r--r--externals/gamemode/include/gamemode_client.h379
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt76
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt24
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt30
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt53
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt72
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt128
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDir.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt61
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt33
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt21
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt39
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt38
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt20
-rw-r--r--src/android/app/src/main/jni/android_config.cpp50
-rw-r--r--src/android/app/src/main/jni/android_config.h8
-rw-r--r--src/android/app/src/main/jni/android_settings.h8
-rw-r--r--src/android/app/src/main/jni/id_cache.cpp16
-rw-r--r--src/android/app/src/main/jni/id_cache.h2
-rw-r--r--src/android/app/src/main/jni/native_config.cpp52
-rw-r--r--src/android/app/src/main/res/layout/card_folder.xml70
-rw-r--r--src/android/app/src/main/res/layout/dialog_add_folder.xml45
-rw-r--r--src/android/app/src/main/res/layout/dialog_folder_properties.xml30
-rw-r--r--src/android/app/src/main/res/layout/fragment_folders.xml48
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml7
-rw-r--r--src/android/app/src/main/res/values/dimens.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml7
-rw-r--r--src/common/CMakeLists.txt9
-rw-r--r--src/common/linux/gamemode.cpp40
-rw-r--r--src/common/linux/gamemode.h24
-rw-r--r--src/common/settings.cpp2
-rw-r--r--src/common/settings.h5
-rw-r--r--src/common/settings_common.h1
-rw-r--r--src/core/frontend/emu_window.h10
-rw-r--r--src/core/hid/emulated_controller.cpp50
-rw-r--r--src/core/hid/emulated_controller.h1
-rw-r--r--src/core/hle/service/am/applets/applet_cabinet.cpp5
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp12
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h4
-rw-r--r--src/core/hle/service/hid/hid_server.cpp16
-rw-r--r--src/core/hle/service/hid/hid_server.h1
-rw-r--r--src/core/hle/service/mii/types/ver3_store_data.cpp2
-rw-r--r--src/core/hle/service/nfc/common/device.cpp68
-rw-r--r--src/core/hle/service/nfc/common/device.h1
-rw-r--r--src/core/hle/service/set/set_sys.cpp70
-rw-r--r--src/core/hle/service/set/set_sys.h24
-rw-r--r--src/core/hle/service/time/clock_types.h5
-rw-r--r--src/frontend_common/CMakeLists.txt2
-rw-r--r--src/frontend_common/config.cpp2
-rw-r--r--src/frontend_common/config.h1
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp9
-rw-r--r--src/shader_recompiler/backend/glsl/glsl_emit_context.cpp5
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp4
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate_program.cpp2
-rw-r--r--src/shader_recompiler/host_translate_info.h1
-rw-r--r--src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp13
-rw-r--r--src/shader_recompiler/ir_opt/passes.h2
-rw-r--r--src/shader_recompiler/profile.h2
-rw-r--r--src/video_core/CMakeLists.txt1
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h117
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h4
-rw-r--r--src/video_core/buffer_cache/usage_tracker.h79
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp7
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h21
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp18
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp40
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h23
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.cpp29
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.h14
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp28
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h21
-rw-r--r--src/video_core/renderer_vulkan/vk_smaa.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp18
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h1
-rw-r--r--src/video_core/texture_cache/slot_vector.h4
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp37
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h4
-rw-r--r--src/yuzu/CMakeLists.txt2
-rw-r--r--src/yuzu/bootmanager.cpp56
-rw-r--r--src/yuzu/bootmanager.h6
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.ui86
-rw-r--r--src/yuzu/configuration/configure_general.cpp43
-rw-r--r--src/yuzu/configuration/configure_general.ui27
-rw-r--r--src/yuzu/configuration/configure_system.ui2
-rw-r--r--src/yuzu/configuration/shared_translation.cpp3
-rw-r--r--src/yuzu/main.cpp113
-rw-r--r--src/yuzu/main.h6
-rw-r--r--src/yuzu_cmd/yuzu.cpp12
102 files changed, 2221 insertions, 433 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 325f39e1e..599f7c831 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -295,6 +295,7 @@ find_package(lz4 REQUIRED)
295find_package(nlohmann_json 3.8 REQUIRED) 295find_package(nlohmann_json 3.8 REQUIRED)
296find_package(Opus 1.3 MODULE) 296find_package(Opus 1.3 MODULE)
297find_package(RenderDoc MODULE) 297find_package(RenderDoc MODULE)
298find_package(SimpleIni MODULE)
298find_package(stb MODULE) 299find_package(stb MODULE)
299find_package(VulkanMemoryAllocator CONFIG) 300find_package(VulkanMemoryAllocator CONFIG)
300find_package(ZLIB 1.2 REQUIRED) 301find_package(ZLIB 1.2 REQUIRED)
diff --git a/CMakeModules/FindSimpleIni.cmake b/CMakeModules/FindSimpleIni.cmake
new file mode 100644
index 000000000..ce75d7690
--- /dev/null
+++ b/CMakeModules/FindSimpleIni.cmake
@@ -0,0 +1,19 @@
1# SPDX-FileCopyrightText: 2023 Alexandre Bouvier <contact@amb.tf>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
4
5find_path(SimpleIni_INCLUDE_DIR SimpleIni.h)
6
7include(FindPackageHandleStandardArgs)
8find_package_handle_standard_args(SimpleIni
9 REQUIRED_VARS SimpleIni_INCLUDE_DIR
10)
11
12if (SimpleIni_FOUND AND NOT TARGET SimpleIni::SimpleIni)
13 add_library(SimpleIni::SimpleIni INTERFACE IMPORTED)
14 set_target_properties(SimpleIni::SimpleIni PROPERTIES
15 INTERFACE_INCLUDE_DIRECTORIES "${SimpleIni_INCLUDE_DIR}"
16 )
17endif()
18
19mark_as_advanced(SimpleIni_INCLUDE_DIR)
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 8f83d4991..8e1fc696a 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -193,6 +193,10 @@ if (ANDROID)
193 endif() 193 endif()
194endif() 194endif()
195 195
196if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
197 add_subdirectory(gamemode)
198endif()
199
196# Breakpad 200# Breakpad
197# https://github.com/microsoft/vcpkg/blob/master/ports/breakpad/CMakeLists.txt 201# https://github.com/microsoft/vcpkg/blob/master/ports/breakpad/CMakeLists.txt
198if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client) 202if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client)
@@ -296,4 +300,6 @@ if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client)
296endif() 300endif()
297 301
298# SimpleIni 302# SimpleIni
299add_subdirectory(simpleini) 303if (NOT TARGET SimpleIni::SimpleIni)
304 add_subdirectory(simpleini)
305endif()
diff --git a/externals/gamemode/CMakeLists.txt b/externals/gamemode/CMakeLists.txt
new file mode 100644
index 000000000..87095642e
--- /dev/null
+++ b/externals/gamemode/CMakeLists.txt
@@ -0,0 +1,11 @@
1# SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-3.0-or-later
3
4project(gamemode LANGUAGES CXX C)
5
6add_library(gamemode include/gamemode_client.h)
7
8target_link_libraries(gamemode PRIVATE common)
9
10target_include_directories(gamemode PUBLIC include)
11set_target_properties(gamemode PROPERTIES LINKER_LANGUAGE C)
diff --git a/externals/gamemode/include/gamemode_client.h b/externals/gamemode/include/gamemode_client.h
new file mode 100644
index 000000000..184812334
--- /dev/null
+++ b/externals/gamemode/include/gamemode_client.h
@@ -0,0 +1,379 @@
1// SPDX-FileCopyrightText: Copyright 2017-2019 Feral Interactive
2// SPDX-License-Identifier: BSD-3-Clause
3
4/*
5
6Copyright (c) 2017-2019, Feral Interactive
7All rights reserved.
8
9Redistribution and use in source and binary forms, with or without
10modification, are permitted provided that the following conditions are met:
11
12 * Redistributions of source code must retain the above copyright notice,
13 this list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright
15 notice, this list of conditions and the following disclaimer in the
16 documentation and/or other materials provided with the distribution.
17 * Neither the name of Feral Interactive nor the names of its contributors
18 may be used to endorse or promote products derived from this software
19 without specific prior written permission.
20
21THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31POSSIBILITY OF SUCH DAMAGE.
32
33 */
34#ifndef CLIENT_GAMEMODE_H
35#define CLIENT_GAMEMODE_H
36/*
37 * GameMode supports the following client functions
38 * Requests are refcounted in the daemon
39 *
40 * int gamemode_request_start() - Request gamemode starts
41 * 0 if the request was sent successfully
42 * -1 if the request failed
43 *
44 * int gamemode_request_end() - Request gamemode ends
45 * 0 if the request was sent successfully
46 * -1 if the request failed
47 *
48 * GAMEMODE_AUTO can be defined to make the above two functions apply during static init and
49 * destruction, as appropriate. In this configuration, errors will be printed to stderr
50 *
51 * int gamemode_query_status() - Query the current status of gamemode
52 * 0 if gamemode is inactive
53 * 1 if gamemode is active
54 * 2 if gamemode is active and this client is registered
55 * -1 if the query failed
56 *
57 * int gamemode_request_start_for(pid_t pid) - Request gamemode starts for another process
58 * 0 if the request was sent successfully
59 * -1 if the request failed
60 * -2 if the request was rejected
61 *
62 * int gamemode_request_end_for(pid_t pid) - Request gamemode ends for another process
63 * 0 if the request was sent successfully
64 * -1 if the request failed
65 * -2 if the request was rejected
66 *
67 * int gamemode_query_status_for(pid_t pid) - Query status of gamemode for another process
68 * 0 if gamemode is inactive
69 * 1 if gamemode is active
70 * 2 if gamemode is active and this client is registered
71 * -1 if the query failed
72 *
73 * const char* gamemode_error_string() - Get an error string
74 * returns a string describing any of the above errors
75 *
76 * Note: All the above requests can be blocking - dbus requests can and will block while the daemon
77 * handles the request. It is not recommended to make these calls in performance critical code
78 */
79
80#include <stdbool.h>
81#include <stdio.h>
82
83#include <dlfcn.h>
84#include <string.h>
85
86#include <assert.h>
87
88#include <sys/types.h>
89
90static char internal_gamemode_client_error_string[512] = { 0 };
91
92/**
93 * Load libgamemode dynamically to dislodge us from most dependencies.
94 * This allows clients to link and/or use this regardless of runtime.
95 * See SDL2 for an example of the reasoning behind this in terms of
96 * dynamic versioning as well.
97 */
98static volatile int internal_libgamemode_loaded = 1;
99
100/* Typedefs for the functions to load */
101typedef int (*api_call_return_int)(void);
102typedef const char *(*api_call_return_cstring)(void);
103typedef int (*api_call_pid_return_int)(pid_t);
104
105/* Storage for functors */
106static api_call_return_int REAL_internal_gamemode_request_start = NULL;
107static api_call_return_int REAL_internal_gamemode_request_end = NULL;
108static api_call_return_int REAL_internal_gamemode_query_status = NULL;
109static api_call_return_cstring REAL_internal_gamemode_error_string = NULL;
110static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL;
111static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL;
112static api_call_pid_return_int REAL_internal_gamemode_query_status_for = NULL;
113
114/**
115 * Internal helper to perform the symbol binding safely.
116 *
117 * Returns 0 on success and -1 on failure
118 */
119__attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol(
120 void *handle, const char *name, void **out_func, size_t func_size, bool required)
121{
122 void *symbol_lookup = NULL;
123 char *dl_error = NULL;
124
125 /* Safely look up the symbol */
126 symbol_lookup = dlsym(handle, name);
127 dl_error = dlerror();
128 if (required && (dl_error || !symbol_lookup)) {
129 snprintf(internal_gamemode_client_error_string,
130 sizeof(internal_gamemode_client_error_string),
131 "dlsym failed - %s",
132 dl_error);
133 return -1;
134 }
135
136 /* Have the symbol correctly, copy it to make it usable */
137 memcpy(out_func, &symbol_lookup, func_size);
138 return 0;
139}
140
141/**
142 * Loads libgamemode and needed functions
143 *
144 * Returns 0 on success and -1 on failure
145 */
146__attribute__((always_inline)) static inline int internal_load_libgamemode(void)
147{
148 /* We start at 1, 0 is a success and -1 is a fail */
149 if (internal_libgamemode_loaded != 1) {
150 return internal_libgamemode_loaded;
151 }
152
153 /* Anonymous struct type to define our bindings */
154 struct binding {
155 const char *name;
156 void **functor;
157 size_t func_size;
158 bool required;
159 } bindings[] = {
160 { "real_gamemode_request_start",
161 (void **)&REAL_internal_gamemode_request_start,
162 sizeof(REAL_internal_gamemode_request_start),
163 true },
164 { "real_gamemode_request_end",
165 (void **)&REAL_internal_gamemode_request_end,
166 sizeof(REAL_internal_gamemode_request_end),
167 true },
168 { "real_gamemode_query_status",
169 (void **)&REAL_internal_gamemode_query_status,
170 sizeof(REAL_internal_gamemode_query_status),
171 false },
172 { "real_gamemode_error_string",
173 (void **)&REAL_internal_gamemode_error_string,
174 sizeof(REAL_internal_gamemode_error_string),
175 true },
176 { "real_gamemode_request_start_for",
177 (void **)&REAL_internal_gamemode_request_start_for,
178 sizeof(REAL_internal_gamemode_request_start_for),
179 false },
180 { "real_gamemode_request_end_for",
181 (void **)&REAL_internal_gamemode_request_end_for,
182 sizeof(REAL_internal_gamemode_request_end_for),
183 false },
184 { "real_gamemode_query_status_for",
185 (void **)&REAL_internal_gamemode_query_status_for,
186 sizeof(REAL_internal_gamemode_query_status_for),
187 false },
188 };
189
190 void *libgamemode = NULL;
191
192 /* Try and load libgamemode */
193 libgamemode = dlopen("libgamemode.so.0", RTLD_NOW);
194 if (!libgamemode) {
195 /* Attempt to load unversioned library for compatibility with older
196 * versions (as of writing, there are no ABI changes between the two -
197 * this may need to change if ever ABI-breaking changes are made) */
198 libgamemode = dlopen("libgamemode.so", RTLD_NOW);
199 if (!libgamemode) {
200 snprintf(internal_gamemode_client_error_string,
201 sizeof(internal_gamemode_client_error_string),
202 "dlopen failed - %s",
203 dlerror());
204 internal_libgamemode_loaded = -1;
205 return -1;
206 }
207 }
208
209 /* Attempt to bind all symbols */
210 for (size_t i = 0; i < sizeof(bindings) / sizeof(bindings[0]); i++) {
211 struct binding *binder = &bindings[i];
212
213 if (internal_bind_libgamemode_symbol(libgamemode,
214 binder->name,
215 binder->functor,
216 binder->func_size,
217 binder->required)) {
218 internal_libgamemode_loaded = -1;
219 return -1;
220 };
221 }
222
223 /* Success */
224 internal_libgamemode_loaded = 0;
225 return 0;
226}
227
228/**
229 * Redirect to the real libgamemode
230 */
231__attribute__((always_inline)) static inline const char *gamemode_error_string(void)
232{
233 /* If we fail to load the system gamemode, or we have an error string already, return our error
234 * string instead of diverting to the system version */
235 if (internal_load_libgamemode() < 0 || internal_gamemode_client_error_string[0] != '\0') {
236 return internal_gamemode_client_error_string;
237 }
238
239 /* Assert for static analyser that the function is not NULL */
240 assert(REAL_internal_gamemode_error_string != NULL);
241
242 return REAL_internal_gamemode_error_string();
243}
244
245/**
246 * Redirect to the real libgamemode
247 * Allow automatically requesting game mode
248 * Also prints errors as they happen.
249 */
250#ifdef GAMEMODE_AUTO
251__attribute__((constructor))
252#else
253__attribute__((always_inline)) static inline
254#endif
255int gamemode_request_start(void)
256{
257 /* Need to load gamemode */
258 if (internal_load_libgamemode() < 0) {
259#ifdef GAMEMODE_AUTO
260 fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string());
261#endif
262 return -1;
263 }
264
265 /* Assert for static analyser that the function is not NULL */
266 assert(REAL_internal_gamemode_request_start != NULL);
267
268 if (REAL_internal_gamemode_request_start() < 0) {
269#ifdef GAMEMODE_AUTO
270 fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string());
271#endif
272 return -1;
273 }
274
275 return 0;
276}
277
278/* Redirect to the real libgamemode */
279#ifdef GAMEMODE_AUTO
280__attribute__((destructor))
281#else
282__attribute__((always_inline)) static inline
283#endif
284int gamemode_request_end(void)
285{
286 /* Need to load gamemode */
287 if (internal_load_libgamemode() < 0) {
288#ifdef GAMEMODE_AUTO
289 fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string());
290#endif
291 return -1;
292 }
293
294 /* Assert for static analyser that the function is not NULL */
295 assert(REAL_internal_gamemode_request_end != NULL);
296
297 if (REAL_internal_gamemode_request_end() < 0) {
298#ifdef GAMEMODE_AUTO
299 fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string());
300#endif
301 return -1;
302 }
303
304 return 0;
305}
306
307/* Redirect to the real libgamemode */
308__attribute__((always_inline)) static inline int gamemode_query_status(void)
309{
310 /* Need to load gamemode */
311 if (internal_load_libgamemode() < 0) {
312 return -1;
313 }
314
315 if (REAL_internal_gamemode_query_status == NULL) {
316 snprintf(internal_gamemode_client_error_string,
317 sizeof(internal_gamemode_client_error_string),
318 "gamemode_query_status missing (older host?)");
319 return -1;
320 }
321
322 return REAL_internal_gamemode_query_status();
323}
324
325/* Redirect to the real libgamemode */
326__attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid)
327{
328 /* Need to load gamemode */
329 if (internal_load_libgamemode() < 0) {
330 return -1;
331 }
332
333 if (REAL_internal_gamemode_request_start_for == NULL) {
334 snprintf(internal_gamemode_client_error_string,
335 sizeof(internal_gamemode_client_error_string),
336 "gamemode_request_start_for missing (older host?)");
337 return -1;
338 }
339
340 return REAL_internal_gamemode_request_start_for(pid);
341}
342
343/* Redirect to the real libgamemode */
344__attribute__((always_inline)) static inline int gamemode_request_end_for(pid_t pid)
345{
346 /* Need to load gamemode */
347 if (internal_load_libgamemode() < 0) {
348 return -1;
349 }
350
351 if (REAL_internal_gamemode_request_end_for == NULL) {
352 snprintf(internal_gamemode_client_error_string,
353 sizeof(internal_gamemode_client_error_string),
354 "gamemode_request_end_for missing (older host?)");
355 return -1;
356 }
357
358 return REAL_internal_gamemode_request_end_for(pid);
359}
360
361/* Redirect to the real libgamemode */
362__attribute__((always_inline)) static inline int gamemode_query_status_for(pid_t pid)
363{
364 /* Need to load gamemode */
365 if (internal_load_libgamemode() < 0) {
366 return -1;
367 }
368
369 if (REAL_internal_gamemode_query_status_for == NULL) {
370 snprintf(internal_gamemode_client_error_string,
371 sizeof(internal_gamemode_client_error_string),
372 "gamemode_query_status_for missing (older host?)");
373 return -1;
374 }
375
376 return REAL_internal_gamemode_query_status_for(pid);
377}
378
379#endif // CLIENT_GAMEMODE_H
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
new file mode 100644
index 000000000..ab657a7b9
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
@@ -0,0 +1,76 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.adapters
5
6import android.net.Uri
7import android.text.TextUtils
8import android.view.LayoutInflater
9import android.view.ViewGroup
10import androidx.fragment.app.FragmentActivity
11import androidx.recyclerview.widget.AsyncDifferConfig
12import androidx.recyclerview.widget.DiffUtil
13import androidx.recyclerview.widget.ListAdapter
14import androidx.recyclerview.widget.RecyclerView
15import org.yuzu.yuzu_emu.databinding.CardFolderBinding
16import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
17import org.yuzu.yuzu_emu.model.GameDir
18import org.yuzu.yuzu_emu.model.GamesViewModel
19
20class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
21 ListAdapter<GameDir, FolderAdapter.FolderViewHolder>(
22 AsyncDifferConfig.Builder(DiffCallback()).build()
23 ) {
24 override fun onCreateViewHolder(
25 parent: ViewGroup,
26 viewType: Int
27 ): FolderAdapter.FolderViewHolder {
28 CardFolderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
29 .also { return FolderViewHolder(it) }
30 }
31
32 override fun onBindViewHolder(holder: FolderAdapter.FolderViewHolder, position: Int) =
33 holder.bind(currentList[position])
34
35 inner class FolderViewHolder(val binding: CardFolderBinding) :
36 RecyclerView.ViewHolder(binding.root) {
37 private lateinit var gameDir: GameDir
38
39 fun bind(gameDir: GameDir) {
40 this.gameDir = gameDir
41
42 binding.apply {
43 path.text = Uri.parse(gameDir.uriString).path
44 path.postDelayed(
45 {
46 path.isSelected = true
47 path.ellipsize = TextUtils.TruncateAt.MARQUEE
48 },
49 3000
50 )
51
52 buttonEdit.setOnClickListener {
53 GameFolderPropertiesDialogFragment.newInstance(this@FolderViewHolder.gameDir)
54 .show(
55 activity.supportFragmentManager,
56 GameFolderPropertiesDialogFragment.TAG
57 )
58 }
59
60 buttonDelete.setOnClickListener {
61 gamesViewModel.removeFolder(this@FolderViewHolder.gameDir)
62 }
63 }
64 }
65 }
66
67 private class DiffCallback : DiffUtil.ItemCallback<GameDir>() {
68 override fun areItemsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
69 return oldItem == newItem
70 }
71
72 override fun areContentsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
73 return oldItem == newItem
74 }
75 }
76}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index d005c656e..e3cd66185 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -3,33 +3,9 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import android.text.TextUtils
7import android.widget.Toast
8import org.yuzu.yuzu_emu.R 6import org.yuzu.yuzu_emu.R
9import org.yuzu.yuzu_emu.YuzuApplication
10import org.yuzu.yuzu_emu.utils.NativeConfig
11 7
12object Settings { 8object Settings {
13 private val context get() = YuzuApplication.appContext
14
15 fun saveSettings(gameId: String = "") {
16 if (TextUtils.isEmpty(gameId)) {
17 Toast.makeText(
18 context,
19 context.getString(R.string.ini_saved),
20 Toast.LENGTH_SHORT
21 ).show()
22 NativeConfig.saveSettings()
23 } else {
24 // TODO: Save custom game settings
25 Toast.makeText(
26 context,
27 context.getString(R.string.gameid_saved, gameId),
28 Toast.LENGTH_SHORT
29 ).show()
30 }
31 }
32
33 enum class Category { 9 enum class Category {
34 Android, 10 Android,
35 Audio, 11 Audio,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index 48bdbdd75..64bfc6dd0 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
@@ -19,12 +19,13 @@ import androidx.lifecycle.repeatOnLifecycle
19import androidx.navigation.fragment.NavHostFragment 19import androidx.navigation.fragment.NavHostFragment
20import androidx.navigation.navArgs 20import androidx.navigation.navArgs
21import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
22import kotlinx.coroutines.CoroutineScope
23import kotlinx.coroutines.Dispatchers
22import kotlinx.coroutines.flow.collectLatest 24import kotlinx.coroutines.flow.collectLatest
23import kotlinx.coroutines.launch 25import kotlinx.coroutines.launch
24import java.io.IOException 26import java.io.IOException
25import org.yuzu.yuzu_emu.R 27import org.yuzu.yuzu_emu.R
26import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding 28import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
27import org.yuzu.yuzu_emu.features.settings.model.Settings
28import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 29import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
29import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment 30import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
30import org.yuzu.yuzu_emu.model.SettingsViewModel 31import org.yuzu.yuzu_emu.model.SettingsViewModel
@@ -53,10 +54,6 @@ class SettingsActivity : AppCompatActivity() {
53 54
54 WindowCompat.setDecorFitsSystemWindows(window, false) 55 WindowCompat.setDecorFitsSystemWindows(window, false)
55 56
56 if (savedInstanceState != null) {
57 settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
58 }
59
60 if (InsetsHelper.getSystemGestureType(applicationContext) != 57 if (InsetsHelper.getSystemGestureType(applicationContext) !=
61 InsetsHelper.GESTURE_NAVIGATION 58 InsetsHelper.GESTURE_NAVIGATION
62 ) { 59 ) {
@@ -127,12 +124,6 @@ class SettingsActivity : AppCompatActivity() {
127 } 124 }
128 } 125 }
129 126
130 override fun onSaveInstanceState(outState: Bundle) {
131 // Critical: If super method is not called, rotations will be busted.
132 super.onSaveInstanceState(outState)
133 outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
134 }
135
136 override fun onStart() { 127 override fun onStart() {
137 super.onStart() 128 super.onStart()
138 // TODO: Load custom settings contextually 129 // TODO: Load custom settings contextually
@@ -141,16 +132,10 @@ class SettingsActivity : AppCompatActivity() {
141 } 132 }
142 } 133 }
143 134
144 /**
145 * If this is called, the user has left the settings screen (potentially through the
146 * home button) and will expect their changes to be persisted. So we kick off an
147 * IntentService which will do so on a background thread.
148 */
149 override fun onStop() { 135 override fun onStop() {
150 super.onStop() 136 super.onStop()
151 if (isFinishing && settingsViewModel.shouldSave) { 137 CoroutineScope(Dispatchers.IO).launch {
152 Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") 138 NativeConfig.saveSettings()
153 Settings.saveSettings()
154 } 139 }
155 } 140 }
156 141
@@ -160,9 +145,6 @@ class SettingsActivity : AppCompatActivity() {
160 } 145 }
161 146
162 fun onSettingsReset() { 147 fun onSettingsReset() {
163 // Prevents saving to a non-existent settings file
164 settingsViewModel.shouldSave = false
165
166 // Delete settings file because the user may have changed values that do not exist in the UI 148 // Delete settings file because the user may have changed values that do not exist in the UI
167 NativeConfig.unloadConfig() 149 NativeConfig.unloadConfig()
168 val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG) 150 val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
@@ -194,8 +176,4 @@ class SettingsActivity : AppCompatActivity() {
194 windowInsets 176 windowInsets
195 } 177 }
196 } 178 }
197
198 companion object {
199 private const val KEY_SHOULD_SAVE = "should_save"
200 }
201} 179}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index a7a029fc1..af2c1e582 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -105,7 +105,6 @@ class SettingsAdapter(
105 fun onBooleanClick(item: SwitchSetting, checked: Boolean) { 105 fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
106 item.checked = checked 106 item.checked = checked
107 settingsViewModel.setShouldReloadSettingsList(true) 107 settingsViewModel.setShouldReloadSettingsList(true)
108 settingsViewModel.shouldSave = true
109 } 108 }
110 109
111 fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) { 110 fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
@@ -161,7 +160,6 @@ class SettingsAdapter(
161 epochTime += timePicker.hour.toLong() * 60 * 60 160 epochTime += timePicker.hour.toLong() * 60 * 60
162 epochTime += timePicker.minute.toLong() * 60 161 epochTime += timePicker.minute.toLong() * 60
163 if (item.value != epochTime) { 162 if (item.value != epochTime) {
164 settingsViewModel.shouldSave = true
165 notifyItemChanged(position) 163 notifyItemChanged(position)
166 item.value = epochTime 164 item.value = epochTime
167 } 165 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt
new file mode 100644
index 000000000..dec2b7cf1
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt
@@ -0,0 +1,53 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.content.DialogInterface
8import android.net.Uri
9import android.os.Bundle
10import androidx.fragment.app.DialogFragment
11import androidx.fragment.app.activityViewModels
12import com.google.android.material.dialog.MaterialAlertDialogBuilder
13import org.yuzu.yuzu_emu.R
14import org.yuzu.yuzu_emu.databinding.DialogAddFolderBinding
15import org.yuzu.yuzu_emu.model.GameDir
16import org.yuzu.yuzu_emu.model.GamesViewModel
17
18class AddGameFolderDialogFragment : DialogFragment() {
19 private val gamesViewModel: GamesViewModel by activityViewModels()
20
21 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
22 val binding = DialogAddFolderBinding.inflate(layoutInflater)
23 val folderUriString = requireArguments().getString(FOLDER_URI_STRING)
24 if (folderUriString == null) {
25 dismiss()
26 }
27 binding.path.text = Uri.parse(folderUriString).path
28
29 return MaterialAlertDialogBuilder(requireContext())
30 .setTitle(R.string.add_game_folder)
31 .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
32 val newGameDir = GameDir(folderUriString!!, binding.deepScanSwitch.isChecked)
33 gamesViewModel.addFolder(newGameDir)
34 }
35 .setNegativeButton(android.R.string.cancel, null)
36 .setView(binding.root)
37 .show()
38 }
39
40 companion object {
41 const val TAG = "AddGameFolderDialogFragment"
42
43 private const val FOLDER_URI_STRING = "FolderUriString"
44
45 fun newInstance(folderUriString: String): AddGameFolderDialogFragment {
46 val args = Bundle()
47 args.putString(FOLDER_URI_STRING, folderUriString)
48 val fragment = AddGameFolderDialogFragment()
49 fragment.arguments = args
50 return fragment
51 }
52 }
53}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt
new file mode 100644
index 000000000..b6c2e4635
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt
@@ -0,0 +1,72 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.content.DialogInterface
8import android.os.Bundle
9import androidx.fragment.app.DialogFragment
10import androidx.fragment.app.activityViewModels
11import com.google.android.material.dialog.MaterialAlertDialogBuilder
12import org.yuzu.yuzu_emu.R
13import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding
14import org.yuzu.yuzu_emu.model.GameDir
15import org.yuzu.yuzu_emu.model.GamesViewModel
16import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
17
18class GameFolderPropertiesDialogFragment : DialogFragment() {
19 private val gamesViewModel: GamesViewModel by activityViewModels()
20
21 private var deepScan = false
22
23 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
24 val binding = DialogFolderPropertiesBinding.inflate(layoutInflater)
25 val gameDir = requireArguments().parcelable<GameDir>(GAME_DIR)!!
26
27 // Restore checkbox state
28 binding.deepScanSwitch.isChecked =
29 savedInstanceState?.getBoolean(DEEP_SCAN) ?: gameDir.deepScan
30
31 // Ensure that we can get the checkbox state even if the view is destroyed
32 deepScan = binding.deepScanSwitch.isChecked
33 binding.deepScanSwitch.setOnClickListener {
34 deepScan = binding.deepScanSwitch.isChecked
35 }
36
37 return MaterialAlertDialogBuilder(requireContext())
38 .setView(binding.root)
39 .setTitle(R.string.game_folder_properties)
40 .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
41 val folderIndex = gamesViewModel.folders.value.indexOf(gameDir)
42 if (folderIndex != -1) {
43 gamesViewModel.folders.value[folderIndex].deepScan =
44 binding.deepScanSwitch.isChecked
45 gamesViewModel.updateGameDirs()
46 }
47 }
48 .setNegativeButton(android.R.string.cancel, null)
49 .show()
50 }
51
52 override fun onSaveInstanceState(outState: Bundle) {
53 super.onSaveInstanceState(outState)
54 outState.putBoolean(DEEP_SCAN, deepScan)
55 }
56
57 companion object {
58 const val TAG = "GameFolderPropertiesDialogFragment"
59
60 private const val GAME_DIR = "GameDir"
61
62 private const val DEEP_SCAN = "DeepScan"
63
64 fun newInstance(gameDir: GameDir): GameFolderPropertiesDialogFragment {
65 val args = Bundle()
66 args.putParcelable(GAME_DIR, gameDir)
67 val fragment = GameFolderPropertiesDialogFragment()
68 fragment.arguments = args
69 return fragment
70 }
71 }
72}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt
new file mode 100644
index 000000000..341a37fdb
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt
@@ -0,0 +1,128 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.content.Intent
7import android.os.Bundle
8import android.view.LayoutInflater
9import android.view.View
10import android.view.ViewGroup
11import androidx.core.view.ViewCompat
12import androidx.core.view.WindowInsetsCompat
13import androidx.core.view.updatePadding
14import androidx.fragment.app.Fragment
15import androidx.fragment.app.activityViewModels
16import androidx.lifecycle.Lifecycle
17import androidx.lifecycle.lifecycleScope
18import androidx.lifecycle.repeatOnLifecycle
19import androidx.navigation.findNavController
20import androidx.recyclerview.widget.GridLayoutManager
21import com.google.android.material.transition.MaterialSharedAxis
22import kotlinx.coroutines.launch
23import org.yuzu.yuzu_emu.R
24import org.yuzu.yuzu_emu.adapters.FolderAdapter
25import org.yuzu.yuzu_emu.databinding.FragmentFoldersBinding
26import org.yuzu.yuzu_emu.model.GamesViewModel
27import org.yuzu.yuzu_emu.model.HomeViewModel
28import org.yuzu.yuzu_emu.ui.main.MainActivity
29
30class GameFoldersFragment : Fragment() {
31 private var _binding: FragmentFoldersBinding? = null
32 private val binding get() = _binding!!
33
34 private val homeViewModel: HomeViewModel by activityViewModels()
35 private val gamesViewModel: GamesViewModel by activityViewModels()
36
37 override fun onCreate(savedInstanceState: Bundle?) {
38 super.onCreate(savedInstanceState)
39 enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
40 returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
41 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
42
43 gamesViewModel.onOpenGameFoldersFragment()
44 }
45
46 override fun onCreateView(
47 inflater: LayoutInflater,
48 container: ViewGroup?,
49 savedInstanceState: Bundle?
50 ): View {
51 _binding = FragmentFoldersBinding.inflate(inflater)
52 return binding.root
53 }
54
55 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
56 super.onViewCreated(view, savedInstanceState)
57 homeViewModel.setNavigationVisibility(visible = false, animated = true)
58 homeViewModel.setStatusBarShadeVisibility(visible = false)
59
60 binding.toolbarFolders.setNavigationOnClickListener {
61 binding.root.findNavController().popBackStack()
62 }
63
64 binding.listFolders.apply {
65 layoutManager = GridLayoutManager(
66 requireContext(),
67 resources.getInteger(R.integer.grid_columns)
68 )
69 adapter = FolderAdapter(requireActivity(), gamesViewModel)
70 }
71
72 viewLifecycleOwner.lifecycleScope.launch {
73 repeatOnLifecycle(Lifecycle.State.CREATED) {
74 gamesViewModel.folders.collect {
75 (binding.listFolders.adapter as FolderAdapter).submitList(it)
76 }
77 }
78 }
79
80 val mainActivity = requireActivity() as MainActivity
81 binding.buttonAdd.setOnClickListener {
82 mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
83 }
84
85 setInsets()
86 }
87
88 override fun onStop() {
89 super.onStop()
90 gamesViewModel.onCloseGameFoldersFragment()
91 }
92
93 private fun setInsets() =
94 ViewCompat.setOnApplyWindowInsetsListener(
95 binding.root
96 ) { _: View, windowInsets: WindowInsetsCompat ->
97 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
98 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
99
100 val leftInsets = barInsets.left + cutoutInsets.left
101 val rightInsets = barInsets.right + cutoutInsets.right
102
103 val mlpToolbar = binding.toolbarFolders.layoutParams as ViewGroup.MarginLayoutParams
104 mlpToolbar.leftMargin = leftInsets
105 mlpToolbar.rightMargin = rightInsets
106 binding.toolbarFolders.layoutParams = mlpToolbar
107
108 val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
109 val mlpFab =
110 binding.buttonAdd.layoutParams as ViewGroup.MarginLayoutParams
111 mlpFab.leftMargin = leftInsets + fabSpacing
112 mlpFab.rightMargin = rightInsets + fabSpacing
113 mlpFab.bottomMargin = barInsets.bottom + fabSpacing
114 binding.buttonAdd.layoutParams = mlpFab
115
116 val mlpListFolders = binding.listFolders.layoutParams as ViewGroup.MarginLayoutParams
117 mlpListFolders.leftMargin = leftInsets
118 mlpListFolders.rightMargin = rightInsets
119 binding.listFolders.layoutParams = mlpListFolders
120
121 binding.listFolders.updatePadding(
122 bottom = barInsets.bottom +
123 resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
124 )
125
126 windowInsets
127 }
128}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index 4720daec4..3addc2e63 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -127,18 +127,13 @@ class HomeSettingsFragment : Fragment() {
127 ) 127 )
128 add( 128 add(
129 HomeSetting( 129 HomeSetting(
130 R.string.select_games_folder, 130 R.string.manage_game_folders,
131 R.string.select_games_folder_description, 131 R.string.select_games_folder_description,
132 R.drawable.ic_add, 132 R.drawable.ic_add,
133 { 133 {
134 mainActivity.getGamesDirectory.launch( 134 binding.root.findNavController()
135 Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data 135 .navigate(R.id.action_homeSettingsFragment_to_gameFoldersFragment)
136 ) 136 }
137 },
138 { true },
139 0,
140 0,
141 homeViewModel.gamesDir
142 ) 137 )
143 ) 138 )
144 add( 139 add(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt
index d18ec6974..b88d2c038 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt
@@ -52,7 +52,6 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
52 .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> 52 .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
53 settingsViewModel.clickedItem!!.setting.reset() 53 settingsViewModel.clickedItem!!.setting.reset()
54 settingsViewModel.setAdapterItemChanged(position) 54 settingsViewModel.setAdapterItemChanged(position)
55 settingsViewModel.shouldSave = true
56 } 55 }
57 .setNegativeButton(android.R.string.cancel, null) 56 .setNegativeButton(android.R.string.cancel, null)
58 .create() 57 .create()
@@ -137,24 +136,17 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
137 is SingleChoiceSetting -> { 136 is SingleChoiceSetting -> {
138 val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting 137 val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting
139 val value = getValueForSingleChoiceSelection(scSetting, which) 138 val value = getValueForSingleChoiceSelection(scSetting, which)
140 if (scSetting.selectedValue != value) {
141 settingsViewModel.shouldSave = true
142 }
143 scSetting.selectedValue = value 139 scSetting.selectedValue = value
144 } 140 }
145 141
146 is StringSingleChoiceSetting -> { 142 is StringSingleChoiceSetting -> {
147 val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting 143 val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting
148 val value = scSetting.getValueAt(which) 144 val value = scSetting.getValueAt(which)
149 if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
150 scSetting.selectedValue = value 145 scSetting.selectedValue = value
151 } 146 }
152 147
153 is SliderSetting -> { 148 is SliderSetting -> {
154 val sliderSetting = settingsViewModel.clickedItem as SliderSetting 149 val sliderSetting = settingsViewModel.clickedItem as SliderSetting
155 if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) {
156 settingsViewModel.shouldSave = true
157 }
158 sliderSetting.selectedValue = settingsViewModel.sliderProgress.value 150 sliderSetting.selectedValue = settingsViewModel.sliderProgress.value
159 } 151 }
160 } 152 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index c66bb635a..c4277735d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -42,7 +42,7 @@ import org.yuzu.yuzu_emu.model.SetupPage
42import org.yuzu.yuzu_emu.model.StepState 42import org.yuzu.yuzu_emu.model.StepState
43import org.yuzu.yuzu_emu.ui.main.MainActivity 43import org.yuzu.yuzu_emu.ui.main.MainActivity
44import org.yuzu.yuzu_emu.utils.DirectoryInitialization 44import org.yuzu.yuzu_emu.utils.DirectoryInitialization
45import org.yuzu.yuzu_emu.utils.GameHelper 45import org.yuzu.yuzu_emu.utils.NativeConfig
46import org.yuzu.yuzu_emu.utils.ViewUtils 46import org.yuzu.yuzu_emu.utils.ViewUtils
47 47
48class SetupFragment : Fragment() { 48class SetupFragment : Fragment() {
@@ -184,11 +184,7 @@ class SetupFragment : Fragment() {
184 R.string.add_games_warning_description, 184 R.string.add_games_warning_description,
185 R.string.add_games_warning_help, 185 R.string.add_games_warning_help,
186 { 186 {
187 val preferences = 187 if (NativeConfig.getGameDirs().isNotEmpty()) {
188 PreferenceManager.getDefaultSharedPreferences(
189 YuzuApplication.appContext
190 )
191 if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) {
192 StepState.COMPLETE 188 StepState.COMPLETE
193 } else { 189 } else {
194 StepState.INCOMPLETE 190 StepState.INCOMPLETE
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDir.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDir.kt
new file mode 100644
index 000000000..274bc1c7b
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDir.kt
@@ -0,0 +1,13 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import android.os.Parcelable
7import kotlinx.parcelize.Parcelize
8
9@Parcelize
10data class GameDir(
11 val uriString: String,
12 var deepScan: Boolean
13) : Parcelable
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
index 8512ed17c..752d98c10 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -12,6 +12,7 @@ import java.util.Locale
12import kotlinx.coroutines.Dispatchers 12import kotlinx.coroutines.Dispatchers
13import kotlinx.coroutines.flow.MutableStateFlow 13import kotlinx.coroutines.flow.MutableStateFlow
14import kotlinx.coroutines.flow.StateFlow 14import kotlinx.coroutines.flow.StateFlow
15import kotlinx.coroutines.flow.asStateFlow
15import kotlinx.coroutines.launch 16import kotlinx.coroutines.launch
16import kotlinx.coroutines.withContext 17import kotlinx.coroutines.withContext
17import kotlinx.serialization.decodeFromString 18import kotlinx.serialization.decodeFromString
@@ -20,6 +21,7 @@ import org.yuzu.yuzu_emu.NativeLibrary
20import org.yuzu.yuzu_emu.YuzuApplication 21import org.yuzu.yuzu_emu.YuzuApplication
21import org.yuzu.yuzu_emu.utils.GameHelper 22import org.yuzu.yuzu_emu.utils.GameHelper
22import org.yuzu.yuzu_emu.utils.GameMetadata 23import org.yuzu.yuzu_emu.utils.GameMetadata
24import org.yuzu.yuzu_emu.utils.NativeConfig
23 25
24class GamesViewModel : ViewModel() { 26class GamesViewModel : ViewModel() {
25 val games: StateFlow<List<Game>> get() = _games 27 val games: StateFlow<List<Game>> get() = _games
@@ -40,6 +42,9 @@ class GamesViewModel : ViewModel() {
40 val searchFocused: StateFlow<Boolean> get() = _searchFocused 42 val searchFocused: StateFlow<Boolean> get() = _searchFocused
41 private val _searchFocused = MutableStateFlow(false) 43 private val _searchFocused = MutableStateFlow(false)
42 44
45 private val _folders = MutableStateFlow(mutableListOf<GameDir>())
46 val folders = _folders.asStateFlow()
47
43 init { 48 init {
44 // Ensure keys are loaded so that ROM metadata can be decrypted. 49 // Ensure keys are loaded so that ROM metadata can be decrypted.
45 NativeLibrary.reloadKeys() 50 NativeLibrary.reloadKeys()
@@ -50,6 +55,7 @@ class GamesViewModel : ViewModel() {
50 55
51 viewModelScope.launch { 56 viewModelScope.launch {
52 withContext(Dispatchers.IO) { 57 withContext(Dispatchers.IO) {
58 getGameDirs()
53 if (storedGames!!.isNotEmpty()) { 59 if (storedGames!!.isNotEmpty()) {
54 val deserializedGames = mutableSetOf<Game>() 60 val deserializedGames = mutableSetOf<Game>()
55 storedGames.forEach { 61 storedGames.forEach {
@@ -104,7 +110,7 @@ class GamesViewModel : ViewModel() {
104 _searchFocused.value = searchFocused 110 _searchFocused.value = searchFocused
105 } 111 }
106 112
107 fun reloadGames(directoryChanged: Boolean) { 113 fun reloadGames(directoriesChanged: Boolean) {
108 if (isReloading.value) { 114 if (isReloading.value) {
109 return 115 return
110 } 116 }
@@ -116,10 +122,61 @@ class GamesViewModel : ViewModel() {
116 setGames(GameHelper.getGames()) 122 setGames(GameHelper.getGames())
117 _isReloading.value = false 123 _isReloading.value = false
118 124
119 if (directoryChanged) { 125 if (directoriesChanged) {
120 setShouldSwapData(true) 126 setShouldSwapData(true)
121 } 127 }
122 } 128 }
123 } 129 }
124 } 130 }
131
132 fun addFolder(gameDir: GameDir) =
133 viewModelScope.launch {
134 withContext(Dispatchers.IO) {
135 NativeConfig.addGameDir(gameDir)
136 getGameDirs()
137 }
138 }
139
140 fun removeFolder(gameDir: GameDir) =
141 viewModelScope.launch {
142 withContext(Dispatchers.IO) {
143 val gameDirs = _folders.value.toMutableList()
144 val removedDirIndex = gameDirs.indexOf(gameDir)
145 if (removedDirIndex != -1) {
146 gameDirs.removeAt(removedDirIndex)
147 NativeConfig.setGameDirs(gameDirs.toTypedArray())
148 getGameDirs()
149 }
150 }
151 }
152
153 fun updateGameDirs() =
154 viewModelScope.launch {
155 withContext(Dispatchers.IO) {
156 NativeConfig.setGameDirs(_folders.value.toTypedArray())
157 getGameDirs()
158 }
159 }
160
161 fun onOpenGameFoldersFragment() =
162 viewModelScope.launch {
163 withContext(Dispatchers.IO) {
164 getGameDirs()
165 }
166 }
167
168 fun onCloseGameFoldersFragment() =
169 viewModelScope.launch {
170 withContext(Dispatchers.IO) {
171 getGameDirs(true)
172 }
173 }
174
175 private fun getGameDirs(reloadList: Boolean = false) {
176 val gameDirs = NativeConfig.getGameDirs()
177 _folders.value = gameDirs.toMutableList()
178 if (reloadList) {
179 reloadGames(true)
180 }
181 }
125} 182}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
index 756f76721..251b5a667 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -3,15 +3,9 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import android.net.Uri
7import androidx.fragment.app.FragmentActivity
8import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
9import androidx.lifecycle.ViewModelProvider
10import androidx.preference.PreferenceManager
11import kotlinx.coroutines.flow.MutableStateFlow 7import kotlinx.coroutines.flow.MutableStateFlow
12import kotlinx.coroutines.flow.StateFlow 8import kotlinx.coroutines.flow.StateFlow
13import org.yuzu.yuzu_emu.YuzuApplication
14import org.yuzu.yuzu_emu.utils.GameHelper
15 9
16class HomeViewModel : ViewModel() { 10class HomeViewModel : ViewModel() {
17 val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible 11 val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
@@ -23,14 +17,6 @@ class HomeViewModel : ViewModel() {
23 val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward 17 val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
24 private val _shouldPageForward = MutableStateFlow(false) 18 private val _shouldPageForward = MutableStateFlow(false)
25 19
26 val gamesDir: StateFlow<String> get() = _gamesDir
27 private val _gamesDir = MutableStateFlow(
28 Uri.parse(
29 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
30 .getString(GameHelper.KEY_GAME_PATH, "")
31 ).path ?: ""
32 )
33
34 var navigatedToSetup = false 20 var navigatedToSetup = false
35 21
36 fun setNavigationVisibility(visible: Boolean, animated: Boolean) { 22 fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
@@ -50,9 +36,4 @@ class HomeViewModel : ViewModel() {
50 fun setShouldPageForward(pageForward: Boolean) { 36 fun setShouldPageForward(pageForward: Boolean) {
51 _shouldPageForward.value = pageForward 37 _shouldPageForward.value = pageForward
52 } 38 }
53
54 fun setGamesDir(activity: FragmentActivity, dir: String) {
55 ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
56 _gamesDir.value = dir
57 }
58} 39}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
index 6f947674e..ccc981e95 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -13,8 +13,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
13class SettingsViewModel : ViewModel() { 13class SettingsViewModel : ViewModel() {
14 var game: Game? = null 14 var game: Game? = null
15 15
16 var shouldSave = false
17
18 var clickedItem: SettingsItem? = null 16 var clickedItem: SettingsItem? = null
19 17
20 val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate 18 val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
@@ -73,6 +71,5 @@ class SettingsViewModel : ViewModel() {
73 71
74 fun clear() { 72 fun clear() {
75 game = null 73 game = null
76 shouldSave = false
77 } 74 }
78} 75}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index bd2f4cd25..16323a316 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -40,6 +40,7 @@ import org.yuzu.yuzu_emu.R
40import org.yuzu.yuzu_emu.activities.EmulationActivity 40import org.yuzu.yuzu_emu.activities.EmulationActivity
41import org.yuzu.yuzu_emu.databinding.ActivityMainBinding 41import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
42import org.yuzu.yuzu_emu.features.settings.model.Settings 42import org.yuzu.yuzu_emu.features.settings.model.Settings
43import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment
43import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment 44import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
44import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 45import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
45import org.yuzu.yuzu_emu.getPublicFilesDir 46import org.yuzu.yuzu_emu.getPublicFilesDir
@@ -252,6 +253,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
252 super.onResume() 253 super.onResume()
253 } 254 }
254 255
256 override fun onStop() {
257 super.onStop()
258 CoroutineScope(Dispatchers.IO).launch {
259 NativeConfig.saveSettings()
260 }
261 }
262
255 override fun onDestroy() { 263 override fun onDestroy() {
256 EmulationActivity.stopForegroundService(this) 264 EmulationActivity.stopForegroundService(this)
257 super.onDestroy() 265 super.onDestroy()
@@ -293,20 +301,19 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
293 Intent.FLAG_GRANT_READ_URI_PERMISSION 301 Intent.FLAG_GRANT_READ_URI_PERMISSION
294 ) 302 )
295 303
296 // When a new directory is picked, we currently will reset the existing games 304 val uriString = result.toString()
297 // database. This effectively means that only one game directory is supported. 305 val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString }
298 PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() 306 if (folder != null) {
299 .putString(GameHelper.KEY_GAME_PATH, result.toString()) 307 Toast.makeText(
300 .apply() 308 applicationContext,
301 309 R.string.folder_already_added,
302 Toast.makeText( 310 Toast.LENGTH_SHORT
303 applicationContext, 311 ).show()
304 R.string.games_dir_selected, 312 return
305 Toast.LENGTH_LONG 313 }
306 ).show()
307 314
308 gamesViewModel.reloadGames(true) 315 AddGameFolderDialogFragment.newInstance(uriString)
309 homeViewModel.setGamesDir(this, result.path!!) 316 .show(supportFragmentManager, AddGameFolderDialogFragment.TAG)
310 } 317 }
311 318
312 val getProdKey = 319 val getProdKey =
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
index 8c3268e9c..bbe7bfa92 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@@ -364,6 +364,27 @@ object FileUtil {
364 .lowercase() 364 .lowercase()
365 } 365 }
366 366
367 fun isTreeUriValid(uri: Uri): Boolean {
368 val resolver = context.contentResolver
369 val columns = arrayOf(
370 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
371 DocumentsContract.Document.COLUMN_DISPLAY_NAME,
372 DocumentsContract.Document.COLUMN_MIME_TYPE
373 )
374 return try {
375 val docId: String = if (isRootTreeUri(uri)) {
376 DocumentsContract.getTreeDocumentId(uri)
377 } else {
378 DocumentsContract.getDocumentId(uri)
379 }
380 val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, docId)
381 resolver.query(childrenUri, columns, null, null, null)
382 true
383 } catch (_: Exception) {
384 false
385 }
386 }
387
367 @Throws(IOException::class) 388 @Throws(IOException::class)
368 fun getStringFromFile(file: File): String = 389 fun getStringFromFile(file: File): String =
369 String(file.readBytes(), StandardCharsets.UTF_8) 390 String(file.readBytes(), StandardCharsets.UTF_8)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
index e6aca6b44..55010dc59 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -11,10 +11,11 @@ import kotlinx.serialization.json.Json
11import org.yuzu.yuzu_emu.NativeLibrary 11import org.yuzu.yuzu_emu.NativeLibrary
12import org.yuzu.yuzu_emu.YuzuApplication 12import org.yuzu.yuzu_emu.YuzuApplication
13import org.yuzu.yuzu_emu.model.Game 13import org.yuzu.yuzu_emu.model.Game
14import org.yuzu.yuzu_emu.model.GameDir
14import org.yuzu.yuzu_emu.model.MinimalDocumentFile 15import org.yuzu.yuzu_emu.model.MinimalDocumentFile
15 16
16object GameHelper { 17object GameHelper {
17 const val KEY_GAME_PATH = "game_path" 18 private const val KEY_OLD_GAME_PATH = "game_path"
18 const val KEY_GAMES = "Games" 19 const val KEY_GAMES = "Games"
19 20
20 private lateinit var preferences: SharedPreferences 21 private lateinit var preferences: SharedPreferences
@@ -22,15 +23,43 @@ object GameHelper {
22 fun getGames(): List<Game> { 23 fun getGames(): List<Game> {
23 val games = mutableListOf<Game>() 24 val games = mutableListOf<Game>()
24 val context = YuzuApplication.appContext 25 val context = YuzuApplication.appContext
25 val gamesDir =
26 PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_GAME_PATH, "")
27 val gamesUri = Uri.parse(gamesDir)
28 preferences = PreferenceManager.getDefaultSharedPreferences(context) 26 preferences = PreferenceManager.getDefaultSharedPreferences(context)
29 27
28 val gameDirs = mutableListOf<GameDir>()
29 val oldGamesDir = preferences.getString(KEY_OLD_GAME_PATH, "") ?: ""
30 if (oldGamesDir.isNotEmpty()) {
31 gameDirs.add(GameDir(oldGamesDir, true))
32 preferences.edit().remove(KEY_OLD_GAME_PATH).apply()
33 }
34 gameDirs.addAll(NativeConfig.getGameDirs())
35
30 // Ensure keys are loaded so that ROM metadata can be decrypted. 36 // Ensure keys are loaded so that ROM metadata can be decrypted.
31 NativeLibrary.reloadKeys() 37 NativeLibrary.reloadKeys()
32 38
33 addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3) 39 val badDirs = mutableListOf<Int>()
40 gameDirs.forEachIndexed { index: Int, gameDir: GameDir ->
41 val gameDirUri = Uri.parse(gameDir.uriString)
42 val isValid = FileUtil.isTreeUriValid(gameDirUri)
43 if (isValid) {
44 addGamesRecursive(
45 games,
46 FileUtil.listFiles(gameDirUri),
47 if (gameDir.deepScan) 3 else 1
48 )
49 } else {
50 badDirs.add(index)
51 }
52 }
53
54 // Remove all game dirs with insufficient permissions from config
55 if (badDirs.isNotEmpty()) {
56 var offset = 0
57 badDirs.forEach {
58 gameDirs.removeAt(it - offset)
59 offset++
60 }
61 }
62 NativeConfig.setGameDirs(gameDirs.toTypedArray())
34 63
35 // Cache list of games found on disk 64 // Cache list of games found on disk
36 val serializedGames = mutableSetOf<String>() 65 val serializedGames = mutableSetOf<String>()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
index 47bde5081..e63382e1d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
@@ -27,6 +27,8 @@ object InputHandler {
27 0x054C -> getInputDS5ButtonKey(event.keyCode) 27 0x054C -> getInputDS5ButtonKey(event.keyCode)
28 0x057E -> getInputJoyconButtonKey(event.keyCode) 28 0x057E -> getInputJoyconButtonKey(event.keyCode)
29 0x1532 -> getInputRazerButtonKey(event.keyCode) 29 0x1532 -> getInputRazerButtonKey(event.keyCode)
30 0x3537 -> getInputRedmagicButtonKey(event.keyCode)
31 0x358A -> getInputBackboneLabsButtonKey(event.keyCode)
30 else -> getInputGenericButtonKey(event.keyCode) 32 else -> getInputGenericButtonKey(event.keyCode)
31 } 33 }
32 34
@@ -227,6 +229,42 @@ object InputHandler {
227 } 229 }
228 } 230 }
229 231
232 private fun getInputRedmagicButtonKey(key: Int): Int {
233 return when (key) {
234 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
235 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
236 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
237 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
238 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
239 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
240 KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
241 KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
242 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
243 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
244 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
245 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
246 else -> -1
247 }
248 }
249
250 private fun getInputBackboneLabsButtonKey(key: Int): Int {
251 return when (key) {
252 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
253 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
254 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
255 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
256 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
257 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
258 KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
259 KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
260 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
261 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
262 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
263 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
264 else -> -1
265 }
266 }
267
230 private fun getInputGenericButtonKey(key: Int): Int { 268 private fun getInputGenericButtonKey(key: Int): Int {
231 return when (key) { 269 return when (key) {
232 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A 270 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
index 87e579fa7..f4e1bb13f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
@@ -3,6 +3,8 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import org.yuzu.yuzu_emu.model.GameDir
7
6object NativeConfig { 8object NativeConfig {
7 /** 9 /**
8 * Creates a Config object and opens the emulation config. 10 * Creates a Config object and opens the emulation config.
@@ -54,4 +56,22 @@ object NativeConfig {
54 external fun getConfigHeader(category: Int): String 56 external fun getConfigHeader(category: Int): String
55 57
56 external fun getPairedSettingKey(key: String): String 58 external fun getPairedSettingKey(key: String): String
59
60 /**
61 * Gets every [GameDir] in AndroidSettings::values.game_dirs
62 */
63 @Synchronized
64 external fun getGameDirs(): Array<GameDir>
65
66 /**
67 * Clears the AndroidSettings::values.game_dirs array and replaces them with the provided array
68 */
69 @Synchronized
70 external fun setGameDirs(dirs: Array<GameDir>)
71
72 /**
73 * Adds a single [GameDir] to the AndroidSettings::values.game_dirs array
74 */
75 @Synchronized
76 external fun addGameDir(dir: GameDir)
57} 77}
diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp
index 3041c25c9..767d8ea83 100644
--- a/src/android/app/src/main/jni/android_config.cpp
+++ b/src/android/app/src/main/jni/android_config.cpp
@@ -34,6 +34,7 @@ void AndroidConfig::SaveAllValues() {
34void AndroidConfig::ReadAndroidValues() { 34void AndroidConfig::ReadAndroidValues() {
35 if (global) { 35 if (global) {
36 ReadAndroidUIValues(); 36 ReadAndroidUIValues();
37 ReadUIValues();
37 } 38 }
38} 39}
39 40
@@ -45,9 +46,35 @@ void AndroidConfig::ReadAndroidUIValues() {
45 EndGroup(); 46 EndGroup();
46} 47}
47 48
49void AndroidConfig::ReadUIValues() {
50 BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
51
52 ReadPathValues();
53
54 EndGroup();
55}
56
57void AndroidConfig::ReadPathValues() {
58 BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
59
60 const int gamedirs_size = BeginArray(std::string("gamedirs"));
61 for (int i = 0; i < gamedirs_size; ++i) {
62 SetArrayIndex(i);
63 AndroidSettings::GameDir game_dir;
64 game_dir.path = ReadStringSetting(std::string("path"));
65 game_dir.deep_scan =
66 ReadBooleanSetting(std::string("deep_scan"), std::make_optional(false));
67 AndroidSettings::values.game_dirs.push_back(game_dir);
68 }
69 EndArray();
70
71 EndGroup();
72}
73
48void AndroidConfig::SaveAndroidValues() { 74void AndroidConfig::SaveAndroidValues() {
49 if (global) { 75 if (global) {
50 SaveAndroidUIValues(); 76 SaveAndroidUIValues();
77 SaveUIValues();
51 } 78 }
52 79
53 WriteToIni(); 80 WriteToIni();
@@ -61,6 +88,29 @@ void AndroidConfig::SaveAndroidUIValues() {
61 EndGroup(); 88 EndGroup();
62} 89}
63 90
91void AndroidConfig::SaveUIValues() {
92 BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
93
94 SavePathValues();
95
96 EndGroup();
97}
98
99void AndroidConfig::SavePathValues() {
100 BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
101
102 BeginArray(std::string("gamedirs"));
103 for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) {
104 SetArrayIndex(i);
105 const auto& game_dir = AndroidSettings::values.game_dirs[i];
106 WriteSetting(std::string("path"), game_dir.path);
107 WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false));
108 }
109 EndArray();
110
111 EndGroup();
112}
113
64std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) { 114std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
65 auto& map = Settings::values.linkage.by_category; 115 auto& map = Settings::values.linkage.by_category;
66 if (map.contains(category)) { 116 if (map.contains(category)) {
diff --git a/src/android/app/src/main/jni/android_config.h b/src/android/app/src/main/jni/android_config.h
index e679392fd..f490be016 100644
--- a/src/android/app/src/main/jni/android_config.h
+++ b/src/android/app/src/main/jni/android_config.h
@@ -19,9 +19,9 @@ protected:
19 void ReadAndroidUIValues(); 19 void ReadAndroidUIValues();
20 void ReadHidbusValues() override {} 20 void ReadHidbusValues() override {}
21 void ReadDebugControlValues() override {} 21 void ReadDebugControlValues() override {}
22 void ReadPathValues() override {} 22 void ReadPathValues() override;
23 void ReadShortcutValues() override {} 23 void ReadShortcutValues() override {}
24 void ReadUIValues() override {} 24 void ReadUIValues() override;
25 void ReadUIGamelistValues() override {} 25 void ReadUIGamelistValues() override {}
26 void ReadUILayoutValues() override {} 26 void ReadUILayoutValues() override {}
27 void ReadMultiplayerValues() override {} 27 void ReadMultiplayerValues() override {}
@@ -30,9 +30,9 @@ protected:
30 void SaveAndroidUIValues(); 30 void SaveAndroidUIValues();
31 void SaveHidbusValues() override {} 31 void SaveHidbusValues() override {}
32 void SaveDebugControlValues() override {} 32 void SaveDebugControlValues() override {}
33 void SavePathValues() override {} 33 void SavePathValues() override;
34 void SaveShortcutValues() override {} 34 void SaveShortcutValues() override {}
35 void SaveUIValues() override {} 35 void SaveUIValues() override;
36 void SaveUIGamelistValues() override {} 36 void SaveUIGamelistValues() override {}
37 void SaveUILayoutValues() override {} 37 void SaveUILayoutValues() override {}
38 void SaveMultiplayerValues() override {} 38 void SaveMultiplayerValues() override {}
diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h
index 37bc33918..fc0523206 100644
--- a/src/android/app/src/main/jni/android_settings.h
+++ b/src/android/app/src/main/jni/android_settings.h
@@ -9,9 +9,17 @@
9 9
10namespace AndroidSettings { 10namespace AndroidSettings {
11 11
12struct GameDir {
13 std::string path;
14 bool deep_scan = false;
15};
16
12struct Values { 17struct Values {
13 Settings::Linkage linkage; 18 Settings::Linkage linkage;
14 19
20 // Path settings
21 std::vector<GameDir> game_dirs;
22
15 // Android 23 // Android
16 Settings::Setting<bool> picture_in_picture{linkage, false, "picture_in_picture", 24 Settings::Setting<bool> picture_in_picture{linkage, false, "picture_in_picture",
17 Settings::Category::Android}; 25 Settings::Category::Android};
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp
index 960abf95a..a56ed5662 100644
--- a/src/android/app/src/main/jni/id_cache.cpp
+++ b/src/android/app/src/main/jni/id_cache.cpp
@@ -13,6 +13,8 @@ static JavaVM* s_java_vm;
13static jclass s_native_library_class; 13static jclass s_native_library_class;
14static jclass s_disk_cache_progress_class; 14static jclass s_disk_cache_progress_class;
15static jclass s_load_callback_stage_class; 15static jclass s_load_callback_stage_class;
16static jclass s_game_dir_class;
17static jmethodID s_game_dir_constructor;
16static jmethodID s_exit_emulation_activity; 18static jmethodID s_exit_emulation_activity;
17static jmethodID s_disk_cache_load_progress; 19static jmethodID s_disk_cache_load_progress;
18static jmethodID s_on_emulation_started; 20static jmethodID s_on_emulation_started;
@@ -53,6 +55,14 @@ jclass GetDiskCacheLoadCallbackStageClass() {
53 return s_load_callback_stage_class; 55 return s_load_callback_stage_class;
54} 56}
55 57
58jclass GetGameDirClass() {
59 return s_game_dir_class;
60}
61
62jmethodID GetGameDirConstructor() {
63 return s_game_dir_constructor;
64}
65
56jmethodID GetExitEmulationActivity() { 66jmethodID GetExitEmulationActivity() {
57 return s_exit_emulation_activity; 67 return s_exit_emulation_activity;
58} 68}
@@ -90,6 +100,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
90 s_load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass( 100 s_load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass(
91 "org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage"))); 101 "org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage")));
92 102
103 const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir");
104 s_game_dir_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_dir_class));
105 s_game_dir_constructor = env->GetMethodID(game_dir_class, "<init>", "(Ljava/lang/String;Z)V");
106 env->DeleteLocalRef(game_dir_class);
107
93 // Initialize methods 108 // Initialize methods
94 s_exit_emulation_activity = 109 s_exit_emulation_activity =
95 env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); 110 env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
@@ -120,6 +135,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
120 env->DeleteGlobalRef(s_native_library_class); 135 env->DeleteGlobalRef(s_native_library_class);
121 env->DeleteGlobalRef(s_disk_cache_progress_class); 136 env->DeleteGlobalRef(s_disk_cache_progress_class);
122 env->DeleteGlobalRef(s_load_callback_stage_class); 137 env->DeleteGlobalRef(s_load_callback_stage_class);
138 env->DeleteGlobalRef(s_game_dir_class);
123 139
124 // UnInitialize applets 140 // UnInitialize applets
125 SoftwareKeyboard::CleanupJNI(env); 141 SoftwareKeyboard::CleanupJNI(env);
diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h
index b76158928..855649efa 100644
--- a/src/android/app/src/main/jni/id_cache.h
+++ b/src/android/app/src/main/jni/id_cache.h
@@ -13,6 +13,8 @@ JNIEnv* GetEnvForThread();
13jclass GetNativeLibraryClass(); 13jclass GetNativeLibraryClass();
14jclass GetDiskCacheProgressClass(); 14jclass GetDiskCacheProgressClass();
15jclass GetDiskCacheLoadCallbackStageClass(); 15jclass GetDiskCacheLoadCallbackStageClass();
16jclass GetGameDirClass();
17jmethodID GetGameDirConstructor();
16jmethodID GetExitEmulationActivity(); 18jmethodID GetExitEmulationActivity();
17jmethodID GetDiskCacheLoadProgress(); 19jmethodID GetDiskCacheLoadProgress();
18jmethodID GetOnEmulationStarted(); 20jmethodID GetOnEmulationStarted();
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
index 8e81816e5..763b2164c 100644
--- a/src/android/app/src/main/jni/native_config.cpp
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -11,6 +11,7 @@
11#include "common/settings.h" 11#include "common/settings.h"
12#include "frontend_common/config.h" 12#include "frontend_common/config.h"
13#include "jni/android_common/android_common.h" 13#include "jni/android_common/android_common.h"
14#include "jni/id_cache.h"
14 15
15std::unique_ptr<AndroidConfig> config; 16std::unique_ptr<AndroidConfig> config;
16 17
@@ -253,4 +254,55 @@ jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* e
253 return ToJString(env, setting->PairedSetting()->GetLabel()); 254 return ToJString(env, setting->PairedSetting()->GetLabel());
254} 255}
255 256
257jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getGameDirs(JNIEnv* env, jobject obj) {
258 jclass gameDirClass = IDCache::GetGameDirClass();
259 jmethodID gameDirConstructor = IDCache::GetGameDirConstructor();
260 jobjectArray jgameDirArray =
261 env->NewObjectArray(AndroidSettings::values.game_dirs.size(), gameDirClass, nullptr);
262 for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) {
263 jobject jgameDir =
264 env->NewObject(gameDirClass, gameDirConstructor,
265 ToJString(env, AndroidSettings::values.game_dirs[i].path),
266 static_cast<jboolean>(AndroidSettings::values.game_dirs[i].deep_scan));
267 env->SetObjectArrayElement(jgameDirArray, i, jgameDir);
268 }
269 return jgameDirArray;
270}
271
272void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setGameDirs(JNIEnv* env, jobject obj,
273 jobjectArray gameDirs) {
274 AndroidSettings::values.game_dirs.clear();
275 int size = env->GetArrayLength(gameDirs);
276
277 if (size == 0) {
278 return;
279 }
280
281 jobject dir = env->GetObjectArrayElement(gameDirs, 0);
282 jclass gameDirClass = IDCache::GetGameDirClass();
283 jfieldID uriStringField = env->GetFieldID(gameDirClass, "uriString", "Ljava/lang/String;");
284 jfieldID deepScanBooleanField = env->GetFieldID(gameDirClass, "deepScan", "Z");
285 for (int i = 0; i < size; ++i) {
286 dir = env->GetObjectArrayElement(gameDirs, i);
287 jstring juriString = static_cast<jstring>(env->GetObjectField(dir, uriStringField));
288 jboolean jdeepScanBoolean = env->GetBooleanField(dir, deepScanBooleanField);
289 std::string uriString = GetJString(env, juriString);
290 AndroidSettings::values.game_dirs.push_back(
291 AndroidSettings::GameDir{uriString, static_cast<bool>(jdeepScanBoolean)});
292 }
293}
294
295void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_addGameDir(JNIEnv* env, jobject obj,
296 jobject gameDir) {
297 jclass gameDirClass = IDCache::GetGameDirClass();
298 jfieldID uriStringField = env->GetFieldID(gameDirClass, "uriString", "Ljava/lang/String;");
299 jfieldID deepScanBooleanField = env->GetFieldID(gameDirClass, "deepScan", "Z");
300
301 jstring juriString = static_cast<jstring>(env->GetObjectField(gameDir, uriStringField));
302 jboolean jdeepScanBoolean = env->GetBooleanField(gameDir, deepScanBooleanField);
303 std::string uriString = GetJString(env, juriString);
304 AndroidSettings::values.game_dirs.push_back(
305 AndroidSettings::GameDir{uriString, static_cast<bool>(jdeepScanBoolean)});
306}
307
256} // extern "C" 308} // extern "C"
diff --git a/src/android/app/src/main/res/layout/card_folder.xml b/src/android/app/src/main/res/layout/card_folder.xml
new file mode 100644
index 000000000..4e0c04b6b
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_folder.xml
@@ -0,0 +1,70 @@
1<?xml version="1.0" encoding="utf-8"?>
2<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 style="?attr/materialCardViewOutlinedStyle"
6 android:layout_width="match_parent"
7 android:layout_height="wrap_content"
8 android:layout_marginHorizontal="16dp"
9 android:layout_marginVertical="12dp"
10 android:focusable="true">
11
12 <androidx.constraintlayout.widget.ConstraintLayout
13 android:layout_width="match_parent"
14 android:layout_height="wrap_content"
15 android:orientation="horizontal"
16 android:padding="16dp"
17 android:layout_gravity="center_vertical"
18 android:animateLayoutChanges="true">
19
20 <com.google.android.material.textview.MaterialTextView
21 android:id="@+id/path"
22 style="@style/TextAppearance.Material3.BodyLarge"
23 android:layout_width="0dp"
24 android:layout_height="wrap_content"
25 android:layout_gravity="center_vertical|start"
26 android:ellipsize="none"
27 android:marqueeRepeatLimit="marquee_forever"
28 android:requiresFadingEdge="horizontal"
29 android:singleLine="true"
30 android:textAlignment="viewStart"
31 app:layout_constraintBottom_toBottomOf="parent"
32 app:layout_constraintEnd_toStartOf="@+id/button_layout"
33 app:layout_constraintStart_toStartOf="parent"
34 app:layout_constraintTop_toTopOf="parent"
35 tools:text="@string/select_gpu_driver_default" />
36
37 <LinearLayout
38 android:id="@+id/button_layout"
39 android:layout_width="wrap_content"
40 android:layout_height="wrap_content"
41 android:orientation="horizontal"
42 app:layout_constraintBottom_toBottomOf="parent"
43 app:layout_constraintEnd_toEndOf="parent"
44 app:layout_constraintTop_toTopOf="parent">
45
46 <Button
47 android:id="@+id/button_edit"
48 style="@style/Widget.Material3.Button.IconButton"
49 android:layout_width="wrap_content"
50 android:layout_height="wrap_content"
51 android:contentDescription="@string/delete"
52 android:tooltipText="@string/edit"
53 app:icon="@drawable/ic_edit"
54 app:iconTint="?attr/colorControlNormal" />
55
56 <Button
57 android:id="@+id/button_delete"
58 style="@style/Widget.Material3.Button.IconButton"
59 android:layout_width="wrap_content"
60 android:layout_height="wrap_content"
61 android:contentDescription="@string/delete"
62 android:tooltipText="@string/delete"
63 app:icon="@drawable/ic_delete"
64 app:iconTint="?attr/colorControlNormal" />
65
66 </LinearLayout>
67
68 </androidx.constraintlayout.widget.ConstraintLayout>
69
70</com.google.android.material.card.MaterialCardView>
diff --git a/src/android/app/src/main/res/layout/dialog_add_folder.xml b/src/android/app/src/main/res/layout/dialog_add_folder.xml
new file mode 100644
index 000000000..01f95e868
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_add_folder.xml
@@ -0,0 +1,45 @@
1<?xml version="1.0" encoding="utf-8"?>
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:tools="http://schemas.android.com/tools"
4 android:layout_width="match_parent"
5 android:layout_height="wrap_content"
6 android:padding="24dp"
7 android:orientation="vertical">
8
9 <com.google.android.material.textview.MaterialTextView
10 android:id="@+id/path"
11 style="@style/TextAppearance.Material3.BodyLarge"
12 android:layout_width="match_parent"
13 android:layout_height="0dp"
14 android:layout_gravity="center_vertical|start"
15 android:layout_weight="1"
16 android:ellipsize="marquee"
17 android:marqueeRepeatLimit="marquee_forever"
18 android:requiresFadingEdge="horizontal"
19 android:singleLine="true"
20 android:textAlignment="viewStart"
21 tools:text="folder/folder/folder/folder" />
22
23 <LinearLayout
24 android:layout_width="match_parent"
25 android:layout_height="wrap_content"
26 android:orientation="horizontal"
27 android:paddingTop="8dp">
28
29 <com.google.android.material.textview.MaterialTextView
30 style="@style/TextAppearance.Material3.BodyMedium"
31 android:layout_width="0dp"
32 android:layout_height="wrap_content"
33 android:layout_gravity="center_vertical|start"
34 android:layout_weight="1"
35 android:text="@string/deep_scan"
36 android:textAlignment="viewStart" />
37
38 <com.google.android.material.checkbox.MaterialCheckBox
39 android:id="@+id/deep_scan_switch"
40 android:layout_width="wrap_content"
41 android:layout_height="wrap_content" />
42
43 </LinearLayout>
44
45</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/dialog_folder_properties.xml b/src/android/app/src/main/res/layout/dialog_folder_properties.xml
new file mode 100644
index 000000000..248d048cb
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_folder_properties.xml
@@ -0,0 +1,30 @@
1<?xml version="1.0" encoding="utf-8"?>
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="wrap_content"
5 android:padding="24dp"
6 android:orientation="vertical">
7
8 <LinearLayout
9 android:id="@+id/deep_scan_layout"
10 android:layout_width="match_parent"
11 android:layout_height="wrap_content"
12 android:orientation="horizontal">
13
14 <com.google.android.material.textview.MaterialTextView
15 style="@style/TextAppearance.Material3.BodyMedium"
16 android:layout_width="0dp"
17 android:layout_height="wrap_content"
18 android:layout_gravity="center_vertical|start"
19 android:layout_weight="1"
20 android:text="@string/deep_scan"
21 android:textAlignment="viewStart" />
22
23 <com.google.android.material.checkbox.MaterialCheckBox
24 android:id="@+id/deep_scan_switch"
25 android:layout_width="wrap_content"
26 android:layout_height="wrap_content" />
27
28 </LinearLayout>
29
30</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_folders.xml b/src/android/app/src/main/res/layout/fragment_folders.xml
new file mode 100644
index 000000000..74f2f3754
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_folders.xml
@@ -0,0 +1,48 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:id="@+id/coordinator_folders"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="?attr/colorSurface">
8
9 <androidx.coordinatorlayout.widget.CoordinatorLayout
10 android:layout_width="match_parent"
11 android:layout_height="match_parent">
12
13 <com.google.android.material.appbar.AppBarLayout
14 android:id="@+id/appbar_folders"
15 android:layout_width="match_parent"
16 android:layout_height="wrap_content"
17 android:fitsSystemWindows="true"
18 app:liftOnScrollTargetViewId="@id/list_folders">
19
20 <com.google.android.material.appbar.MaterialToolbar
21 android:id="@+id/toolbar_folders"
22 android:layout_width="match_parent"
23 android:layout_height="?attr/actionBarSize"
24 app:navigationIcon="@drawable/ic_back"
25 app:title="@string/game_folders" />
26
27 </com.google.android.material.appbar.AppBarLayout>
28
29 <androidx.recyclerview.widget.RecyclerView
30 android:id="@+id/list_folders"
31 android:layout_width="match_parent"
32 android:layout_height="wrap_content"
33 android:clipToPadding="false"
34 app:layout_behavior="@string/appbar_scrolling_view_behavior" />
35
36 </androidx.coordinatorlayout.widget.CoordinatorLayout>
37
38 <com.google.android.material.floatingactionbutton.FloatingActionButton
39 android:id="@+id/button_add"
40 android:layout_width="wrap_content"
41 android:layout_height="wrap_content"
42 android:layout_gravity="bottom|end"
43 android:contentDescription="@string/add_games"
44 app:srcCompat="@drawable/ic_add"
45 app:layout_constraintBottom_toBottomOf="parent"
46 app:layout_constraintEnd_toEndOf="parent" />
47
48</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index 6d4c1f86d..cf70b4bc4 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -28,6 +28,9 @@
28 <action 28 <action
29 android:id="@+id/action_homeSettingsFragment_to_appletLauncherFragment" 29 android:id="@+id/action_homeSettingsFragment_to_appletLauncherFragment"
30 app:destination="@id/appletLauncherFragment" /> 30 app:destination="@id/appletLauncherFragment" />
31 <action
32 android:id="@+id/action_homeSettingsFragment_to_gameFoldersFragment"
33 app:destination="@id/gameFoldersFragment" />
31 </fragment> 34 </fragment>
32 35
33 <fragment 36 <fragment
@@ -117,5 +120,9 @@
117 android:id="@+id/cabinetLauncherDialogFragment" 120 android:id="@+id/cabinetLauncherDialogFragment"
118 android:name="org.yuzu.yuzu_emu.fragments.CabinetLauncherDialogFragment" 121 android:name="org.yuzu.yuzu_emu.fragments.CabinetLauncherDialogFragment"
119 android:label="CabinetLauncherDialogFragment" /> 122 android:label="CabinetLauncherDialogFragment" />
123 <fragment
124 android:id="@+id/gameFoldersFragment"
125 android:name="org.yuzu.yuzu_emu.fragments.GameFoldersFragment"
126 android:label="GameFoldersFragment" />
120 127
121</navigation> 128</navigation>
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index ef855ea6f..380d14213 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -13,7 +13,7 @@
13 <dimen name="menu_width">256dp</dimen> 13 <dimen name="menu_width">256dp</dimen>
14 <dimen name="card_width">165dp</dimen> 14 <dimen name="card_width">165dp</dimen>
15 <dimen name="icon_inset">24dp</dimen> 15 <dimen name="icon_inset">24dp</dimen>
16 <dimen name="spacing_bottom_list_fab">72dp</dimen> 16 <dimen name="spacing_bottom_list_fab">76dp</dimen>
17 <dimen name="spacing_fab">24dp</dimen> 17 <dimen name="spacing_fab">24dp</dimen>
18 18
19 <dimen name="dialog_margin">20dp</dimen> 19 <dimen name="dialog_margin">20dp</dimen>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 95b90fd6d..a6ccef8a1 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -38,6 +38,7 @@
38 <string name="empty_gamelist">No files were found or no game directory has been selected yet.</string> 38 <string name="empty_gamelist">No files were found or no game directory has been selected yet.</string>
39 <string name="search_and_filter_games">Search and filter games</string> 39 <string name="search_and_filter_games">Search and filter games</string>
40 <string name="select_games_folder">Select games folder</string> 40 <string name="select_games_folder">Select games folder</string>
41 <string name="manage_game_folders">Manage game folders</string>
41 <string name="select_games_folder_description">Allows yuzu to populate the games list</string> 42 <string name="select_games_folder_description">Allows yuzu to populate the games list</string>
42 <string name="add_games_warning">Skip selecting games folder?</string> 43 <string name="add_games_warning">Skip selecting games folder?</string>
43 <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string> 44 <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
@@ -124,6 +125,11 @@
124 <string name="manage_yuzu_data_description">Import/export firmware, keys, user data, and more!</string> 125 <string name="manage_yuzu_data_description">Import/export firmware, keys, user data, and more!</string>
125 <string name="share_save_file">Share save file</string> 126 <string name="share_save_file">Share save file</string>
126 <string name="export_save_failed">Failed to export save</string> 127 <string name="export_save_failed">Failed to export save</string>
128 <string name="game_folders">Game folders</string>
129 <string name="deep_scan">Deep scan</string>
130 <string name="add_game_folder">Add game folder</string>
131 <string name="folder_already_added">This folder was already added!</string>
132 <string name="game_folder_properties">Game folder properties</string>
127 133
128 <!-- Applet launcher strings --> 134 <!-- Applet launcher strings -->
129 <string name="applets">Applet launcher</string> 135 <string name="applets">Applet launcher</string>
@@ -258,6 +264,7 @@
258 <string name="cancelling">Cancelling</string> 264 <string name="cancelling">Cancelling</string>
259 <string name="install">Install</string> 265 <string name="install">Install</string>
260 <string name="delete">Delete</string> 266 <string name="delete">Delete</string>
267 <string name="edit">Edit</string>
261 <string name="export_success">Exported successfully</string> 268 <string name="export_success">Exported successfully</string>
262 269
263 <!-- GPU driver installation --> 270 <!-- GPU driver installation -->
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index d38d5c6d1..bbc55eb56 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -182,6 +182,15 @@ if(ANDROID)
182 ) 182 )
183endif() 183endif()
184 184
185if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
186 target_sources(common PRIVATE
187 linux/gamemode.cpp
188 linux/gamemode.h
189 )
190
191 target_link_libraries(common PRIVATE gamemode)
192endif()
193
185if(ARCHITECTURE_x86_64) 194if(ARCHITECTURE_x86_64)
186 target_sources(common 195 target_sources(common
187 PRIVATE 196 PRIVATE
diff --git a/src/common/linux/gamemode.cpp b/src/common/linux/gamemode.cpp
new file mode 100644
index 000000000..8d3e2934a
--- /dev/null
+++ b/src/common/linux/gamemode.cpp
@@ -0,0 +1,40 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <gamemode_client.h>
5
6#include "common/linux/gamemode.h"
7#include "common/logging/log.h"
8#include "common/settings.h"
9
10namespace Common::Linux {
11
12void StartGamemode() {
13 if (Settings::values.enable_gamemode) {
14 if (gamemode_request_start() < 0) {
15 LOG_WARNING(Frontend, "Failed to start gamemode: {}", gamemode_error_string());
16 } else {
17 LOG_INFO(Frontend, "Started gamemode");
18 }
19 }
20}
21
22void StopGamemode() {
23 if (Settings::values.enable_gamemode) {
24 if (gamemode_request_end() < 0) {
25 LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string());
26 } else {
27 LOG_INFO(Frontend, "Stopped gamemode");
28 }
29 }
30}
31
32void SetGamemodeState(bool state) {
33 if (state) {
34 StartGamemode();
35 } else {
36 StopGamemode();
37 }
38}
39
40} // namespace Common::Linux
diff --git a/src/common/linux/gamemode.h b/src/common/linux/gamemode.h
new file mode 100644
index 000000000..b80646ae2
--- /dev/null
+++ b/src/common/linux/gamemode.h
@@ -0,0 +1,24 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6namespace Common::Linux {
7
8/**
9 * Start the (Feral Interactive) Linux gamemode if it is installed and it is activated
10 */
11void StartGamemode();
12
13/**
14 * Stop the (Feral Interactive) Linux gamemode if it is installed and it is activated
15 */
16void StopGamemode();
17
18/**
19 * Start or stop the (Feral Interactive) Linux gamemode if it is installed and it is activated
20 * @param state The new state the gamemode should have
21 */
22void SetGamemodeState(bool state);
23
24} // namespace Common::Linux
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 16c2feeab..4666bd0a0 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -236,6 +236,8 @@ const char* TranslateCategory(Category category) {
236 return "Services"; 236 return "Services";
237 case Category::Paths: 237 case Category::Paths:
238 return "Paths"; 238 return "Paths";
239 case Category::Linux:
240 return "Linux";
239 case Category::MaxEnum: 241 case Category::MaxEnum:
240 break; 242 break;
241 } 243 }
diff --git a/src/common/settings.h b/src/common/settings.h
index 508615011..98341ad96 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -393,6 +393,8 @@ struct Values {
393 Category::RendererDebug}; 393 Category::RendererDebug};
394 // TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control 394 // TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control
395 bool renderer_amdvlk_depth_bias_workaround{}; 395 bool renderer_amdvlk_depth_bias_workaround{};
396 Setting<bool> disable_buffer_reorder{linkage, false, "disable_buffer_reorder",
397 Category::RendererDebug};
396 398
397 // System 399 // System
398 SwitchableSetting<Language, true> language_index{linkage, 400 SwitchableSetting<Language, true> language_index{linkage,
@@ -436,6 +438,9 @@ struct Values {
436 true, 438 true,
437 true}; 439 true};
438 440
441 // Linux
442 SwitchableSetting<bool> enable_gamemode{linkage, true, "enable_gamemode", Category::Linux};
443
439 // Controls 444 // Controls
440 InputSetting<std::array<PlayerInput, 10>> players; 445 InputSetting<std::array<PlayerInput, 10>> players;
441 446
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 7943223eb..344c04439 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -41,6 +41,7 @@ enum class Category : u32 {
41 Multiplayer, 41 Multiplayer,
42 Services, 42 Services,
43 Paths, 43 Paths,
44 Linux,
44 MaxEnum, 45 MaxEnum,
45}; 46};
46 47
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index a72df034e..c7b48a58d 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -167,6 +167,11 @@ protected:
167 */ 167 */
168 std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const; 168 std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const;
169 169
170 /**
171 * Clip the provided coordinates to be inside the touchscreen area.
172 */
173 std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
174
170 WindowSystemInfo window_info; 175 WindowSystemInfo window_info;
171 176
172 bool strict_context_required = false; 177 bool strict_context_required = false;
@@ -181,11 +186,6 @@ private:
181 // By default, ignore this request and do nothing. 186 // By default, ignore this request and do nothing.
182 } 187 }
183 188
184 /**
185 * Clip the provided coordinates to be inside the touchscreen area.
186 */
187 std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
188
189 Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout 189 Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout
190 190
191 u32 client_area_width; ///< Current client width, should be set by window impl. 191 u32 client_area_width; ///< Current client width, should be set by window impl.
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index a22015ada..a6e681e15 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -509,9 +509,11 @@ void EmulatedController::ReloadInput() {
509 }); 509 });
510 } 510 }
511 turbo_button_state = 0; 511 turbo_button_state = 0;
512 is_initalized = true;
512} 513}
513 514
514void EmulatedController::UnloadInput() { 515void EmulatedController::UnloadInput() {
516 is_initalized = false;
515 for (auto& button : button_devices) { 517 for (auto& button : button_devices) {
516 button.reset(); 518 button.reset();
517 } 519 }
@@ -1207,6 +1209,9 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
1207} 1209}
1208 1210
1209bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { 1211bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
1212 if (!is_initalized) {
1213 return false;
1214 }
1210 if (device_index >= output_devices.size()) { 1215 if (device_index >= output_devices.size()) {
1211 return false; 1216 return false;
1212 } 1217 }
@@ -1242,6 +1247,10 @@ bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
1242 const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type); 1247 const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
1243 const auto& player = Settings::values.players.GetValue()[player_index]; 1248 const auto& player = Settings::values.players.GetValue()[player_index];
1244 1249
1250 if (!is_initalized) {
1251 return false;
1252 }
1253
1245 if (!player.vibration_enabled) { 1254 if (!player.vibration_enabled) {
1246 return false; 1255 return false;
1247 } 1256 }
@@ -1261,6 +1270,10 @@ Common::Input::DriverResult EmulatedController::SetPollingMode(
1261 EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) { 1270 EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) {
1262 LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index); 1271 LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index);
1263 1272
1273 if (!is_initalized) {
1274 return Common::Input::DriverResult::InvalidHandle;
1275 }
1276
1264 auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)]; 1277 auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)];
1265 auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1278 auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1266 auto& nfc_output_device = output_devices[3]; 1279 auto& nfc_output_device = output_devices[3];
@@ -1306,6 +1319,10 @@ bool EmulatedController::SetCameraFormat(
1306 Core::IrSensor::ImageTransferProcessorFormat camera_format) { 1319 Core::IrSensor::ImageTransferProcessorFormat camera_format) {
1307 LOG_INFO(Service_HID, "Set camera format {}", camera_format); 1320 LOG_INFO(Service_HID, "Set camera format {}", camera_format);
1308 1321
1322 if (!is_initalized) {
1323 return false;
1324 }
1325
1309 auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1326 auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1310 auto& camera_output_device = output_devices[2]; 1327 auto& camera_output_device = output_devices[2];
1311 1328
@@ -1329,6 +1346,11 @@ void EmulatedController::SetRingParam(Common::ParamPackage param) {
1329} 1346}
1330 1347
1331bool EmulatedController::HasNfc() const { 1348bool EmulatedController::HasNfc() const {
1349
1350 if (!is_initalized) {
1351 return false;
1352 }
1353
1332 const auto& nfc_output_device = output_devices[3]; 1354 const auto& nfc_output_device = output_devices[3];
1333 1355
1334 switch (npad_type) { 1356 switch (npad_type) {
@@ -1366,6 +1388,10 @@ bool EmulatedController::RemoveNfcHandle() {
1366} 1388}
1367 1389
1368bool EmulatedController::StartNfcPolling() { 1390bool EmulatedController::StartNfcPolling() {
1391 if (!is_initalized) {
1392 return false;
1393 }
1394
1369 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1395 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1370 auto& nfc_virtual_output_device = output_devices[3]; 1396 auto& nfc_virtual_output_device = output_devices[3];
1371 1397
@@ -1377,6 +1403,10 @@ bool EmulatedController::StartNfcPolling() {
1377} 1403}
1378 1404
1379bool EmulatedController::StopNfcPolling() { 1405bool EmulatedController::StopNfcPolling() {
1406 if (!is_initalized) {
1407 return false;
1408 }
1409
1380 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1410 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1381 auto& nfc_virtual_output_device = output_devices[3]; 1411 auto& nfc_virtual_output_device = output_devices[3];
1382 1412
@@ -1388,6 +1418,10 @@ bool EmulatedController::StopNfcPolling() {
1388} 1418}
1389 1419
1390bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) { 1420bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
1421 if (!is_initalized) {
1422 return false;
1423 }
1424
1391 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1425 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1392 auto& nfc_virtual_output_device = output_devices[3]; 1426 auto& nfc_virtual_output_device = output_devices[3];
1393 1427
@@ -1400,6 +1434,10 @@ bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
1400 1434
1401bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request, 1435bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request,
1402 Common::Input::MifareRequest& out_data) { 1436 Common::Input::MifareRequest& out_data) {
1437 if (!is_initalized) {
1438 return false;
1439 }
1440
1403 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1441 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1404 auto& nfc_virtual_output_device = output_devices[3]; 1442 auto& nfc_virtual_output_device = output_devices[3];
1405 1443
@@ -1412,6 +1450,10 @@ bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& requ
1412} 1450}
1413 1451
1414bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) { 1452bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) {
1453 if (!is_initalized) {
1454 return false;
1455 }
1456
1415 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1457 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1416 auto& nfc_virtual_output_device = output_devices[3]; 1458 auto& nfc_virtual_output_device = output_devices[3];
1417 1459
@@ -1423,6 +1465,10 @@ bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& req
1423} 1465}
1424 1466
1425bool EmulatedController::WriteNfc(const std::vector<u8>& data) { 1467bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
1468 if (!is_initalized) {
1469 return false;
1470 }
1471
1426 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1472 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1427 auto& nfc_virtual_output_device = output_devices[3]; 1473 auto& nfc_virtual_output_device = output_devices[3];
1428 1474
@@ -1434,6 +1480,10 @@ bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
1434} 1480}
1435 1481
1436void EmulatedController::SetLedPattern() { 1482void EmulatedController::SetLedPattern() {
1483 if (!is_initalized) {
1484 return;
1485 }
1486
1437 for (auto& device : output_devices) { 1487 for (auto& device : output_devices) {
1438 if (!device) { 1488 if (!device) {
1439 continue; 1489 continue;
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index ea18c2343..d6e20ab66 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -559,6 +559,7 @@ private:
559 NpadStyleTag supported_style_tag{NpadStyleSet::All}; 559 NpadStyleTag supported_style_tag{NpadStyleSet::All};
560 bool is_connected{false}; 560 bool is_connected{false};
561 bool is_configuring{false}; 561 bool is_configuring{false};
562 bool is_initalized{false};
562 bool system_buttons_enabled{true}; 563 bool system_buttons_enabled{true};
563 f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard}; 564 f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
564 u32 turbo_button_state{0}; 565 u32 turbo_button_state{0};
diff --git a/src/core/hle/service/am/applets/applet_cabinet.cpp b/src/core/hle/service/am/applets/applet_cabinet.cpp
index b379dadeb..3906c0fa4 100644
--- a/src/core/hle/service/am/applets/applet_cabinet.cpp
+++ b/src/core/hle/service/am/applets/applet_cabinet.cpp
@@ -122,7 +122,8 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
122 Service::NFP::RegisterInfoPrivate register_info{}; 122 Service::NFP::RegisterInfoPrivate register_info{};
123 std::memcpy(register_info.amiibo_name.data(), amiibo_name.data(), 123 std::memcpy(register_info.amiibo_name.data(), amiibo_name.data(),
124 std::min(amiibo_name.size(), register_info.amiibo_name.size() - 1)); 124 std::min(amiibo_name.size(), register_info.amiibo_name.size() - 1));
125 125 register_info.mii_store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
126 register_info.mii_store_data.SetNickname({u'y', u'u', u'z', u'u'});
126 nfp_device->SetRegisterInfoPrivate(register_info); 127 nfp_device->SetRegisterInfoPrivate(register_info);
127 break; 128 break;
128 } 129 }
@@ -130,7 +131,7 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
130 nfp_device->DeleteApplicationArea(); 131 nfp_device->DeleteApplicationArea();
131 break; 132 break;
132 case Service::NFP::CabinetMode::StartRestorer: 133 case Service::NFP::CabinetMode::StartRestorer:
133 nfp_device->RestoreAmiibo(); 134 nfp_device->Restore();
134 break; 135 break;
135 case Service::NFP::CabinetMode::StartFormatter: 136 case Service::NFP::CabinetMode::StartFormatter:
136 nfp_device->Format(); 137 nfp_device->Format();
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
index 3bcf0ee9f..fcd973414 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.cpp
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -16,7 +16,8 @@ namespace Service::HID {
16constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400; 16constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
17 17
18TouchScreen::TouchScreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_) 18TouchScreen::TouchScreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
19 : ControllerBase{hid_core_} { 19 : ControllerBase{hid_core_}, touchscreen_width(Layout::ScreenUndocked::Width),
20 touchscreen_height(Layout::ScreenUndocked::Height) {
20 static_assert(SHARED_MEMORY_OFFSET + sizeof(TouchSharedMemory) < shared_memory_size, 21 static_assert(SHARED_MEMORY_OFFSET + sizeof(TouchSharedMemory) < shared_memory_size,
21 "TouchSharedMemory is bigger than the shared memory"); 22 "TouchSharedMemory is bigger than the shared memory");
22 shared_memory = std::construct_at( 23 shared_memory = std::construct_at(
@@ -95,8 +96,8 @@ void TouchScreen::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
95 if (id < active_fingers_count) { 96 if (id < active_fingers_count) {
96 const auto& [active_x, active_y] = active_fingers[id].position; 97 const auto& [active_x, active_y] = active_fingers[id].position;
97 touch_entry.position = { 98 touch_entry.position = {
98 .x = static_cast<u16>(active_x * Layout::ScreenUndocked::Width), 99 .x = static_cast<u16>(active_x * static_cast<float>(touchscreen_width)),
99 .y = static_cast<u16>(active_y * Layout::ScreenUndocked::Height), 100 .y = static_cast<u16>(active_y * static_cast<float>(touchscreen_height)),
100 }; 101 };
101 touch_entry.diameter_x = Settings::values.touchscreen.diameter_x; 102 touch_entry.diameter_x = Settings::values.touchscreen.diameter_x;
102 touch_entry.diameter_y = Settings::values.touchscreen.diameter_y; 103 touch_entry.diameter_y = Settings::values.touchscreen.diameter_y;
@@ -120,4 +121,9 @@ void TouchScreen::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
120 shared_memory->touch_screen_lifo.WriteNextEntry(next_state); 121 shared_memory->touch_screen_lifo.WriteNextEntry(next_state);
121} 122}
122 123
124void TouchScreen::SetTouchscreenDimensions(u32 width, u32 height) {
125 touchscreen_width = width;
126 touchscreen_height = height;
127}
128
123} // namespace Service::HID 129} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index cd342ce91..79f026a81 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -28,6 +28,8 @@ public:
28 // When the controller is requesting an update for the shared memory 28 // When the controller is requesting an update for the shared memory
29 void OnUpdate(const Core::Timing::CoreTiming& core_timing) override; 29 void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
30 30
31 void SetTouchscreenDimensions(u32 width, u32 height);
32
31private: 33private:
32 static constexpr std::size_t MAX_FINGERS = 16; 34 static constexpr std::size_t MAX_FINGERS = 16;
33 35
@@ -53,5 +55,7 @@ private:
53 Core::HID::EmulatedConsole* console = nullptr; 55 Core::HID::EmulatedConsole* console = nullptr;
54 56
55 std::array<Core::HID::TouchFinger, MAX_FINGERS> fingers{}; 57 std::array<Core::HID::TouchFinger, MAX_FINGERS> fingers{};
58 u32 touchscreen_width;
59 u32 touchscreen_height;
56}; 60};
57} // namespace Service::HID 61} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp
index 583142e35..a7d1578d9 100644
--- a/src/core/hle/service/hid/hid_server.cpp
+++ b/src/core/hle/service/hid/hid_server.cpp
@@ -208,6 +208,7 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r
208 {1001, &IHidServer::GetNpadCommunicationMode, "GetNpadCommunicationMode"}, 208 {1001, &IHidServer::GetNpadCommunicationMode, "GetNpadCommunicationMode"},
209 {1002, &IHidServer::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"}, 209 {1002, &IHidServer::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"},
210 {1003, &IHidServer::IsFirmwareUpdateNeededForNotification, "IsFirmwareUpdateNeededForNotification"}, 210 {1003, &IHidServer::IsFirmwareUpdateNeededForNotification, "IsFirmwareUpdateNeededForNotification"},
211 {1004, &IHidServer::SetTouchScreenResolution, "SetTouchScreenResolution"},
211 {2000, nullptr, "ActivateDigitizer"}, 212 {2000, nullptr, "ActivateDigitizer"},
212 }; 213 };
213 // clang-format on 214 // clang-format on
@@ -2363,6 +2364,21 @@ void IHidServer::IsFirmwareUpdateNeededForNotification(HLERequestContext& ctx) {
2363 rb.Push(false); 2364 rb.Push(false);
2364} 2365}
2365 2366
2367void IHidServer::SetTouchScreenResolution(HLERequestContext& ctx) {
2368 IPC::RequestParser rp{ctx};
2369 const auto width{rp.Pop<u32>()};
2370 const auto height{rp.Pop<u32>()};
2371 const auto applet_resource_user_id{rp.Pop<u64>()};
2372
2373 GetResourceManager()->GetTouchScreen()->SetTouchscreenDimensions(width, height);
2374
2375 LOG_INFO(Service_HID, "called, width={}, height={}, applet_resource_user_id={}", width, height,
2376 applet_resource_user_id);
2377
2378 IPC::ResponseBuilder rb{ctx, 2};
2379 rb.Push(ResultSuccess);
2380}
2381
2366std::shared_ptr<ResourceManager> IHidServer::GetResourceManager() { 2382std::shared_ptr<ResourceManager> IHidServer::GetResourceManager() {
2367 resource_manager->Initialize(); 2383 resource_manager->Initialize();
2368 return resource_manager; 2384 return resource_manager;
diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h
index eb2e8e7f4..cc7c4ebdd 100644
--- a/src/core/hle/service/hid/hid_server.h
+++ b/src/core/hle/service/hid/hid_server.h
@@ -141,6 +141,7 @@ private:
141 void GetNpadCommunicationMode(HLERequestContext& ctx); 141 void GetNpadCommunicationMode(HLERequestContext& ctx);
142 void SetTouchScreenConfiguration(HLERequestContext& ctx); 142 void SetTouchScreenConfiguration(HLERequestContext& ctx);
143 void IsFirmwareUpdateNeededForNotification(HLERequestContext& ctx); 143 void IsFirmwareUpdateNeededForNotification(HLERequestContext& ctx);
144 void SetTouchScreenResolution(HLERequestContext& ctx);
144 145
145 std::shared_ptr<ResourceManager> resource_manager; 146 std::shared_ptr<ResourceManager> resource_manager;
146 std::shared_ptr<HidFirmwareSettings> firmware_settings; 147 std::shared_ptr<HidFirmwareSettings> firmware_settings;
diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp
index a019cc9f7..c27646fcf 100644
--- a/src/core/hle/service/mii/types/ver3_store_data.cpp
+++ b/src/core/hle/service/mii/types/ver3_store_data.cpp
@@ -98,7 +98,7 @@ void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
98} 98}
99 99
100void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) { 100void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
101 version = 1; 101 version = 3;
102 mii_information.gender.Assign(static_cast<u8>(store_data.GetGender())); 102 mii_information.gender.Assign(static_cast<u8>(store_data.GetGender()));
103 mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor())); 103 mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor()));
104 height = store_data.GetHeight(); 104 height = store_data.GetHeight();
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index e7a00deb3..47516f883 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -401,6 +401,12 @@ Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& time
401} 401}
402 402
403Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target_) { 403Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target_) {
404 bool is_corrupted = false;
405
406 if (model_type != NFP::ModelType::Amiibo) {
407 return ResultInvalidArgument;
408 }
409
404 if (device_state != DeviceState::TagFound) { 410 if (device_state != DeviceState::TagFound) {
405 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); 411 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
406 return ResultWrongDeviceState; 412 return ResultWrongDeviceState;
@@ -420,26 +426,32 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
420 if (is_plain_amiibo) { 426 if (is_plain_amiibo) {
421 std::vector<u8> data(sizeof(NFP::NTAG215File)); 427 std::vector<u8> data(sizeof(NFP::NTAG215File));
422 memcpy(data.data(), &tag_data, sizeof(tag_data)); 428 memcpy(data.data(), &tag_data, sizeof(tag_data));
423 WriteBackupData(tag_data.uid, data); 429 }
424 430
425 device_state = DeviceState::TagMounted; 431 if (!is_plain_amiibo && !NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
426 mount_target = mount_target_; 432 LOG_ERROR(Service_NFP, "Can't decode amiibo");
427 return ResultSuccess; 433 is_corrupted = true;
428 } 434 }
429 435
430 if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { 436 if (tag_data.settings.settings.amiibo_initialized && !tag_data.owner_mii.IsValid()) {
431 bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess(); 437 LOG_ERROR(Service_NFP, "Invalid mii data");
432 LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup); 438 is_corrupted = true;
433 return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
434 } 439 }
435 440
436 std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); 441 if (!is_corrupted) {
437 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); 442 std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
438 WriteBackupData(encrypted_tag_data.uuid, data); 443 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
444 WriteBackupData(encrypted_tag_data.uuid, data);
445 }
439 446
440 device_state = DeviceState::TagMounted; 447 device_state = DeviceState::TagMounted;
441 mount_target = mount_target_; 448 mount_target = mount_target_;
442 449
450 if (is_corrupted) {
451 bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess();
452 return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
453 }
454
443 return ResultSuccess; 455 return ResultSuccess;
444} 456}
445 457
@@ -606,6 +618,17 @@ Result NfcDevice::Restore() {
606 } 618 }
607 } 619 }
608 620
621 // Restore mii data in case is corrupted by previous instances of yuzu
622 if (tag_data.settings.settings.amiibo_initialized && !tag_data.owner_mii.IsValid()) {
623 LOG_ERROR(Service_NFP, "Regenerating mii data");
624 Mii::StoreData new_mii{};
625 new_mii.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
626 new_mii.SetNickname({u'y', u'u', u'z', u'u', u'\0'});
627
628 tag_data.owner_mii.BuildFromStoreData(new_mii);
629 tag_data.mii_extension.SetFromStoreData(new_mii);
630 }
631
609 // Overwrite tag contents with backup and mount the tag 632 // Overwrite tag contents with backup and mount the tag
610 tag_data = temporary_tag_data; 633 tag_data = temporary_tag_data;
611 encrypted_tag_data = temporary_encrypted_tag_data; 634 encrypted_tag_data = temporary_encrypted_tag_data;
@@ -851,25 +874,6 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
851 return Flush(); 874 return Flush();
852} 875}
853 876
854Result NfcDevice::RestoreAmiibo() {
855 if (device_state != DeviceState::TagMounted) {
856 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
857 if (device_state == DeviceState::TagRemoved) {
858 return ResultTagRemoved;
859 }
860 return ResultWrongDeviceState;
861 }
862
863 if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
864 LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
865 return ResultWrongDeviceState;
866 }
867
868 // TODO: Load amiibo from backup on system
869 LOG_ERROR(Service_NFP, "Not Implemented");
870 return ResultSuccess;
871}
872
873Result NfcDevice::Format() { 877Result NfcDevice::Format() {
874 Result result = ResultSuccess; 878 Result result = ResultSuccess;
875 879
@@ -877,7 +881,9 @@ Result NfcDevice::Format() {
877 result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All); 881 result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All);
878 } 882 }
879 883
880 if (result.IsError()) { 884 // We are formatting all data. Corruption is not an issue.
885 if (result.IsError() &&
886 (result != ResultCorruptedData && result != ResultCorruptedDataWithBackup)) {
881 return result; 887 return result;
882 } 888 }
883 889
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h
index 0ed1ff34c..d8efe25ec 100644
--- a/src/core/hle/service/nfc/common/device.h
+++ b/src/core/hle/service/nfc/common/device.h
@@ -68,7 +68,6 @@ public:
68 68
69 Result DeleteRegisterInfo(); 69 Result DeleteRegisterInfo();
70 Result SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info); 70 Result SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info);
71 Result RestoreAmiibo();
72 Result Format(); 71 Result Format();
73 72
74 Result OpenApplicationArea(u32 access_id); 73 Result OpenApplicationArea(u32 access_id);
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index 19c667b42..48304e6d1 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -19,19 +19,8 @@
19 19
20namespace Service::Set { 20namespace Service::Set {
21 21
22namespace { 22Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
23constexpr u64 SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET = 0x05; 23 GetFirmwareVersionType type) {
24
25enum class GetFirmwareVersionType {
26 Version1,
27 Version2,
28};
29
30void GetFirmwareVersionImpl(Core::System& system, HLERequestContext& ctx,
31 GetFirmwareVersionType type) {
32 ASSERT_MSG(ctx.GetWriteBufferSize() == 0x100,
33 "FirmwareVersion output buffer must be 0x100 bytes in size!");
34
35 constexpr u64 FirmwareVersionSystemDataId = 0x0100000000000809; 24 constexpr u64 FirmwareVersionSystemDataId = 0x0100000000000809;
36 auto& fsc = system.GetFileSystemController(); 25 auto& fsc = system.GetFileSystemController();
37 26
@@ -45,46 +34,43 @@ void GetFirmwareVersionImpl(Core::System& system, HLERequestContext& ctx,
45 nca = bis_system->GetEntry(FirmwareVersionSystemDataId, FileSys::ContentRecordType::Data); 34 nca = bis_system->GetEntry(FirmwareVersionSystemDataId, FileSys::ContentRecordType::Data);
46 } 35 }
47 if (nca) { 36 if (nca) {
48 romfs = FileSys::ExtractRomFS(nca->GetRomFS()); 37 if (auto nca_romfs = nca->GetRomFS(); nca_romfs) {
38 romfs = FileSys::ExtractRomFS(nca_romfs);
39 }
49 } 40 }
50 if (!romfs) { 41 if (!romfs) {
51 romfs = FileSys::ExtractRomFS( 42 romfs = FileSys::ExtractRomFS(
52 FileSys::SystemArchive::SynthesizeSystemArchive(FirmwareVersionSystemDataId)); 43 FileSys::SystemArchive::SynthesizeSystemArchive(FirmwareVersionSystemDataId));
53 } 44 }
54 45
55 const auto early_exit_failure = [&ctx](std::string_view desc, Result code) { 46 const auto early_exit_failure = [](std::string_view desc, Result code) {
56 LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).", 47 LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).",
57 desc); 48 desc);
58 IPC::ResponseBuilder rb{ctx, 2}; 49 return code;
59 rb.Push(code);
60 }; 50 };
61 51
62 const auto ver_file = romfs->GetFile("file"); 52 const auto ver_file = romfs->GetFile("file");
63 if (ver_file == nullptr) { 53 if (ver_file == nullptr) {
64 early_exit_failure("The system version archive didn't contain the file 'file'.", 54 return early_exit_failure("The system version archive didn't contain the file 'file'.",
65 FileSys::ERROR_INVALID_ARGUMENT); 55 FileSys::ERROR_INVALID_ARGUMENT);
66 return;
67 } 56 }
68 57
69 auto data = ver_file->ReadAllBytes(); 58 auto data = ver_file->ReadAllBytes();
70 if (data.size() != 0x100) { 59 if (data.size() != sizeof(FirmwareVersionFormat)) {
71 early_exit_failure("The system version file 'file' was not the correct size.", 60 return early_exit_failure("The system version file 'file' was not the correct size.",
72 FileSys::ERROR_OUT_OF_BOUNDS); 61 FileSys::ERROR_OUT_OF_BOUNDS);
73 return;
74 } 62 }
75 63
64 std::memcpy(&out_firmware, data.data(), sizeof(FirmwareVersionFormat));
65
76 // If the command is GetFirmwareVersion (as opposed to GetFirmwareVersion2), hardware will 66 // If the command is GetFirmwareVersion (as opposed to GetFirmwareVersion2), hardware will
77 // zero out the REVISION_MINOR field. 67 // zero out the REVISION_MINOR field.
78 if (type == GetFirmwareVersionType::Version1) { 68 if (type == GetFirmwareVersionType::Version1) {
79 data[SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET] = 0; 69 out_firmware.revision_minor = 0;
80 } 70 }
81 71
82 ctx.WriteBuffer(data); 72 return ResultSuccess;
83
84 IPC::ResponseBuilder rb{ctx, 2};
85 rb.Push(ResultSuccess);
86} 73}
87} // Anonymous namespace
88 74
89void SET_SYS::SetLanguageCode(HLERequestContext& ctx) { 75void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
90 IPC::RequestParser rp{ctx}; 76 IPC::RequestParser rp{ctx};
@@ -98,12 +84,32 @@ void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
98 84
99void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) { 85void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) {
100 LOG_DEBUG(Service_SET, "called"); 86 LOG_DEBUG(Service_SET, "called");
101 GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version1); 87
88 FirmwareVersionFormat firmware_data{};
89 const auto result =
90 GetFirmwareVersionImpl(firmware_data, system, GetFirmwareVersionType::Version1);
91
92 if (result.IsSuccess()) {
93 ctx.WriteBuffer(firmware_data);
94 }
95
96 IPC::ResponseBuilder rb{ctx, 2};
97 rb.Push(result);
102} 98}
103 99
104void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) { 100void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) {
105 LOG_DEBUG(Service_SET, "called"); 101 LOG_DEBUG(Service_SET, "called");
106 GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version2); 102
103 FirmwareVersionFormat firmware_data{};
104 const auto result =
105 GetFirmwareVersionImpl(firmware_data, system, GetFirmwareVersionType::Version2);
106
107 if (result.IsSuccess()) {
108 ctx.WriteBuffer(firmware_data);
109 }
110
111 IPC::ResponseBuilder rb{ctx, 2};
112 rb.Push(result);
107} 113}
108 114
109void SET_SYS::GetAccountSettings(HLERequestContext& ctx) { 115void SET_SYS::GetAccountSettings(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index 93023c6dd..5f770fd32 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -4,6 +4,7 @@
4#pragma once 4#pragma once
5 5
6#include "common/uuid.h" 6#include "common/uuid.h"
7#include "core/hle/result.h"
7#include "core/hle/service/service.h" 8#include "core/hle/service/service.h"
8#include "core/hle/service/time/clock_types.h" 9#include "core/hle/service/time/clock_types.h"
9 10
@@ -12,6 +13,29 @@ class System;
12} 13}
13 14
14namespace Service::Set { 15namespace Service::Set {
16enum class LanguageCode : u64;
17enum class GetFirmwareVersionType {
18 Version1,
19 Version2,
20};
21
22struct FirmwareVersionFormat {
23 u8 major;
24 u8 minor;
25 u8 micro;
26 INSERT_PADDING_BYTES(1);
27 u8 revision_major;
28 u8 revision_minor;
29 INSERT_PADDING_BYTES(2);
30 std::array<char, 0x20> platform;
31 std::array<u8, 0x40> version_hash;
32 std::array<char, 0x18> display_version;
33 std::array<char, 0x80> display_title;
34};
35static_assert(sizeof(FirmwareVersionFormat) == 0x100, "FirmwareVersionFormat is an invalid size");
36
37Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
38 GetFirmwareVersionType type);
15 39
16class SET_SYS final : public ServiceFramework<SET_SYS> { 40class SET_SYS final : public ServiceFramework<SET_SYS> {
17public: 41public:
diff --git a/src/core/hle/service/time/clock_types.h b/src/core/hle/service/time/clock_types.h
index 9fc01ea90..7149fffeb 100644
--- a/src/core/hle/service/time/clock_types.h
+++ b/src/core/hle/service/time/clock_types.h
@@ -11,6 +11,11 @@
11#include "core/hle/service/time/errors.h" 11#include "core/hle/service/time/errors.h"
12#include "core/hle/service/time/time_zone_types.h" 12#include "core/hle/service/time/time_zone_types.h"
13 13
14// Defined by WinBase.h on Windows
15#ifdef GetCurrentTime
16#undef GetCurrentTime
17#endif
18
14namespace Service::Time::Clock { 19namespace Service::Time::Clock {
15 20
16enum class TimeType : u8 { 21enum class TimeType : u8 {
diff --git a/src/frontend_common/CMakeLists.txt b/src/frontend_common/CMakeLists.txt
index 1537271fc..22e9337c4 100644
--- a/src/frontend_common/CMakeLists.txt
+++ b/src/frontend_common/CMakeLists.txt
@@ -7,4 +7,4 @@ add_library(frontend_common STATIC
7) 7)
8 8
9create_target_directory_groups(frontend_common) 9create_target_directory_groups(frontend_common)
10target_link_libraries(frontend_common PUBLIC core SimpleIni PRIVATE common Boost::headers) 10target_link_libraries(frontend_common PUBLIC core SimpleIni::SimpleIni PRIVATE common Boost::headers)
diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp
index 7474cb0f9..1a0491c2c 100644
--- a/src/frontend_common/config.cpp
+++ b/src/frontend_common/config.cpp
@@ -924,12 +924,14 @@ std::string Config::AdjustOutputString(const std::string& string) {
924 924
925 // Windows requires that two forward slashes are used at the start of a path for unmapped 925 // Windows requires that two forward slashes are used at the start of a path for unmapped
926 // network drives so we have to watch for that here 926 // network drives so we have to watch for that here
927#ifndef ANDROID
927 if (string.substr(0, 2) == "//") { 928 if (string.substr(0, 2) == "//") {
928 boost::replace_all(adjusted_string, "//", "/"); 929 boost::replace_all(adjusted_string, "//", "/");
929 adjusted_string.insert(0, "/"); 930 adjusted_string.insert(0, "/");
930 } else { 931 } else {
931 boost::replace_all(adjusted_string, "//", "/"); 932 boost::replace_all(adjusted_string, "//", "/");
932 } 933 }
934#endif
933 935
934 // Needed for backwards compatibility with QSettings deserialization 936 // Needed for backwards compatibility with QSettings deserialization
935 for (const auto& special_character : special_characters) { 937 for (const auto& special_character : special_characters) {
diff --git a/src/frontend_common/config.h b/src/frontend_common/config.h
index 20a1a8056..b3812af17 100644
--- a/src/frontend_common/config.h
+++ b/src/frontend_common/config.h
@@ -7,6 +7,7 @@
7#include <string> 7#include <string>
8#include "common/settings.h" 8#include "common/settings.h"
9 9
10#define SI_NO_CONVERSION
10#include <SimpleIni.h> 11#include <SimpleIni.h>
11#include <boost/algorithm/string/replace.hpp> 12#include <boost/algorithm/string/replace.hpp>
12 13
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
index 2705ab140..9319ea007 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
@@ -5,6 +5,7 @@
5#include "shader_recompiler/backend/glasm/glasm_emit_context.h" 5#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
6#include "shader_recompiler/frontend/ir/program.h" 6#include "shader_recompiler/frontend/ir/program.h"
7#include "shader_recompiler/frontend/ir/value.h" 7#include "shader_recompiler/frontend/ir/value.h"
8#include "shader_recompiler/profile.h"
8#include "shader_recompiler/runtime_info.h" 9#include "shader_recompiler/runtime_info.h"
9 10
10namespace Shader::Backend::GLASM { 11namespace Shader::Backend::GLASM {
@@ -35,7 +36,9 @@ void GlobalStorageOp(EmitContext& ctx, Register address, bool pointer_based, std
35 continue; 36 continue;
36 } 37 }
37 const auto& ssbo{ctx.info.storage_buffers_descriptors[index]}; 38 const auto& ssbo{ctx.info.storage_buffers_descriptors[index]};
38 ctx.Add("LDC.U64 DC.x,c{}[{}];" // ssbo_addr 39 const u64 ssbo_align_mask{~(ctx.profile.min_ssbo_alignment - 1U)};
40 ctx.Add("LDC.U64 DC.x,c{}[{}];" // unaligned_ssbo_addr
41 "AND.U64 DC.x,DC.x,{};" // ssbo_addr = unaligned_ssbo_addr & ssbo_align_mask
39 "LDC.U32 RC.x,c{}[{}];" // ssbo_size_u32 42 "LDC.U32 RC.x,c{}[{}];" // ssbo_size_u32
40 "CVT.U64.U32 DC.y,RC.x;" // ssbo_size = ssbo_size_u32 43 "CVT.U64.U32 DC.y,RC.x;" // ssbo_size = ssbo_size_u32
41 "ADD.U64 DC.y,DC.y,DC.x;" // ssbo_end = ssbo_addr + ssbo_size 44 "ADD.U64 DC.y,DC.y,DC.x;" // ssbo_end = ssbo_addr + ssbo_size
@@ -44,8 +47,8 @@ void GlobalStorageOp(EmitContext& ctx, Register address, bool pointer_based, std
44 "AND.U.CC RC.x,RC.x,RC.y;" // cond = a && b 47 "AND.U.CC RC.x,RC.x,RC.y;" // cond = a && b
45 "IF NE.x;" // if cond 48 "IF NE.x;" // if cond
46 "SUB.U64 DC.x,{}.x,DC.x;", // offset = input_addr - ssbo_addr 49 "SUB.U64 DC.x,{}.x,DC.x;", // offset = input_addr - ssbo_addr
47 ssbo.cbuf_index, ssbo.cbuf_offset, ssbo.cbuf_index, ssbo.cbuf_offset + 8, address, 50 ssbo.cbuf_index, ssbo.cbuf_offset, ssbo_align_mask, ssbo.cbuf_index,
48 address, address); 51 ssbo.cbuf_offset + 8, address, address, address);
49 if (pointer_based) { 52 if (pointer_based) {
50 ctx.Add("PK64.U DC.y,c[{}];" // host_ssbo = cbuf 53 ctx.Add("PK64.U DC.y,c[{}];" // host_ssbo = cbuf
51 "ADD.U64 DC.x,DC.x,DC.y;" // host_addr = host_ssbo + offset 54 "ADD.U64 DC.x,DC.x,DC.y;" // host_addr = host_ssbo + offset
diff --git a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
index 9ff4028c2..fd9a99449 100644
--- a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
+++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
@@ -601,7 +601,10 @@ std::string EmitContext::DefineGlobalMemoryFunctions() {
601 addr_xy[i] = fmt::format("ftou({}[{}].{})", cbuf, addr_loc / 16, Swizzle(addr_loc)); 601 addr_xy[i] = fmt::format("ftou({}[{}].{})", cbuf, addr_loc / 16, Swizzle(addr_loc));
602 size_xy[i] = fmt::format("ftou({}[{}].{})", cbuf, size_loc / 16, Swizzle(size_loc)); 602 size_xy[i] = fmt::format("ftou({}[{}].{})", cbuf, size_loc / 16, Swizzle(size_loc));
603 } 603 }
604 const auto addr_pack{fmt::format("packUint2x32(uvec2({},{}))", addr_xy[0], addr_xy[1])}; 604 const u32 ssbo_align_mask{~(static_cast<u32>(profile.min_ssbo_alignment) - 1U)};
605 const auto aligned_low_addr{fmt::format("{}&{}", addr_xy[0], ssbo_align_mask)};
606 const auto aligned_addr{fmt::format("uvec2({},{})", aligned_low_addr, addr_xy[1])};
607 const auto addr_pack{fmt::format("packUint2x32({})", aligned_addr)};
605 const auto addr_statment{fmt::format("uint64_t {}={};", ssbo_addr, addr_pack)}; 608 const auto addr_statment{fmt::format("uint64_t {}={};", ssbo_addr, addr_pack)};
606 func += addr_statment; 609 func += addr_statment;
607 610
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 57df6fc34..3350f1f85 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -891,7 +891,9 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
891 const Id ssbo_size_pointer{OpAccessChain(uniform_types.U32, cbufs[ssbo.cbuf_index].U32, 891 const Id ssbo_size_pointer{OpAccessChain(uniform_types.U32, cbufs[ssbo.cbuf_index].U32,
892 zero, ssbo_size_cbuf_offset)}; 892 zero, ssbo_size_cbuf_offset)};
893 893
894 const Id ssbo_addr{OpBitcast(U64, OpLoad(U32[2], ssbo_addr_pointer))}; 894 const u64 ssbo_align_mask{~(profile.min_ssbo_alignment - 1U)};
895 const Id unaligned_addr{OpBitcast(U64, OpLoad(U32[2], ssbo_addr_pointer))};
896 const Id ssbo_addr{OpBitwiseAnd(U64, unaligned_addr, Constant(U64, ssbo_align_mask))};
895 const Id ssbo_size{OpUConvert(U64, OpLoad(U32[1], ssbo_size_pointer))}; 897 const Id ssbo_size{OpUConvert(U64, OpLoad(U32[1], ssbo_size_pointer))};
896 const Id ssbo_end{OpIAdd(U64, ssbo_addr, ssbo_size)}; 898 const Id ssbo_end{OpIAdd(U64, ssbo_addr, ssbo_size)};
897 const Id cond{OpLogicalAnd(U1, OpUGreaterThanEqual(U1, addr, ssbo_addr), 899 const Id cond{OpLogicalAnd(U1, OpUGreaterThanEqual(U1, addr, ssbo_addr),
diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
index 8fac6bad3..321ea625b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
@@ -298,7 +298,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
298 298
299 Optimization::PositionPass(env, program); 299 Optimization::PositionPass(env, program);
300 300
301 Optimization::GlobalMemoryToStorageBufferPass(program); 301 Optimization::GlobalMemoryToStorageBufferPass(program, host_info);
302 Optimization::TexturePass(env, program, host_info); 302 Optimization::TexturePass(env, program, host_info);
303 303
304 if (Settings::values.resolution_info.active) { 304 if (Settings::values.resolution_info.active) {
diff --git a/src/shader_recompiler/host_translate_info.h b/src/shader_recompiler/host_translate_info.h
index 7d2ded907..1b53404fc 100644
--- a/src/shader_recompiler/host_translate_info.h
+++ b/src/shader_recompiler/host_translate_info.h
@@ -16,6 +16,7 @@ struct HostTranslateInfo {
16 bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered 16 bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered
17 bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers 17 bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers
18 bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS 18 bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS
19 u32 min_ssbo_alignment{}; ///< Minimum alignment supported by the device for SSBOs
19 bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry 20 bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry
20 ///< passthrough shaders 21 ///< passthrough shaders
21 bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional 22 bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional
diff --git a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
index d1e59f22e..0cea79945 100644
--- a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
+++ b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
@@ -11,6 +11,7 @@
11#include "shader_recompiler/frontend/ir/breadth_first_search.h" 11#include "shader_recompiler/frontend/ir/breadth_first_search.h"
12#include "shader_recompiler/frontend/ir/ir_emitter.h" 12#include "shader_recompiler/frontend/ir/ir_emitter.h"
13#include "shader_recompiler/frontend/ir/value.h" 13#include "shader_recompiler/frontend/ir/value.h"
14#include "shader_recompiler/host_translate_info.h"
14#include "shader_recompiler/ir_opt/passes.h" 15#include "shader_recompiler/ir_opt/passes.h"
15 16
16namespace Shader::Optimization { 17namespace Shader::Optimization {
@@ -408,7 +409,7 @@ void CollectStorageBuffers(IR::Block& block, IR::Inst& inst, StorageInfo& info)
408} 409}
409 410
410/// Returns the offset in indices (not bytes) for an equivalent storage instruction 411/// Returns the offset in indices (not bytes) for an equivalent storage instruction
411IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer) { 412IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer, u32 alignment) {
412 IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; 413 IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
413 IR::U32 offset; 414 IR::U32 offset;
414 if (const std::optional<LowAddrInfo> low_addr{TrackLowAddress(&inst)}) { 415 if (const std::optional<LowAddrInfo> low_addr{TrackLowAddress(&inst)}) {
@@ -421,7 +422,10 @@ IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer
421 } 422 }
422 // Subtract the least significant 32 bits from the guest offset. The result is the storage 423 // Subtract the least significant 32 bits from the guest offset. The result is the storage
423 // buffer offset in bytes. 424 // buffer offset in bytes.
424 const IR::U32 low_cbuf{ir.GetCbuf(ir.Imm32(buffer.index), ir.Imm32(buffer.offset))}; 425 IR::U32 low_cbuf{ir.GetCbuf(ir.Imm32(buffer.index), ir.Imm32(buffer.offset))};
426
427 // Align the offset base to match the host alignment requirements
428 low_cbuf = ir.BitwiseAnd(low_cbuf, ir.Imm32(~(alignment - 1U)));
425 return ir.ISub(offset, low_cbuf); 429 return ir.ISub(offset, low_cbuf);
426} 430}
427 431
@@ -516,7 +520,7 @@ void Replace(IR::Block& block, IR::Inst& inst, const IR::U32& storage_index,
516} 520}
517} // Anonymous namespace 521} // Anonymous namespace
518 522
519void GlobalMemoryToStorageBufferPass(IR::Program& program) { 523void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateInfo& host_info) {
520 StorageInfo info; 524 StorageInfo info;
521 for (IR::Block* const block : program.post_order_blocks) { 525 for (IR::Block* const block : program.post_order_blocks) {
522 for (IR::Inst& inst : block->Instructions()) { 526 for (IR::Inst& inst : block->Instructions()) {
@@ -540,7 +544,8 @@ void GlobalMemoryToStorageBufferPass(IR::Program& program) {
540 const IR::U32 index{IR::Value{static_cast<u32>(info.set.index_of(it))}}; 544 const IR::U32 index{IR::Value{static_cast<u32>(info.set.index_of(it))}};
541 IR::Block* const block{storage_inst.block}; 545 IR::Block* const block{storage_inst.block};
542 IR::Inst* const inst{storage_inst.inst}; 546 IR::Inst* const inst{storage_inst.inst};
543 const IR::U32 offset{StorageOffset(*block, *inst, storage_buffer)}; 547 const IR::U32 offset{
548 StorageOffset(*block, *inst, storage_buffer, host_info.min_ssbo_alignment)};
544 Replace(*block, *inst, index, offset); 549 Replace(*block, *inst, index, offset);
545 } 550 }
546} 551}
diff --git a/src/shader_recompiler/ir_opt/passes.h b/src/shader_recompiler/ir_opt/passes.h
index d4d5285e5..1e637cb23 100644
--- a/src/shader_recompiler/ir_opt/passes.h
+++ b/src/shader_recompiler/ir_opt/passes.h
@@ -16,7 +16,7 @@ void CollectShaderInfoPass(Environment& env, IR::Program& program);
16void ConditionalBarrierPass(IR::Program& program); 16void ConditionalBarrierPass(IR::Program& program);
17void ConstantPropagationPass(Environment& env, IR::Program& program); 17void ConstantPropagationPass(Environment& env, IR::Program& program);
18void DeadCodeEliminationPass(IR::Program& program); 18void DeadCodeEliminationPass(IR::Program& program);
19void GlobalMemoryToStorageBufferPass(IR::Program& program); 19void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateInfo& host_info);
20void IdentityRemovalPass(IR::Program& program); 20void IdentityRemovalPass(IR::Program& program);
21void LowerFp64ToFp32(IR::Program& program); 21void LowerFp64ToFp32(IR::Program& program);
22void LowerFp16ToFp32(IR::Program& program); 22void LowerFp16ToFp32(IR::Program& program);
diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h
index a9de9f4a9..66901a965 100644
--- a/src/shader_recompiler/profile.h
+++ b/src/shader_recompiler/profile.h
@@ -85,6 +85,8 @@ struct Profile {
85 85
86 /// Maxwell and earlier nVidia architectures have broken robust support 86 /// Maxwell and earlier nVidia architectures have broken robust support
87 bool has_broken_robust{}; 87 bool has_broken_robust{};
88
89 u64 min_ssbo_alignment{};
88}; 90};
89 91
90} // namespace Shader 92} // namespace Shader
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index b65b9f2a2..c22c7631c 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -15,6 +15,7 @@ add_library(video_core STATIC
15 buffer_cache/buffer_cache.cpp 15 buffer_cache/buffer_cache.cpp
16 buffer_cache/buffer_cache.h 16 buffer_cache/buffer_cache.h
17 buffer_cache/memory_tracker_base.h 17 buffer_cache/memory_tracker_base.h
18 buffer_cache/usage_tracker.h
18 buffer_cache/word_manager.h 19 buffer_cache/word_manager.h
19 cache_types.h 20 cache_types.h
20 cdma_pusher.cpp 21 cdma_pusher.cpp
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index f5b10411b..6d1fc3887 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -67,6 +67,7 @@ void BufferCache<P>::TickFrame() {
67 if (!channel_state) { 67 if (!channel_state) {
68 return; 68 return;
69 } 69 }
70 runtime.TickFrame(slot_buffers);
70 71
71 // Calculate hits and shots and move hit bits to the right 72 // Calculate hits and shots and move hit bits to the right
72 const u32 hits = std::reduce(channel_state->uniform_cache_hits.begin(), 73 const u32 hits = std::reduce(channel_state->uniform_cache_hits.begin(),
@@ -230,7 +231,10 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
230 for (const IntervalType& add_interval : tmp_intervals) { 231 for (const IntervalType& add_interval : tmp_intervals) {
231 common_ranges.add(add_interval); 232 common_ranges.add(add_interval);
232 } 233 }
233 runtime.CopyBuffer(dest_buffer, src_buffer, copies); 234 const auto& copy = copies[0];
235 src_buffer.MarkUsage(copy.src_offset, copy.size);
236 dest_buffer.MarkUsage(copy.dst_offset, copy.size);
237 runtime.CopyBuffer(dest_buffer, src_buffer, copies, true);
234 if (has_new_downloads) { 238 if (has_new_downloads) {
235 memory_tracker.MarkRegionAsGpuModified(*cpu_dest_address, amount); 239 memory_tracker.MarkRegionAsGpuModified(*cpu_dest_address, amount);
236 } 240 }
@@ -258,9 +262,10 @@ bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
258 common_ranges.subtract(subtract_interval); 262 common_ranges.subtract(subtract_interval);
259 263
260 const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast<u32>(size)); 264 const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast<u32>(size));
261 auto& dest_buffer = slot_buffers[buffer]; 265 Buffer& dest_buffer = slot_buffers[buffer];
262 const u32 offset = dest_buffer.Offset(*cpu_dst_address); 266 const u32 offset = dest_buffer.Offset(*cpu_dst_address);
263 runtime.ClearBuffer(dest_buffer, offset, size, value); 267 runtime.ClearBuffer(dest_buffer, offset, size, value);
268 dest_buffer.MarkUsage(offset, size);
264 return true; 269 return true;
265} 270}
266 271
@@ -603,6 +608,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
603 VAddr orig_cpu_addr = static_cast<VAddr>(second_copy.src_offset); 608 VAddr orig_cpu_addr = static_cast<VAddr>(second_copy.src_offset);
604 const IntervalType base_interval{orig_cpu_addr, orig_cpu_addr + copy.size}; 609 const IntervalType base_interval{orig_cpu_addr, orig_cpu_addr + copy.size};
605 async_downloads += std::make_pair(base_interval, 1); 610 async_downloads += std::make_pair(base_interval, 1);
611 buffer.MarkUsage(copy.src_offset, copy.size);
606 runtime.CopyBuffer(download_staging.buffer, buffer, copies, false); 612 runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
607 normalized_copies.push_back(second_copy); 613 normalized_copies.push_back(second_copy);
608 } 614 }
@@ -621,8 +627,9 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
621 // Have in mind the staging buffer offset for the copy 627 // Have in mind the staging buffer offset for the copy
622 copy.dst_offset += download_staging.offset; 628 copy.dst_offset += download_staging.offset;
623 const std::array copies{copy}; 629 const std::array copies{copy};
624 runtime.CopyBuffer(download_staging.buffer, slot_buffers[buffer_id], copies, 630 Buffer& buffer = slot_buffers[buffer_id];
625 false); 631 buffer.MarkUsage(copy.src_offset, copy.size);
632 runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
626 } 633 }
627 runtime.PostCopyBarrier(); 634 runtime.PostCopyBarrier();
628 runtime.Finish(); 635 runtime.Finish();
@@ -742,7 +749,7 @@ void BufferCache<P>::BindHostIndexBuffer() {
742 {BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}}; 749 {BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}};
743 std::memcpy(upload_staging.mapped_span.data(), 750 std::memcpy(upload_staging.mapped_span.data(),
744 draw_state.inline_index_draw_indexes.data(), size); 751 draw_state.inline_index_draw_indexes.data(), size);
745 runtime.CopyBuffer(buffer, upload_staging.buffer, copies); 752 runtime.CopyBuffer(buffer, upload_staging.buffer, copies, true);
746 } else { 753 } else {
747 buffer.ImmediateUpload(0, draw_state.inline_index_draw_indexes); 754 buffer.ImmediateUpload(0, draw_state.inline_index_draw_indexes);
748 } 755 }
@@ -754,6 +761,7 @@ void BufferCache<P>::BindHostIndexBuffer() {
754 offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes(); 761 offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes();
755 runtime.BindIndexBuffer(buffer, new_offset, size); 762 runtime.BindIndexBuffer(buffer, new_offset, size);
756 } else { 763 } else {
764 buffer.MarkUsage(offset, size);
757 runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format, 765 runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format,
758 draw_state.index_buffer.first, draw_state.index_buffer.count, 766 draw_state.index_buffer.first, draw_state.index_buffer.count,
759 buffer, offset, size); 767 buffer, offset, size);
@@ -790,6 +798,7 @@ void BufferCache<P>::BindHostVertexBuffers() {
790 798
791 const u32 stride = maxwell3d->regs.vertex_streams[index].stride; 799 const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
792 const u32 offset = buffer.Offset(binding.cpu_addr); 800 const u32 offset = buffer.Offset(binding.cpu_addr);
801 buffer.MarkUsage(offset, binding.size);
793 802
794 host_bindings.buffers.push_back(&buffer); 803 host_bindings.buffers.push_back(&buffer);
795 host_bindings.offsets.push_back(offset); 804 host_bindings.offsets.push_back(offset);
@@ -895,6 +904,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
895 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { 904 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
896 channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; 905 channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size;
897 } 906 }
907 buffer.MarkUsage(offset, size);
898 if constexpr (NEEDS_BIND_UNIFORM_INDEX) { 908 if constexpr (NEEDS_BIND_UNIFORM_INDEX) {
899 runtime.BindUniformBuffer(stage, binding_index, buffer, offset, size); 909 runtime.BindUniformBuffer(stage, binding_index, buffer, offset, size);
900 } else { 910 } else {
@@ -913,6 +923,7 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
913 SynchronizeBuffer(buffer, binding.cpu_addr, size); 923 SynchronizeBuffer(buffer, binding.cpu_addr, size);
914 924
915 const u32 offset = buffer.Offset(binding.cpu_addr); 925 const u32 offset = buffer.Offset(binding.cpu_addr);
926 buffer.MarkUsage(offset, size);
916 const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0; 927 const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
917 928
918 if (is_written) { 929 if (is_written) {
@@ -943,6 +954,7 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
943 954
944 const u32 offset = buffer.Offset(binding.cpu_addr); 955 const u32 offset = buffer.Offset(binding.cpu_addr);
945 const PixelFormat format = binding.format; 956 const PixelFormat format = binding.format;
957 buffer.MarkUsage(offset, size);
946 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 958 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
947 if (((channel_state->image_texture_buffers[stage] >> index) & 1) != 0) { 959 if (((channel_state->image_texture_buffers[stage] >> index) & 1) != 0) {
948 runtime.BindImageBuffer(buffer, offset, size, format); 960 runtime.BindImageBuffer(buffer, offset, size, format);
@@ -975,9 +987,10 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
975 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size); 987 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
976 988
977 const u32 offset = buffer.Offset(binding.cpu_addr); 989 const u32 offset = buffer.Offset(binding.cpu_addr);
990 buffer.MarkUsage(offset, size);
978 host_bindings.buffers.push_back(&buffer); 991 host_bindings.buffers.push_back(&buffer);
979 host_bindings.offsets.push_back(offset); 992 host_bindings.offsets.push_back(offset);
980 host_bindings.sizes.push_back(binding.size); 993 host_bindings.sizes.push_back(size);
981 } 994 }
982 if (host_bindings.buffers.size() > 0) { 995 if (host_bindings.buffers.size() > 0) {
983 runtime.BindTransformFeedbackBuffers(host_bindings); 996 runtime.BindTransformFeedbackBuffers(host_bindings);
@@ -1001,6 +1014,7 @@ void BufferCache<P>::BindHostComputeUniformBuffers() {
1001 SynchronizeBuffer(buffer, binding.cpu_addr, size); 1014 SynchronizeBuffer(buffer, binding.cpu_addr, size);
1002 1015
1003 const u32 offset = buffer.Offset(binding.cpu_addr); 1016 const u32 offset = buffer.Offset(binding.cpu_addr);
1017 buffer.MarkUsage(offset, size);
1004 if constexpr (NEEDS_BIND_UNIFORM_INDEX) { 1018 if constexpr (NEEDS_BIND_UNIFORM_INDEX) {
1005 runtime.BindComputeUniformBuffer(binding_index, buffer, offset, size); 1019 runtime.BindComputeUniformBuffer(binding_index, buffer, offset, size);
1006 ++binding_index; 1020 ++binding_index;
@@ -1021,6 +1035,7 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
1021 SynchronizeBuffer(buffer, binding.cpu_addr, size); 1035 SynchronizeBuffer(buffer, binding.cpu_addr, size);
1022 1036
1023 const u32 offset = buffer.Offset(binding.cpu_addr); 1037 const u32 offset = buffer.Offset(binding.cpu_addr);
1038 buffer.MarkUsage(offset, size);
1024 const bool is_written = 1039 const bool is_written =
1025 ((channel_state->written_compute_storage_buffers >> index) & 1) != 0; 1040 ((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
1026 1041
@@ -1053,6 +1068,7 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
1053 1068
1054 const u32 offset = buffer.Offset(binding.cpu_addr); 1069 const u32 offset = buffer.Offset(binding.cpu_addr);
1055 const PixelFormat format = binding.format; 1070 const PixelFormat format = binding.format;
1071 buffer.MarkUsage(offset, size);
1056 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 1072 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
1057 if (((channel_state->image_compute_texture_buffers >> index) & 1) != 0) { 1073 if (((channel_state->image_compute_texture_buffers >> index) & 1) != 0) {
1058 runtime.BindImageBuffer(buffer, offset, size, format); 1074 runtime.BindImageBuffer(buffer, offset, size, format);
@@ -1172,10 +1188,11 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) {
1172 if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end)) { 1188 if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end)) {
1173 size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size)); 1189 size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
1174 } 1190 }
1191 const BufferId buffer_id = FindBuffer(*cpu_addr, size);
1175 channel_state->vertex_buffers[index] = Binding{ 1192 channel_state->vertex_buffers[index] = Binding{
1176 .cpu_addr = *cpu_addr, 1193 .cpu_addr = *cpu_addr,
1177 .size = size, 1194 .size = size,
1178 .buffer_id = FindBuffer(*cpu_addr, size), 1195 .buffer_id = buffer_id,
1179 }; 1196 };
1180} 1197}
1181 1198
@@ -1401,7 +1418,8 @@ void BufferCache<P>::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id,
1401 .dst_offset = dst_base_offset, 1418 .dst_offset = dst_base_offset,
1402 .size = overlap.SizeBytes(), 1419 .size = overlap.SizeBytes(),
1403 }); 1420 });
1404 runtime.CopyBuffer(new_buffer, overlap, copies); 1421 new_buffer.MarkUsage(copies[0].dst_offset, copies[0].size);
1422 runtime.CopyBuffer(new_buffer, overlap, copies, true);
1405 DeleteBuffer(overlap_id, true); 1423 DeleteBuffer(overlap_id, true);
1406} 1424}
1407 1425
@@ -1414,7 +1432,9 @@ BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) {
1414 const u32 size = static_cast<u32>(overlap.end - overlap.begin); 1432 const u32 size = static_cast<u32>(overlap.end - overlap.begin);
1415 const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size); 1433 const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size);
1416 auto& new_buffer = slot_buffers[new_buffer_id]; 1434 auto& new_buffer = slot_buffers[new_buffer_id];
1417 runtime.ClearBuffer(new_buffer, 0, new_buffer.SizeBytes(), 0); 1435 const size_t size_bytes = new_buffer.SizeBytes();
1436 runtime.ClearBuffer(new_buffer, 0, size_bytes, 0);
1437 new_buffer.MarkUsage(0, size_bytes);
1418 for (const BufferId overlap_id : overlap.ids) { 1438 for (const BufferId overlap_id : overlap.ids) {
1419 JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap); 1439 JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap);
1420 } 1440 }
@@ -1467,11 +1487,6 @@ void BufferCache<P>::TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept {
1467 1487
1468template <class P> 1488template <class P>
1469bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) { 1489bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) {
1470 return SynchronizeBufferImpl(buffer, cpu_addr, size);
1471}
1472
1473template <class P>
1474bool BufferCache<P>::SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size) {
1475 boost::container::small_vector<BufferCopy, 4> copies; 1490 boost::container::small_vector<BufferCopy, 4> copies;
1476 u64 total_size_bytes = 0; 1491 u64 total_size_bytes = 0;
1477 u64 largest_copy = 0; 1492 u64 largest_copy = 0;
@@ -1494,51 +1509,6 @@ bool BufferCache<P>::SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 s
1494} 1509}
1495 1510
1496template <class P> 1511template <class P>
1497bool BufferCache<P>::SynchronizeBufferNoModified(Buffer& buffer, VAddr cpu_addr, u32 size) {
1498 boost::container::small_vector<BufferCopy, 4> copies;
1499 u64 total_size_bytes = 0;
1500 u64 largest_copy = 0;
1501 IntervalSet found_sets{};
1502 auto make_copies = [&] {
1503 for (auto& interval : found_sets) {
1504 const std::size_t sub_size = interval.upper() - interval.lower();
1505 const VAddr cpu_addr_ = interval.lower();
1506 copies.push_back(BufferCopy{
1507 .src_offset = total_size_bytes,
1508 .dst_offset = cpu_addr_ - buffer.CpuAddr(),
1509 .size = sub_size,
1510 });
1511 total_size_bytes += sub_size;
1512 largest_copy = std::max<u64>(largest_copy, sub_size);
1513 }
1514 const std::span<BufferCopy> copies_span(copies.data(), copies.size());
1515 UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
1516 };
1517 memory_tracker.ForEachUploadRange(cpu_addr, size, [&](u64 cpu_addr_out, u64 range_size) {
1518 const VAddr base_adr = cpu_addr_out;
1519 const VAddr end_adr = base_adr + range_size;
1520 const IntervalType add_interval{base_adr, end_adr};
1521 found_sets.add(add_interval);
1522 });
1523 if (found_sets.empty()) {
1524 return true;
1525 }
1526 const IntervalType search_interval{cpu_addr, cpu_addr + size};
1527 auto it = common_ranges.lower_bound(search_interval);
1528 auto it_end = common_ranges.upper_bound(search_interval);
1529 if (it == common_ranges.end()) {
1530 make_copies();
1531 return false;
1532 }
1533 while (it != it_end) {
1534 found_sets.subtract(*it);
1535 it++;
1536 }
1537 make_copies();
1538 return false;
1539}
1540
1541template <class P>
1542void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy, 1512void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy,
1543 std::span<BufferCopy> copies) { 1513 std::span<BufferCopy> copies) {
1544 if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) { 1514 if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
@@ -1586,7 +1556,8 @@ void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
1586 // Apply the staging offset 1556 // Apply the staging offset
1587 copy.src_offset += upload_staging.offset; 1557 copy.src_offset += upload_staging.offset;
1588 } 1558 }
1589 runtime.CopyBuffer(buffer, upload_staging.buffer, copies); 1559 const bool can_reorder = runtime.CanReorderUpload(buffer, copies);
1560 runtime.CopyBuffer(buffer, upload_staging.buffer, copies, true, can_reorder);
1590 } 1561 }
1591} 1562}
1592 1563
@@ -1628,7 +1599,8 @@ void BufferCache<P>::InlineMemoryImplementation(VAddr dest_address, size_t copy_
1628 }}; 1599 }};
1629 u8* const src_pointer = upload_staging.mapped_span.data(); 1600 u8* const src_pointer = upload_staging.mapped_span.data();
1630 std::memcpy(src_pointer, inlined_buffer.data(), copy_size); 1601 std::memcpy(src_pointer, inlined_buffer.data(), copy_size);
1631 runtime.CopyBuffer(buffer, upload_staging.buffer, copies); 1602 const bool can_reorder = runtime.CanReorderUpload(buffer, copies);
1603 runtime.CopyBuffer(buffer, upload_staging.buffer, copies, true, can_reorder);
1632 } else { 1604 } else {
1633 buffer.ImmediateUpload(buffer.Offset(dest_address), inlined_buffer.first(copy_size)); 1605 buffer.ImmediateUpload(buffer.Offset(dest_address), inlined_buffer.first(copy_size));
1634 } 1606 }
@@ -1681,8 +1653,9 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 si
1681 for (BufferCopy& copy : copies) { 1653 for (BufferCopy& copy : copies) {
1682 // Modify copies to have the staging offset in mind 1654 // Modify copies to have the staging offset in mind
1683 copy.dst_offset += download_staging.offset; 1655 copy.dst_offset += download_staging.offset;
1656 buffer.MarkUsage(copy.src_offset, copy.size);
1684 } 1657 }
1685 runtime.CopyBuffer(download_staging.buffer, buffer, copies_span); 1658 runtime.CopyBuffer(download_staging.buffer, buffer, copies_span, true);
1686 runtime.Finish(); 1659 runtime.Finish();
1687 for (const BufferCopy& copy : copies) { 1660 for (const BufferCopy& copy : copies) {
1688 const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset; 1661 const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset;
@@ -1780,15 +1753,25 @@ Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
1780 const u32 memory_layout_size = static_cast<u32>(gpu_memory->GetMemoryLayoutSize(gpu_addr)); 1753 const u32 memory_layout_size = static_cast<u32>(gpu_memory->GetMemoryLayoutSize(gpu_addr));
1781 return std::min(memory_layout_size, static_cast<u32>(8_MiB)); 1754 return std::min(memory_layout_size, static_cast<u32>(8_MiB));
1782 }(); 1755 }();
1783 const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr); 1756 // Alignment only applies to the offset of the buffer
1784 if (!cpu_addr || size == 0) { 1757 const u32 alignment = runtime.GetStorageBufferAlignment();
1758 const GPUVAddr aligned_gpu_addr = Common::AlignDown(gpu_addr, alignment);
1759 const u32 aligned_size = static_cast<u32>(gpu_addr - aligned_gpu_addr) + size;
1760
1761 const std::optional<VAddr> aligned_cpu_addr = gpu_memory->GpuToCpuAddress(aligned_gpu_addr);
1762 if (!aligned_cpu_addr || size == 0) {
1785 LOG_WARNING(HW_GPU, "Failed to find storage buffer for cbuf index {}", cbuf_index); 1763 LOG_WARNING(HW_GPU, "Failed to find storage buffer for cbuf index {}", cbuf_index);
1786 return NULL_BINDING; 1764 return NULL_BINDING;
1787 } 1765 }
1788 const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, YUZU_PAGESIZE); 1766 const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
1767 ASSERT_MSG(cpu_addr, "Unaligned storage buffer address not found for cbuf index {}",
1768 cbuf_index);
1769 // The end address used for size calculation does not need to be aligned
1770 const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, Core::Memory::YUZU_PAGESIZE);
1771
1789 const Binding binding{ 1772 const Binding binding{
1790 .cpu_addr = *cpu_addr, 1773 .cpu_addr = *aligned_cpu_addr,
1791 .size = is_written ? size : static_cast<u32>(cpu_end - *cpu_addr), 1774 .size = is_written ? aligned_size : static_cast<u32>(cpu_end - *aligned_cpu_addr),
1792 .buffer_id = BufferId{}, 1775 .buffer_id = BufferId{},
1793 }; 1776 };
1794 return binding; 1777 return binding;
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index eed267361..d6d696d8c 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -529,10 +529,6 @@ private:
529 529
530 bool SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size); 530 bool SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size);
531 531
532 bool SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size);
533
534 bool SynchronizeBufferNoModified(Buffer& buffer, VAddr cpu_addr, u32 size);
535
536 void UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy, 532 void UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy,
537 std::span<BufferCopy> copies); 533 std::span<BufferCopy> copies);
538 534
diff --git a/src/video_core/buffer_cache/usage_tracker.h b/src/video_core/buffer_cache/usage_tracker.h
new file mode 100644
index 000000000..5f8688d31
--- /dev/null
+++ b/src/video_core/buffer_cache/usage_tracker.h
@@ -0,0 +1,79 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/alignment.h"
7#include "common/common_types.h"
8
9namespace VideoCommon {
10
11class UsageTracker {
12 static constexpr size_t BYTES_PER_BIT_SHIFT = 6;
13 static constexpr size_t PAGE_SHIFT = 6 + BYTES_PER_BIT_SHIFT;
14 static constexpr size_t PAGE_BYTES = 1 << PAGE_SHIFT;
15
16public:
17 explicit UsageTracker(size_t size) {
18 const size_t num_pages = (size >> PAGE_SHIFT) + 1;
19 pages.resize(num_pages, 0ULL);
20 }
21
22 void Reset() noexcept {
23 std::ranges::fill(pages, 0ULL);
24 }
25
26 void Track(u64 offset, u64 size) noexcept {
27 const size_t page = offset >> PAGE_SHIFT;
28 const size_t page_end = (offset + size) >> PAGE_SHIFT;
29 TrackPage(page, offset, size);
30 if (page == page_end) {
31 return;
32 }
33 for (size_t i = page + 1; i < page_end; i++) {
34 pages[i] = ~u64{0};
35 }
36 const size_t offset_end = offset + size;
37 const size_t offset_end_page_aligned = Common::AlignDown(offset_end, PAGE_BYTES);
38 TrackPage(page_end, offset_end_page_aligned, offset_end - offset_end_page_aligned);
39 }
40
41 [[nodiscard]] bool IsUsed(u64 offset, u64 size) const noexcept {
42 const size_t page = offset >> PAGE_SHIFT;
43 const size_t page_end = (offset + size) >> PAGE_SHIFT;
44 if (IsPageUsed(page, offset, size)) {
45 return true;
46 }
47 for (size_t i = page + 1; i < page_end; i++) {
48 if (pages[i] != 0) {
49 return true;
50 }
51 }
52 const size_t offset_end = offset + size;
53 const size_t offset_end_page_aligned = Common::AlignDown(offset_end, PAGE_BYTES);
54 return IsPageUsed(page_end, offset_end_page_aligned, offset_end - offset_end_page_aligned);
55 }
56
57private:
58 void TrackPage(u64 page, u64 offset, u64 size) noexcept {
59 const size_t offset_in_page = offset % PAGE_BYTES;
60 const size_t first_bit = offset_in_page >> BYTES_PER_BIT_SHIFT;
61 const size_t num_bits = std::min<size_t>(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT;
62 const size_t mask = ~u64{0} >> (64 - num_bits);
63 pages[page] |= (~u64{0} & mask) << first_bit;
64 }
65
66 bool IsPageUsed(u64 page, u64 offset, u64 size) const noexcept {
67 const size_t offset_in_page = offset % PAGE_BYTES;
68 const size_t first_bit = offset_in_page >> BYTES_PER_BIT_SHIFT;
69 const size_t num_bits = std::min<size_t>(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT;
70 const size_t mask = ~u64{0} >> (64 - num_bits);
71 const size_t mask2 = (~u64{0} & mask) << first_bit;
72 return (pages[page] & mask2) != 0;
73 }
74
75private:
76 std::vector<u64> pages;
77};
78
79} // namespace VideoCommon
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 38d553d3c..dfd696de6 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -178,13 +178,14 @@ void BufferCacheRuntime::CopyBuffer(GLuint dst_buffer, Buffer& src_buffer,
178} 178}
179 179
180void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, GLuint src_buffer, 180void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, GLuint src_buffer,
181 std::span<const VideoCommon::BufferCopy> copies, bool barrier) { 181 std::span<const VideoCommon::BufferCopy> copies, bool barrier,
182 bool) {
182 CopyBuffer(dst_buffer.Handle(), src_buffer, copies, barrier); 183 CopyBuffer(dst_buffer.Handle(), src_buffer, copies, barrier);
183} 184}
184 185
185void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer, 186void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer,
186 std::span<const VideoCommon::BufferCopy> copies) { 187 std::span<const VideoCommon::BufferCopy> copies, bool) {
187 CopyBuffer(dst_buffer.Handle(), src_buffer.Handle(), copies); 188 CopyBuffer(dst_buffer.Handle(), src_buffer.Handle(), copies, true);
188} 189}
189 190
190void BufferCacheRuntime::PreCopyBarrier() { 191void BufferCacheRuntime::PreCopyBarrier() {
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index 41b746f3b..000f29a82 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -30,6 +30,8 @@ public:
30 30
31 void MakeResident(GLenum access) noexcept; 31 void MakeResident(GLenum access) noexcept;
32 32
33 void MarkUsage(u64 offset, u64 size) {}
34
33 [[nodiscard]] GLuint View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format); 35 [[nodiscard]] GLuint View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format);
34 36
35 [[nodiscard]] GLuint64EXT HostGpuAddr() const noexcept { 37 [[nodiscard]] GLuint64EXT HostGpuAddr() const noexcept {
@@ -66,22 +68,29 @@ public:
66 68
67 [[nodiscard]] StagingBufferMap DownloadStagingBuffer(size_t size); 69 [[nodiscard]] StagingBufferMap DownloadStagingBuffer(size_t size);
68 70
71 bool CanReorderUpload(const Buffer&, std::span<const VideoCommon::BufferCopy>) {
72 return false;
73 }
74
69 void CopyBuffer(GLuint dst_buffer, GLuint src_buffer, 75 void CopyBuffer(GLuint dst_buffer, GLuint src_buffer,
70 std::span<const VideoCommon::BufferCopy> copies, bool barrier = true); 76 std::span<const VideoCommon::BufferCopy> copies, bool barrier);
71 77
72 void CopyBuffer(GLuint dst_buffer, Buffer& src_buffer, 78 void CopyBuffer(GLuint dst_buffer, Buffer& src_buffer,
73 std::span<const VideoCommon::BufferCopy> copies, bool barrier = true); 79 std::span<const VideoCommon::BufferCopy> copies, bool barrier);
74 80
75 void CopyBuffer(Buffer& dst_buffer, GLuint src_buffer, 81 void CopyBuffer(Buffer& dst_buffer, GLuint src_buffer,
76 std::span<const VideoCommon::BufferCopy> copies, bool barrier = true); 82 std::span<const VideoCommon::BufferCopy> copies, bool barrier,
83 bool can_reorder_upload = false);
77 84
78 void CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer, 85 void CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer,
79 std::span<const VideoCommon::BufferCopy> copies); 86 std::span<const VideoCommon::BufferCopy> copies, bool);
80 87
81 void PreCopyBarrier(); 88 void PreCopyBarrier();
82 void PostCopyBarrier(); 89 void PostCopyBarrier();
83 void Finish(); 90 void Finish();
84 91
92 void TickFrame(VideoCommon::SlotVector<Buffer>&) noexcept {}
93
85 void ClearBuffer(Buffer& dest_buffer, u32 offset, size_t size, u32 value); 94 void ClearBuffer(Buffer& dest_buffer, u32 offset, size_t size, u32 value);
86 95
87 void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size); 96 void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size);
@@ -182,6 +191,10 @@ public:
182 return device.CanReportMemoryUsage(); 191 return device.CanReportMemoryUsage();
183 } 192 }
184 193
194 u32 GetStorageBufferAlignment() const {
195 return static_cast<u32>(device.GetShaderStorageBufferAlignment());
196 }
197
185private: 198private:
186 static constexpr std::array PABO_LUT{ 199 static constexpr std::array PABO_LUT{
187 GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV, GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV, 200 GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV, GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV,
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 94258ccd0..993438a27 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -265,33 +265,33 @@ std::string Device::GetVendorName() const {
265 if (vendor_name == "Intel") { 265 if (vendor_name == "Intel") {
266 // For Mesa, `Intel` is an overloaded vendor string that could mean crocus or iris. 266 // For Mesa, `Intel` is an overloaded vendor string that could mean crocus or iris.
267 // Simply return `INTEL` for those as well as the Windows driver. 267 // Simply return `INTEL` for those as well as the Windows driver.
268 return "INTEL"; 268 return "Intel";
269 } 269 }
270 if (vendor_name == "Intel Open Source Technology Center") { 270 if (vendor_name == "Intel Open Source Technology Center") {
271 return "I965"; 271 return "i965";
272 } 272 }
273 if (vendor_name == "Mesa Project") { 273 if (vendor_name == "Mesa Project") {
274 return "I915"; 274 return "i915";
275 } 275 }
276 if (vendor_name == "Mesa/X.org") { 276 if (vendor_name == "Mesa/X.org") {
277 // This vendor string is overloaded between llvmpipe, softpipe, and virgl, so just return 277 // This vendor string is overloaded between llvmpipe, softpipe, and virgl, so just return
278 // MESA instead of one of those driver names. 278 // MESA instead of one of those driver names.
279 return "MESA"; 279 return "Mesa";
280 } 280 }
281 if (vendor_name == "AMD") { 281 if (vendor_name == "AMD") {
282 return "RADEONSI"; 282 return "RadeonSI";
283 } 283 }
284 if (vendor_name == "nouveau") { 284 if (vendor_name == "nouveau") {
285 return "NOUVEAU"; 285 return "Nouveau";
286 } 286 }
287 if (vendor_name == "X.Org") { 287 if (vendor_name == "X.Org") {
288 return "R600"; 288 return "R600";
289 } 289 }
290 if (vendor_name == "Collabora Ltd") { 290 if (vendor_name == "Collabora Ltd") {
291 return "ZINK"; 291 return "Zink";
292 } 292 }
293 if (vendor_name == "Intel Corporation") { 293 if (vendor_name == "Intel Corporation") {
294 return "OPENSWR"; 294 return "OpenSWR";
295 } 295 }
296 if (vendor_name == "Microsoft Corporation") { 296 if (vendor_name == "Microsoft Corporation") {
297 return "D3D12"; 297 return "D3D12";
@@ -300,7 +300,7 @@ std::string Device::GetVendorName() const {
300 // Mesa's tegra driver reports `NVIDIA`. Only present in this list because the default 300 // Mesa's tegra driver reports `NVIDIA`. Only present in this list because the default
301 // strategy would have returned `NVIDIA` here for this driver, the same result as the 301 // strategy would have returned `NVIDIA` here for this driver, the same result as the
302 // proprietary driver. 302 // proprietary driver.
303 return "TEGRA"; 303 return "Tegra";
304 } 304 }
305 return vendor_name; 305 return vendor_name;
306} 306}
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 2888e0238..26f2d0ea7 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -232,6 +232,7 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo
232 .has_gl_bool_ref_bug = device.HasBoolRefBug(), 232 .has_gl_bool_ref_bug = device.HasBoolRefBug(),
233 .ignore_nan_fp_comparisons = true, 233 .ignore_nan_fp_comparisons = true,
234 .gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(), 234 .gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(),
235 .min_ssbo_alignment = device.GetShaderStorageBufferAlignment(),
235 }, 236 },
236 host_info{ 237 host_info{
237 .support_float64 = true, 238 .support_float64 = true,
@@ -240,6 +241,7 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo
240 .needs_demote_reorder = device.IsAmd(), 241 .needs_demote_reorder = device.IsAmd(),
241 .support_snorm_render_buffer = false, 242 .support_snorm_render_buffer = false,
242 .support_viewport_index_layer = device.HasVertexViewportLayer(), 243 .support_viewport_index_layer = device.HasVertexViewportLayer(),
244 .min_ssbo_alignment = static_cast<u32>(device.GetShaderStorageBufferAlignment()),
243 .support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(), 245 .support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(),
244 .support_conditional_barrier = device.SupportsConditionalBarriers(), 246 .support_conditional_barrier = device.SupportsConditionalBarriers(),
245 } { 247 } {
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index d8148e89a..5958f52f7 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -79,13 +79,13 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
79} // Anonymous namespace 79} // Anonymous namespace
80 80
81Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params) 81Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params)
82 : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params) {} 82 : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params), tracker{4096} {}
83 83
84Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_, 84Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_,
85 VAddr cpu_addr_, u64 size_bytes_) 85 VAddr cpu_addr_, u64 size_bytes_)
86 : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_), 86 : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_),
87 device{&runtime.device}, buffer{ 87 device{&runtime.device}, buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())},
88 CreateBuffer(*device, runtime.memory_allocator, SizeBytes())} { 88 tracker{SizeBytes()} {
89 if (runtime.device.HasDebuggingToolAttached()) { 89 if (runtime.device.HasDebuggingToolAttached()) {
90 buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str()); 90 buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str());
91 } 91 }
@@ -355,12 +355,35 @@ bool BufferCacheRuntime::CanReportMemoryUsage() const {
355 return device.CanReportMemoryUsage(); 355 return device.CanReportMemoryUsage();
356} 356}
357 357
358u32 BufferCacheRuntime::GetStorageBufferAlignment() const {
359 return static_cast<u32>(device.GetStorageBufferAlignment());
360}
361
362void BufferCacheRuntime::TickFrame(VideoCommon::SlotVector<Buffer>& slot_buffers) noexcept {
363 for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) {
364 it->ResetUsageTracking();
365 }
366}
367
358void BufferCacheRuntime::Finish() { 368void BufferCacheRuntime::Finish() {
359 scheduler.Finish(); 369 scheduler.Finish();
360} 370}
361 371
372bool BufferCacheRuntime::CanReorderUpload(const Buffer& buffer,
373 std::span<const VideoCommon::BufferCopy> copies) {
374 if (Settings::values.disable_buffer_reorder) {
375 return false;
376 }
377 const bool can_use_upload_cmdbuf =
378 std::ranges::all_of(copies, [&](const VideoCommon::BufferCopy& copy) {
379 return !buffer.IsRegionUsed(copy.dst_offset, copy.size);
380 });
381 return can_use_upload_cmdbuf;
382}
383
362void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer, 384void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer,
363 std::span<const VideoCommon::BufferCopy> copies, bool barrier) { 385 std::span<const VideoCommon::BufferCopy> copies, bool barrier,
386 bool can_reorder_upload) {
364 if (dst_buffer == VK_NULL_HANDLE || src_buffer == VK_NULL_HANDLE) { 387 if (dst_buffer == VK_NULL_HANDLE || src_buffer == VK_NULL_HANDLE) {
365 return; 388 return;
366 } 389 }
@@ -376,9 +399,18 @@ void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer,
376 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, 399 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
377 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, 400 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
378 }; 401 };
402
379 // Measuring a popular game, this number never exceeds the specified size once data is warmed up 403 // Measuring a popular game, this number never exceeds the specified size once data is warmed up
380 boost::container::small_vector<VkBufferCopy, 8> vk_copies(copies.size()); 404 boost::container::small_vector<VkBufferCopy, 8> vk_copies(copies.size());
381 std::ranges::transform(copies, vk_copies.begin(), MakeBufferCopy); 405 std::ranges::transform(copies, vk_copies.begin(), MakeBufferCopy);
406 if (src_buffer == staging_pool.StreamBuf() && can_reorder_upload) {
407 scheduler.RecordWithUploadBuffer([src_buffer, dst_buffer, vk_copies](
408 vk::CommandBuffer, vk::CommandBuffer upload_cmdbuf) {
409 upload_cmdbuf.CopyBuffer(src_buffer, dst_buffer, vk_copies);
410 });
411 return;
412 }
413
382 scheduler.RequestOutsideRenderPassOperationContext(); 414 scheduler.RequestOutsideRenderPassOperationContext();
383 scheduler.Record([src_buffer, dst_buffer, vk_copies, barrier](vk::CommandBuffer cmdbuf) { 415 scheduler.Record([src_buffer, dst_buffer, vk_copies, barrier](vk::CommandBuffer cmdbuf) {
384 if (barrier) { 416 if (barrier) {
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 95446c732..0b3fbd6d0 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -5,6 +5,7 @@
5 5
6#include "video_core/buffer_cache/buffer_cache_base.h" 6#include "video_core/buffer_cache/buffer_cache_base.h"
7#include "video_core/buffer_cache/memory_tracker_base.h" 7#include "video_core/buffer_cache/memory_tracker_base.h"
8#include "video_core/buffer_cache/usage_tracker.h"
8#include "video_core/engines/maxwell_3d.h" 9#include "video_core/engines/maxwell_3d.h"
9#include "video_core/renderer_vulkan/vk_compute_pass.h" 10#include "video_core/renderer_vulkan/vk_compute_pass.h"
10#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" 11#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -34,6 +35,18 @@ public:
34 return *buffer; 35 return *buffer;
35 } 36 }
36 37
38 [[nodiscard]] bool IsRegionUsed(u64 offset, u64 size) const noexcept {
39 return tracker.IsUsed(offset, size);
40 }
41
42 void MarkUsage(u64 offset, u64 size) noexcept {
43 tracker.Track(offset, size);
44 }
45
46 void ResetUsageTracking() noexcept {
47 tracker.Reset();
48 }
49
37 operator VkBuffer() const noexcept { 50 operator VkBuffer() const noexcept {
38 return *buffer; 51 return *buffer;
39 } 52 }
@@ -49,6 +62,7 @@ private:
49 const Device* device{}; 62 const Device* device{};
50 vk::Buffer buffer; 63 vk::Buffer buffer;
51 std::vector<BufferView> views; 64 std::vector<BufferView> views;
65 VideoCommon::UsageTracker tracker;
52}; 66};
53 67
54class QuadArrayIndexBuffer; 68class QuadArrayIndexBuffer;
@@ -67,6 +81,8 @@ public:
67 ComputePassDescriptorQueue& compute_pass_descriptor_queue, 81 ComputePassDescriptorQueue& compute_pass_descriptor_queue,
68 DescriptorPool& descriptor_pool); 82 DescriptorPool& descriptor_pool);
69 83
84 void TickFrame(VideoCommon::SlotVector<Buffer>& slot_buffers) noexcept;
85
70 void Finish(); 86 void Finish();
71 87
72 u64 GetDeviceLocalMemory() const; 88 u64 GetDeviceLocalMemory() const;
@@ -75,16 +91,21 @@ public:
75 91
76 bool CanReportMemoryUsage() const; 92 bool CanReportMemoryUsage() const;
77 93
94 u32 GetStorageBufferAlignment() const;
95
78 [[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size); 96 [[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size);
79 97
80 [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size, bool deferred = false); 98 [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size, bool deferred = false);
81 99
100 bool CanReorderUpload(const Buffer& buffer, std::span<const VideoCommon::BufferCopy> copies);
101
82 void FreeDeferredStagingBuffer(StagingBufferRef& ref); 102 void FreeDeferredStagingBuffer(StagingBufferRef& ref);
83 103
84 void PreCopyBarrier(); 104 void PreCopyBarrier();
85 105
86 void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer, 106 void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer,
87 std::span<const VideoCommon::BufferCopy> copies, bool barrier = true); 107 std::span<const VideoCommon::BufferCopy> copies, bool barrier,
108 bool can_reorder_upload = false);
88 109
89 void PostCopyBarrier(); 110 void PostCopyBarrier();
90 111
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
index 6b288b994..ac8b6e838 100644
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
@@ -100,12 +100,14 @@ void MasterSemaphore::Wait(u64 tick) {
100 Refresh(); 100 Refresh();
101} 101}
102 102
103VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, 103VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf,
104 VkSemaphore wait_semaphore, u64 host_tick) { 104 VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
105 u64 host_tick) {
105 if (semaphore) { 106 if (semaphore) {
106 return SubmitQueueTimeline(cmdbuf, signal_semaphore, wait_semaphore, host_tick); 107 return SubmitQueueTimeline(cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore,
108 host_tick);
107 } else { 109 } else {
108 return SubmitQueueFence(cmdbuf, signal_semaphore, wait_semaphore, host_tick); 110 return SubmitQueueFence(cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore, host_tick);
109 } 111 }
110} 112}
111 113
@@ -115,6 +117,7 @@ static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{
115}; 117};
116 118
117VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, 119VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
120 vk::CommandBuffer& upload_cmdbuf,
118 VkSemaphore signal_semaphore, 121 VkSemaphore signal_semaphore,
119 VkSemaphore wait_semaphore, u64 host_tick) { 122 VkSemaphore wait_semaphore, u64 host_tick) {
120 const VkSemaphore timeline_semaphore = *semaphore; 123 const VkSemaphore timeline_semaphore = *semaphore;
@@ -123,6 +126,8 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
123 const std::array signal_values{host_tick, u64(0)}; 126 const std::array signal_values{host_tick, u64(0)};
124 const std::array signal_semaphores{timeline_semaphore, signal_semaphore}; 127 const std::array signal_semaphores{timeline_semaphore, signal_semaphore};
125 128
129 const std::array cmdbuffers{*upload_cmdbuf, *cmdbuf};
130
126 const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; 131 const u32 num_wait_semaphores = wait_semaphore ? 1 : 0;
127 const VkTimelineSemaphoreSubmitInfo timeline_si{ 132 const VkTimelineSemaphoreSubmitInfo timeline_si{
128 .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, 133 .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
@@ -138,8 +143,8 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
138 .waitSemaphoreCount = num_wait_semaphores, 143 .waitSemaphoreCount = num_wait_semaphores,
139 .pWaitSemaphores = &wait_semaphore, 144 .pWaitSemaphores = &wait_semaphore,
140 .pWaitDstStageMask = wait_stage_masks.data(), 145 .pWaitDstStageMask = wait_stage_masks.data(),
141 .commandBufferCount = 1, 146 .commandBufferCount = static_cast<u32>(cmdbuffers.size()),
142 .pCommandBuffers = cmdbuf.address(), 147 .pCommandBuffers = cmdbuffers.data(),
143 .signalSemaphoreCount = num_signal_semaphores, 148 .signalSemaphoreCount = num_signal_semaphores,
144 .pSignalSemaphores = signal_semaphores.data(), 149 .pSignalSemaphores = signal_semaphores.data(),
145 }; 150 };
@@ -147,19 +152,23 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
147 return device.GetGraphicsQueue().Submit(submit_info); 152 return device.GetGraphicsQueue().Submit(submit_info);
148} 153}
149 154
150VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, 155VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf,
151 VkSemaphore wait_semaphore, u64 host_tick) { 156 vk::CommandBuffer& upload_cmdbuf,
157 VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
158 u64 host_tick) {
152 const u32 num_signal_semaphores = signal_semaphore ? 1 : 0; 159 const u32 num_signal_semaphores = signal_semaphore ? 1 : 0;
153 const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; 160 const u32 num_wait_semaphores = wait_semaphore ? 1 : 0;
154 161
162 const std::array cmdbuffers{*upload_cmdbuf, *cmdbuf};
163
155 const VkSubmitInfo submit_info{ 164 const VkSubmitInfo submit_info{
156 .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, 165 .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
157 .pNext = nullptr, 166 .pNext = nullptr,
158 .waitSemaphoreCount = num_wait_semaphores, 167 .waitSemaphoreCount = num_wait_semaphores,
159 .pWaitSemaphores = &wait_semaphore, 168 .pWaitSemaphores = &wait_semaphore,
160 .pWaitDstStageMask = wait_stage_masks.data(), 169 .pWaitDstStageMask = wait_stage_masks.data(),
161 .commandBufferCount = 1, 170 .commandBufferCount = static_cast<u32>(cmdbuffers.size()),
162 .pCommandBuffers = cmdbuf.address(), 171 .pCommandBuffers = cmdbuffers.data(),
163 .signalSemaphoreCount = num_signal_semaphores, 172 .signalSemaphoreCount = num_signal_semaphores,
164 .pSignalSemaphores = &signal_semaphore, 173 .pSignalSemaphores = &signal_semaphore,
165 }; 174 };
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h
index 3f599d7bd..7dfb93ffb 100644
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.h
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h
@@ -52,14 +52,16 @@ public:
52 void Wait(u64 tick); 52 void Wait(u64 tick);
53 53
54 /// Submits the device graphics queue, updating the tick as necessary 54 /// Submits the device graphics queue, updating the tick as necessary
55 VkResult SubmitQueue(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, 55 VkResult SubmitQueue(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf,
56 VkSemaphore wait_semaphore, u64 host_tick); 56 VkSemaphore signal_semaphore, VkSemaphore wait_semaphore, u64 host_tick);
57 57
58private: 58private:
59 VkResult SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, 59 VkResult SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf,
60 VkSemaphore wait_semaphore, u64 host_tick); 60 VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
61 VkResult SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, 61 u64 host_tick);
62 VkSemaphore wait_semaphore, u64 host_tick); 62 VkResult SubmitQueueFence(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf,
63 VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
64 u64 host_tick);
63 65
64 void WaitThread(std::stop_token token); 66 void WaitThread(std::stop_token token);
65 67
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 89b455bff..2a13b2a72 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -373,6 +373,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
373 driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY, 373 driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
374 .has_broken_robust = 374 .has_broken_robust =
375 device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal, 375 device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal,
376 .min_ssbo_alignment = device.GetStorageBufferAlignment(),
376 }; 377 };
377 378
378 host_info = Shader::HostTranslateInfo{ 379 host_info = Shader::HostTranslateInfo{
@@ -383,6 +384,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
383 driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE, 384 driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE,
384 .support_snorm_render_buffer = true, 385 .support_snorm_render_buffer = true,
385 .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(), 386 .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(),
387 .min_ssbo_alignment = static_cast<u32>(device.GetStorageBufferAlignment()),
386 .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), 388 .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
387 .support_conditional_barrier = device.SupportsConditionalBarriers(), 389 .support_conditional_barrier = device.SupportsConditionalBarriers(),
388 }; 390 };
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index 3be7837f4..146923db4 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -22,11 +22,12 @@ namespace Vulkan {
22 22
23MICROPROFILE_DECLARE(Vulkan_WaitForWorker); 23MICROPROFILE_DECLARE(Vulkan_WaitForWorker);
24 24
25void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) { 25void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf,
26 vk::CommandBuffer upload_cmdbuf) {
26 auto command = first; 27 auto command = first;
27 while (command != nullptr) { 28 while (command != nullptr) {
28 auto next = command->GetNext(); 29 auto next = command->GetNext();
29 command->Execute(cmdbuf); 30 command->Execute(cmdbuf, upload_cmdbuf);
30 command->~Command(); 31 command->~Command();
31 command = next; 32 command = next;
32 } 33 }
@@ -180,7 +181,7 @@ void Scheduler::WorkerThread(std::stop_token stop_token) {
180 // Perform the work, tracking whether the chunk was a submission 181 // Perform the work, tracking whether the chunk was a submission
181 // before executing. 182 // before executing.
182 const bool has_submit = work->HasSubmit(); 183 const bool has_submit = work->HasSubmit();
183 work->ExecuteAll(current_cmdbuf); 184 work->ExecuteAll(current_cmdbuf, current_upload_cmdbuf);
184 185
185 // If the chunk was a submission, reallocate the command buffer. 186 // If the chunk was a submission, reallocate the command buffer.
186 if (has_submit) { 187 if (has_submit) {
@@ -205,6 +206,13 @@ void Scheduler::AllocateWorkerCommandBuffer() {
205 .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 206 .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
206 .pInheritanceInfo = nullptr, 207 .pInheritanceInfo = nullptr,
207 }); 208 });
209 current_upload_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader());
210 current_upload_cmdbuf.Begin({
211 .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
212 .pNext = nullptr,
213 .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
214 .pInheritanceInfo = nullptr,
215 });
208} 216}
209 217
210u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { 218u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
@@ -212,7 +220,17 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
212 InvalidateState(); 220 InvalidateState();
213 221
214 const u64 signal_value = master_semaphore->NextTick(); 222 const u64 signal_value = master_semaphore->NextTick();
215 Record([signal_semaphore, wait_semaphore, signal_value, this](vk::CommandBuffer cmdbuf) { 223 RecordWithUploadBuffer([signal_semaphore, wait_semaphore, signal_value,
224 this](vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) {
225 static constexpr VkMemoryBarrier WRITE_BARRIER{
226 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
227 .pNext = nullptr,
228 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
229 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
230 };
231 upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
232 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
233 upload_cmdbuf.End();
216 cmdbuf.End(); 234 cmdbuf.End();
217 235
218 if (on_submit) { 236 if (on_submit) {
@@ -221,7 +239,7 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
221 239
222 std::scoped_lock lock{submit_mutex}; 240 std::scoped_lock lock{submit_mutex};
223 switch (const VkResult result = master_semaphore->SubmitQueue( 241 switch (const VkResult result = master_semaphore->SubmitQueue(
224 cmdbuf, signal_semaphore, wait_semaphore, signal_value)) { 242 cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore, signal_value)) {
225 case VK_SUCCESS: 243 case VK_SUCCESS:
226 break; 244 break;
227 case VK_ERROR_DEVICE_LOST: 245 case VK_ERROR_DEVICE_LOST:
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index da03803aa..f8d8ca80a 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -80,7 +80,8 @@ public:
80 80
81 /// Send work to a separate thread. 81 /// Send work to a separate thread.
82 template <typename T> 82 template <typename T>
83 void Record(T&& command) { 83 requires std::is_invocable_v<T, vk::CommandBuffer, vk::CommandBuffer>
84 void RecordWithUploadBuffer(T&& command) {
84 if (chunk->Record(command)) { 85 if (chunk->Record(command)) {
85 return; 86 return;
86 } 87 }
@@ -88,6 +89,15 @@ public:
88 (void)chunk->Record(command); 89 (void)chunk->Record(command);
89 } 90 }
90 91
92 template <typename T>
93 requires std::is_invocable_v<T, vk::CommandBuffer>
94 void Record(T&& c) {
95 this->RecordWithUploadBuffer(
96 [command = std::move(c)](vk::CommandBuffer cmdbuf, vk::CommandBuffer) {
97 command(cmdbuf);
98 });
99 }
100
91 /// Returns the current command buffer tick. 101 /// Returns the current command buffer tick.
92 [[nodiscard]] u64 CurrentTick() const noexcept { 102 [[nodiscard]] u64 CurrentTick() const noexcept {
93 return master_semaphore->CurrentTick(); 103 return master_semaphore->CurrentTick();
@@ -119,7 +129,7 @@ private:
119 public: 129 public:
120 virtual ~Command() = default; 130 virtual ~Command() = default;
121 131
122 virtual void Execute(vk::CommandBuffer cmdbuf) const = 0; 132 virtual void Execute(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) const = 0;
123 133
124 Command* GetNext() const { 134 Command* GetNext() const {
125 return next; 135 return next;
@@ -142,8 +152,8 @@ private:
142 TypedCommand(TypedCommand&&) = delete; 152 TypedCommand(TypedCommand&&) = delete;
143 TypedCommand& operator=(TypedCommand&&) = delete; 153 TypedCommand& operator=(TypedCommand&&) = delete;
144 154
145 void Execute(vk::CommandBuffer cmdbuf) const override { 155 void Execute(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) const override {
146 command(cmdbuf); 156 command(cmdbuf, upload_cmdbuf);
147 } 157 }
148 158
149 private: 159 private:
@@ -152,7 +162,7 @@ private:
152 162
153 class CommandChunk final { 163 class CommandChunk final {
154 public: 164 public:
155 void ExecuteAll(vk::CommandBuffer cmdbuf); 165 void ExecuteAll(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf);
156 166
157 template <typename T> 167 template <typename T>
158 bool Record(T& command) { 168 bool Record(T& command) {
@@ -228,6 +238,7 @@ private:
228 VideoCommon::QueryCacheBase<QueryCacheParams>* query_cache = nullptr; 238 VideoCommon::QueryCacheBase<QueryCacheParams>* query_cache = nullptr;
229 239
230 vk::CommandBuffer current_cmdbuf; 240 vk::CommandBuffer current_cmdbuf;
241 vk::CommandBuffer current_upload_cmdbuf;
231 242
232 std::unique_ptr<CommandChunk> chunk; 243 std::unique_ptr<CommandChunk> chunk;
233 std::function<void()> on_submit; 244 std::function<void()> on_submit;
diff --git a/src/video_core/renderer_vulkan/vk_smaa.cpp b/src/video_core/renderer_vulkan/vk_smaa.cpp
index 5efd7d66e..70644ea82 100644
--- a/src/video_core/renderer_vulkan/vk_smaa.cpp
+++ b/src/video_core/renderer_vulkan/vk_smaa.cpp
@@ -672,7 +672,7 @@ void SMAA::UploadImages(Scheduler& scheduler) {
672 UploadImage(m_device, m_allocator, scheduler, m_static_images[Search], search_extent, 672 UploadImage(m_device, m_allocator, scheduler, m_static_images[Search], search_extent,
673 VK_FORMAT_R8_UNORM, ARRAY_TO_SPAN(searchTexBytes)); 673 VK_FORMAT_R8_UNORM, ARRAY_TO_SPAN(searchTexBytes));
674 674
675 scheduler.Record([&](vk::CommandBuffer& cmdbuf) { 675 scheduler.Record([&](vk::CommandBuffer cmdbuf) {
676 for (auto& images : m_dynamic_images) { 676 for (auto& images : m_dynamic_images) {
677 for (size_t i = 0; i < MaxDynamicImage; i++) { 677 for (size_t i = 0; i < MaxDynamicImage; i++) {
678 ClearColorImage(cmdbuf, *images.images[i]); 678 ClearColorImage(cmdbuf, *images.images[i]);
@@ -707,7 +707,7 @@ VkImageView SMAA::Draw(Scheduler& scheduler, size_t image_index, VkImage source_
707 UpdateDescriptorSets(source_image_view, image_index); 707 UpdateDescriptorSets(source_image_view, image_index);
708 708
709 scheduler.RequestOutsideRenderPassOperationContext(); 709 scheduler.RequestOutsideRenderPassOperationContext();
710 scheduler.Record([=, this](vk::CommandBuffer& cmdbuf) { 710 scheduler.Record([=, this](vk::CommandBuffer cmdbuf) {
711 TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL); 711 TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL);
712 TransitionImageLayout(cmdbuf, edges_image, VK_IMAGE_LAYOUT_GENERAL); 712 TransitionImageLayout(cmdbuf, edges_image, VK_IMAGE_LAYOUT_GENERAL);
713 BeginRenderPass(cmdbuf, m_renderpasses[EdgeDetection], edge_detection_framebuffer, 713 BeginRenderPass(cmdbuf, m_renderpasses[EdgeDetection], edge_detection_framebuffer,
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
index d3deb9072..f63a20327 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@@ -36,6 +36,10 @@ public:
36 StagingBufferRef Request(size_t size, MemoryUsage usage, bool deferred = false); 36 StagingBufferRef Request(size_t size, MemoryUsage usage, bool deferred = false);
37 void FreeDeferred(StagingBufferRef& ref); 37 void FreeDeferred(StagingBufferRef& ref);
38 38
39 [[nodiscard]] VkBuffer StreamBuf() const noexcept {
40 return *stream_buffer;
41 }
42
39 void TickFrame(); 43 void TickFrame();
40 44
41private: 45private:
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index de34f6d49..5dbec2e62 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -1785,8 +1785,22 @@ ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
1785 : VideoCommon::ImageViewBase{info, view_info, gpu_addr_}, 1785 : VideoCommon::ImageViewBase{info, view_info, gpu_addr_},
1786 buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {} 1786 buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {}
1787 1787
1788ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams& params) 1788ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageViewParams& params)
1789 : VideoCommon::ImageViewBase{params} {} 1789 : VideoCommon::ImageViewBase{params}, device{&runtime.device} {
1790 if (device->HasNullDescriptor()) {
1791 return;
1792 }
1793
1794 // Handle fallback for devices without nullDescriptor
1795 ImageInfo info{};
1796 info.format = PixelFormat::A8B8G8R8_UNORM;
1797
1798 null_image = MakeImage(*device, runtime.memory_allocator, info, {});
1799 image_handle = *null_image;
1800 for (u32 i = 0; i < Shader::NUM_TEXTURE_TYPES; i++) {
1801 image_views[i] = MakeView(VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_IMAGE_ASPECT_COLOR_BIT);
1802 }
1803}
1790 1804
1791ImageView::~ImageView() = default; 1805ImageView::~ImageView() = default;
1792 1806
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 7a0807709..edf5d7635 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -267,6 +267,7 @@ private:
267 vk::ImageView depth_view; 267 vk::ImageView depth_view;
268 vk::ImageView stencil_view; 268 vk::ImageView stencil_view;
269 vk::ImageView color_view; 269 vk::ImageView color_view;
270 vk::Image null_image;
270 VkImage image_handle = VK_NULL_HANDLE; 271 VkImage image_handle = VK_NULL_HANDLE;
271 VkImageView render_target = VK_NULL_HANDLE; 272 VkImageView render_target = VK_NULL_HANDLE;
272 VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; 273 VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
diff --git a/src/video_core/texture_cache/slot_vector.h b/src/video_core/texture_cache/slot_vector.h
index 9df6a2903..3ffa2a661 100644
--- a/src/video_core/texture_cache/slot_vector.h
+++ b/src/video_core/texture_cache/slot_vector.h
@@ -138,6 +138,10 @@ public:
138 return Iterator(this, SlotId{SlotId::INVALID_INDEX}); 138 return Iterator(this, SlotId{SlotId::INVALID_INDEX});
139 } 139 }
140 140
141 [[nodiscard]] size_t size() const noexcept {
142 return values_capacity - free_list.size();
143 }
144
141private: 145private:
142 struct NonTrivialDummy { 146 struct NonTrivialDummy {
143 NonTrivialDummy() noexcept {} 147 NonTrivialDummy() noexcept {}
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 6900b8ffa..188ceeed7 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -847,11 +847,41 @@ std::string Device::GetDriverName() const {
847 case VK_DRIVER_ID_NVIDIA_PROPRIETARY: 847 case VK_DRIVER_ID_NVIDIA_PROPRIETARY:
848 return "NVIDIA"; 848 return "NVIDIA";
849 case VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS: 849 case VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS:
850 return "INTEL"; 850 return "Intel";
851 case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA: 851 case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA:
852 return "ANV"; 852 return "ANV";
853 case VK_DRIVER_ID_IMAGINATION_PROPRIETARY:
854 return "PowerVR";
855 case VK_DRIVER_ID_QUALCOMM_PROPRIETARY:
856 return "Qualcomm";
857 case VK_DRIVER_ID_ARM_PROPRIETARY:
858 return "Mali";
859 case VK_DRIVER_ID_GOOGLE_SWIFTSHADER:
860 return "SwiftShader";
861 case VK_DRIVER_ID_BROADCOM_PROPRIETARY:
862 return "Broadcom";
853 case VK_DRIVER_ID_MESA_LLVMPIPE: 863 case VK_DRIVER_ID_MESA_LLVMPIPE:
854 return "LAVAPIPE"; 864 return "Lavapipe";
865 case VK_DRIVER_ID_MOLTENVK:
866 return "MoltenVK";
867 case VK_DRIVER_ID_VERISILICON_PROPRIETARY:
868 return "Vivante";
869 case VK_DRIVER_ID_MESA_TURNIP:
870 return "Turnip";
871 case VK_DRIVER_ID_MESA_V3DV:
872 return "V3DV";
873 case VK_DRIVER_ID_MESA_PANVK:
874 return "PanVK";
875 case VK_DRIVER_ID_MESA_VENUS:
876 return "Venus";
877 case VK_DRIVER_ID_MESA_DOZEN:
878 return "Dozen";
879 case VK_DRIVER_ID_MESA_NVK:
880 return "NVK";
881 case VK_DRIVER_ID_IMAGINATION_OPEN_SOURCE_MESA:
882 return "PVR";
883 // case VK_DRIVER_ID_MESA_AGXV:
884 // return "Asahi";
855 default: 885 default:
856 return properties.driver.driverName; 886 return properties.driver.driverName;
857 } 887 }
@@ -869,7 +899,8 @@ bool Device::ShouldBoostClocks() const {
869 driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA || 899 driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA ||
870 driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP; 900 driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP;
871 901
872 const bool is_steam_deck = vendor_id == 0x1002 && device_id == 0x163F; 902 const bool is_steam_deck = (vendor_id == 0x1002 && device_id == 0x163F) ||
903 (vendor_id == 0x1002 && device_id == 0x1435);
873 904
874 const bool is_debugging = this->HasDebuggingToolAttached(); 905 const bool is_debugging = this->HasDebuggingToolAttached();
875 906
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 0487cd3b6..a0c70797f 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -1101,6 +1101,10 @@ public:
1101 return &handle; 1101 return &handle;
1102 } 1102 }
1103 1103
1104 VkCommandBuffer operator*() const noexcept {
1105 return handle;
1106 }
1107
1104 void Begin(const VkCommandBufferBeginInfo& begin_info) const { 1108 void Begin(const VkCommandBufferBeginInfo& begin_info) const {
1105 Check(dld->vkBeginCommandBuffer(handle, &begin_info)); 1109 Check(dld->vkBeginCommandBuffer(handle, &begin_info));
1106 } 1110 }
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 90278052a..f3ad2214b 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -386,7 +386,7 @@ if (NOT WIN32)
386 target_include_directories(yuzu PRIVATE ${Qt${QT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS}) 386 target_include_directories(yuzu PRIVATE ${Qt${QT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS})
387endif() 387endif()
388if (UNIX AND NOT APPLE) 388if (UNIX AND NOT APPLE)
389 target_link_libraries(yuzu PRIVATE Qt${QT_MAJOR_VERSION}::DBus) 389 target_link_libraries(yuzu PRIVATE Qt${QT_MAJOR_VERSION}::DBus gamemode)
390endif() 390endif()
391 391
392target_compile_definitions(yuzu PRIVATE 392target_compile_definitions(yuzu PRIVATE
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 2afa72140..ed5750155 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -30,7 +30,6 @@
30#include <QSize> 30#include <QSize>
31#include <QStringLiteral> 31#include <QStringLiteral>
32#include <QSurfaceFormat> 32#include <QSurfaceFormat>
33#include <QTimer>
34#include <QWindow> 33#include <QWindow>
35#include <QtCore/qobjectdefs.h> 34#include <QtCore/qobjectdefs.h>
36 35
@@ -66,6 +65,8 @@ class QObject;
66class QPaintEngine; 65class QPaintEngine;
67class QSurface; 66class QSurface;
68 67
68constexpr int default_mouse_constrain_timeout = 10;
69
69EmuThread::EmuThread(Core::System& system) : m_system{system} {} 70EmuThread::EmuThread(Core::System& system) : m_system{system} {}
70 71
71EmuThread::~EmuThread() = default; 72EmuThread::~EmuThread() = default;
@@ -304,6 +305,9 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
304 Qt::QueuedConnection); 305 Qt::QueuedConnection);
305 connect(this, &GRenderWindow::ExitSignal, parent, &GMainWindow::OnExit, Qt::QueuedConnection); 306 connect(this, &GRenderWindow::ExitSignal, parent, &GMainWindow::OnExit, Qt::QueuedConnection);
306 connect(this, &GRenderWindow::TasPlaybackStateChanged, parent, &GMainWindow::OnTasStateChanged); 307 connect(this, &GRenderWindow::TasPlaybackStateChanged, parent, &GMainWindow::OnTasStateChanged);
308
309 mouse_constrain_timer.setInterval(default_mouse_constrain_timeout);
310 connect(&mouse_constrain_timer, &QTimer::timeout, this, &GRenderWindow::ConstrainMouse);
307} 311}
308 312
309void GRenderWindow::ExecuteProgram(std::size_t program_index) { 313void GRenderWindow::ExecuteProgram(std::size_t program_index) {
@@ -393,6 +397,22 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
393 QWidget::closeEvent(event); 397 QWidget::closeEvent(event);
394} 398}
395 399
400void GRenderWindow::leaveEvent(QEvent* event) {
401 if (Settings::values.mouse_panning) {
402 const QRect& rect = QWidget::geometry();
403 QPoint position = QCursor::pos();
404
405 qint32 x = qBound(rect.left(), position.x(), rect.right());
406 qint32 y = qBound(rect.top(), position.y(), rect.bottom());
407 // Only start the timer if the mouse has left the window bound.
408 // The leave event is also triggered when the window looses focus.
409 if (x != position.x() || y != position.y()) {
410 mouse_constrain_timer.start();
411 }
412 event->accept();
413 }
414}
415
396int GRenderWindow::QtKeyToSwitchKey(Qt::Key qt_key) { 416int GRenderWindow::QtKeyToSwitchKey(Qt::Key qt_key) {
397 static constexpr std::array<std::pair<Qt::Key, Settings::NativeKeyboard::Keys>, 106> key_map = { 417 static constexpr std::array<std::pair<Qt::Key, Settings::NativeKeyboard::Keys>, 106> key_map = {
398 std::pair<Qt::Key, Settings::NativeKeyboard::Keys>{Qt::Key_A, Settings::NativeKeyboard::A}, 418 std::pair<Qt::Key, Settings::NativeKeyboard::Keys>{Qt::Key_A, Settings::NativeKeyboard::A},
@@ -658,10 +678,19 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
658 input_subsystem->GetMouse()->TouchMove(touch_x, touch_y); 678 input_subsystem->GetMouse()->TouchMove(touch_x, touch_y);
659 input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y); 679 input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y);
660 680
681 // Center mouse for mouse panning
661 if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { 682 if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
662 QCursor::setPos(mapToGlobal(QPoint{center_x, center_y})); 683 QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
663 } 684 }
664 685
686 // Constrain mouse for mouse emulation with mouse panning
687 if (Settings::values.mouse_panning && Settings::values.mouse_enabled) {
688 const auto [clamped_mouse_x, clamped_mouse_y] = ClipToTouchScreen(x, y);
689 QCursor::setPos(mapToGlobal(
690 QPoint{static_cast<int>(clamped_mouse_x), static_cast<int>(clamped_mouse_y)}));
691 }
692
693 mouse_constrain_timer.stop();
665 emit MouseActivity(); 694 emit MouseActivity();
666} 695}
667 696
@@ -675,6 +704,31 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
675 input_subsystem->GetMouse()->ReleaseButton(button); 704 input_subsystem->GetMouse()->ReleaseButton(button);
676} 705}
677 706
707void GRenderWindow::ConstrainMouse() {
708 if (emu_thread == nullptr || !Settings::values.mouse_panning) {
709 mouse_constrain_timer.stop();
710 return;
711 }
712 if (!this->isActiveWindow()) {
713 mouse_constrain_timer.stop();
714 return;
715 }
716
717 if (Settings::values.mouse_enabled) {
718 const auto pos = mapFromGlobal(QCursor::pos());
719 const int new_pos_x = std::clamp(pos.x(), 0, width());
720 const int new_pos_y = std::clamp(pos.y(), 0, height());
721
722 QCursor::setPos(mapToGlobal(QPoint{new_pos_x, new_pos_y}));
723 return;
724 }
725
726 const int center_x = width() / 2;
727 const int center_y = height() / 2;
728
729 QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
730}
731
678void GRenderWindow::wheelEvent(QWheelEvent* event) { 732void GRenderWindow::wheelEvent(QWheelEvent* event) {
679 const int x = event->angleDelta().x(); 733 const int x = event->angleDelta().x();
680 const int y = event->angleDelta().y(); 734 const int y = event->angleDelta().y();
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 87b23df12..60edd464c 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -17,6 +17,7 @@
17#include <QString> 17#include <QString>
18#include <QStringList> 18#include <QStringList>
19#include <QThread> 19#include <QThread>
20#include <QTimer>
20#include <QWidget> 21#include <QWidget>
21#include <qglobal.h> 22#include <qglobal.h>
22#include <qnamespace.h> 23#include <qnamespace.h>
@@ -38,7 +39,6 @@ class QMouseEvent;
38class QObject; 39class QObject;
39class QResizeEvent; 40class QResizeEvent;
40class QShowEvent; 41class QShowEvent;
41class QTimer;
42class QTouchEvent; 42class QTouchEvent;
43class QWheelEvent; 43class QWheelEvent;
44 44
@@ -166,6 +166,7 @@ public:
166 std::pair<u32, u32> ScaleTouch(const QPointF& pos) const; 166 std::pair<u32, u32> ScaleTouch(const QPointF& pos) const;
167 167
168 void closeEvent(QCloseEvent* event) override; 168 void closeEvent(QCloseEvent* event) override;
169 void leaveEvent(QEvent* event) override;
169 170
170 void resizeEvent(QResizeEvent* event) override; 171 void resizeEvent(QResizeEvent* event) override;
171 172
@@ -229,6 +230,7 @@ private:
229 void TouchBeginEvent(const QTouchEvent* event); 230 void TouchBeginEvent(const QTouchEvent* event);
230 void TouchUpdateEvent(const QTouchEvent* event); 231 void TouchUpdateEvent(const QTouchEvent* event);
231 void TouchEndEvent(); 232 void TouchEndEvent();
233 void ConstrainMouse();
232 234
233 void RequestCameraCapture(); 235 void RequestCameraCapture();
234 void OnCameraCapture(int requestId, const QImage& img); 236 void OnCameraCapture(int requestId, const QImage& img);
@@ -268,6 +270,8 @@ private:
268 std::unique_ptr<QTimer> camera_timer; 270 std::unique_ptr<QTimer> camera_timer;
269#endif 271#endif
270 272
273 QTimer mouse_constrain_timer;
274
271 Core::System& system; 275 Core::System& system;
272 276
273protected: 277protected:
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index ef421c754..1010038b7 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -51,6 +51,8 @@ void ConfigureDebug::SetConfiguration() {
51 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue()); 51 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue());
52 ui->enable_renderdoc_hotkey->setEnabled(runtime_lock); 52 ui->enable_renderdoc_hotkey->setEnabled(runtime_lock);
53 ui->enable_renderdoc_hotkey->setChecked(Settings::values.enable_renderdoc_hotkey.GetValue()); 53 ui->enable_renderdoc_hotkey->setChecked(Settings::values.enable_renderdoc_hotkey.GetValue());
54 ui->disable_buffer_reorder->setEnabled(runtime_lock);
55 ui->disable_buffer_reorder->setChecked(Settings::values.disable_buffer_reorder.GetValue());
54 ui->enable_graphics_debugging->setEnabled(runtime_lock); 56 ui->enable_graphics_debugging->setEnabled(runtime_lock);
55 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue()); 57 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue());
56 ui->enable_shader_feedback->setEnabled(runtime_lock); 58 ui->enable_shader_feedback->setEnabled(runtime_lock);
@@ -96,6 +98,7 @@ void ConfigureDebug::ApplyConfiguration() {
96 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked(); 98 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked();
97 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); 99 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
98 Settings::values.enable_renderdoc_hotkey = ui->enable_renderdoc_hotkey->isChecked(); 100 Settings::values.enable_renderdoc_hotkey = ui->enable_renderdoc_hotkey->isChecked();
101 Settings::values.disable_buffer_reorder = ui->disable_buffer_reorder->isChecked();
99 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); 102 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked();
100 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); 103 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked();
101 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); 104 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked();
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 76fe98924..22b51f39c 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -271,19 +271,6 @@
271 </widget> 271 </widget>
272 </item> 272 </item>
273 <item row="8" column="0"> 273 <item row="8" column="0">
274 <widget class="QCheckBox" name="disable_macro_hle">
275 <property name="enabled">
276 <bool>true</bool>
277 </property>
278 <property name="toolTip">
279 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
280 </property>
281 <property name="text">
282 <string>Disable Macro HLE</string>
283 </property>
284 </widget>
285 </item>
286 <item row="7" column="0">
287 <widget class="QCheckBox" name="dump_macros"> 274 <widget class="QCheckBox" name="dump_macros">
288 <property name="enabled"> 275 <property name="enabled">
289 <bool>true</bool> 276 <bool>true</bool>
@@ -306,17 +293,27 @@
306 </property> 293 </property>
307 </widget> 294 </widget>
308 </item> 295 </item>
309 <item row="2" column="0"> 296 <item row="6" column="0">
310 <widget class="QCheckBox" name="enable_shader_feedback"> 297 <widget class="QCheckBox" name="dump_shaders">
298 <property name="enabled">
299 <bool>true</bool>
300 </property>
311 <property name="toolTip"> 301 <property name="toolTip">
312 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string> 302 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string>
313 </property> 303 </property>
314 <property name="text"> 304 <property name="text">
315 <string>Enable Shader Feedback</string> 305 <string>Dump Game Shaders</string>
316 </property> 306 </property>
317 </widget> 307 </widget>
318 </item> 308 </item>
319 <item row="6" column="0"> 309 <item row="1" column="0">
310 <widget class="QCheckBox" name="enable_renderdoc_hotkey">
311 <property name="text">
312 <string>Enable Renderdoc Hotkey</string>
313 </property>
314 </widget>
315 </item>
316 <item row="7" column="0">
320 <widget class="QCheckBox" name="disable_macro_jit"> 317 <widget class="QCheckBox" name="disable_macro_jit">
321 <property name="enabled"> 318 <property name="enabled">
322 <bool>true</bool> 319 <bool>true</bool>
@@ -330,20 +327,17 @@
330 </widget> 327 </widget>
331 </item> 328 </item>
332 <item row="9" column="0"> 329 <item row="9" column="0">
333 <spacer name="verticalSpacer_5"> 330 <widget class="QCheckBox" name="disable_macro_hle">
334 <property name="orientation"> 331 <property name="enabled">
335 <enum>Qt::Vertical</enum> 332 <bool>true</bool>
336 </property> 333 </property>
337 <property name="sizeType"> 334 <property name="toolTip">
338 <enum>QSizePolicy::Preferred</enum> 335 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
339 </property> 336 </property>
340 <property name="sizeHint" stdset="0"> 337 <property name="text">
341 <size> 338 <string>Disable Macro HLE</string>
342 <width>20</width>
343 <height>0</height>
344 </size>
345 </property> 339 </property>
346 </spacer> 340 </widget>
347 </item> 341 </item>
348 <item row="0" column="0"> 342 <item row="0" column="0">
349 <widget class="QCheckBox" name="enable_graphics_debugging"> 343 <widget class="QCheckBox" name="enable_graphics_debugging">
@@ -358,23 +352,39 @@
358 </property> 352 </property>
359 </widget> 353 </widget>
360 </item> 354 </item>
361 <item row="5" column="0"> 355 <item row="10" column="0">
362 <widget class="QCheckBox" name="dump_shaders"> 356 <spacer name="verticalSpacer_5">
363 <property name="enabled"> 357 <property name="orientation">
364 <bool>true</bool> 358 <enum>Qt::Vertical</enum>
359 </property>
360 <property name="sizeType">
361 <enum>QSizePolicy::Preferred</enum>
365 </property> 362 </property>
363 <property name="sizeHint" stdset="0">
364 <size>
365 <width>20</width>
366 <height>0</height>
367 </size>
368 </property>
369 </spacer>
370 </item>
371 <item row="2" column="0">
372 <widget class="QCheckBox" name="enable_shader_feedback">
366 <property name="toolTip"> 373 <property name="toolTip">
367 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string> 374 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
368 </property> 375 </property>
369 <property name="text"> 376 <property name="text">
370 <string>Dump Game Shaders</string> 377 <string>Enable Shader Feedback</string>
371 </property> 378 </property>
372 </widget> 379 </widget>
373 </item> 380 </item>
374 <item row="1" column="0"> 381 <item row="5" column="0">
375 <widget class="QCheckBox" name="enable_renderdoc_hotkey"> 382 <widget class="QCheckBox" name="disable_buffer_reorder">
383 <property name="toolTip">
384 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When checked, disables reording of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
385 </property>
376 <property name="text"> 386 <property name="text">
377 <string>Enable Renderdoc Hotkey</string> 387 <string>Disable Buffer Reorder</string>
378 </property> 388 </property>
379 </widget> 389 </widget>
380 </item> 390 </item>
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index c727fadd1..701b895e7 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -36,12 +36,29 @@ ConfigureGeneral::~ConfigureGeneral() = default;
36void ConfigureGeneral::SetConfiguration() {} 36void ConfigureGeneral::SetConfiguration() {}
37 37
38void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) { 38void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) {
39 QLayout& layout = *ui->general_widget->layout(); 39 QLayout& general_layout = *ui->general_widget->layout();
40 QLayout& linux_layout = *ui->linux_widget->layout();
40 41
41 std::map<u32, QWidget*> hold{}; 42 std::map<u32, QWidget*> general_hold{};
43 std::map<u32, QWidget*> linux_hold{};
42 44
43 for (const auto setting : 45 std::vector<Settings::BasicSetting*> settings;
44 UISettings::values.linkage.by_category[Settings::Category::UiGeneral]) { 46
47 auto push = [&settings](auto& list) {
48 for (auto setting : list) {
49 settings.push_back(setting);
50 }
51 };
52
53 push(UISettings::values.linkage.by_category[Settings::Category::UiGeneral]);
54 push(Settings::values.linkage.by_category[Settings::Category::Linux]);
55
56 // Only show Linux group on Unix
57#ifndef __unix__
58 ui->LinuxGroupBox->setVisible(false);
59#endif
60
61 for (const auto setting : settings) {
45 auto* widget = builder.BuildWidget(setting, apply_funcs); 62 auto* widget = builder.BuildWidget(setting, apply_funcs);
46 63
47 if (widget == nullptr) { 64 if (widget == nullptr) {
@@ -52,11 +69,23 @@ void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) {
52 continue; 69 continue;
53 } 70 }
54 71
55 hold.emplace(setting->Id(), widget); 72 switch (setting->GetCategory()) {
73 case Settings::Category::UiGeneral:
74 general_hold.emplace(setting->Id(), widget);
75 break;
76 case Settings::Category::Linux:
77 linux_hold.emplace(setting->Id(), widget);
78 break;
79 default:
80 widget->deleteLater();
81 }
56 } 82 }
57 83
58 for (const auto& [id, widget] : hold) { 84 for (const auto& [id, widget] : general_hold) {
59 layout.addWidget(widget); 85 general_layout.addWidget(widget);
86 }
87 for (const auto& [id, widget] : linux_hold) {
88 linux_layout.addWidget(widget);
60 } 89 }
61} 90}
62 91
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index a10e7d3a5..ef20891a3 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -47,6 +47,33 @@
47 </widget> 47 </widget>
48 </item> 48 </item>
49 <item> 49 <item>
50 <widget class="QGroupBox" name="LinuxGroupBox">
51 <property name="title">
52 <string>Linux</string>
53 </property>
54 <layout class="QVBoxLayout" name="LinuxVerticalLayout_1">
55 <item>
56 <widget class="QWidget" name="linux_widget" native="true">
57 <layout class="QVBoxLayout" name="LinuxVerticalLayout_2">
58 <property name="leftMargin">
59 <number>0</number>
60 </property>
61 <property name="topMargin">
62 <number>0</number>
63 </property>
64 <property name="rightMargin">
65 <number>0</number>
66 </property>
67 <property name="bottomMargin">
68 <number>0</number>
69 </property>
70 </layout>
71 </widget>
72 </item>
73 </layout>
74 </widget>
75 </item>
76 <item>
50 <spacer name="verticalSpacer"> 77 <spacer name="verticalSpacer">
51 <property name="orientation"> 78 <property name="orientation">
52 <enum>Qt::Vertical</enum> 79 <enum>Qt::Vertical</enum>
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index 2a735836e..04b771129 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -57,7 +57,7 @@
57 </widget> 57 </widget>
58 </item> 58 </item>
59 <item> 59 <item>
60 <widget class="QGroupBox" name="groupBox"> 60 <widget class="QGroupBox" name="coreGroup">
61 <property name="title"> 61 <property name="title">
62 <string>Core</string> 62 <string>Core</string>
63 </property> 63 </property>
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index daf0e1663..7e908924c 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -177,6 +177,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
177 INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"), 177 INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"),
178 QStringLiteral()); 178 QStringLiteral());
179 179
180 // Linux
181 INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QStringLiteral());
182
180 // Ui Debugging 183 // Ui Debugging
181 184
182 // Ui Multiplayer 185 // Ui Multiplayer
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index defe45198..10c788290 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -17,6 +17,7 @@
17#ifdef __unix__ 17#ifdef __unix__
18#include <csignal> 18#include <csignal>
19#include <sys/socket.h> 19#include <sys/socket.h>
20#include "common/linux/gamemode.h"
20#endif 21#endif
21 22
22#include <boost/container/flat_set.hpp> 23#include <boost/container/flat_set.hpp>
@@ -47,6 +48,7 @@
47#include "core/hle/service/am/applet_ae.h" 48#include "core/hle/service/am/applet_ae.h"
48#include "core/hle/service/am/applet_oe.h" 49#include "core/hle/service/am/applet_oe.h"
49#include "core/hle/service/am/applets/applets.h" 50#include "core/hle/service/am/applets/applets.h"
51#include "core/hle/service/set/set_sys.h"
50#include "yuzu/multiplayer/state.h" 52#include "yuzu/multiplayer/state.h"
51#include "yuzu/util/controller_navigation.h" 53#include "yuzu/util/controller_navigation.h"
52 54
@@ -186,7 +188,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
186#endif 188#endif
187 189
188constexpr int default_mouse_hide_timeout = 2500; 190constexpr int default_mouse_hide_timeout = 2500;
189constexpr int default_mouse_center_timeout = 10;
190constexpr int default_input_update_timeout = 1; 191constexpr int default_input_update_timeout = 1;
191 192
192constexpr size_t CopyBufferSize = 1_MiB; 193constexpr size_t CopyBufferSize = 1_MiB;
@@ -319,6 +320,7 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
319 provider{std::make_unique<FileSys::ManualContentProvider>()} { 320 provider{std::make_unique<FileSys::ManualContentProvider>()} {
320#ifdef __unix__ 321#ifdef __unix__
321 SetupSigInterrupts(); 322 SetupSigInterrupts();
323 SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue());
322#endif 324#endif
323 system->Initialize(); 325 system->Initialize();
324 326
@@ -436,9 +438,6 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
436 connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor); 438 connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
437 connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor); 439 connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor);
438 440
439 mouse_center_timer.setInterval(default_mouse_center_timeout);
440 connect(&mouse_center_timer, &QTimer::timeout, this, &GMainWindow::CenterMouseCursor);
441
442 update_input_timer.setInterval(default_input_update_timeout); 441 update_input_timer.setInterval(default_input_update_timeout);
443 connect(&update_input_timer, &QTimer::timeout, this, &GMainWindow::UpdateInputDrivers); 442 connect(&update_input_timer, &QTimer::timeout, this, &GMainWindow::UpdateInputDrivers);
444 update_input_timer.start(); 443 update_input_timer.start();
@@ -1048,7 +1047,12 @@ void GMainWindow::InitializeWidgets() {
1048 statusBar()->addPermanentWidget(label); 1047 statusBar()->addPermanentWidget(label);
1049 } 1048 }
1050 1049
1051 // TODO (flTobi): Add the widget when multiplayer is fully implemented 1050 firmware_label = new QLabel();
1051 firmware_label->setObjectName(QStringLiteral("FirmwareLabel"));
1052 firmware_label->setVisible(false);
1053 firmware_label->setFocusPolicy(Qt::NoFocus);
1054 statusBar()->addPermanentWidget(firmware_label);
1055
1052 statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0); 1056 statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
1053 statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0); 1057 statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
1054 1058
@@ -1370,14 +1374,6 @@ void GMainWindow::InitializeHotkeys() {
1370 } 1374 }
1371 }); 1375 });
1372 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { 1376 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
1373 if (Settings::values.mouse_enabled) {
1374 Settings::values.mouse_panning = false;
1375 QMessageBox::warning(
1376 this, tr("Emulated mouse is enabled"),
1377 tr("Real mouse input and mouse panning are incompatible. Please disable the "
1378 "emulated mouse in input advanced settings to allow mouse panning."));
1379 return;
1380 }
1381 Settings::values.mouse_panning = !Settings::values.mouse_panning; 1377 Settings::values.mouse_panning = !Settings::values.mouse_panning;
1382 if (Settings::values.mouse_panning) { 1378 if (Settings::values.mouse_panning) {
1383 render_window->installEventFilter(render_window); 1379 render_window->installEventFilter(render_window);
@@ -2126,6 +2122,10 @@ void GMainWindow::OnEmulationStopped() {
2126 2122
2127 discord_rpc->Update(); 2123 discord_rpc->Update();
2128 2124
2125#ifdef __unix__
2126 Common::Linux::StopGamemode();
2127#endif
2128
2129 // The emulation is stopped, so closing the window or not does not matter anymore 2129 // The emulation is stopped, so closing the window or not does not matter anymore
2130 disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); 2130 disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
2131 2131
@@ -2165,6 +2165,10 @@ void GMainWindow::OnEmulationStopped() {
2165 emu_frametime_label->setVisible(false); 2165 emu_frametime_label->setVisible(false);
2166 renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan); 2166 renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan);
2167 2167
2168 if (!firmware_label->text().isEmpty()) {
2169 firmware_label->setVisible(true);
2170 }
2171
2168 current_game_path.clear(); 2172 current_game_path.clear();
2169 2173
2170 // When closing the game, destroy the GLWindow to clear the context after the game is closed 2174 // When closing the game, destroy the GLWindow to clear the context after the game is closed
@@ -3504,6 +3508,10 @@ void GMainWindow::OnStartGame() {
3504 play_time_manager->Start(); 3508 play_time_manager->Start();
3505 3509
3506 discord_rpc->Update(); 3510 discord_rpc->Update();
3511
3512#ifdef __unix__
3513 Common::Linux::StartGamemode();
3514#endif
3507} 3515}
3508 3516
3509void GMainWindow::OnRestartGame() { 3517void GMainWindow::OnRestartGame() {
@@ -3524,6 +3532,10 @@ void GMainWindow::OnPauseGame() {
3524 play_time_manager->Stop(); 3532 play_time_manager->Stop();
3525 UpdateMenuState(); 3533 UpdateMenuState();
3526 AllowOSSleep(); 3534 AllowOSSleep();
3535
3536#ifdef __unix__
3537 Common::Linux::StopGamemode();
3538#endif
3527} 3539}
3528 3540
3529void GMainWindow::OnPauseContinueGame() { 3541void GMainWindow::OnPauseContinueGame() {
@@ -3805,6 +3817,9 @@ void GMainWindow::OnConfigure() {
3805 const auto old_theme = UISettings::values.theme; 3817 const auto old_theme = UISettings::values.theme;
3806 const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); 3818 const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue();
3807 const auto old_language_index = Settings::values.language_index.GetValue(); 3819 const auto old_language_index = Settings::values.language_index.GetValue();
3820#ifdef __unix__
3821 const bool old_gamemode = Settings::values.enable_gamemode.GetValue();
3822#endif
3808 3823
3809 Settings::SetConfiguringGlobal(true); 3824 Settings::SetConfiguringGlobal(true);
3810 ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), 3825 ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(),
@@ -3866,6 +3881,11 @@ void GMainWindow::OnConfigure() {
3866 if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) { 3881 if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) {
3867 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); 3882 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
3868 } 3883 }
3884#ifdef __unix__
3885 if (Settings::values.enable_gamemode.GetValue() != old_gamemode) {
3886 SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue());
3887 }
3888#endif
3869 3889
3870 if (!multiplayer_state->IsHostingPublicRoom()) { 3890 if (!multiplayer_state->IsHostingPublicRoom()) {
3871 multiplayer_state->UpdateCredentials(); 3891 multiplayer_state->UpdateCredentials();
@@ -4591,6 +4611,7 @@ void GMainWindow::UpdateStatusBar() {
4591 emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue()); 4611 emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue());
4592 game_fps_label->setVisible(true); 4612 game_fps_label->setVisible(true);
4593 emu_frametime_label->setVisible(true); 4613 emu_frametime_label->setVisible(true);
4614 firmware_label->setVisible(false);
4594} 4615}
4595 4616
4596void GMainWindow::UpdateGPUAccuracyButton() { 4617void GMainWindow::UpdateGPUAccuracyButton() {
@@ -4700,26 +4721,10 @@ void GMainWindow::ShowMouseCursor() {
4700 } 4721 }
4701} 4722}
4702 4723
4703void GMainWindow::CenterMouseCursor() {
4704 if (emu_thread == nullptr || !Settings::values.mouse_panning) {
4705 mouse_center_timer.stop();
4706 return;
4707 }
4708 if (!this->isActiveWindow()) {
4709 mouse_center_timer.stop();
4710 return;
4711 }
4712 const int center_x = render_window->width() / 2;
4713 const int center_y = render_window->height() / 2;
4714
4715 QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
4716}
4717
4718void GMainWindow::OnMouseActivity() { 4724void GMainWindow::OnMouseActivity() {
4719 if (!Settings::values.mouse_panning) { 4725 if (!Settings::values.mouse_panning) {
4720 ShowMouseCursor(); 4726 ShowMouseCursor();
4721 } 4727 }
4722 mouse_center_timer.stop();
4723} 4728}
4724 4729
4725void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { 4730void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
@@ -4810,6 +4815,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
4810 "games.")); 4815 "games."));
4811 } 4816 }
4812 4817
4818 SetFirmwareVersion();
4819
4813 if (behavior == ReinitializeKeyBehavior::Warning) { 4820 if (behavior == ReinitializeKeyBehavior::Warning) {
4814 game_list->PopulateAsync(UISettings::values.game_dirs); 4821 game_list->PopulateAsync(UISettings::values.game_dirs);
4815 } 4822 }
@@ -4837,7 +4844,7 @@ bool GMainWindow::CheckSystemArchiveDecryption() {
4837} 4844}
4838 4845
4839bool GMainWindow::CheckFirmwarePresence() { 4846bool GMainWindow::CheckFirmwarePresence() {
4840 constexpr u64 MiiEditId = 0x0100000000001009ull; 4847 constexpr u64 MiiEditId = static_cast<u64>(Service::AM::Applets::AppletProgramId::MiiEdit);
4841 4848
4842 auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); 4849 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4843 if (!bis_system) { 4850 if (!bis_system) {
@@ -4852,6 +4859,28 @@ bool GMainWindow::CheckFirmwarePresence() {
4852 return true; 4859 return true;
4853} 4860}
4854 4861
4862void GMainWindow::SetFirmwareVersion() {
4863 Service::Set::FirmwareVersionFormat firmware_data{};
4864 const auto result = Service::Set::GetFirmwareVersionImpl(
4865 firmware_data, *system, Service::Set::GetFirmwareVersionType::Version2);
4866
4867 if (result.IsError() || !CheckFirmwarePresence()) {
4868 LOG_INFO(Frontend, "Installed firmware: No firmware available");
4869 firmware_label->setVisible(false);
4870 return;
4871 }
4872
4873 firmware_label->setVisible(true);
4874
4875 const std::string display_version(firmware_data.display_version.data());
4876 const std::string display_title(firmware_data.display_title.data());
4877
4878 LOG_INFO(Frontend, "Installed firmware: {}", display_title);
4879
4880 firmware_label->setText(QString::fromStdString(display_version));
4881 firmware_label->setToolTip(QString::fromStdString(display_title));
4882}
4883
4855bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, 4884bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
4856 u64* selected_title_id, u8* selected_content_record_type) { 4885 u64* selected_title_id, u8* selected_content_record_type) {
4857 using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>; 4886 using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
@@ -4996,22 +5025,6 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
4996 AcceptDropEvent(event); 5025 AcceptDropEvent(event);
4997} 5026}
4998 5027
4999void GMainWindow::leaveEvent(QEvent* event) {
5000 if (Settings::values.mouse_panning) {
5001 const QRect& rect = geometry();
5002 QPoint position = QCursor::pos();
5003
5004 qint32 x = qBound(rect.left(), position.x(), rect.right());
5005 qint32 y = qBound(rect.top(), position.y(), rect.bottom());
5006 // Only start the timer if the mouse has left the window bound.
5007 // The leave event is also triggered when the window looses focus.
5008 if (x != position.x() || y != position.y()) {
5009 mouse_center_timer.start();
5010 }
5011 event->accept();
5012 }
5013}
5014
5015bool GMainWindow::ConfirmChangeGame() { 5028bool GMainWindow::ConfirmChangeGame() {
5016 if (emu_thread == nullptr) 5029 if (emu_thread == nullptr)
5017 return true; 5030 return true;
@@ -5181,6 +5194,14 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
5181 discord_rpc->Update(); 5194 discord_rpc->Update();
5182} 5195}
5183 5196
5197#ifdef __unix__
5198void GMainWindow::SetGamemodeEnabled(bool state) {
5199 if (emulation_running) {
5200 Common::Linux::SetGamemodeState(state);
5201 }
5202}
5203#endif
5204
5184void GMainWindow::changeEvent(QEvent* event) { 5205void GMainWindow::changeEvent(QEvent* event) {
5185#ifdef __unix__ 5206#ifdef __unix__
5186 // PaletteChange event appears to only reach so far into the GUI, explicitly asking to 5207 // PaletteChange event appears to only reach so far into the GUI, explicitly asking to
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index c989c079d..530e445f9 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -340,6 +340,7 @@ private:
340 void SetupSigInterrupts(); 340 void SetupSigInterrupts();
341 static void HandleSigInterrupt(int); 341 static void HandleSigInterrupt(int);
342 void OnSigInterruptNotifierActivated(); 342 void OnSigInterruptNotifierActivated();
343 void SetGamemodeEnabled(bool state);
343#endif 344#endif
344 345
345private slots: 346private slots:
@@ -451,13 +452,13 @@ private:
451 void UpdateInputDrivers(); 452 void UpdateInputDrivers();
452 void HideMouseCursor(); 453 void HideMouseCursor();
453 void ShowMouseCursor(); 454 void ShowMouseCursor();
454 void CenterMouseCursor();
455 void OpenURL(const QUrl& url); 455 void OpenURL(const QUrl& url);
456 void LoadTranslation(); 456 void LoadTranslation();
457 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); 457 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
458 bool CheckDarkMode(); 458 bool CheckDarkMode();
459 bool CheckSystemArchiveDecryption(); 459 bool CheckSystemArchiveDecryption();
460 bool CheckFirmwarePresence(); 460 bool CheckFirmwarePresence();
461 void SetFirmwareVersion();
461 void ConfigureFilesystemProvider(const std::string& filepath); 462 void ConfigureFilesystemProvider(const std::string& filepath);
462 /** 463 /**
463 * Open (or not) the right confirm dialog based on current setting and game exit lock 464 * Open (or not) the right confirm dialog based on current setting and game exit lock
@@ -512,6 +513,7 @@ private:
512 QLabel* game_fps_label = nullptr; 513 QLabel* game_fps_label = nullptr;
513 QLabel* emu_frametime_label = nullptr; 514 QLabel* emu_frametime_label = nullptr;
514 QLabel* tas_label = nullptr; 515 QLabel* tas_label = nullptr;
516 QLabel* firmware_label = nullptr;
515 QPushButton* gpu_accuracy_button = nullptr; 517 QPushButton* gpu_accuracy_button = nullptr;
516 QPushButton* renderer_status_button = nullptr; 518 QPushButton* renderer_status_button = nullptr;
517 QPushButton* dock_status_button = nullptr; 519 QPushButton* dock_status_button = nullptr;
@@ -533,7 +535,6 @@ private:
533 bool auto_paused = false; 535 bool auto_paused = false;
534 bool auto_muted = false; 536 bool auto_muted = false;
535 QTimer mouse_hide_timer; 537 QTimer mouse_hide_timer;
536 QTimer mouse_center_timer;
537 QTimer update_input_timer; 538 QTimer update_input_timer;
538 539
539 QString startup_icon_theme; 540 QString startup_icon_theme;
@@ -590,5 +591,4 @@ protected:
590 void dropEvent(QDropEvent* event) override; 591 void dropEvent(QDropEvent* event) override;
591 void dragEnterEvent(QDragEnterEvent* event) override; 592 void dragEnterEvent(QDragEnterEvent* event) override;
592 void dragMoveEvent(QDragMoveEvent* event) override; 593 void dragMoveEvent(QDragMoveEvent* event) override;
593 void leaveEvent(QEvent* event) override;
594}; 594};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 0416d5951..a81635fa4 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -63,6 +63,10 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
63} 63}
64#endif 64#endif
65 65
66#ifdef __unix__
67#include "common/linux/gamemode.h"
68#endif
69
66static void PrintHelp(const char* argv0) { 70static void PrintHelp(const char* argv0) {
67 std::cout << "Usage: " << argv0 71 std::cout << "Usage: " << argv0
68 << " [options] <filename>\n" 72 << " [options] <filename>\n"
@@ -425,6 +429,10 @@ int main(int argc, char** argv) {
425 exit(0); 429 exit(0);
426 }); 430 });
427 431
432#ifdef __unix__
433 Common::Linux::StartGamemode();
434#endif
435
428 void(system.Run()); 436 void(system.Run());
429 if (system.DebuggerEnabled()) { 437 if (system.DebuggerEnabled()) {
430 system.InitializeDebugger(); 438 system.InitializeDebugger();
@@ -436,6 +444,10 @@ int main(int argc, char** argv) {
436 void(system.Pause()); 444 void(system.Pause());
437 system.ShutdownMainProcess(); 445 system.ShutdownMainProcess();
438 446
447#ifdef __unix__
448 Common::Linux::StopGamemode();
449#endif
450
439 detached_tasks.WaitForAllTasks(); 451 detached_tasks.WaitForAllTasks();
440 return 0; 452 return 0;
441} 453}