diff options
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) | |||
| 295 | find_package(nlohmann_json 3.8 REQUIRED) | 295 | find_package(nlohmann_json 3.8 REQUIRED) |
| 296 | find_package(Opus 1.3 MODULE) | 296 | find_package(Opus 1.3 MODULE) |
| 297 | find_package(RenderDoc MODULE) | 297 | find_package(RenderDoc MODULE) |
| 298 | find_package(SimpleIni MODULE) | ||
| 298 | find_package(stb MODULE) | 299 | find_package(stb MODULE) |
| 299 | find_package(VulkanMemoryAllocator CONFIG) | 300 | find_package(VulkanMemoryAllocator CONFIG) |
| 300 | find_package(ZLIB 1.2 REQUIRED) | 301 | find_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 | |||
| 5 | find_path(SimpleIni_INCLUDE_DIR SimpleIni.h) | ||
| 6 | |||
| 7 | include(FindPackageHandleStandardArgs) | ||
| 8 | find_package_handle_standard_args(SimpleIni | ||
| 9 | REQUIRED_VARS SimpleIni_INCLUDE_DIR | ||
| 10 | ) | ||
| 11 | |||
| 12 | if (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 | ) | ||
| 17 | endif() | ||
| 18 | |||
| 19 | mark_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() |
| 194 | endif() | 194 | endif() |
| 195 | 195 | ||
| 196 | if (CMAKE_SYSTEM_NAME STREQUAL "Linux") | ||
| 197 | add_subdirectory(gamemode) | ||
| 198 | endif() | ||
| 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 |
| 198 | if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client) | 202 | if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client) |
| @@ -296,4 +300,6 @@ if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client) | |||
| 296 | endif() | 300 | endif() |
| 297 | 301 | ||
| 298 | # SimpleIni | 302 | # SimpleIni |
| 299 | add_subdirectory(simpleini) | 303 | if (NOT TARGET SimpleIni::SimpleIni) |
| 304 | add_subdirectory(simpleini) | ||
| 305 | endif() | ||
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 | |||
| 4 | project(gamemode LANGUAGES CXX C) | ||
| 5 | |||
| 6 | add_library(gamemode include/gamemode_client.h) | ||
| 7 | |||
| 8 | target_link_libraries(gamemode PRIVATE common) | ||
| 9 | |||
| 10 | target_include_directories(gamemode PUBLIC include) | ||
| 11 | set_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 | |||
| 6 | Copyright (c) 2017-2019, Feral Interactive | ||
| 7 | All rights reserved. | ||
| 8 | |||
| 9 | Redistribution and use in source and binary forms, with or without | ||
| 10 | modification, 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 | |||
| 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
| 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
| 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
| 24 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | ||
| 25 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
| 26 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
| 27 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
| 28 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
| 29 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
| 30 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
| 31 | POSSIBILITY 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 | |||
| 90 | static 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 | */ | ||
| 98 | static volatile int internal_libgamemode_loaded = 1; | ||
| 99 | |||
| 100 | /* Typedefs for the functions to load */ | ||
| 101 | typedef int (*api_call_return_int)(void); | ||
| 102 | typedef const char *(*api_call_return_cstring)(void); | ||
| 103 | typedef int (*api_call_pid_return_int)(pid_t); | ||
| 104 | |||
| 105 | /* Storage for functors */ | ||
| 106 | static api_call_return_int REAL_internal_gamemode_request_start = NULL; | ||
| 107 | static api_call_return_int REAL_internal_gamemode_request_end = NULL; | ||
| 108 | static api_call_return_int REAL_internal_gamemode_query_status = NULL; | ||
| 109 | static api_call_return_cstring REAL_internal_gamemode_error_string = NULL; | ||
| 110 | static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL; | ||
| 111 | static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL; | ||
| 112 | static 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 | ||
| 255 | int 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 | ||
| 284 | int 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 | |||
| 4 | package org.yuzu.yuzu_emu.adapters | ||
| 5 | |||
| 6 | import android.net.Uri | ||
| 7 | import android.text.TextUtils | ||
| 8 | import android.view.LayoutInflater | ||
| 9 | import android.view.ViewGroup | ||
| 10 | import androidx.fragment.app.FragmentActivity | ||
| 11 | import androidx.recyclerview.widget.AsyncDifferConfig | ||
| 12 | import androidx.recyclerview.widget.DiffUtil | ||
| 13 | import androidx.recyclerview.widget.ListAdapter | ||
| 14 | import androidx.recyclerview.widget.RecyclerView | ||
| 15 | import org.yuzu.yuzu_emu.databinding.CardFolderBinding | ||
| 16 | import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment | ||
| 17 | import org.yuzu.yuzu_emu.model.GameDir | ||
| 18 | import org.yuzu.yuzu_emu.model.GamesViewModel | ||
| 19 | |||
| 20 | class 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 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import android.text.TextUtils | ||
| 7 | import android.widget.Toast | ||
| 8 | import org.yuzu.yuzu_emu.R | 6 | import org.yuzu.yuzu_emu.R |
| 9 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 10 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 11 | 7 | ||
| 12 | object Settings { | 8 | object 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 | |||
| 19 | import androidx.navigation.fragment.NavHostFragment | 19 | import androidx.navigation.fragment.NavHostFragment |
| 20 | import androidx.navigation.navArgs | 20 | import androidx.navigation.navArgs |
| 21 | import com.google.android.material.color.MaterialColors | 21 | import com.google.android.material.color.MaterialColors |
| 22 | import kotlinx.coroutines.CoroutineScope | ||
| 23 | import kotlinx.coroutines.Dispatchers | ||
| 22 | import kotlinx.coroutines.flow.collectLatest | 24 | import kotlinx.coroutines.flow.collectLatest |
| 23 | import kotlinx.coroutines.launch | 25 | import kotlinx.coroutines.launch |
| 24 | import java.io.IOException | 26 | import java.io.IOException |
| 25 | import org.yuzu.yuzu_emu.R | 27 | import org.yuzu.yuzu_emu.R |
| 26 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding | 28 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding |
| 27 | import org.yuzu.yuzu_emu.features.settings.model.Settings | ||
| 28 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 29 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 29 | import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment | 30 | import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment |
| 30 | import org.yuzu.yuzu_emu.model.SettingsViewModel | 31 | import 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 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.app.Dialog | ||
| 7 | import android.content.DialogInterface | ||
| 8 | import android.net.Uri | ||
| 9 | import android.os.Bundle | ||
| 10 | import androidx.fragment.app.DialogFragment | ||
| 11 | import androidx.fragment.app.activityViewModels | ||
| 12 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 13 | import org.yuzu.yuzu_emu.R | ||
| 14 | import org.yuzu.yuzu_emu.databinding.DialogAddFolderBinding | ||
| 15 | import org.yuzu.yuzu_emu.model.GameDir | ||
| 16 | import org.yuzu.yuzu_emu.model.GamesViewModel | ||
| 17 | |||
| 18 | class 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 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.app.Dialog | ||
| 7 | import android.content.DialogInterface | ||
| 8 | import android.os.Bundle | ||
| 9 | import androidx.fragment.app.DialogFragment | ||
| 10 | import androidx.fragment.app.activityViewModels | ||
| 11 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 12 | import org.yuzu.yuzu_emu.R | ||
| 13 | import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding | ||
| 14 | import org.yuzu.yuzu_emu.model.GameDir | ||
| 15 | import org.yuzu.yuzu_emu.model.GamesViewModel | ||
| 16 | import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable | ||
| 17 | |||
| 18 | class 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 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.content.Intent | ||
| 7 | import android.os.Bundle | ||
| 8 | import android.view.LayoutInflater | ||
| 9 | import android.view.View | ||
| 10 | import android.view.ViewGroup | ||
| 11 | import androidx.core.view.ViewCompat | ||
| 12 | import androidx.core.view.WindowInsetsCompat | ||
| 13 | import androidx.core.view.updatePadding | ||
| 14 | import androidx.fragment.app.Fragment | ||
| 15 | import androidx.fragment.app.activityViewModels | ||
| 16 | import androidx.lifecycle.Lifecycle | ||
| 17 | import androidx.lifecycle.lifecycleScope | ||
| 18 | import androidx.lifecycle.repeatOnLifecycle | ||
| 19 | import androidx.navigation.findNavController | ||
| 20 | import androidx.recyclerview.widget.GridLayoutManager | ||
| 21 | import com.google.android.material.transition.MaterialSharedAxis | ||
| 22 | import kotlinx.coroutines.launch | ||
| 23 | import org.yuzu.yuzu_emu.R | ||
| 24 | import org.yuzu.yuzu_emu.adapters.FolderAdapter | ||
| 25 | import org.yuzu.yuzu_emu.databinding.FragmentFoldersBinding | ||
| 26 | import org.yuzu.yuzu_emu.model.GamesViewModel | ||
| 27 | import org.yuzu.yuzu_emu.model.HomeViewModel | ||
| 28 | import org.yuzu.yuzu_emu.ui.main.MainActivity | ||
| 29 | |||
| 30 | class 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 | |||
| 42 | import org.yuzu.yuzu_emu.model.StepState | 42 | import org.yuzu.yuzu_emu.model.StepState |
| 43 | import org.yuzu.yuzu_emu.ui.main.MainActivity | 43 | import org.yuzu.yuzu_emu.ui.main.MainActivity |
| 44 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 44 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 45 | import org.yuzu.yuzu_emu.utils.GameHelper | 45 | import org.yuzu.yuzu_emu.utils.NativeConfig |
| 46 | import org.yuzu.yuzu_emu.utils.ViewUtils | 46 | import org.yuzu.yuzu_emu.utils.ViewUtils |
| 47 | 47 | ||
| 48 | class SetupFragment : Fragment() { | 48 | class 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 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | import android.os.Parcelable | ||
| 7 | import kotlinx.parcelize.Parcelize | ||
| 8 | |||
| 9 | @Parcelize | ||
| 10 | data 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 | |||
| 12 | import kotlinx.coroutines.Dispatchers | 12 | import kotlinx.coroutines.Dispatchers |
| 13 | import kotlinx.coroutines.flow.MutableStateFlow | 13 | import kotlinx.coroutines.flow.MutableStateFlow |
| 14 | import kotlinx.coroutines.flow.StateFlow | 14 | import kotlinx.coroutines.flow.StateFlow |
| 15 | import kotlinx.coroutines.flow.asStateFlow | ||
| 15 | import kotlinx.coroutines.launch | 16 | import kotlinx.coroutines.launch |
| 16 | import kotlinx.coroutines.withContext | 17 | import kotlinx.coroutines.withContext |
| 17 | import kotlinx.serialization.decodeFromString | 18 | import kotlinx.serialization.decodeFromString |
| @@ -20,6 +21,7 @@ import org.yuzu.yuzu_emu.NativeLibrary | |||
| 20 | import org.yuzu.yuzu_emu.YuzuApplication | 21 | import org.yuzu.yuzu_emu.YuzuApplication |
| 21 | import org.yuzu.yuzu_emu.utils.GameHelper | 22 | import org.yuzu.yuzu_emu.utils.GameHelper |
| 22 | import org.yuzu.yuzu_emu.utils.GameMetadata | 23 | import org.yuzu.yuzu_emu.utils.GameMetadata |
| 24 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 23 | 25 | ||
| 24 | class GamesViewModel : ViewModel() { | 26 | class 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 | ||
| 4 | package org.yuzu.yuzu_emu.model | 4 | package org.yuzu.yuzu_emu.model |
| 5 | 5 | ||
| 6 | import android.net.Uri | ||
| 7 | import androidx.fragment.app.FragmentActivity | ||
| 8 | import androidx.lifecycle.ViewModel | 6 | import androidx.lifecycle.ViewModel |
| 9 | import androidx.lifecycle.ViewModelProvider | ||
| 10 | import androidx.preference.PreferenceManager | ||
| 11 | import kotlinx.coroutines.flow.MutableStateFlow | 7 | import kotlinx.coroutines.flow.MutableStateFlow |
| 12 | import kotlinx.coroutines.flow.StateFlow | 8 | import kotlinx.coroutines.flow.StateFlow |
| 13 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 14 | import org.yuzu.yuzu_emu.utils.GameHelper | ||
| 15 | 9 | ||
| 16 | class HomeViewModel : ViewModel() { | 10 | class 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 | |||
| 13 | class SettingsViewModel : ViewModel() { | 13 | class 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 | |||
| 40 | import org.yuzu.yuzu_emu.activities.EmulationActivity | 40 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
| 41 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding | 41 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding |
| 42 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 42 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 43 | import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment | ||
| 43 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment | 44 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment |
| 44 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | 45 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
| 45 | import org.yuzu.yuzu_emu.getPublicFilesDir | 46 | import 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 | |||
| 11 | import org.yuzu.yuzu_emu.NativeLibrary | 11 | import org.yuzu.yuzu_emu.NativeLibrary |
| 12 | import org.yuzu.yuzu_emu.YuzuApplication | 12 | import org.yuzu.yuzu_emu.YuzuApplication |
| 13 | import org.yuzu.yuzu_emu.model.Game | 13 | import org.yuzu.yuzu_emu.model.Game |
| 14 | import org.yuzu.yuzu_emu.model.GameDir | ||
| 14 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | 15 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile |
| 15 | 16 | ||
| 16 | object GameHelper { | 17 | object 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 | ||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.model.GameDir | ||
| 7 | |||
| 6 | object NativeConfig { | 8 | object 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() { | |||
| 34 | void AndroidConfig::ReadAndroidValues() { | 34 | void 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 | ||
| 49 | void AndroidConfig::ReadUIValues() { | ||
| 50 | BeginGroup(Settings::TranslateCategory(Settings::Category::Ui)); | ||
| 51 | |||
| 52 | ReadPathValues(); | ||
| 53 | |||
| 54 | EndGroup(); | ||
| 55 | } | ||
| 56 | |||
| 57 | void 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 | |||
| 48 | void AndroidConfig::SaveAndroidValues() { | 74 | void 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 | ||
| 91 | void AndroidConfig::SaveUIValues() { | ||
| 92 | BeginGroup(Settings::TranslateCategory(Settings::Category::Ui)); | ||
| 93 | |||
| 94 | SavePathValues(); | ||
| 95 | |||
| 96 | EndGroup(); | ||
| 97 | } | ||
| 98 | |||
| 99 | void 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 | |||
| 64 | std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) { | 114 | std::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 | ||
| 10 | namespace AndroidSettings { | 10 | namespace AndroidSettings { |
| 11 | 11 | ||
| 12 | struct GameDir { | ||
| 13 | std::string path; | ||
| 14 | bool deep_scan = false; | ||
| 15 | }; | ||
| 16 | |||
| 12 | struct Values { | 17 | struct 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; | |||
| 13 | static jclass s_native_library_class; | 13 | static jclass s_native_library_class; |
| 14 | static jclass s_disk_cache_progress_class; | 14 | static jclass s_disk_cache_progress_class; |
| 15 | static jclass s_load_callback_stage_class; | 15 | static jclass s_load_callback_stage_class; |
| 16 | static jclass s_game_dir_class; | ||
| 17 | static jmethodID s_game_dir_constructor; | ||
| 16 | static jmethodID s_exit_emulation_activity; | 18 | static jmethodID s_exit_emulation_activity; |
| 17 | static jmethodID s_disk_cache_load_progress; | 19 | static jmethodID s_disk_cache_load_progress; |
| 18 | static jmethodID s_on_emulation_started; | 20 | static 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 | ||
| 58 | jclass GetGameDirClass() { | ||
| 59 | return s_game_dir_class; | ||
| 60 | } | ||
| 61 | |||
| 62 | jmethodID GetGameDirConstructor() { | ||
| 63 | return s_game_dir_constructor; | ||
| 64 | } | ||
| 65 | |||
| 56 | jmethodID GetExitEmulationActivity() { | 66 | jmethodID 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(); | |||
| 13 | jclass GetNativeLibraryClass(); | 13 | jclass GetNativeLibraryClass(); |
| 14 | jclass GetDiskCacheProgressClass(); | 14 | jclass GetDiskCacheProgressClass(); |
| 15 | jclass GetDiskCacheLoadCallbackStageClass(); | 15 | jclass GetDiskCacheLoadCallbackStageClass(); |
| 16 | jclass GetGameDirClass(); | ||
| 17 | jmethodID GetGameDirConstructor(); | ||
| 16 | jmethodID GetExitEmulationActivity(); | 18 | jmethodID GetExitEmulationActivity(); |
| 17 | jmethodID GetDiskCacheLoadProgress(); | 19 | jmethodID GetDiskCacheLoadProgress(); |
| 18 | jmethodID GetOnEmulationStarted(); | 20 | jmethodID 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 | ||
| 15 | std::unique_ptr<AndroidConfig> config; | 16 | std::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 | ||
| 257 | jobjectArray 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 | |||
| 272 | void 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 | |||
| 295 | void 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 | ) |
| 183 | endif() | 183 | endif() |
| 184 | 184 | ||
| 185 | if (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) | ||
| 192 | endif() | ||
| 193 | |||
| 185 | if(ARCHITECTURE_x86_64) | 194 | if(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 | |||
| 10 | namespace Common::Linux { | ||
| 11 | |||
| 12 | void 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 | |||
| 22 | void 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 | |||
| 32 | void 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 | |||
| 6 | namespace Common::Linux { | ||
| 7 | |||
| 8 | /** | ||
| 9 | * Start the (Feral Interactive) Linux gamemode if it is installed and it is activated | ||
| 10 | */ | ||
| 11 | void StartGamemode(); | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Stop the (Feral Interactive) Linux gamemode if it is installed and it is activated | ||
| 15 | */ | ||
| 16 | void 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 | */ | ||
| 22 | void 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 | ||
| 514 | void EmulatedController::UnloadInput() { | 515 | void 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 | ||
| 1209 | bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { | 1211 | bool 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 | ||
| 1331 | bool EmulatedController::HasNfc() const { | 1348 | bool 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 | ||
| 1368 | bool EmulatedController::StartNfcPolling() { | 1390 | bool 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 | ||
| 1379 | bool EmulatedController::StopNfcPolling() { | 1405 | bool 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 | ||
| 1390 | bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) { | 1420 | bool 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 | ||
| 1401 | bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request, | 1435 | bool 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 | ||
| 1414 | bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) { | 1452 | bool 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 | ||
| 1425 | bool EmulatedController::WriteNfc(const std::vector<u8>& data) { | 1467 | bool 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 | ||
| 1436 | void EmulatedController::SetLedPattern() { | 1482 | void 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 { | |||
| 16 | constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400; | 16 | constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400; |
| 17 | 17 | ||
| 18 | TouchScreen::TouchScreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_) | 18 | TouchScreen::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 | ||
| 124 | void 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 | |||
| 31 | private: | 33 | private: |
| 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 | ||
| 2367 | void 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 | |||
| 2366 | std::shared_ptr<ResourceManager> IHidServer::GetResourceManager() { | 2382 | std::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 | ||
| 100 | void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) { | 100 | void 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 | ||
| 403 | Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target_) { | 403 | Result 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 | ||
| 854 | Result 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 | |||
| 873 | Result NfcDevice::Format() { | 877 | Result 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 | ||
| 20 | namespace Service::Set { | 20 | namespace Service::Set { |
| 21 | 21 | ||
| 22 | namespace { | 22 | Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system, |
| 23 | constexpr u64 SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET = 0x05; | 23 | GetFirmwareVersionType type) { |
| 24 | |||
| 25 | enum class GetFirmwareVersionType { | ||
| 26 | Version1, | ||
| 27 | Version2, | ||
| 28 | }; | ||
| 29 | |||
| 30 | void 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 | ||
| 89 | void SET_SYS::SetLanguageCode(HLERequestContext& ctx) { | 75 | void 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 | ||
| 99 | void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) { | 85 | void 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 | ||
| 104 | void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) { | 100 | void 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 | ||
| 109 | void SET_SYS::GetAccountSettings(HLERequestContext& ctx) { | 115 | void 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 | ||
| 14 | namespace Service::Set { | 15 | namespace Service::Set { |
| 16 | enum class LanguageCode : u64; | ||
| 17 | enum class GetFirmwareVersionType { | ||
| 18 | Version1, | ||
| 19 | Version2, | ||
| 20 | }; | ||
| 21 | |||
| 22 | struct 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 | }; | ||
| 35 | static_assert(sizeof(FirmwareVersionFormat) == 0x100, "FirmwareVersionFormat is an invalid size"); | ||
| 36 | |||
| 37 | Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system, | ||
| 38 | GetFirmwareVersionType type); | ||
| 15 | 39 | ||
| 16 | class SET_SYS final : public ServiceFramework<SET_SYS> { | 40 | class SET_SYS final : public ServiceFramework<SET_SYS> { |
| 17 | public: | 41 | public: |
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 | |||
| 14 | namespace Service::Time::Clock { | 19 | namespace Service::Time::Clock { |
| 15 | 20 | ||
| 16 | enum class TimeType : u8 { | 21 | enum 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 | ||
| 9 | create_target_directory_groups(frontend_common) | 9 | create_target_directory_groups(frontend_common) |
| 10 | target_link_libraries(frontend_common PUBLIC core SimpleIni PRIVATE common Boost::headers) | 10 | target_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 | ||
| 10 | namespace Shader::Backend::GLASM { | 11 | namespace 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 | ||
| 16 | namespace Shader::Optimization { | 17 | namespace 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 |
| 411 | IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer) { | 412 | IR::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 | ||
| 519 | void GlobalMemoryToStorageBufferPass(IR::Program& program) { | 523 | void 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); | |||
| 16 | void ConditionalBarrierPass(IR::Program& program); | 16 | void ConditionalBarrierPass(IR::Program& program); |
| 17 | void ConstantPropagationPass(Environment& env, IR::Program& program); | 17 | void ConstantPropagationPass(Environment& env, IR::Program& program); |
| 18 | void DeadCodeEliminationPass(IR::Program& program); | 18 | void DeadCodeEliminationPass(IR::Program& program); |
| 19 | void GlobalMemoryToStorageBufferPass(IR::Program& program); | 19 | void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateInfo& host_info); |
| 20 | void IdentityRemovalPass(IR::Program& program); | 20 | void IdentityRemovalPass(IR::Program& program); |
| 21 | void LowerFp64ToFp32(IR::Program& program); | 21 | void LowerFp64ToFp32(IR::Program& program); |
| 22 | void LowerFp16ToFp32(IR::Program& program); | 22 | void 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 | ||
| 1468 | template <class P> | 1488 | template <class P> |
| 1469 | bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) { | 1489 | bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) { |
| 1470 | return SynchronizeBufferImpl(buffer, cpu_addr, size); | ||
| 1471 | } | ||
| 1472 | |||
| 1473 | template <class P> | ||
| 1474 | bool 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 | ||
| 1496 | template <class P> | 1511 | template <class P> |
| 1497 | bool 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 | |||
| 1541 | template <class P> | ||
| 1542 | void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy, | 1512 | void 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 | |||
| 9 | namespace VideoCommon { | ||
| 10 | |||
| 11 | class 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 | |||
| 16 | public: | ||
| 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 | |||
| 57 | private: | ||
| 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 | |||
| 75 | private: | ||
| 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 | ||
| 180 | void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, GLuint src_buffer, | 180 | void 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 | ||
| 185 | void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer, | 186 | void 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 | ||
| 190 | void BufferCacheRuntime::PreCopyBarrier() { | 191 | void 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 | |||
| 185 | private: | 198 | private: |
| 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 | ||
| 81 | Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params) | 81 | Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params) |
| 82 | : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params) {} | 82 | : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params), tracker{4096} {} |
| 83 | 83 | ||
| 84 | Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_, | 84 | Buffer::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 | ||
| 358 | u32 BufferCacheRuntime::GetStorageBufferAlignment() const { | ||
| 359 | return static_cast<u32>(device.GetStorageBufferAlignment()); | ||
| 360 | } | ||
| 361 | |||
| 362 | void 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 | |||
| 358 | void BufferCacheRuntime::Finish() { | 368 | void BufferCacheRuntime::Finish() { |
| 359 | scheduler.Finish(); | 369 | scheduler.Finish(); |
| 360 | } | 370 | } |
| 361 | 371 | ||
| 372 | bool 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 | |||
| 362 | void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer, | 384 | void 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 | ||
| 54 | class QuadArrayIndexBuffer; | 68 | class 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 | ||
| 103 | VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, | 103 | VkResult 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 | ||
| 117 | VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, | 119 | VkResult 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 | ||
| 150 | VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, | 155 | VkResult 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 | ||
| 58 | private: | 58 | private: |
| 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 | ||
| 23 | MICROPROFILE_DECLARE(Vulkan_WaitForWorker); | 23 | MICROPROFILE_DECLARE(Vulkan_WaitForWorker); |
| 24 | 24 | ||
| 25 | void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) { | 25 | void 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 | ||
| 210 | u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { | 218 | u64 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 | ||
| 41 | private: | 45 | private: |
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 | ||
| 1788 | ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams& params) | 1788 | ImageView::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 | ||
| 1791 | ImageView::~ImageView() = default; | 1805 | ImageView::~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 | |||
| 141 | private: | 145 | private: |
| 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}) |
| 387 | endif() | 387 | endif() |
| 388 | if (UNIX AND NOT APPLE) | 388 | if (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) |
| 390 | endif() | 390 | endif() |
| 391 | 391 | ||
| 392 | target_compile_definitions(yuzu PRIVATE | 392 | target_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; | |||
| 66 | class QPaintEngine; | 65 | class QPaintEngine; |
| 67 | class QSurface; | 66 | class QSurface; |
| 68 | 67 | ||
| 68 | constexpr int default_mouse_constrain_timeout = 10; | ||
| 69 | |||
| 69 | EmuThread::EmuThread(Core::System& system) : m_system{system} {} | 70 | EmuThread::EmuThread(Core::System& system) : m_system{system} {} |
| 70 | 71 | ||
| 71 | EmuThread::~EmuThread() = default; | 72 | EmuThread::~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 | ||
| 309 | void GRenderWindow::ExecuteProgram(std::size_t program_index) { | 313 | void 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 | ||
| 400 | void 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 | |||
| 396 | int GRenderWindow::QtKeyToSwitchKey(Qt::Key qt_key) { | 416 | int 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 | ||
| 707 | void 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 | |||
| 678 | void GRenderWindow::wheelEvent(QWheelEvent* event) { | 732 | void 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; | |||
| 38 | class QObject; | 39 | class QObject; |
| 39 | class QResizeEvent; | 40 | class QResizeEvent; |
| 40 | class QShowEvent; | 41 | class QShowEvent; |
| 41 | class QTimer; | ||
| 42 | class QTouchEvent; | 42 | class QTouchEvent; |
| 43 | class QWheelEvent; | 43 | class 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 | ||
| 273 | protected: | 277 | protected: |
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><html><head/><body><p>When checked, disables reording of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html></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; | |||
| 36 | void ConfigureGeneral::SetConfiguration() {} | 36 | void ConfigureGeneral::SetConfiguration() {} |
| 37 | 37 | ||
| 38 | void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) { | 38 | void 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 | ||
| 188 | constexpr int default_mouse_hide_timeout = 2500; | 190 | constexpr int default_mouse_hide_timeout = 2500; |
| 189 | constexpr int default_mouse_center_timeout = 10; | ||
| 190 | constexpr int default_input_update_timeout = 1; | 191 | constexpr int default_input_update_timeout = 1; |
| 191 | 192 | ||
| 192 | constexpr size_t CopyBufferSize = 1_MiB; | 193 | constexpr 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 | ||
| 3509 | void GMainWindow::OnRestartGame() { | 3517 | void 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 | ||
| 3529 | void GMainWindow::OnPauseContinueGame() { | 3541 | void 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 | ||
| 4596 | void GMainWindow::UpdateGPUAccuracyButton() { | 4617 | void GMainWindow::UpdateGPUAccuracyButton() { |
| @@ -4700,26 +4721,10 @@ void GMainWindow::ShowMouseCursor() { | |||
| 4700 | } | 4721 | } |
| 4701 | } | 4722 | } |
| 4702 | 4723 | ||
| 4703 | void 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 | |||
| 4718 | void GMainWindow::OnMouseActivity() { | 4724 | void 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 | ||
| 4725 | void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { | 4730 | void 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 | ||
| 4839 | bool GMainWindow::CheckFirmwarePresence() { | 4846 | bool 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 | ||
| 4862 | void 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 | |||
| 4855 | bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, | 4884 | bool 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 | ||
| 4999 | void 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 | |||
| 5015 | bool GMainWindow::ConfirmChangeGame() { | 5028 | bool 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__ | ||
| 5198 | void GMainWindow::SetGamemodeEnabled(bool state) { | ||
| 5199 | if (emulation_running) { | ||
| 5200 | Common::Linux::SetGamemodeState(state); | ||
| 5201 | } | ||
| 5202 | } | ||
| 5203 | #endif | ||
| 5204 | |||
| 5184 | void GMainWindow::changeEvent(QEvent* event) { | 5205 | void 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 | ||
| 345 | private slots: | 346 | private 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 | |||
| 66 | static void PrintHelp(const char* argv0) { | 70 | static 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 | } |