summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt37
-rw-r--r--src/common/file_util.cpp49
-rw-r--r--src/common/file_util.h15
-rw-r--r--src/common/zstd_compression.cpp2
-rw-r--r--src/core/core_timing_util.cpp8
-rw-r--r--src/core/frontend/emu_window.cpp2
-rw-r--r--src/core/frontend/emu_window.h5
-rw-r--r--src/core/hle/ipc_helpers.h2
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp9
-rw-r--r--src/core/hle/kernel/hle_ipc.h8
-rw-r--r--src/core/hle/kernel/object.h4
-rw-r--r--src/core/hle/kernel/process.cpp3
-rw-r--r--src/core/hle/kernel/readable_event.cpp3
-rw-r--r--src/core/hle/kernel/server_session.cpp2
-rw-r--r--src/core/hle/kernel/svc.cpp89
-rw-r--r--src/core/hle/kernel/thread.h21
-rw-r--r--src/core/hle/kernel/vm_manager.cpp7
-rw-r--r--src/core/hle/kernel/vm_manager.h8
-rw-r--r--src/core/hle/service/am/am.cpp8
-rw-r--r--src/core/hle/service/am/applets/applets.cpp6
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp71
-rw-r--r--src/core/hle/service/audio/audctl.cpp4
-rw-r--r--src/core/hle/service/audio/audout_u.cpp2
-rw-r--r--src/core/hle/service/audio/audren_u.cpp352
-rw-r--r--src/core/hle/service/audio/audren_u.h2
-rw-r--r--src/core/hle/service/btdrv/btdrv.cpp4
-rw-r--r--src/core/hle/service/btm/btm.cpp8
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp2
-rw-r--r--src/core/hle/service/nfp/nfp.cpp6
-rw-r--r--src/core/hle/service/nifm/nifm.cpp4
-rw-r--r--src/core/hle/service/nim/nim.cpp2
-rw-r--r--src/core/hle/service/nvdrv/interface.cpp2
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.cpp2
-rw-r--r--src/core/hle/service/set/set.cpp56
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp2
-rw-r--r--src/core/hle/service/vi/vi.cpp2
-rw-r--r--src/core/loader/nso.cpp4
-rw-r--r--src/core/memory.h9
-rw-r--r--src/core/telemetry_session.cpp17
-rw-r--r--src/core/telemetry_session.h1
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/dma_pusher.cpp9
-rw-r--r--src/video_core/engines/engine_upload.cpp52
-rw-r--r--src/video_core/engines/engine_upload.h73
-rw-r--r--src/video_core/engines/fermi_2d.h6
-rw-r--r--src/video_core/engines/kepler_compute.cpp37
-rw-r--r--src/video_core/engines/kepler_compute.h175
-rw-r--r--src/video_core/engines/kepler_memory.cpp45
-rw-r--r--src/video_core/engines/kepler_memory.h66
-rw-r--r--src/video_core/engines/maxwell_3d.cpp54
-rw-r--r--src/video_core/engines/maxwell_3d.h33
-rw-r--r--src/video_core/engines/maxwell_dma.cpp83
-rw-r--r--src/video_core/engines/maxwell_dma.h43
-rw-r--r--src/video_core/engines/shader_bytecode.h40
-rw-r--r--src/video_core/gpu.cpp4
-rw-r--r--src/video_core/gpu_thread.cpp4
-rw-r--r--src/video_core/gpu_thread.h8
-rw-r--r--src/video_core/macro_interpreter.cpp6
-rw-r--r--src/video_core/memory_manager.cpp6
-rw-r--r--src/video_core/memory_manager.h31
-rw-r--r--src/video_core/rasterizer_cache.h10
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h6
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp10
-rw-r--r--src/video_core/renderer_opengl/gl_device.h14
-rw-r--r--src/video_core/renderer_opengl/gl_global_cache.h7
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp22
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h3
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp26
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h28
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp128
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h21
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp702
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp280
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.h83
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp23
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h55
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp6
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h3
-rw-r--r--src/video_core/renderer_opengl/utils.cpp16
-rw-r--r--src/video_core/renderer_opengl/utils.h4
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp10
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h7
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp26
-rw-r--r--src/video_core/shader/decode.cpp4
-rw-r--r--src/video_core/shader/decode/arithmetic.cpp3
-rw-r--r--src/video_core/shader/decode/arithmetic_half.cpp23
-rw-r--r--src/video_core/shader/decode/arithmetic_half_immediate.cpp3
-rw-r--r--src/video_core/shader/decode/arithmetic_immediate.cpp2
-rw-r--r--src/video_core/shader/decode/arithmetic_integer_immediate.cpp2
-rw-r--r--src/video_core/shader/decode/bfe.cpp2
-rw-r--r--src/video_core/shader/decode/bfi.cpp2
-rw-r--r--src/video_core/shader/decode/conversion.cpp7
-rw-r--r--src/video_core/shader/decode/ffma.cpp2
-rw-r--r--src/video_core/shader/decode/float_set.cpp2
-rw-r--r--src/video_core/shader/decode/float_set_predicate.cpp2
-rw-r--r--src/video_core/shader/decode/half_set.cpp3
-rw-r--r--src/video_core/shader/decode/half_set_predicate.cpp2
-rw-r--r--src/video_core/shader/decode/hfma2.cpp9
-rw-r--r--src/video_core/shader/decode/integer_set.cpp3
-rw-r--r--src/video_core/shader/decode/integer_set_predicate.cpp2
-rw-r--r--src/video_core/shader/decode/memory.cpp110
-rw-r--r--src/video_core/shader/decode/other.cpp56
-rw-r--r--src/video_core/shader/decode/predicate_set_predicate.cpp2
-rw-r--r--src/video_core/shader/decode/predicate_set_register.cpp2
-rw-r--r--src/video_core/shader/decode/register_set_predicate.cpp2
-rw-r--r--src/video_core/shader/decode/shift.cpp2
-rw-r--r--src/video_core/shader/decode/texture.cpp4
-rw-r--r--src/video_core/shader/decode/video.cpp2
-rw-r--r--src/video_core/shader/decode/xmad.cpp5
-rw-r--r--src/video_core/shader/shader_ir.cpp33
-rw-r--r--src/video_core/shader/shader_ir.h107
-rw-r--r--src/video_core/shader/track.cpp12
-rw-r--r--src/video_core/surface.cpp86
-rw-r--r--src/video_core/textures/astc.cpp12
-rw-r--r--src/yuzu/CMakeLists.txt6
-rw-r--r--src/yuzu/about_dialog.cpp8
-rw-r--r--src/yuzu/applets/error.cpp8
-rw-r--r--src/yuzu/applets/profile_select.cpp4
-rw-r--r--src/yuzu/applets/software_keyboard.cpp23
-rw-r--r--src/yuzu/applets/software_keyboard.h1
-rw-r--r--src/yuzu/bootmanager.cpp28
-rw-r--r--src/yuzu/bootmanager.h3
-rw-r--r--src/yuzu/compatdb.cpp6
-rw-r--r--src/yuzu/configuration/config.cpp926
-rw-r--r--src/yuzu/configuration/config.h37
-rw-r--r--src/yuzu/configuration/configure_audio.cpp14
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp7
-rw-r--r--src/yuzu/configuration/configure_gamelist.cpp8
-rw-r--r--src/yuzu/configuration/configure_general.cpp3
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp8
-rw-r--r--src/yuzu/configuration/configure_hotkeys.cpp38
-rw-r--r--src/yuzu/configuration/configure_hotkeys.h6
-rw-r--r--src/yuzu/configuration/configure_input.cpp13
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp153
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.cpp75
-rw-r--r--src/yuzu/configuration/configure_per_general.cpp24
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp2
-rw-r--r--src/yuzu/configuration/configure_system.cpp5
-rw-r--r--src/yuzu/configuration/configure_web.cpp24
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints.cpp2
-rw-r--r--src/yuzu/debugger/profiler.cpp4
-rw-r--r--src/yuzu/debugger/wait_tree.cpp39
-rw-r--r--src/yuzu/game_list.cpp103
-rw-r--r--src/yuzu/game_list.h2
-rw-r--r--src/yuzu/game_list_p.h25
-rw-r--r--src/yuzu/game_list_worker.cpp128
-rw-r--r--src/yuzu/hotkeys.h2
-rw-r--r--src/yuzu/loading_screen.cpp18
-rw-r--r--src/yuzu/main.cpp287
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu/ui_settings.h1
-rw-r--r--src/yuzu/util/sequence_dialog/sequence_dialog.cpp13
-rw-r--r--src/yuzu/util/spinbox.cpp278
-rw-r--r--src/yuzu/util/spinbox.h86
-rw-r--r--src/yuzu/util/util.cpp18
-rw-r--r--src/yuzu_cmd/CMakeLists.txt2
-rw-r--r--src/yuzu_cmd/config.cpp4
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp170
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h26
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp154
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h34
-rw-r--r--src/yuzu_cmd/yuzu.cpp4
162 files changed, 3841 insertions, 2605 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9aea4af87..04018233f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -21,15 +21,29 @@ if (MSVC)
21 # Ensure that projects build with Unicode support. 21 # Ensure that projects build with Unicode support.
22 add_definitions(-DUNICODE -D_UNICODE) 22 add_definitions(-DUNICODE -D_UNICODE)
23 23
24 # /W3 - Level 3 warnings 24 # /W3 - Level 3 warnings
25 # /MP - Multi-threaded compilation 25 # /MP - Multi-threaded compilation
26 # /Zi - Output debugging information 26 # /Zi - Output debugging information
27 # /Zo - enhanced debug info for optimized builds 27 # /Zo - Enhanced debug info for optimized builds
28 # /permissive- - enables stricter C++ standards conformance checks 28 # /permissive- - Enables stricter C++ standards conformance checks
29 # /EHsc - C++-only exception handling semantics 29 # /EHsc - C++-only exception handling semantics
30 # /Zc:throwingNew - let codegen assume `operator new` will never return null 30 # /volatile:iso - Use strict standards-compliant volatile semantics.
31 # /Zc:inline - let codegen omit inline functions in object files 31 # /Zc:externConstexpr - Allow extern constexpr variables to have external linkage, like the standard mandates
32 add_compile_options(/W3 /MP /Zi /Zo /permissive- /EHsc /std:c++latest /Zc:throwingNew,inline) 32 # /Zc:inline - Let codegen omit inline functions in object files
33 # /Zc:throwingNew - Let codegen assume `operator new` (without std::nothrow) will never return null
34 add_compile_options(
35 /W3
36 /MP
37 /Zi
38 /Zo
39 /permissive-
40 /EHsc
41 /std:c++latest
42 /volatile:iso
43 /Zc:externConstexpr
44 /Zc:inline
45 /Zc:throwingNew
46 )
33 47
34 # /GS- - No stack buffer overflow checks 48 # /GS- - No stack buffer overflow checks
35 add_compile_options("$<$<CONFIG:Release>:/GS->") 49 add_compile_options("$<$<CONFIG:Release>:/GS->")
@@ -37,7 +51,10 @@ if (MSVC)
37 set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE) 51 set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
38 set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) 52 set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
39else() 53else()
40 add_compile_options("-Wno-attributes") 54 add_compile_options(
55 -Wall
56 -Wno-attributes
57 )
41 58
42 if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) 59 if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
43 add_compile_options("-stdlib=libc++") 60 add_compile_options("-stdlib=libc++")
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index aecb66c32..2d9374783 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -78,16 +78,17 @@ namespace FileUtil {
78// Remove any ending forward slashes from directory paths 78// Remove any ending forward slashes from directory paths
79// Modifies argument. 79// Modifies argument.
80static void StripTailDirSlashes(std::string& fname) { 80static void StripTailDirSlashes(std::string& fname) {
81 if (fname.length() > 1) { 81 if (fname.length() <= 1) {
82 std::size_t i = fname.length(); 82 return;
83 while (i > 0 && fname[i - 1] == DIR_SEP_CHR) 83 }
84 --i; 84
85 fname.resize(i); 85 std::size_t i = fname.length();
86 while (i > 0 && fname[i - 1] == DIR_SEP_CHR) {
87 --i;
86 } 88 }
87 return; 89 fname.resize(i);
88} 90}
89 91
90// Returns true if file filename exists
91bool Exists(const std::string& filename) { 92bool Exists(const std::string& filename) {
92 struct stat file_info; 93 struct stat file_info;
93 94
@@ -107,7 +108,6 @@ bool Exists(const std::string& filename) {
107 return (result == 0); 108 return (result == 0);
108} 109}
109 110
110// Returns true if filename is a directory
111bool IsDirectory(const std::string& filename) { 111bool IsDirectory(const std::string& filename) {
112 struct stat file_info; 112 struct stat file_info;
113 113
@@ -132,8 +132,6 @@ bool IsDirectory(const std::string& filename) {
132 return S_ISDIR(file_info.st_mode); 132 return S_ISDIR(file_info.st_mode);
133} 133}
134 134
135// Deletes a given filename, return true on success
136// Doesn't supports deleting a directory
137bool Delete(const std::string& filename) { 135bool Delete(const std::string& filename) {
138 LOG_TRACE(Common_Filesystem, "file {}", filename); 136 LOG_TRACE(Common_Filesystem, "file {}", filename);
139 137
@@ -165,7 +163,6 @@ bool Delete(const std::string& filename) {
165 return true; 163 return true;
166} 164}
167 165
168// Returns true if successful, or path already exists.
169bool CreateDir(const std::string& path) { 166bool CreateDir(const std::string& path) {
170 LOG_TRACE(Common_Filesystem, "directory {}", path); 167 LOG_TRACE(Common_Filesystem, "directory {}", path);
171#ifdef _WIN32 168#ifdef _WIN32
@@ -194,7 +191,6 @@ bool CreateDir(const std::string& path) {
194#endif 191#endif
195} 192}
196 193
197// Creates the full path of fullPath returns true on success
198bool CreateFullPath(const std::string& fullPath) { 194bool CreateFullPath(const std::string& fullPath) {
199 int panicCounter = 100; 195 int panicCounter = 100;
200 LOG_TRACE(Common_Filesystem, "path {}", fullPath); 196 LOG_TRACE(Common_Filesystem, "path {}", fullPath);
@@ -230,7 +226,6 @@ bool CreateFullPath(const std::string& fullPath) {
230 } 226 }
231} 227}
232 228
233// Deletes a directory filename, returns true on success
234bool DeleteDir(const std::string& filename) { 229bool DeleteDir(const std::string& filename) {
235 LOG_TRACE(Common_Filesystem, "directory {}", filename); 230 LOG_TRACE(Common_Filesystem, "directory {}", filename);
236 231
@@ -252,7 +247,6 @@ bool DeleteDir(const std::string& filename) {
252 return false; 247 return false;
253} 248}
254 249
255// renames file srcFilename to destFilename, returns true on success
256bool Rename(const std::string& srcFilename, const std::string& destFilename) { 250bool Rename(const std::string& srcFilename, const std::string& destFilename) {
257 LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename); 251 LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
258#ifdef _WIN32 252#ifdef _WIN32
@@ -268,7 +262,6 @@ bool Rename(const std::string& srcFilename, const std::string& destFilename) {
268 return false; 262 return false;
269} 263}
270 264
271// copies file srcFilename to destFilename, returns true on success
272bool Copy(const std::string& srcFilename, const std::string& destFilename) { 265bool Copy(const std::string& srcFilename, const std::string& destFilename) {
273 LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename); 266 LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
274#ifdef _WIN32 267#ifdef _WIN32
@@ -324,7 +317,6 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
324#endif 317#endif
325} 318}
326 319
327// Returns the size of filename (64bit)
328u64 GetSize(const std::string& filename) { 320u64 GetSize(const std::string& filename) {
329 if (!Exists(filename)) { 321 if (!Exists(filename)) {
330 LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename); 322 LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename);
@@ -351,7 +343,6 @@ u64 GetSize(const std::string& filename) {
351 return 0; 343 return 0;
352} 344}
353 345
354// Overloaded GetSize, accepts file descriptor
355u64 GetSize(const int fd) { 346u64 GetSize(const int fd) {
356 struct stat buf; 347 struct stat buf;
357 if (fstat(fd, &buf) != 0) { 348 if (fstat(fd, &buf) != 0) {
@@ -361,7 +352,6 @@ u64 GetSize(const int fd) {
361 return buf.st_size; 352 return buf.st_size;
362} 353}
363 354
364// Overloaded GetSize, accepts FILE*
365u64 GetSize(FILE* f) { 355u64 GetSize(FILE* f) {
366 // can't use off_t here because it can be 32-bit 356 // can't use off_t here because it can be 32-bit
367 u64 pos = ftello(f); 357 u64 pos = ftello(f);
@@ -377,7 +367,6 @@ u64 GetSize(FILE* f) {
377 return size; 367 return size;
378} 368}
379 369
380// creates an empty file filename, returns true on success
381bool CreateEmptyFile(const std::string& filename) { 370bool CreateEmptyFile(const std::string& filename) {
382 LOG_TRACE(Common_Filesystem, "{}", filename); 371 LOG_TRACE(Common_Filesystem, "{}", filename);
383 372
@@ -502,7 +491,6 @@ bool DeleteDirRecursively(const std::string& directory, unsigned int recursion)
502 return true; 491 return true;
503} 492}
504 493
505// Create directory and copy contents (does not overwrite existing files)
506void CopyDir(const std::string& source_path, const std::string& dest_path) { 494void CopyDir(const std::string& source_path, const std::string& dest_path) {
507#ifndef _WIN32 495#ifndef _WIN32
508 if (source_path == dest_path) 496 if (source_path == dest_path)
@@ -539,8 +527,7 @@ void CopyDir(const std::string& source_path, const std::string& dest_path) {
539#endif 527#endif
540} 528}
541 529
542// Returns the current directory 530std::optional<std::string> GetCurrentDir() {
543std::string GetCurrentDir() {
544// Get the current working directory (getcwd uses malloc) 531// Get the current working directory (getcwd uses malloc)
545#ifdef _WIN32 532#ifdef _WIN32
546 wchar_t* dir; 533 wchar_t* dir;
@@ -550,7 +537,7 @@ std::string GetCurrentDir() {
550 if (!(dir = getcwd(nullptr, 0))) { 537 if (!(dir = getcwd(nullptr, 0))) {
551#endif 538#endif
552 LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg()); 539 LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
553 return nullptr; 540 return {};
554 } 541 }
555#ifdef _WIN32 542#ifdef _WIN32
556 std::string strDir = Common::UTF16ToUTF8(dir); 543 std::string strDir = Common::UTF16ToUTF8(dir);
@@ -561,7 +548,6 @@ std::string GetCurrentDir() {
561 return strDir; 548 return strDir;
562} 549}
563 550
564// Sets the current directory to the given directory
565bool SetCurrentDir(const std::string& directory) { 551bool SetCurrentDir(const std::string& directory) {
566#ifdef _WIN32 552#ifdef _WIN32
567 return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0; 553 return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0;
@@ -673,8 +659,6 @@ std::string GetSysDirectory() {
673 return sysDir; 659 return sysDir;
674} 660}
675 661
676// Returns a string with a yuzu data dir or file in the user's home
677// directory. To be used in "multi-user" mode (that is, installed).
678const std::string& GetUserPath(UserPath path, const std::string& new_path) { 662const std::string& GetUserPath(UserPath path, const std::string& new_path) {
679 static std::unordered_map<UserPath, std::string> paths; 663 static std::unordered_map<UserPath, std::string> paths;
680 auto& user_path = paths[UserPath::UserDir]; 664 auto& user_path = paths[UserPath::UserDir];
@@ -762,11 +746,11 @@ std::string GetNANDRegistrationDir(bool system) {
762 return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/"; 746 return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/";
763} 747}
764 748
765std::size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) { 749std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str) {
766 return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size()); 750 return IOFile(filename, text_file ? "w" : "wb").WriteString(str);
767} 751}
768 752
769std::size_t ReadFileToString(bool text_file, const char* filename, std::string& str) { 753std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str) {
770 IOFile file(filename, text_file ? "r" : "rb"); 754 IOFile file(filename, text_file ? "r" : "rb");
771 755
772 if (!file.IsOpen()) 756 if (!file.IsOpen())
@@ -776,13 +760,6 @@ std::size_t ReadFileToString(bool text_file, const char* filename, std::string&
776 return file.ReadArray(&str[0], str.size()); 760 return file.ReadArray(&str[0], str.size());
777} 761}
778 762
779/**
780 * Splits the filename into 8.3 format
781 * Loosely implemented following https://en.wikipedia.org/wiki/8.3_filename
782 * @param filename The normal filename to use
783 * @param short_name A 9-char array in which the short name will be written
784 * @param extension A 4-char array in which the extension will be written
785 */
786void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name, 763void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
787 std::array<char, 4>& extension) { 764 std::array<char, 4>& extension) {
788 const std::string forbidden_characters = ".\"/\\[]:;=, "; 765 const std::string forbidden_characters = ".\"/\\[]:;=, ";
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 38cc7f059..cde7ddf2d 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -9,6 +9,7 @@
9#include <fstream> 9#include <fstream>
10#include <functional> 10#include <functional>
11#include <limits> 11#include <limits>
12#include <optional>
12#include <string> 13#include <string>
13#include <string_view> 14#include <string_view>
14#include <type_traits> 15#include <type_traits>
@@ -118,7 +119,7 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
118bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256); 119bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);
119 120
120// Returns the current directory 121// Returns the current directory
121std::string GetCurrentDir(); 122std::optional<std::string> GetCurrentDir();
122 123
123// Create directory and copy contents (does not overwrite existing files) 124// Create directory and copy contents (does not overwrite existing files)
124void CopyDir(const std::string& source_path, const std::string& dest_path); 125void CopyDir(const std::string& source_path, const std::string& dest_path);
@@ -146,9 +147,9 @@ const std::string& GetExeDirectory();
146std::string AppDataRoamingDirectory(); 147std::string AppDataRoamingDirectory();
147#endif 148#endif
148 149
149std::size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename); 150std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str);
150 151
151std::size_t ReadFileToString(bool text_file, const char* filename, std::string& str); 152std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str);
152 153
153/** 154/**
154 * Splits the filename into 8.3 format 155 * Splits the filename into 8.3 format
@@ -257,8 +258,8 @@ public:
257 return WriteArray(&object, 1); 258 return WriteArray(&object, 1);
258 } 259 }
259 260
260 std::size_t WriteString(const std::string& str) { 261 std::size_t WriteString(std::string_view str) {
261 return WriteArray(str.c_str(), str.length()); 262 return WriteArray(str.data(), str.length());
262 } 263 }
263 264
264 bool IsOpen() const { 265 bool IsOpen() const {
@@ -286,8 +287,8 @@ private:
286template <typename T> 287template <typename T>
287void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) { 288void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
288#ifdef _MSC_VER 289#ifdef _MSC_VER
289 fstream.open(Common::UTF8ToUTF16W(filename).c_str(), openmode); 290 fstream.open(Common::UTF8ToUTF16W(filename), openmode);
290#else 291#else
291 fstream.open(filename.c_str(), openmode); 292 fstream.open(filename, openmode);
292#endif 293#endif
293} 294}
diff --git a/src/common/zstd_compression.cpp b/src/common/zstd_compression.cpp
index 60a35c67c..978526492 100644
--- a/src/common/zstd_compression.cpp
+++ b/src/common/zstd_compression.cpp
@@ -2,8 +2,6 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#pragma once
6
7#include <algorithm> 5#include <algorithm>
8#include <zstd.h> 6#include <zstd.h>
9 7
diff --git a/src/core/core_timing_util.cpp b/src/core/core_timing_util.cpp
index 7942f30d6..c0f08cddb 100644
--- a/src/core/core_timing_util.cpp
+++ b/src/core/core_timing_util.cpp
@@ -14,11 +14,11 @@ namespace Core::Timing {
14constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE; 14constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE;
15 15
16s64 usToCycles(s64 us) { 16s64 usToCycles(s64 us) {
17 if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) { 17 if (static_cast<u64>(us / 1000000) > MAX_VALUE_TO_MULTIPLY) {
18 LOG_ERROR(Core_Timing, "Integer overflow, use max value"); 18 LOG_ERROR(Core_Timing, "Integer overflow, use max value");
19 return std::numeric_limits<s64>::max(); 19 return std::numeric_limits<s64>::max();
20 } 20 }
21 if (us > MAX_VALUE_TO_MULTIPLY) { 21 if (static_cast<u64>(us) > MAX_VALUE_TO_MULTIPLY) {
22 LOG_DEBUG(Core_Timing, "Time very big, do rounding"); 22 LOG_DEBUG(Core_Timing, "Time very big, do rounding");
23 return BASE_CLOCK_RATE * (us / 1000000); 23 return BASE_CLOCK_RATE * (us / 1000000);
24 } 24 }
@@ -38,11 +38,11 @@ s64 usToCycles(u64 us) {
38} 38}
39 39
40s64 nsToCycles(s64 ns) { 40s64 nsToCycles(s64 ns) {
41 if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) { 41 if (static_cast<u64>(ns / 1000000000) > MAX_VALUE_TO_MULTIPLY) {
42 LOG_ERROR(Core_Timing, "Integer overflow, use max value"); 42 LOG_ERROR(Core_Timing, "Integer overflow, use max value");
43 return std::numeric_limits<s64>::max(); 43 return std::numeric_limits<s64>::max();
44 } 44 }
45 if (ns > MAX_VALUE_TO_MULTIPLY) { 45 if (static_cast<u64>(ns) > MAX_VALUE_TO_MULTIPLY) {
46 LOG_DEBUG(Core_Timing, "Time very big, do rounding"); 46 LOG_DEBUG(Core_Timing, "Time very big, do rounding");
47 return BASE_CLOCK_RATE * (ns / 1000000000); 47 return BASE_CLOCK_RATE * (ns / 1000000000);
48 } 48 }
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index 1320bbe77..eda466a5d 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -10,6 +10,8 @@
10 10
11namespace Core::Frontend { 11namespace Core::Frontend {
12 12
13GraphicsContext::~GraphicsContext() = default;
14
13class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>, 15class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>,
14 public std::enable_shared_from_this<TouchState> { 16 public std::enable_shared_from_this<TouchState> {
15public: 17public:
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 70a522556..4a9912641 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -19,6 +19,8 @@ namespace Core::Frontend {
19 */ 19 */
20class GraphicsContext { 20class GraphicsContext {
21public: 21public:
22 virtual ~GraphicsContext();
23
22 /// Makes the graphics context current for the caller thread 24 /// Makes the graphics context current for the caller thread
23 virtual void MakeCurrent() = 0; 25 virtual void MakeCurrent() = 0;
24 26
@@ -167,8 +169,7 @@ private:
167 * For the request to be honored, EmuWindow implementations will usually reimplement this 169 * For the request to be honored, EmuWindow implementations will usually reimplement this
168 * function. 170 * function.
169 */ 171 */
170 virtual void OnMinimalClientAreaChangeRequest( 172 virtual void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned>) {
171 const std::pair<unsigned, unsigned>& minimal_size) {
172 // By default, ignore this request and do nothing. 173 // By default, ignore this request and do nothing.
173 } 174 }
174 175
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index ac0e1d796..5bb139483 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -438,7 +438,7 @@ inline float RequestParser::Pop() {
438template <> 438template <>
439inline double RequestParser::Pop() { 439inline double RequestParser::Pop() {
440 const u64 value = Pop<u64>(); 440 const u64 value = Pop<u64>();
441 float real; 441 double real;
442 std::memcpy(&real, &value, sizeof(real)); 442 std::memcpy(&real, &value, sizeof(real));
443 return real; 443 return real;
444} 444}
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index fe710eb6e..f3da525d6 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -43,7 +43,7 @@ void SessionRequestHandler::ClientDisconnected(const SharedPtr<ServerSession>& s
43} 43}
44 44
45SharedPtr<WritableEvent> HLERequestContext::SleepClientThread( 45SharedPtr<WritableEvent> HLERequestContext::SleepClientThread(
46 SharedPtr<Thread> thread, const std::string& reason, u64 timeout, WakeupCallback&& callback, 46 const std::string& reason, u64 timeout, WakeupCallback&& callback,
47 SharedPtr<WritableEvent> writable_event) { 47 SharedPtr<WritableEvent> writable_event) {
48 // Put the client thread to sleep until the wait event is signaled or the timeout expires. 48 // Put the client thread to sleep until the wait event is signaled or the timeout expires.
49 thread->SetWakeupCallback([context = *this, callback]( 49 thread->SetWakeupCallback([context = *this, callback](
@@ -58,7 +58,7 @@ SharedPtr<WritableEvent> HLERequestContext::SleepClientThread(
58 auto& kernel = Core::System::GetInstance().Kernel(); 58 auto& kernel = Core::System::GetInstance().Kernel();
59 if (!writable_event) { 59 if (!writable_event) {
60 // Create event if not provided 60 // Create event if not provided
61 const auto pair = WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, 61 const auto pair = WritableEvent::CreateEventPair(kernel, ResetType::Automatic,
62 "HLE Pause Event: " + reason); 62 "HLE Pause Event: " + reason);
63 writable_event = pair.writable; 63 writable_event = pair.writable;
64 } 64 }
@@ -76,8 +76,9 @@ SharedPtr<WritableEvent> HLERequestContext::SleepClientThread(
76 return writable_event; 76 return writable_event;
77} 77}
78 78
79HLERequestContext::HLERequestContext(SharedPtr<Kernel::ServerSession> server_session) 79HLERequestContext::HLERequestContext(SharedPtr<Kernel::ServerSession> server_session,
80 : server_session(std::move(server_session)) { 80 SharedPtr<Thread> thread)
81 : server_session(std::move(server_session)), thread(std::move(thread)) {
81 cmd_buf[0] = 0; 82 cmd_buf[0] = 0;
82} 83}
83 84
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index 2bdd9f02c..ccf5e56aa 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -97,7 +97,7 @@ protected:
97 */ 97 */
98class HLERequestContext { 98class HLERequestContext {
99public: 99public:
100 explicit HLERequestContext(SharedPtr<ServerSession> session); 100 explicit HLERequestContext(SharedPtr<ServerSession> session, SharedPtr<Thread> thread);
101 ~HLERequestContext(); 101 ~HLERequestContext();
102 102
103 /// Returns a pointer to the IPC command buffer for this request. 103 /// Returns a pointer to the IPC command buffer for this request.
@@ -119,7 +119,6 @@ public:
119 /** 119 /**
120 * Puts the specified guest thread to sleep until the returned event is signaled or until the 120 * Puts the specified guest thread to sleep until the returned event is signaled or until the
121 * specified timeout expires. 121 * specified timeout expires.
122 * @param thread Thread to be put to sleep.
123 * @param reason Reason for pausing the thread, to be used for debugging purposes. 122 * @param reason Reason for pausing the thread, to be used for debugging purposes.
124 * @param timeout Timeout in nanoseconds after which the thread will be awoken and the callback 123 * @param timeout Timeout in nanoseconds after which the thread will be awoken and the callback
125 * invoked with a Timeout reason. 124 * invoked with a Timeout reason.
@@ -130,8 +129,8 @@ public:
130 * created. 129 * created.
131 * @returns Event that when signaled will resume the thread and call the callback function. 130 * @returns Event that when signaled will resume the thread and call the callback function.
132 */ 131 */
133 SharedPtr<WritableEvent> SleepClientThread(SharedPtr<Thread> thread, const std::string& reason, 132 SharedPtr<WritableEvent> SleepClientThread(const std::string& reason, u64 timeout,
134 u64 timeout, WakeupCallback&& callback, 133 WakeupCallback&& callback,
135 SharedPtr<WritableEvent> writable_event = nullptr); 134 SharedPtr<WritableEvent> writable_event = nullptr);
136 135
137 /// Populates this context with data from the requesting process/thread. 136 /// Populates this context with data from the requesting process/thread.
@@ -268,6 +267,7 @@ private:
268 267
269 std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf; 268 std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
270 SharedPtr<Kernel::ServerSession> server_session; 269 SharedPtr<Kernel::ServerSession> server_session;
270 SharedPtr<Thread> thread;
271 // TODO(yuriks): Check common usage of this and optimize size accordingly 271 // TODO(yuriks): Check common usage of this and optimize size accordingly
272 boost::container::small_vector<SharedPtr<Object>, 8> move_objects; 272 boost::container::small_vector<SharedPtr<Object>, 8> move_objects;
273 boost::container::small_vector<SharedPtr<Object>, 8> copy_objects; 273 boost::container::small_vector<SharedPtr<Object>, 8> copy_objects;
diff --git a/src/core/hle/kernel/object.h b/src/core/hle/kernel/object.h
index 332876c27..2821176a7 100644
--- a/src/core/hle/kernel/object.h
+++ b/src/core/hle/kernel/object.h
@@ -33,8 +33,8 @@ enum class HandleType : u32 {
33}; 33};
34 34
35enum class ResetType { 35enum class ResetType {
36 OneShot, ///< Reset automatically on object acquisition 36 Automatic, ///< Reset automatically on object acquisition
37 Sticky, ///< Never reset automatically 37 Manual, ///< Never reset automatically
38}; 38};
39 39
40class Object : NonCopyable { 40class Object : NonCopyable {
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 20d01fc88..0775a89fb 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -241,7 +241,8 @@ void Process::LoadModule(CodeSet module_, VAddr base_addr) {
241} 241}
242 242
243Process::Process(Core::System& system) 243Process::Process(Core::System& system)
244 : WaitObject{system.Kernel()}, address_arbiter{system}, mutex{system}, system{system} {} 244 : WaitObject{system.Kernel()}, vm_manager{system},
245 address_arbiter{system}, mutex{system}, system{system} {}
245 246
246Process::~Process() = default; 247Process::~Process() = default;
247 248
diff --git a/src/core/hle/kernel/readable_event.cpp b/src/core/hle/kernel/readable_event.cpp
index c2b798a4e..06463cd26 100644
--- a/src/core/hle/kernel/readable_event.cpp
+++ b/src/core/hle/kernel/readable_event.cpp
@@ -21,8 +21,9 @@ bool ReadableEvent::ShouldWait(const Thread* thread) const {
21void ReadableEvent::Acquire(Thread* thread) { 21void ReadableEvent::Acquire(Thread* thread) {
22 ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); 22 ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
23 23
24 if (reset_type == ResetType::OneShot) 24 if (reset_type == ResetType::Automatic) {
25 signaled = false; 25 signaled = false;
26 }
26} 27}
27 28
28void ReadableEvent::Signal() { 29void ReadableEvent::Signal() {
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 696a82cd9..30b2bfb5a 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -130,7 +130,7 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
130 // The ServerSession received a sync request, this means that there's new data available 130 // The ServerSession received a sync request, this means that there's new data available
131 // from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or 131 // from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or
132 // similar. 132 // similar.
133 Kernel::HLERequestContext context(this); 133 Kernel::HLERequestContext context(this, thread);
134 u32* cmd_buf = (u32*)Memory::GetPointer(thread->GetTLSAddress()); 134 u32* cmd_buf = (u32*)Memory::GetPointer(thread->GetTLSAddress());
135 context.PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf); 135 context.PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
136 136
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 2dcf174c5..5a5851f66 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -1255,8 +1255,8 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand
1255 return vm_manager.MapCodeMemory(dst_address, src_address, size); 1255 return vm_manager.MapCodeMemory(dst_address, src_address, size);
1256} 1256}
1257 1257
1258ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, 1258static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_handle,
1259 u64 src_address, u64 size) { 1259 u64 dst_address, u64 src_address, u64 size) {
1260 LOG_DEBUG(Kernel_SVC, 1260 LOG_DEBUG(Kernel_SVC,
1261 "called. process_handle=0x{:08X}, dst_address=0x{:016X}, src_address=0x{:016X}, " 1261 "called. process_handle=0x{:08X}, dst_address=0x{:016X}, src_address=0x{:016X}, "
1262 "size=0x{:016X}", 1262 "size=0x{:016X}",
@@ -1342,7 +1342,7 @@ static void ExitProcess(Core::System& system) {
1342/// Creates a new thread 1342/// Creates a new thread
1343static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg, 1343static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg,
1344 VAddr stack_top, u32 priority, s32 processor_id) { 1344 VAddr stack_top, u32 priority, s32 processor_id) {
1345 LOG_TRACE(Kernel_SVC, 1345 LOG_DEBUG(Kernel_SVC,
1346 "called entrypoint=0x{:08X}, arg=0x{:08X}, stacktop=0x{:08X}, " 1346 "called entrypoint=0x{:08X}, arg=0x{:08X}, stacktop=0x{:08X}, "
1347 "threadpriority=0x{:08X}, processorid=0x{:08X} : created handle=0x{:08X}", 1347 "threadpriority=0x{:08X}, processorid=0x{:08X} : created handle=0x{:08X}",
1348 entry_point, arg, stack_top, priority, processor_id, *out_handle); 1348 entry_point, arg, stack_top, priority, processor_id, *out_handle);
@@ -1402,7 +1402,7 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e
1402 1402
1403/// Starts the thread for the provided handle 1403/// Starts the thread for the provided handle
1404static ResultCode StartThread(Core::System& system, Handle thread_handle) { 1404static ResultCode StartThread(Core::System& system, Handle thread_handle) {
1405 LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle); 1405 LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
1406 1406
1407 const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); 1407 const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
1408 const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); 1408 const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
@@ -1425,7 +1425,7 @@ static ResultCode StartThread(Core::System& system, Handle thread_handle) {
1425 1425
1426/// Called when a thread exits 1426/// Called when a thread exits
1427static void ExitThread(Core::System& system) { 1427static void ExitThread(Core::System& system) {
1428 LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC()); 1428 LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC());
1429 1429
1430 auto* const current_thread = system.CurrentScheduler().GetCurrentThread(); 1430 auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
1431 current_thread->Stop(); 1431 current_thread->Stop();
@@ -1435,7 +1435,7 @@ static void ExitThread(Core::System& system) {
1435 1435
1436/// Sleep the current thread 1436/// Sleep the current thread
1437static void SleepThread(Core::System& system, s64 nanoseconds) { 1437static void SleepThread(Core::System& system, s64 nanoseconds) {
1438 LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); 1438 LOG_DEBUG(Kernel_SVC, "called nanoseconds={}", nanoseconds);
1439 1439
1440 enum class SleepType : s64 { 1440 enum class SleepType : s64 {
1441 YieldWithoutLoadBalancing = 0, 1441 YieldWithoutLoadBalancing = 0,
@@ -1880,52 +1880,59 @@ static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle,
1880} 1880}
1881 1881
1882static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle, u32 core, 1882static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle, u32 core,
1883 u64 mask) { 1883 u64 affinity_mask) {
1884 LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:016X}, core=0x{:X}", thread_handle, 1884 LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, core=0x{:X}, affinity_mask=0x{:016X}",
1885 mask, core); 1885 thread_handle, core, affinity_mask);
1886 1886
1887 const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); 1887 const auto* const current_process = system.Kernel().CurrentProcess();
1888 const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
1889 if (!thread) {
1890 LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}",
1891 thread_handle);
1892 return ERR_INVALID_HANDLE;
1893 }
1894 1888
1895 if (core == static_cast<u32>(THREADPROCESSORID_IDEAL)) { 1889 if (core == static_cast<u32>(THREADPROCESSORID_IDEAL)) {
1896 const u8 ideal_cpu_core = thread->GetOwnerProcess()->GetIdealCore(); 1890 const u8 ideal_cpu_core = current_process->GetIdealCore();
1897 1891
1898 ASSERT(ideal_cpu_core != static_cast<u8>(THREADPROCESSORID_IDEAL)); 1892 ASSERT(ideal_cpu_core != static_cast<u8>(THREADPROCESSORID_IDEAL));
1899 1893
1900 // Set the target CPU to the ideal core specified by the process. 1894 // Set the target CPU to the ideal core specified by the process.
1901 core = ideal_cpu_core; 1895 core = ideal_cpu_core;
1902 mask = 1ULL << core; 1896 affinity_mask = 1ULL << core;
1903 } 1897 } else {
1904 1898 const u64 core_mask = current_process->GetCoreMask();
1905 if (mask == 0) { 1899
1906 LOG_ERROR(Kernel_SVC, "Mask is 0"); 1900 if ((core_mask | affinity_mask) != core_mask) {
1907 return ERR_INVALID_COMBINATION; 1901 LOG_ERROR(
1908 } 1902 Kernel_SVC,
1903 "Invalid processor ID specified (core_mask=0x{:08X}, affinity_mask=0x{:016X})",
1904 core_mask, affinity_mask);
1905 return ERR_INVALID_PROCESSOR_ID;
1906 }
1909 1907
1910 /// This value is used to only change the affinity mask without changing the current ideal core. 1908 if (affinity_mask == 0) {
1911 static constexpr u32 OnlyChangeMask = static_cast<u32>(-3); 1909 LOG_ERROR(Kernel_SVC, "Specfified affinity mask is zero.");
1910 return ERR_INVALID_COMBINATION;
1911 }
1912 1912
1913 if (core == OnlyChangeMask) { 1913 if (core < Core::NUM_CPU_CORES) {
1914 core = thread->GetIdealCore(); 1914 if ((affinity_mask & (1ULL << core)) == 0) {
1915 } else if (core >= Core::NUM_CPU_CORES && core != static_cast<u32>(-1)) { 1915 LOG_ERROR(Kernel_SVC,
1916 LOG_ERROR(Kernel_SVC, "Invalid core specified, got {}", core); 1916 "Core is not enabled for the current mask, core={}, mask={:016X}", core,
1917 return ERR_INVALID_PROCESSOR_ID; 1917 affinity_mask);
1918 return ERR_INVALID_COMBINATION;
1919 }
1920 } else if (core != static_cast<u32>(THREADPROCESSORID_DONT_CARE) &&
1921 core != static_cast<u32>(THREADPROCESSORID_DONT_UPDATE)) {
1922 LOG_ERROR(Kernel_SVC, "Invalid processor ID specified (core={}).", core);
1923 return ERR_INVALID_PROCESSOR_ID;
1924 }
1918 } 1925 }
1919 1926
1920 // Error out if the input core isn't enabled in the input mask. 1927 const auto& handle_table = current_process->GetHandleTable();
1921 if (core < Core::NUM_CPU_CORES && (mask & (1ull << core)) == 0) { 1928 const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
1922 LOG_ERROR(Kernel_SVC, "Core is not enabled for the current mask, core={}, mask={:016X}", 1929 if (!thread) {
1923 core, mask); 1930 LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}",
1924 return ERR_INVALID_COMBINATION; 1931 thread_handle);
1932 return ERR_INVALID_HANDLE;
1925 } 1933 }
1926 1934
1927 thread->ChangeCore(core, mask); 1935 thread->ChangeCore(core, affinity_mask);
1928
1929 return RESULT_SUCCESS; 1936 return RESULT_SUCCESS;
1930} 1937}
1931 1938
@@ -1980,7 +1987,7 @@ static ResultCode CreateEvent(Core::System& system, Handle* write_handle, Handle
1980 1987
1981 auto& kernel = system.Kernel(); 1988 auto& kernel = system.Kernel();
1982 const auto [readable_event, writable_event] = 1989 const auto [readable_event, writable_event] =
1983 WritableEvent::CreateEventPair(kernel, ResetType::Sticky, "CreateEvent"); 1990 WritableEvent::CreateEventPair(kernel, ResetType::Manual, "CreateEvent");
1984 1991
1985 HandleTable& handle_table = kernel.CurrentProcess()->GetHandleTable(); 1992 HandleTable& handle_table = kernel.CurrentProcess()->GetHandleTable();
1986 1993
@@ -2183,8 +2190,8 @@ static ResultCode GetProcessList(Core::System& system, u32* out_num_processes,
2183 return RESULT_SUCCESS; 2190 return RESULT_SUCCESS;
2184} 2191}
2185 2192
2186ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAddr out_thread_ids, 2193static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAddr out_thread_ids,
2187 u32 out_thread_ids_size, Handle debug_handle) { 2194 u32 out_thread_ids_size, Handle debug_handle) {
2188 // TODO: Handle this case when debug events are supported. 2195 // TODO: Handle this case when debug events are supported.
2189 UNIMPLEMENTED_IF(debug_handle != InvalidHandle); 2196 UNIMPLEMENTED_IF(debug_handle != InvalidHandle);
2190 2197
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index f07332f02..b4b9cda7c 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -30,12 +30,21 @@ enum ThreadPriority : u32 {
30}; 30};
31 31
32enum ThreadProcessorId : s32 { 32enum ThreadProcessorId : s32 {
33 THREADPROCESSORID_IDEAL = -2, ///< Run thread on the ideal core specified by the process. 33 /// Indicates that no particular processor core is preferred.
34 THREADPROCESSORID_0 = 0, ///< Run thread on core 0 34 THREADPROCESSORID_DONT_CARE = -1,
35 THREADPROCESSORID_1 = 1, ///< Run thread on core 1 35
36 THREADPROCESSORID_2 = 2, ///< Run thread on core 2 36 /// Run thread on the ideal core specified by the process.
37 THREADPROCESSORID_3 = 3, ///< Run thread on core 3 37 THREADPROCESSORID_IDEAL = -2,
38 THREADPROCESSORID_MAX = 4, ///< Processor ID must be less than this 38
39 /// Indicates that the preferred processor ID shouldn't be updated in
40 /// a core mask setting operation.
41 THREADPROCESSORID_DONT_UPDATE = -3,
42
43 THREADPROCESSORID_0 = 0, ///< Run thread on core 0
44 THREADPROCESSORID_1 = 1, ///< Run thread on core 1
45 THREADPROCESSORID_2 = 2, ///< Run thread on core 2
46 THREADPROCESSORID_3 = 3, ///< Run thread on core 3
47 THREADPROCESSORID_MAX = 4, ///< Processor ID must be less than this
39 48
40 /// Allowed CPU mask 49 /// Allowed CPU mask
41 THREADPROCESSORID_DEFAULT_MASK = (1 << THREADPROCESSORID_0) | (1 << THREADPROCESSORID_1) | 50 THREADPROCESSORID_DEFAULT_MASK = (1 << THREADPROCESSORID_0) | (1 << THREADPROCESSORID_1) |
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index f0c0c12fc..48b13cfdd 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -62,7 +62,7 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
62 return true; 62 return true;
63} 63}
64 64
65VMManager::VMManager() { 65VMManager::VMManager(Core::System& system) : system{system} {
66 // Default to assuming a 39-bit address space. This way we have a sane 66 // Default to assuming a 39-bit address space. This way we have a sane
67 // starting point with executables that don't provide metadata. 67 // starting point with executables that don't provide metadata.
68 Reset(FileSys::ProgramAddressSpaceType::Is39Bit); 68 Reset(FileSys::ProgramAddressSpaceType::Is39Bit);
@@ -111,7 +111,6 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
111 VirtualMemoryArea& final_vma = vma_handle->second; 111 VirtualMemoryArea& final_vma = vma_handle->second;
112 ASSERT(final_vma.size == size); 112 ASSERT(final_vma.size == size);
113 113
114 auto& system = Core::System::GetInstance();
115 system.ArmInterface(0).MapBackingMemory(target, size, block->data() + offset, 114 system.ArmInterface(0).MapBackingMemory(target, size, block->data() + offset,
116 VMAPermission::ReadWriteExecute); 115 VMAPermission::ReadWriteExecute);
117 system.ArmInterface(1).MapBackingMemory(target, size, block->data() + offset, 116 system.ArmInterface(1).MapBackingMemory(target, size, block->data() + offset,
@@ -140,7 +139,6 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
140 VirtualMemoryArea& final_vma = vma_handle->second; 139 VirtualMemoryArea& final_vma = vma_handle->second;
141 ASSERT(final_vma.size == size); 140 ASSERT(final_vma.size == size);
142 141
143 auto& system = Core::System::GetInstance();
144 system.ArmInterface(0).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); 142 system.ArmInterface(0).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
145 system.ArmInterface(1).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); 143 system.ArmInterface(1).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
146 system.ArmInterface(2).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); 144 system.ArmInterface(2).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
@@ -223,7 +221,6 @@ ResultCode VMManager::UnmapRange(VAddr target, u64 size) {
223 221
224 ASSERT(FindVMA(target)->second.size >= size); 222 ASSERT(FindVMA(target)->second.size >= size);
225 223
226 auto& system = Core::System::GetInstance();
227 system.ArmInterface(0).UnmapMemory(target, size); 224 system.ArmInterface(0).UnmapMemory(target, size);
228 system.ArmInterface(1).UnmapMemory(target, size); 225 system.ArmInterface(1).UnmapMemory(target, size);
229 system.ArmInterface(2).UnmapMemory(target, size); 226 system.ArmInterface(2).UnmapMemory(target, size);
@@ -376,7 +373,7 @@ ResultCode VMManager::UnmapCodeMemory(VAddr dst_address, VAddr src_address, u64
376 Reprotect(src_vma_iter, VMAPermission::ReadWrite); 373 Reprotect(src_vma_iter, VMAPermission::ReadWrite);
377 374
378 if (dst_memory_state == MemoryState::ModuleCode) { 375 if (dst_memory_state == MemoryState::ModuleCode) {
379 Core::System::GetInstance().InvalidateCpuInstructionCaches(); 376 system.InvalidateCpuInstructionCaches();
380 } 377 }
381 378
382 return unmap_result; 379 return unmap_result;
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 288eb9450..ec84d9a70 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -14,6 +14,10 @@
14#include "core/hle/result.h" 14#include "core/hle/result.h"
15#include "core/memory.h" 15#include "core/memory.h"
16 16
17namespace Core {
18class System;
19}
20
17namespace FileSys { 21namespace FileSys {
18enum class ProgramAddressSpaceType : u8; 22enum class ProgramAddressSpaceType : u8;
19} 23}
@@ -321,7 +325,7 @@ class VMManager final {
321public: 325public:
322 using VMAHandle = VMAMap::const_iterator; 326 using VMAHandle = VMAMap::const_iterator;
323 327
324 VMManager(); 328 explicit VMManager(Core::System& system);
325 ~VMManager(); 329 ~VMManager();
326 330
327 /// Clears the address space map, re-initializing with a single free area. 331 /// Clears the address space map, re-initializing with a single free area.
@@ -712,5 +716,7 @@ private:
712 // The end of the currently allocated heap. This is not an inclusive 716 // The end of the currently allocated heap. This is not an inclusive
713 // end of the range. This is essentially 'base_address + current_size'. 717 // end of the range. This is essentially 'base_address + current_size'.
714 VAddr heap_end = 0; 718 VAddr heap_end = 0;
719
720 Core::System& system;
715}; 721};
716} // namespace Kernel 722} // namespace Kernel
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 26a665bfd..1a32a109f 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -276,7 +276,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
276 RegisterHandlers(functions); 276 RegisterHandlers(functions);
277 277
278 auto& kernel = Core::System::GetInstance().Kernel(); 278 auto& kernel = Core::System::GetInstance().Kernel();
279 launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky, 279 launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
280 "ISelfController:LaunchableEvent"); 280 "ISelfController:LaunchableEvent");
281} 281}
282 282
@@ -442,10 +442,10 @@ void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& c
442 442
443AppletMessageQueue::AppletMessageQueue() { 443AppletMessageQueue::AppletMessageQueue() {
444 auto& kernel = Core::System::GetInstance().Kernel(); 444 auto& kernel = Core::System::GetInstance().Kernel();
445 on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky, 445 on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
446 "AMMessageQueue:OnMessageRecieved"); 446 "AMMessageQueue:OnMessageRecieved");
447 on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair( 447 on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair(
448 kernel, Kernel::ResetType::OneShot, "AMMessageQueue:OperationModeChanged"); 448 kernel, Kernel::ResetType::Automatic, "AMMessageQueue:OperationModeChanged");
449} 449}
450 450
451AppletMessageQueue::~AppletMessageQueue() = default; 451AppletMessageQueue::~AppletMessageQueue() = default;
@@ -835,6 +835,7 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) {
835 835
836 IPC::ResponseBuilder rb{ctx, 2}; 836 IPC::ResponseBuilder rb{ctx, 2};
837 rb.Push(ERR_SIZE_OUT_OF_BOUNDS); 837 rb.Push(ERR_SIZE_OUT_OF_BOUNDS);
838 return;
838 } 839 }
839 840
840 std::memcpy(backing.buffer.data() + offset, data.data(), data.size()); 841 std::memcpy(backing.buffer.data() + offset, data.data(), data.size());
@@ -857,6 +858,7 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
857 858
858 IPC::ResponseBuilder rb{ctx, 2}; 859 IPC::ResponseBuilder rb{ctx, 2};
859 rb.Push(ERR_SIZE_OUT_OF_BOUNDS); 860 rb.Push(ERR_SIZE_OUT_OF_BOUNDS);
861 return;
860 } 862 }
861 863
862 ctx.WriteBuffer(backing.buffer.data() + offset, size); 864 ctx.WriteBuffer(backing.buffer.data() + offset, size);
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index 7f70b10df..e812c66e9 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -26,11 +26,11 @@ namespace Service::AM::Applets {
26AppletDataBroker::AppletDataBroker() { 26AppletDataBroker::AppletDataBroker() {
27 auto& kernel = Core::System::GetInstance().Kernel(); 27 auto& kernel = Core::System::GetInstance().Kernel();
28 state_changed_event = Kernel::WritableEvent::CreateEventPair( 28 state_changed_event = Kernel::WritableEvent::CreateEventPair(
29 kernel, Kernel::ResetType::Sticky, "ILibraryAppletAccessor:StateChangedEvent"); 29 kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:StateChangedEvent");
30 pop_out_data_event = Kernel::WritableEvent::CreateEventPair( 30 pop_out_data_event = Kernel::WritableEvent::CreateEventPair(
31 kernel, Kernel::ResetType::Sticky, "ILibraryAppletAccessor:PopDataOutEvent"); 31 kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:PopDataOutEvent");
32 pop_interactive_out_data_event = Kernel::WritableEvent::CreateEventPair( 32 pop_interactive_out_data_event = Kernel::WritableEvent::CreateEventPair(
33 kernel, Kernel::ResetType::Sticky, "ILibraryAppletAccessor:PopInteractiveDataOutEvent"); 33 kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:PopInteractiveDataOutEvent");
34} 34}
35 35
36AppletDataBroker::~AppletDataBroker() = default; 36AppletDataBroker::~AppletDataBroker() = default;
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index 51d8c26b4..d3e97776b 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -9,7 +9,6 @@
9#include "core/file_sys/content_archive.h" 9#include "core/file_sys/content_archive.h"
10#include "core/file_sys/control_metadata.h" 10#include "core/file_sys/control_metadata.h"
11#include "core/file_sys/nca_metadata.h" 11#include "core/file_sys/nca_metadata.h"
12#include "core/file_sys/partition_filesystem.h"
13#include "core/file_sys/patch_manager.h" 12#include "core/file_sys/patch_manager.h"
14#include "core/file_sys/registered_cache.h" 13#include "core/file_sys/registered_cache.h"
15#include "core/hle/ipc_helpers.h" 14#include "core/hle/ipc_helpers.h"
@@ -18,7 +17,6 @@
18#include "core/hle/kernel/readable_event.h" 17#include "core/hle/kernel/readable_event.h"
19#include "core/hle/kernel/writable_event.h" 18#include "core/hle/kernel/writable_event.h"
20#include "core/hle/service/aoc/aoc_u.h" 19#include "core/hle/service/aoc/aoc_u.h"
21#include "core/hle/service/filesystem/filesystem.h"
22#include "core/loader/loader.h" 20#include "core/loader/loader.h"
23#include "core/settings.h" 21#include "core/settings.h"
24 22
@@ -68,14 +66,22 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs
68 RegisterHandlers(functions); 66 RegisterHandlers(functions);
69 67
70 auto& kernel = Core::System::GetInstance().Kernel(); 68 auto& kernel = Core::System::GetInstance().Kernel();
71 aoc_change_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky, 69 aoc_change_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
72 "GetAddOnContentListChanged:Event"); 70 "GetAddOnContentListChanged:Event");
73} 71}
74 72
75AOC_U::~AOC_U() = default; 73AOC_U::~AOC_U() = default;
76 74
77void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) { 75void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
78 LOG_DEBUG(Service_AOC, "called"); 76 struct Parameters {
77 u64 process_id;
78 };
79 static_assert(sizeof(Parameters) == 8);
80
81 IPC::RequestParser rp{ctx};
82 const auto params = rp.PopRaw<Parameters>();
83
84 LOG_DEBUG(Service_AOC, "called. process_id={}", params.process_id);
79 85
80 IPC::ResponseBuilder rb{ctx, 3}; 86 IPC::ResponseBuilder rb{ctx, 3};
81 rb.Push(RESULT_SUCCESS); 87 rb.Push(RESULT_SUCCESS);
@@ -94,23 +100,32 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
94} 100}
95 101
96void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { 102void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
103 struct Parameters {
104 u32 offset;
105 u32 count;
106 u64 process_id;
107 };
108 static_assert(sizeof(Parameters) == 16);
109
97 IPC::RequestParser rp{ctx}; 110 IPC::RequestParser rp{ctx};
111 const auto [offset, count, process_id] = rp.PopRaw<Parameters>();
98 112
99 const auto offset = rp.PopRaw<u32>(); 113 LOG_DEBUG(Service_AOC, "called with offset={}, count={}, process_id={}", offset, count,
100 auto count = rp.PopRaw<u32>(); 114 process_id);
101 LOG_DEBUG(Service_AOC, "called with offset={}, count={}", offset, count);
102 115
103 const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID(); 116 const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
104 117
105 std::vector<u32> out; 118 std::vector<u32> out;
106 for (size_t i = 0; i < add_on_content.size(); ++i) {
107 if ((add_on_content[i] & DLC_BASE_TITLE_ID_MASK) == current)
108 out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF));
109 }
110
111 const auto& disabled = Settings::values.disabled_addons[current]; 119 const auto& disabled = Settings::values.disabled_addons[current];
112 if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) 120 if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) {
113 out = {}; 121 for (u64 content_id : add_on_content) {
122 if ((content_id & DLC_BASE_TITLE_ID_MASK) != current) {
123 continue;
124 }
125
126 out.push_back(static_cast<u32>(content_id & 0x7FF));
127 }
128 }
114 129
115 if (out.size() < offset) { 130 if (out.size() < offset) {
116 IPC::ResponseBuilder rb{ctx, 2}; 131 IPC::ResponseBuilder rb{ctx, 2};
@@ -119,22 +134,31 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
119 return; 134 return;
120 } 135 }
121 136
122 count = static_cast<u32>(std::min<size_t>(out.size() - offset, count)); 137 const auto out_count = static_cast<u32>(std::min<size_t>(out.size() - offset, count));
123 std::rotate(out.begin(), out.begin() + offset, out.end()); 138 std::rotate(out.begin(), out.begin() + offset, out.end());
124 out.resize(count); 139 out.resize(out_count);
125 140
126 ctx.WriteBuffer(out); 141 ctx.WriteBuffer(out);
127 142
128 IPC::ResponseBuilder rb{ctx, 3}; 143 IPC::ResponseBuilder rb{ctx, 3};
129 rb.Push(RESULT_SUCCESS); 144 rb.Push(RESULT_SUCCESS);
130 rb.Push(count); 145 rb.Push(out_count);
131} 146}
132 147
133void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) { 148void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
134 LOG_DEBUG(Service_AOC, "called"); 149 struct Parameters {
150 u64 process_id;
151 };
152 static_assert(sizeof(Parameters) == 8);
153
154 IPC::RequestParser rp{ctx};
155 const auto params = rp.PopRaw<Parameters>();
156
157 LOG_DEBUG(Service_AOC, "called. process_id={}", params.process_id);
135 158
136 IPC::ResponseBuilder rb{ctx, 4}; 159 IPC::ResponseBuilder rb{ctx, 4};
137 rb.Push(RESULT_SUCCESS); 160 rb.Push(RESULT_SUCCESS);
161
138 const auto title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); 162 const auto title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
139 FileSys::PatchManager pm{title_id}; 163 FileSys::PatchManager pm{title_id};
140 164
@@ -148,10 +172,17 @@ void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
148} 172}
149 173
150void AOC_U::PrepareAddOnContent(Kernel::HLERequestContext& ctx) { 174void AOC_U::PrepareAddOnContent(Kernel::HLERequestContext& ctx) {
175 struct Parameters {
176 s32 addon_index;
177 u64 process_id;
178 };
179 static_assert(sizeof(Parameters) == 16);
180
151 IPC::RequestParser rp{ctx}; 181 IPC::RequestParser rp{ctx};
182 const auto [addon_index, process_id] = rp.PopRaw<Parameters>();
152 183
153 const auto aoc_id = rp.PopRaw<u32>(); 184 LOG_WARNING(Service_AOC, "(STUBBED) called with addon_index={}, process_id={}", addon_index,
154 LOG_WARNING(Service_AOC, "(STUBBED) called with aoc_id={:08X}", aoc_id); 185 process_id);
155 186
156 IPC::ResponseBuilder rb{ctx, 2}; 187 IPC::ResponseBuilder rb{ctx, 2};
157 rb.Push(RESULT_SUCCESS); 188 rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp
index f43e512e9..6a01d4d29 100644
--- a/src/core/hle/service/audio/audctl.cpp
+++ b/src/core/hle/service/audio/audctl.cpp
@@ -50,7 +50,7 @@ void AudCtl::GetTargetVolumeMin(Kernel::HLERequestContext& ctx) {
50 LOG_DEBUG(Audio, "called."); 50 LOG_DEBUG(Audio, "called.");
51 51
52 // This service function is currently hardcoded on the 52 // This service function is currently hardcoded on the
53 // actual console to this value (as of 6.0.0). 53 // actual console to this value (as of 8.0.0).
54 constexpr s32 target_min_volume = 0; 54 constexpr s32 target_min_volume = 0;
55 55
56 IPC::ResponseBuilder rb{ctx, 3}; 56 IPC::ResponseBuilder rb{ctx, 3};
@@ -62,7 +62,7 @@ void AudCtl::GetTargetVolumeMax(Kernel::HLERequestContext& ctx) {
62 LOG_DEBUG(Audio, "called."); 62 LOG_DEBUG(Audio, "called.");
63 63
64 // This service function is currently hardcoded on the 64 // This service function is currently hardcoded on the
65 // actual console to this value (as of 6.0.0). 65 // actual console to this value (as of 8.0.0).
66 constexpr s32 target_max_volume = 15; 66 constexpr s32 target_max_volume = 15;
67 67
68 IPC::ResponseBuilder rb{ctx, 3}; 68 IPC::ResponseBuilder rb{ctx, 3};
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index 12875fb42..6ba41b20a 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -67,7 +67,7 @@ public:
67 // This is the event handle used to check if the audio buffer was released 67 // This is the event handle used to check if the audio buffer was released
68 auto& system = Core::System::GetInstance(); 68 auto& system = Core::System::GetInstance();
69 buffer_event = Kernel::WritableEvent::CreateEventPair( 69 buffer_event = Kernel::WritableEvent::CreateEventPair(
70 system.Kernel(), Kernel::ResetType::Sticky, "IAudioOutBufferReleased"); 70 system.Kernel(), Kernel::ResetType::Manual, "IAudioOutBufferReleased");
71 71
72 stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate, 72 stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate,
73 audio_params.channel_count, std::move(unique_name), 73 audio_params.channel_count, std::move(unique_name),
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 1dde6edb7..75db0c2dc 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -8,6 +8,7 @@
8 8
9#include "audio_core/audio_renderer.h" 9#include "audio_core/audio_renderer.h"
10#include "common/alignment.h" 10#include "common/alignment.h"
11#include "common/bit_util.h"
11#include "common/common_funcs.h" 12#include "common/common_funcs.h"
12#include "common/logging/log.h" 13#include "common/logging/log.h"
13#include "common/string_util.h" 14#include "common/string_util.h"
@@ -46,7 +47,7 @@ public:
46 47
47 auto& system = Core::System::GetInstance(); 48 auto& system = Core::System::GetInstance();
48 system_event = Kernel::WritableEvent::CreateEventPair( 49 system_event = Kernel::WritableEvent::CreateEventPair(
49 system.Kernel(), Kernel::ResetType::Sticky, "IAudioRenderer:SystemEvent"); 50 system.Kernel(), Kernel::ResetType::Manual, "IAudioRenderer:SystemEvent");
50 renderer = std::make_unique<AudioCore::AudioRenderer>(system.CoreTiming(), audren_params, 51 renderer = std::make_unique<AudioCore::AudioRenderer>(system.CoreTiming(), audren_params,
51 system_event.writable); 52 system_event.writable);
52 } 53 }
@@ -178,7 +179,7 @@ public:
178 RegisterHandlers(functions); 179 RegisterHandlers(functions);
179 180
180 auto& kernel = Core::System::GetInstance().Kernel(); 181 auto& kernel = Core::System::GetInstance().Kernel();
181 buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, 182 buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
182 "IAudioOutBufferReleasedEvent"); 183 "IAudioOutBufferReleasedEvent");
183 } 184 }
184 185
@@ -262,64 +263,304 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
262 OpenAudioRendererImpl(ctx); 263 OpenAudioRendererImpl(ctx);
263} 264}
264 265
266static u64 CalculateNumPerformanceEntries(const AudioCore::AudioRendererParameter& params) {
267 // +1 represents the final mix.
268 return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count +
269 1;
270}
271
265void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { 272void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
266 IPC::RequestParser rp{ctx};
267 auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
268 LOG_DEBUG(Service_Audio, "called"); 273 LOG_DEBUG(Service_Audio, "called");
269 274
270 u64 buffer_sz = Common::AlignUp(4 * params.mix_buffer_count, 0x40); 275 // Several calculations below align the sizes being calculated
271 buffer_sz += params.submix_count * 1024; 276 // onto a 64 byte boundary.
272 buffer_sz += 0x940 * (params.submix_count + 1); 277 static constexpr u64 buffer_alignment_size = 64;
273 buffer_sz += 0x3F0 * params.voice_count; 278
274 buffer_sz += Common::AlignUp(8 * (params.submix_count + 1), 0x10); 279 // Some calculations that calculate portions of the buffer
275 buffer_sz += Common::AlignUp(8 * params.voice_count, 0x10); 280 // that will contain information, on the other hand, align
276 buffer_sz += Common::AlignUp( 281 // the result of some of their calcularions on a 16 byte boundary.
277 (0x3C0 * (params.sink_count + params.submix_count) + 4 * params.sample_count) * 282 static constexpr u64 info_field_alignment_size = 16;
278 (params.mix_buffer_count + 6), 283
279 0x40); 284 // Maximum detail entries that may exist at one time for performance
280 285 // frame statistics.
281 if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { 286 static constexpr u64 max_perf_detail_entries = 100;
282 const u32 count = params.submix_count + 1; 287
283 u64 node_count = Common::AlignUp(count, 0x40); 288 // Size of the data structure representing the bulk of the voice-related state.
284 const u64 node_state_buffer_sz = 289 static constexpr u64 voice_state_size = 0x100;
285 4 * (node_count * node_count) + 0xC * node_count + 2 * (node_count / 8); 290
286 u64 edge_matrix_buffer_sz = 0; 291 // Size of the upsampler manager data structure
287 node_count = Common::AlignUp(count * count, 0x40); 292 constexpr u64 upsampler_manager_size = 0x48;
288 if (node_count >> 31 != 0) { 293
289 edge_matrix_buffer_sz = (node_count | 7) / 8; 294 // Calculates the part of the size that relates to mix buffers.
290 } else { 295 const auto calculate_mix_buffer_sizes = [](const AudioCore::AudioRendererParameter& params) {
291 edge_matrix_buffer_sz = node_count / 8; 296 // As of 8.0.0 this is the maximum on voice channels.
297 constexpr u64 max_voice_channels = 6;
298
299 // The service expects the sample_count member of the parameters to either be
300 // a value of 160 or 240, so the maximum sample count is assumed in order
301 // to adequately handle all values at runtime.
302 constexpr u64 default_max_sample_count = 240;
303
304 const u64 total_mix_buffers = params.mix_buffer_count + max_voice_channels;
305
306 u64 size = 0;
307 size += total_mix_buffers * (sizeof(s32) * params.sample_count);
308 size += total_mix_buffers * (sizeof(s32) * default_max_sample_count);
309 size += u64{params.submix_count} + params.sink_count;
310 size = Common::AlignUp(size, buffer_alignment_size);
311 size += Common::AlignUp(params.unknown_30, buffer_alignment_size);
312 size += Common::AlignUp(sizeof(s32) * params.mix_buffer_count, buffer_alignment_size);
313 return size;
314 };
315
316 // Calculates the portion of the size related to the mix data (and the sorting thereof).
317 const auto calculate_mix_info_size = [this](const AudioCore::AudioRendererParameter& params) {
318 // The size of the mixing info data structure.
319 constexpr u64 mix_info_size = 0x940;
320
321 // Consists of total submixes with the final mix included.
322 const u64 total_mix_count = u64{params.submix_count} + 1;
323
324 // The total number of effects that may be available to the audio renderer at any time.
325 constexpr u64 max_effects = 256;
326
327 // Calculates the part of the size related to the audio node state.
328 // This will only be used if the audio revision supports the splitter.
329 const auto calculate_node_state_size = [](std::size_t num_nodes) {
330 // Internally within a nodestate, it appears to use a data structure
331 // similar to a std::bitset<64> twice.
332 constexpr u64 bit_size = Common::BitSize<u64>();
333 constexpr u64 num_bitsets = 2;
334
335 // Node state instances have three states internally for performing
336 // depth-first searches of nodes. Initialized, Found, and Done Sorting.
337 constexpr u64 num_states = 3;
338
339 u64 size = 0;
340 size += (num_nodes * num_nodes) * sizeof(s32);
341 size += num_states * (num_nodes * sizeof(s32));
342 size += num_bitsets * (Common::AlignUp(num_nodes, bit_size) / Common::BitSize<u8>());
343 return size;
344 };
345
346 // Calculates the part of the size related to the adjacency (aka edge) matrix.
347 const auto calculate_edge_matrix_size = [](std::size_t num_nodes) {
348 return (num_nodes * num_nodes) * sizeof(s32);
349 };
350
351 u64 size = 0;
352 size += Common::AlignUp(sizeof(void*) * total_mix_count, info_field_alignment_size);
353 size += Common::AlignUp(mix_info_size * total_mix_count, info_field_alignment_size);
354 size += Common::AlignUp(sizeof(s32) * max_effects * params.submix_count,
355 info_field_alignment_size);
356
357 if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
358 size += Common::AlignUp(calculate_node_state_size(total_mix_count) +
359 calculate_edge_matrix_size(total_mix_count),
360 info_field_alignment_size);
292 } 361 }
293 buffer_sz += Common::AlignUp(node_state_buffer_sz + edge_matrix_buffer_sz, 0x10);
294 }
295 362
296 buffer_sz += 0x20 * (params.effect_count + 4 * params.voice_count) + 0x50; 363 return size;
297 if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { 364 };
298 buffer_sz += 0xE0 * params.num_splitter_send_channels;
299 buffer_sz += 0x20 * params.splitter_count;
300 buffer_sz += Common::AlignUp(4 * params.num_splitter_send_channels, 0x10);
301 }
302 buffer_sz = Common::AlignUp(buffer_sz, 0x40) + 0x170 * params.sink_count;
303 u64 output_sz = buffer_sz + 0x280 * params.sink_count + 0x4B0 * params.effect_count +
304 ((params.voice_count * 256) | 0x40);
305
306 if (params.performance_frame_count >= 1) {
307 output_sz = Common::AlignUp(((16 * params.sink_count + 16 * params.effect_count +
308 16 * params.voice_count + 16) +
309 0x658) *
310 (params.performance_frame_count + 1) +
311 0xc0,
312 0x40) +
313 output_sz;
314 }
315 output_sz = Common::AlignUp(output_sz + 0x1807e, 0x1000);
316 365
317 IPC::ResponseBuilder rb{ctx, 4}; 366 // Calculates the part of the size related to voice channel info.
367 const auto calculate_voice_info_size = [](const AudioCore::AudioRendererParameter& params) {
368 constexpr u64 voice_info_size = 0x220;
369 constexpr u64 voice_resource_size = 0xD0;
370
371 u64 size = 0;
372 size += Common::AlignUp(sizeof(void*) * params.voice_count, info_field_alignment_size);
373 size += Common::AlignUp(voice_info_size * params.voice_count, info_field_alignment_size);
374 size +=
375 Common::AlignUp(voice_resource_size * params.voice_count, info_field_alignment_size);
376 size += Common::AlignUp(voice_state_size * params.voice_count, info_field_alignment_size);
377 return size;
378 };
379
380 // Calculates the part of the size related to memory pools.
381 const auto calculate_memory_pools_size = [](const AudioCore::AudioRendererParameter& params) {
382 const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count);
383 const u64 memory_pool_info_size = 0x20;
384 return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size);
385 };
386
387 // Calculates the part of the size related to the splitter context.
388 const auto calculate_splitter_context_size =
389 [this](const AudioCore::AudioRendererParameter& params) -> u64 {
390 if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
391 return 0;
392 }
393
394 constexpr u64 splitter_info_size = 0x20;
395 constexpr u64 splitter_destination_data_size = 0xE0;
396
397 u64 size = 0;
398 size += params.num_splitter_send_channels;
399 size +=
400 Common::AlignUp(splitter_info_size * params.splitter_count, info_field_alignment_size);
401 size += Common::AlignUp(splitter_destination_data_size * params.num_splitter_send_channels,
402 info_field_alignment_size);
403
404 return size;
405 };
406
407 // Calculates the part of the size related to the upsampler info.
408 const auto calculate_upsampler_info_size = [](const AudioCore::AudioRendererParameter& params) {
409 constexpr u64 upsampler_info_size = 0x280;
410 // Yes, using the buffer size over info alignment size is intentional here.
411 return Common::AlignUp(upsampler_info_size * (u64{params.submix_count} + params.sink_count),
412 buffer_alignment_size);
413 };
414
415 // Calculates the part of the size related to effect info.
416 const auto calculate_effect_info_size = [](const AudioCore::AudioRendererParameter& params) {
417 constexpr u64 effect_info_size = 0x2B0;
418 return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size);
419 };
420
421 // Calculates the part of the size related to audio sink info.
422 const auto calculate_sink_info_size = [](const AudioCore::AudioRendererParameter& params) {
423 const u64 sink_info_size = 0x170;
424 return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size);
425 };
426
427 // Calculates the part of the size related to voice state info.
428 const auto calculate_voice_state_size = [](const AudioCore::AudioRendererParameter& params) {
429 const u64 voice_state_size = 0x100;
430 const u64 additional_size = buffer_alignment_size - 1;
431 return Common::AlignUp(voice_state_size * params.voice_count + additional_size,
432 info_field_alignment_size);
433 };
434
435 // Calculates the part of the size related to performance statistics.
436 const auto calculate_perf_size = [this](const AudioCore::AudioRendererParameter& params) {
437 // Extra size value appended to the end of the calculation.
438 constexpr u64 appended = 128;
439
440 // Whether or not we assume the newer version of performance metrics data structures.
441 const bool is_v2 =
442 IsFeatureSupported(AudioFeatures::PerformanceMetricsVersion2, params.revision);
443
444 // Data structure sizes
445 constexpr u64 perf_statistics_size = 0x0C;
446 const u64 header_size = is_v2 ? 0x30 : 0x18;
447 const u64 entry_size = is_v2 ? 0x18 : 0x10;
448 const u64 detail_size = is_v2 ? 0x18 : 0x10;
449
450 const u64 entry_count = CalculateNumPerformanceEntries(params);
451 const u64 size_per_frame =
452 header_size + (entry_size * entry_count) + (detail_size * max_perf_detail_entries);
453
454 u64 size = 0;
455 size += Common::AlignUp(size_per_frame * params.performance_frame_count + 1,
456 buffer_alignment_size);
457 size += Common::AlignUp(perf_statistics_size, buffer_alignment_size);
458 size += appended;
459 return size;
460 };
461
462 // Calculates the part of the size that relates to the audio command buffer.
463 const auto calculate_command_buffer_size =
464 [this](const AudioCore::AudioRendererParameter& params) {
465 constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
466
467 if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
468 constexpr u64 command_buffer_size = 0x18000;
469
470 return command_buffer_size + alignment;
471 }
472
473 // When the variadic command buffer is supported, this means
474 // the command generator for the audio renderer can issue commands
475 // that are (as one would expect), variable in size. So what we need to do
476 // is determine the maximum possible size for a few command data structures
477 // then multiply them by the amount of present commands indicated by the given
478 // respective audio parameters.
479
480 constexpr u64 max_biquad_filters = 2;
481 constexpr u64 max_mix_buffers = 24;
482
483 constexpr u64 biquad_filter_command_size = 0x2C;
484
485 constexpr u64 depop_mix_command_size = 0x24;
486 constexpr u64 depop_setup_command_size = 0x50;
487
488 constexpr u64 effect_command_max_size = 0x540;
489
490 constexpr u64 mix_command_size = 0x1C;
491 constexpr u64 mix_ramp_command_size = 0x24;
492 constexpr u64 mix_ramp_grouped_command_size = 0x13C;
493
494 constexpr u64 perf_command_size = 0x28;
495
496 constexpr u64 sink_command_size = 0x130;
497
498 constexpr u64 submix_command_max_size =
499 depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
500
501 constexpr u64 volume_command_size = 0x1C;
502 constexpr u64 volume_ramp_command_size = 0x20;
503
504 constexpr u64 voice_biquad_filter_command_size =
505 biquad_filter_command_size * max_biquad_filters;
506 constexpr u64 voice_data_command_size = 0x9C;
507 const u64 voice_command_max_size =
508 (params.splitter_count * depop_setup_command_size) +
509 (voice_data_command_size + voice_biquad_filter_command_size +
510 volume_ramp_command_size + mix_ramp_grouped_command_size);
511
512 // Now calculate the individual elements that comprise the size and add them together.
513 const u64 effect_commands_size = params.effect_count * effect_command_max_size;
514
515 const u64 final_mix_commands_size =
516 depop_mix_command_size + volume_command_size * max_mix_buffers;
318 517
518 const u64 perf_commands_size =
519 perf_command_size *
520 (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
521
522 const u64 sink_commands_size = params.sink_count * sink_command_size;
523
524 const u64 splitter_commands_size =
525 params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
526
527 const u64 submix_commands_size = params.submix_count * submix_command_max_size;
528
529 const u64 voice_commands_size = params.voice_count * voice_command_max_size;
530
531 return effect_commands_size + final_mix_commands_size + perf_commands_size +
532 sink_commands_size + splitter_commands_size + submix_commands_size +
533 voice_commands_size + alignment;
534 };
535
536 IPC::RequestParser rp{ctx};
537 const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
538
539 u64 size = 0;
540 size += calculate_mix_buffer_sizes(params);
541 size += calculate_mix_info_size(params);
542 size += calculate_voice_info_size(params);
543 size += upsampler_manager_size;
544 size += calculate_memory_pools_size(params);
545 size += calculate_splitter_context_size(params);
546
547 size = Common::AlignUp(size, buffer_alignment_size);
548
549 size += calculate_upsampler_info_size(params);
550 size += calculate_effect_info_size(params);
551 size += calculate_sink_info_size(params);
552 size += calculate_voice_state_size(params);
553 size += calculate_perf_size(params);
554 size += calculate_command_buffer_size(params);
555
556 // finally, 4KB page align the size, and we're done.
557 size = Common::AlignUp(size, 4096);
558
559 IPC::ResponseBuilder rb{ctx, 4};
319 rb.Push(RESULT_SUCCESS); 560 rb.Push(RESULT_SUCCESS);
320 rb.Push<u64>(output_sz); 561 rb.Push<u64>(size);
321 562
322 LOG_DEBUG(Service_Audio, "buffer_size=0x{:X}", output_sz); 563 LOG_DEBUG(Service_Audio, "buffer_size=0x{:X}", size);
323} 564}
324 565
325void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) { 566void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) {
@@ -357,10 +598,15 @@ void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
357} 598}
358 599
359bool AudRenU::IsFeatureSupported(AudioFeatures feature, u32_le revision) const { 600bool AudRenU::IsFeatureSupported(AudioFeatures feature, u32_le revision) const {
360 u32_be version_num = (revision - Common::MakeMagic('R', 'E', 'V', '0')); // Byte swap 601 // Byte swap
602 const u32_be version_num = revision - Common::MakeMagic('R', 'E', 'V', '0');
603
361 switch (feature) { 604 switch (feature) {
362 case AudioFeatures::Splitter: 605 case AudioFeatures::Splitter:
363 return version_num >= 2u; 606 return version_num >= 2U;
607 case AudioFeatures::PerformanceMetricsVersion2:
608 case AudioFeatures::VariadicCommandBuffer:
609 return version_num >= 5U;
364 default: 610 default:
365 return false; 611 return false;
366 } 612 }
diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h
index e55d25973..1d3c8df61 100644
--- a/src/core/hle/service/audio/audren_u.h
+++ b/src/core/hle/service/audio/audren_u.h
@@ -28,6 +28,8 @@ private:
28 28
29 enum class AudioFeatures : u32 { 29 enum class AudioFeatures : u32 {
30 Splitter, 30 Splitter,
31 PerformanceMetricsVersion2,
32 VariadicCommandBuffer,
31 }; 33 };
32 34
33 bool IsFeatureSupported(AudioFeatures feature, u32_le revision) const; 35 bool IsFeatureSupported(AudioFeatures feature, u32_le revision) const;
diff --git a/src/core/hle/service/btdrv/btdrv.cpp b/src/core/hle/service/btdrv/btdrv.cpp
index 974ff8e1a..3c7ca2c44 100644
--- a/src/core/hle/service/btdrv/btdrv.cpp
+++ b/src/core/hle/service/btdrv/btdrv.cpp
@@ -34,8 +34,8 @@ public:
34 RegisterHandlers(functions); 34 RegisterHandlers(functions);
35 35
36 auto& kernel = Core::System::GetInstance().Kernel(); 36 auto& kernel = Core::System::GetInstance().Kernel();
37 register_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, 37 register_event = Kernel::WritableEvent::CreateEventPair(
38 "BT:RegisterEvent"); 38 kernel, Kernel::ResetType::Automatic, "BT:RegisterEvent");
39 } 39 }
40 40
41private: 41private:
diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp
index 4f15c3f19..b439ee7ec 100644
--- a/src/core/hle/service/btm/btm.cpp
+++ b/src/core/hle/service/btm/btm.cpp
@@ -57,13 +57,13 @@ public:
57 RegisterHandlers(functions); 57 RegisterHandlers(functions);
58 58
59 auto& kernel = Core::System::GetInstance().Kernel(); 59 auto& kernel = Core::System::GetInstance().Kernel();
60 scan_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, 60 scan_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
61 "IBtmUserCore:ScanEvent"); 61 "IBtmUserCore:ScanEvent");
62 connection_event = Kernel::WritableEvent::CreateEventPair( 62 connection_event = Kernel::WritableEvent::CreateEventPair(
63 kernel, Kernel::ResetType::OneShot, "IBtmUserCore:ConnectionEvent"); 63 kernel, Kernel::ResetType::Automatic, "IBtmUserCore:ConnectionEvent");
64 service_discovery = Kernel::WritableEvent::CreateEventPair( 64 service_discovery = Kernel::WritableEvent::CreateEventPair(
65 kernel, Kernel::ResetType::OneShot, "IBtmUserCore:Discovery"); 65 kernel, Kernel::ResetType::Automatic, "IBtmUserCore:Discovery");
66 config_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, 66 config_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
67 "IBtmUserCore:ConfigEvent"); 67 "IBtmUserCore:ConfigEvent");
68 } 68 }
69 69
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index e7fc7a619..fdd6d79a2 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -170,7 +170,7 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
170void Controller_NPad::OnInit() { 170void Controller_NPad::OnInit() {
171 auto& kernel = Core::System::GetInstance().Kernel(); 171 auto& kernel = Core::System::GetInstance().Kernel();
172 styleset_changed_event = Kernel::WritableEvent::CreateEventPair( 172 styleset_changed_event = Kernel::WritableEvent::CreateEventPair(
173 kernel, Kernel::ResetType::OneShot, "npad:NpadStyleSetChanged"); 173 kernel, Kernel::ResetType::Automatic, "npad:NpadStyleSetChanged");
174 174
175 if (!IsControllerActivated()) { 175 if (!IsControllerActivated()) {
176 return; 176 return;
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index c6babdd4d..a5cb06f8a 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -26,7 +26,7 @@ constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152);
26Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) 26Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
27 : ServiceFramework(name), module(std::move(module)) { 27 : ServiceFramework(name), module(std::move(module)) {
28 auto& kernel = Core::System::GetInstance().Kernel(); 28 auto& kernel = Core::System::GetInstance().Kernel();
29 nfc_tag_load = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, 29 nfc_tag_load = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
30 "IUser:NFCTagDetected"); 30 "IUser:NFCTagDetected");
31} 31}
32 32
@@ -67,9 +67,9 @@ public:
67 67
68 auto& kernel = Core::System::GetInstance().Kernel(); 68 auto& kernel = Core::System::GetInstance().Kernel();
69 deactivate_event = Kernel::WritableEvent::CreateEventPair( 69 deactivate_event = Kernel::WritableEvent::CreateEventPair(
70 kernel, Kernel::ResetType::OneShot, "IUser:DeactivateEvent"); 70 kernel, Kernel::ResetType::Automatic, "IUser:DeactivateEvent");
71 availability_change_event = Kernel::WritableEvent::CreateEventPair( 71 availability_change_event = Kernel::WritableEvent::CreateEventPair(
72 kernel, Kernel::ResetType::OneShot, "IUser:AvailabilityChangeEvent"); 72 kernel, Kernel::ResetType::Automatic, "IUser:AvailabilityChangeEvent");
73 } 73 }
74 74
75private: 75private:
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index f92571008..76b12b482 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -62,9 +62,9 @@ public:
62 RegisterHandlers(functions); 62 RegisterHandlers(functions);
63 63
64 auto& kernel = Core::System::GetInstance().Kernel(); 64 auto& kernel = Core::System::GetInstance().Kernel();
65 event1 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, 65 event1 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
66 "IRequest:Event1"); 66 "IRequest:Event1");
67 event2 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, 67 event2 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
68 "IRequest:Event2"); 68 "IRequest:Event2");
69 } 69 }
70 70
diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp
index 0dabcd23b..f319a3ca1 100644
--- a/src/core/hle/service/nim/nim.cpp
+++ b/src/core/hle/service/nim/nim.cpp
@@ -141,7 +141,7 @@ public:
141 141
142 auto& kernel = Core::System::GetInstance().Kernel(); 142 auto& kernel = Core::System::GetInstance().Kernel();
143 finished_event = Kernel::WritableEvent::CreateEventPair( 143 finished_event = Kernel::WritableEvent::CreateEventPair(
144 kernel, Kernel::ResetType::OneShot, 144 kernel, Kernel::ResetType::Automatic,
145 "IEnsureNetworkClockAvailabilityService:FinishEvent"); 145 "IEnsureNetworkClockAvailabilityService:FinishEvent");
146 } 146 }
147 147
diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp
index 3b9ab4b14..b60fc748b 100644
--- a/src/core/hle/service/nvdrv/interface.cpp
+++ b/src/core/hle/service/nvdrv/interface.cpp
@@ -129,7 +129,7 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name)
129 RegisterHandlers(functions); 129 RegisterHandlers(functions);
130 130
131 auto& kernel = Core::System::GetInstance().Kernel(); 131 auto& kernel = Core::System::GetInstance().Kernel();
132 query_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, 132 query_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
133 "NVDRV::query_event"); 133 "NVDRV::query_event");
134} 134}
135 135
diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp
index 4d150fc71..5731e815f 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue.cpp
@@ -16,7 +16,7 @@ namespace Service::NVFlinger {
16 16
17BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) { 17BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) {
18 auto& kernel = Core::System::GetInstance().Kernel(); 18 auto& kernel = Core::System::GetInstance().Kernel();
19 buffer_wait_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky, 19 buffer_wait_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
20 "BufferQueue NativeHandle"); 20 "BufferQueue NativeHandle");
21} 21}
22 22
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index 4ecb6bcef..298d85011 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -2,16 +2,15 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm>
5#include <chrono> 6#include <chrono>
6#include "common/logging/log.h" 7#include "common/logging/log.h"
7#include "core/hle/ipc_helpers.h" 8#include "core/hle/ipc_helpers.h"
8#include "core/hle/kernel/client_port.h"
9#include "core/hle/kernel/client_session.h"
10#include "core/hle/service/set/set.h" 9#include "core/hle/service/set/set.h"
11#include "core/settings.h" 10#include "core/settings.h"
12 11
13namespace Service::Set { 12namespace Service::Set {
14 13namespace {
15constexpr std::array<LanguageCode, 17> available_language_codes = {{ 14constexpr std::array<LanguageCode, 17> available_language_codes = {{
16 LanguageCode::JA, 15 LanguageCode::JA,
17 LanguageCode::EN_US, 16 LanguageCode::EN_US,
@@ -32,41 +31,35 @@ constexpr std::array<LanguageCode, 17> available_language_codes = {{
32 LanguageCode::ZH_HANT, 31 LanguageCode::ZH_HANT,
33}}; 32}};
34 33
35constexpr std::size_t pre4_0_0_max_entries = 0xF; 34constexpr std::size_t pre4_0_0_max_entries = 15;
36constexpr std::size_t post4_0_0_max_entries = 0x40; 35constexpr std::size_t post4_0_0_max_entries = 17;
37 36
38constexpr ResultCode ERR_INVALID_LANGUAGE{ErrorModule::Settings, 625}; 37constexpr ResultCode ERR_INVALID_LANGUAGE{ErrorModule::Settings, 625};
39 38
40LanguageCode GetLanguageCodeFromIndex(std::size_t index) { 39void PushResponseLanguageCode(Kernel::HLERequestContext& ctx, std::size_t num_language_codes) {
41 return available_language_codes.at(index); 40 IPC::ResponseBuilder rb{ctx, 3};
41 rb.Push(RESULT_SUCCESS);
42 rb.Push(static_cast<u32>(num_language_codes));
42} 43}
43 44
44template <std::size_t size> 45void GetAvailableLanguageCodesImpl(Kernel::HLERequestContext& ctx, std::size_t max_size) {
45static std::array<LanguageCode, size> MakeLanguageCodeSubset() { 46 const std::size_t requested_amount = ctx.GetWriteBufferSize() / sizeof(LanguageCode);
46 std::array<LanguageCode, size> arr; 47 const std::size_t copy_amount = std::min(requested_amount, max_size);
47 std::copy_n(available_language_codes.begin(), size, arr.begin()); 48 const std::size_t copy_size = copy_amount * sizeof(LanguageCode);
48 return arr; 49
50 ctx.WriteBuffer(available_language_codes.data(), copy_size);
51 PushResponseLanguageCode(ctx, copy_amount);
49} 52}
53} // Anonymous namespace
50 54
51static void PushResponseLanguageCode(Kernel::HLERequestContext& ctx, std::size_t max_size) { 55LanguageCode GetLanguageCodeFromIndex(std::size_t index) {
52 IPC::ResponseBuilder rb{ctx, 3}; 56 return available_language_codes.at(index);
53 rb.Push(RESULT_SUCCESS);
54 if (available_language_codes.size() > max_size) {
55 rb.Push(static_cast<u32>(max_size));
56 } else {
57 rb.Push(static_cast<u32>(available_language_codes.size()));
58 }
59} 57}
60 58
61void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) { 59void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) {
62 LOG_DEBUG(Service_SET, "called"); 60 LOG_DEBUG(Service_SET, "called");
63 61
64 if (available_language_codes.size() > pre4_0_0_max_entries) { 62 GetAvailableLanguageCodesImpl(ctx, pre4_0_0_max_entries);
65 ctx.WriteBuffer(MakeLanguageCodeSubset<pre4_0_0_max_entries>());
66 } else {
67 ctx.WriteBuffer(available_language_codes);
68 }
69 PushResponseLanguageCode(ctx, pre4_0_0_max_entries);
70} 63}
71 64
72void SET::MakeLanguageCode(Kernel::HLERequestContext& ctx) { 65void SET::MakeLanguageCode(Kernel::HLERequestContext& ctx) {
@@ -87,12 +80,7 @@ void SET::MakeLanguageCode(Kernel::HLERequestContext& ctx) {
87void SET::GetAvailableLanguageCodes2(Kernel::HLERequestContext& ctx) { 80void SET::GetAvailableLanguageCodes2(Kernel::HLERequestContext& ctx) {
88 LOG_DEBUG(Service_SET, "called"); 81 LOG_DEBUG(Service_SET, "called");
89 82
90 if (available_language_codes.size() > post4_0_0_max_entries) { 83 GetAvailableLanguageCodesImpl(ctx, post4_0_0_max_entries);
91 ctx.WriteBuffer(MakeLanguageCodeSubset<post4_0_0_max_entries>());
92 } else {
93 ctx.WriteBuffer(available_language_codes);
94 }
95 PushResponseLanguageCode(ctx, post4_0_0_max_entries);
96} 84}
97 85
98void SET::GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx) { 86void SET::GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx) {
@@ -102,9 +90,9 @@ void SET::GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx) {
102} 90}
103 91
104void SET::GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx) { 92void SET::GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx) {
105 PushResponseLanguageCode(ctx, post4_0_0_max_entries);
106
107 LOG_DEBUG(Service_SET, "called"); 93 LOG_DEBUG(Service_SET, "called");
94
95 PushResponseLanguageCode(ctx, post4_0_0_max_entries);
108} 96}
109 97
110void SET::GetLanguageCode(Kernel::HLERequestContext& ctx) { 98void SET::GetLanguageCode(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
index 01d80311b..a8d088305 100644
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ b/src/core/hle/service/vi/display/vi_display.cpp
@@ -17,7 +17,7 @@ namespace Service::VI {
17 17
18Display::Display(u64 id, std::string name) : id{id}, name{std::move(name)} { 18Display::Display(u64 id, std::string name) : id{id}, name{std::move(name)} {
19 auto& kernel = Core::System::GetInstance().Kernel(); 19 auto& kernel = Core::System::GetInstance().Kernel();
20 vsync_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky, 20 vsync_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
21 fmt::format("Display VSync Event {}", id)); 21 fmt::format("Display VSync Event {}", id));
22} 22}
23 23
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 4e17249a9..f1fa6ccd1 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -556,7 +556,7 @@ private:
556 } else { 556 } else {
557 // Wait the current thread until a buffer becomes available 557 // Wait the current thread until a buffer becomes available
558 ctx.SleepClientThread( 558 ctx.SleepClientThread(
559 Kernel::GetCurrentThread(), "IHOSBinderDriver::DequeueBuffer", -1, 559 "IHOSBinderDriver::DequeueBuffer", -1,
560 [=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx, 560 [=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
561 Kernel::ThreadWakeupReason reason) { 561 Kernel::ThreadWakeupReason reason) {
562 // Repeat TransactParcel DequeueBuffer when a buffer is available 562 // Repeat TransactParcel DequeueBuffer when a buffer is available
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index a86653204..62c090353 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -21,8 +21,6 @@
21#include "core/memory.h" 21#include "core/memory.h"
22#include "core/settings.h" 22#include "core/settings.h"
23 23
24#pragma optimize("", off)
25
26namespace Loader { 24namespace Loader {
27namespace { 25namespace {
28struct MODHeader { 26struct MODHeader {
@@ -41,7 +39,7 @@ std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
41 const std::vector<u8> uncompressed_data = 39 const std::vector<u8> uncompressed_data =
42 Common::Compression::DecompressDataLZ4(compressed_data, header.size); 40 Common::Compression::DecompressDataLZ4(compressed_data, header.size);
43 41
44 ASSERT_MSG(uncompressed_data.size() == static_cast<int>(header.size), "{} != {}", header.size, 42 ASSERT_MSG(uncompressed_data.size() == header.size, "{} != {}", header.size,
45 uncompressed_data.size()); 43 uncompressed_data.size());
46 44
47 return uncompressed_data; 45 return uncompressed_data;
diff --git a/src/core/memory.h b/src/core/memory.h
index b9fa18b1d..04e2c5f1d 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -72,15 +72,6 @@ u8* GetPointer(VAddr vaddr);
72 72
73std::string ReadCString(VAddr vaddr, std::size_t max_length); 73std::string ReadCString(VAddr vaddr, std::size_t max_length);
74 74
75enum class FlushMode {
76 /// Write back modified surfaces to RAM
77 Flush,
78 /// Remove region from the cache
79 Invalidate,
80 /// Write back modified surfaces to RAM, and also remove them from the cache
81 FlushAndInvalidate,
82};
83
84/** 75/**
85 * Mark each page touching the region as cached. 76 * Mark each page touching the region as cached.
86 */ 77 */
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index e1db06811..4b17bada5 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -102,12 +102,6 @@ bool VerifyLogin(const std::string& username, const std::string& token) {
102} 102}
103 103
104TelemetrySession::TelemetrySession() { 104TelemetrySession::TelemetrySession() {
105#ifdef ENABLE_WEB_SERVICE
106 backend = std::make_unique<WebService::TelemetryJson>(
107 Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token);
108#else
109 backend = std::make_unique<Telemetry::NullVisitor>();
110#endif
111 // Log one-time top-level information 105 // Log one-time top-level information
112 AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId()); 106 AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId());
113 107
@@ -175,9 +169,14 @@ TelemetrySession::~TelemetrySession() {
175 .count()}; 169 .count()};
176 AddField(Telemetry::FieldType::Session, "Shutdown_Time", shutdown_time); 170 AddField(Telemetry::FieldType::Session, "Shutdown_Time", shutdown_time);
177 171
172#ifdef ENABLE_WEB_SERVICE
173 auto backend = std::make_unique<WebService::TelemetryJson>(
174 Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token);
175#else
176 auto backend = std::make_unique<Telemetry::NullVisitor>();
177#endif
178
178 // Complete the session, submitting to web service if necessary 179 // Complete the session, submitting to web service if necessary
179 // This is just a placeholder to wrap up the session once the core completes and this is
180 // destroyed. This will be moved elsewhere once we are actually doing real I/O with the service.
181 field_collection.Accept(*backend); 180 field_collection.Accept(*backend);
182 if (Settings::values.enable_telemetry) 181 if (Settings::values.enable_telemetry)
183 backend->Complete(); 182 backend->Complete();
@@ -186,6 +185,8 @@ TelemetrySession::~TelemetrySession() {
186 185
187bool TelemetrySession::SubmitTestcase() { 186bool TelemetrySession::SubmitTestcase() {
188#ifdef ENABLE_WEB_SERVICE 187#ifdef ENABLE_WEB_SERVICE
188 auto backend = std::make_unique<WebService::TelemetryJson>(
189 Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token);
189 field_collection.Accept(*backend); 190 field_collection.Accept(*backend);
190 return backend->SubmitTestcase(); 191 return backend->SubmitTestcase();
191#else 192#else
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index 023612b79..cae5a45a0 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -39,7 +39,6 @@ public:
39 39
40private: 40private:
41 Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session 41 Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session
42 std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
43}; 42};
44 43
45/** 44/**
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 6821f275d..1e010e4da 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -3,6 +3,8 @@ add_library(video_core STATIC
3 dma_pusher.h 3 dma_pusher.h
4 debug_utils/debug_utils.cpp 4 debug_utils/debug_utils.cpp
5 debug_utils/debug_utils.h 5 debug_utils/debug_utils.h
6 engines/engine_upload.cpp
7 engines/engine_upload.h
6 engines/fermi_2d.cpp 8 engines/fermi_2d.cpp
7 engines/fermi_2d.h 9 engines/fermi_2d.h
8 engines/kepler_compute.cpp 10 engines/kepler_compute.cpp
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp
index 6674d9405..3175579cc 100644
--- a/src/video_core/dma_pusher.cpp
+++ b/src/video_core/dma_pusher.cpp
@@ -40,6 +40,13 @@ bool DmaPusher::Step() {
40 } 40 }
41 41
42 const CommandList& command_list{dma_pushbuffer.front()}; 42 const CommandList& command_list{dma_pushbuffer.front()};
43 ASSERT_OR_EXECUTE(!command_list.empty(), {
44 // Somehow the command_list is empty, in order to avoid a crash
45 // We ignore it and assume its size is 0.
46 dma_pushbuffer.pop();
47 dma_pushbuffer_subindex = 0;
48 return true;
49 });
43 const CommandListHeader command_list_header{command_list[dma_pushbuffer_subindex++]}; 50 const CommandListHeader command_list_header{command_list[dma_pushbuffer_subindex++]};
44 GPUVAddr dma_get = command_list_header.addr; 51 GPUVAddr dma_get = command_list_header.addr;
45 GPUVAddr dma_put = dma_get + command_list_header.size * sizeof(u32); 52 GPUVAddr dma_put = dma_get + command_list_header.size * sizeof(u32);
@@ -105,6 +112,8 @@ bool DmaPusher::Step() {
105 dma_state.non_incrementing = false; 112 dma_state.non_incrementing = false;
106 dma_increment_once = true; 113 dma_increment_once = true;
107 break; 114 break;
115 default:
116 break;
108 } 117 }
109 } 118 }
110 } 119 }
diff --git a/src/video_core/engines/engine_upload.cpp b/src/video_core/engines/engine_upload.cpp
new file mode 100644
index 000000000..082a40cd9
--- /dev/null
+++ b/src/video_core/engines/engine_upload.cpp
@@ -0,0 +1,52 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cstring>
6
7#include "common/assert.h"
8#include "video_core/engines/engine_upload.h"
9#include "video_core/memory_manager.h"
10#include "video_core/textures/decoders.h"
11
12namespace Tegra::Engines::Upload {
13
14State::State(MemoryManager& memory_manager, Registers& regs)
15 : regs{regs}, memory_manager{memory_manager} {}
16
17State::~State() = default;
18
19void State::ProcessExec(const bool is_linear) {
20 write_offset = 0;
21 copy_size = regs.line_length_in * regs.line_count;
22 inner_buffer.resize(copy_size);
23 this->is_linear = is_linear;
24}
25
26void State::ProcessData(const u32 data, const bool is_last_call) {
27 const u32 sub_copy_size = std::min(4U, copy_size - write_offset);
28 std::memcpy(&inner_buffer[write_offset], &data, sub_copy_size);
29 write_offset += sub_copy_size;
30 if (!is_last_call) {
31 return;
32 }
33 const GPUVAddr address{regs.dest.Address()};
34 if (is_linear) {
35 memory_manager.WriteBlock(address, inner_buffer.data(), copy_size);
36 } else {
37 UNIMPLEMENTED_IF(regs.dest.z != 0);
38 UNIMPLEMENTED_IF(regs.dest.depth != 1);
39 UNIMPLEMENTED_IF(regs.dest.BlockWidth() != 1);
40 UNIMPLEMENTED_IF(regs.dest.BlockDepth() != 1);
41 const std::size_t dst_size = Tegra::Texture::CalculateSize(
42 true, 1, regs.dest.width, regs.dest.height, 1, regs.dest.BlockHeight(), 1);
43 tmp_buffer.resize(dst_size);
44 memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);
45 Tegra::Texture::SwizzleKepler(regs.dest.width, regs.dest.height, regs.dest.x, regs.dest.y,
46 regs.dest.BlockHeight(), copy_size, inner_buffer.data(),
47 tmp_buffer.data());
48 memory_manager.WriteBlock(address, tmp_buffer.data(), dst_size);
49 }
50}
51
52} // namespace Tegra::Engines::Upload
diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h
new file mode 100644
index 000000000..ef4f5839a
--- /dev/null
+++ b/src/video_core/engines/engine_upload.h
@@ -0,0 +1,73 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <vector>
8#include "common/bit_field.h"
9#include "common/common_types.h"
10
11namespace Tegra {
12class MemoryManager;
13}
14
15namespace Tegra::Engines::Upload {
16
17struct Registers {
18 u32 line_length_in;
19 u32 line_count;
20
21 struct {
22 u32 address_high;
23 u32 address_low;
24 u32 pitch;
25 union {
26 BitField<0, 4, u32> block_width;
27 BitField<4, 4, u32> block_height;
28 BitField<8, 4, u32> block_depth;
29 };
30 u32 width;
31 u32 height;
32 u32 depth;
33 u32 z;
34 u32 x;
35 u32 y;
36
37 GPUVAddr Address() const {
38 return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low);
39 }
40
41 u32 BlockWidth() const {
42 return 1U << block_width.Value();
43 }
44
45 u32 BlockHeight() const {
46 return 1U << block_height.Value();
47 }
48
49 u32 BlockDepth() const {
50 return 1U << block_depth.Value();
51 }
52 } dest;
53};
54
55class State {
56public:
57 State(MemoryManager& memory_manager, Registers& regs);
58 ~State();
59
60 void ProcessExec(bool is_linear);
61 void ProcessData(u32 data, bool is_last_call);
62
63private:
64 u32 write_offset = 0;
65 u32 copy_size = 0;
66 std::vector<u8> inner_buffer;
67 std::vector<u8> tmp_buffer;
68 bool is_linear = false;
69 Registers& regs;
70 MemoryManager& memory_manager;
71};
72
73} // namespace Tegra::Engines::Upload
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
index 2e51b7f13..45f59a4d9 100644
--- a/src/video_core/engines/fermi_2d.h
+++ b/src/video_core/engines/fermi_2d.h
@@ -21,6 +21,12 @@ class RasterizerInterface;
21 21
22namespace Tegra::Engines { 22namespace Tegra::Engines {
23 23
24/**
25 * This Engine is known as G80_2D. Documentation can be found in:
26 * https://github.com/envytools/envytools/blob/master/rnndb/graph/g80_2d.xml
27 * https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_2d.xml.h
28 */
29
24#define FERMI2D_REG_INDEX(field_name) \ 30#define FERMI2D_REG_INDEX(field_name) \
25 (offsetof(Tegra::Engines::Fermi2D::Regs, field_name) / sizeof(u32)) 31 (offsetof(Tegra::Engines::Fermi2D::Regs, field_name) / sizeof(u32))
26 32
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index b1d950460..7404a8163 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -4,12 +4,21 @@
4 4
5#include "common/assert.h" 5#include "common/assert.h"
6#include "common/logging/log.h" 6#include "common/logging/log.h"
7#include "core/core.h"
7#include "video_core/engines/kepler_compute.h" 8#include "video_core/engines/kepler_compute.h"
9#include "video_core/engines/maxwell_3d.h"
8#include "video_core/memory_manager.h" 10#include "video_core/memory_manager.h"
11#include "video_core/rasterizer_interface.h"
12#include "video_core/renderer_base.h"
13#include "video_core/textures/decoders.h"
9 14
10namespace Tegra::Engines { 15namespace Tegra::Engines {
11 16
12KeplerCompute::KeplerCompute(MemoryManager& memory_manager) : memory_manager{memory_manager} {} 17KeplerCompute::KeplerCompute(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
18 MemoryManager& memory_manager)
19 : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager}, upload_state{
20 memory_manager,
21 regs.upload} {}
13 22
14KeplerCompute::~KeplerCompute() = default; 23KeplerCompute::~KeplerCompute() = default;
15 24
@@ -20,14 +29,34 @@ void KeplerCompute::CallMethod(const GPU::MethodCall& method_call) {
20 regs.reg_array[method_call.method] = method_call.argument; 29 regs.reg_array[method_call.method] = method_call.argument;
21 30
22 switch (method_call.method) { 31 switch (method_call.method) {
32 case KEPLER_COMPUTE_REG_INDEX(exec_upload): {
33 upload_state.ProcessExec(regs.exec_upload.linear != 0);
34 break;
35 }
36 case KEPLER_COMPUTE_REG_INDEX(data_upload): {
37 const bool is_last_call = method_call.IsLastCall();
38 upload_state.ProcessData(method_call.argument, is_last_call);
39 if (is_last_call) {
40 system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite();
41 }
42 break;
43 }
23 case KEPLER_COMPUTE_REG_INDEX(launch): 44 case KEPLER_COMPUTE_REG_INDEX(launch):
24 // Abort execution since compute shaders can be used to alter game memory (e.g. CUDA 45 ProcessLaunch();
25 // kernels)
26 UNREACHABLE_MSG("Compute shaders are not implemented");
27 break; 46 break;
28 default: 47 default:
29 break; 48 break;
30 } 49 }
31} 50}
32 51
52void KeplerCompute::ProcessLaunch() {
53
54 const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address();
55 memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description,
56 LaunchParams::NUM_LAUNCH_PARAMETERS * sizeof(u32));
57
58 const GPUVAddr code_loc = regs.code_loc.Address() + launch_description.program_start;
59 LOG_WARNING(HW_GPU, "Compute Kernel Execute at Address 0x{:016x}, STUBBED", code_loc);
60}
61
33} // namespace Tegra::Engines 62} // namespace Tegra::Engines
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index fb6cdf432..5250b8d9b 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -6,22 +6,40 @@
6 6
7#include <array> 7#include <array>
8#include <cstddef> 8#include <cstddef>
9#include <vector>
10#include "common/bit_field.h"
9#include "common/common_funcs.h" 11#include "common/common_funcs.h"
10#include "common/common_types.h" 12#include "common/common_types.h"
13#include "video_core/engines/engine_upload.h"
11#include "video_core/gpu.h" 14#include "video_core/gpu.h"
12 15
16namespace Core {
17class System;
18}
19
13namespace Tegra { 20namespace Tegra {
14class MemoryManager; 21class MemoryManager;
15} 22}
16 23
24namespace VideoCore {
25class RasterizerInterface;
26}
27
17namespace Tegra::Engines { 28namespace Tegra::Engines {
18 29
30/**
31 * This Engine is known as GK104_Compute. Documentation can be found in:
32 * https://github.com/envytools/envytools/blob/master/rnndb/graph/gk104_compute.xml
33 * https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nvc0/nve4_compute.xml.h
34 */
35
19#define KEPLER_COMPUTE_REG_INDEX(field_name) \ 36#define KEPLER_COMPUTE_REG_INDEX(field_name) \
20 (offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32)) 37 (offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32))
21 38
22class KeplerCompute final { 39class KeplerCompute final {
23public: 40public:
24 explicit KeplerCompute(MemoryManager& memory_manager); 41 explicit KeplerCompute(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
42 MemoryManager& memory_manager);
25 ~KeplerCompute(); 43 ~KeplerCompute();
26 44
27 static constexpr std::size_t NumConstBuffers = 8; 45 static constexpr std::size_t NumConstBuffers = 8;
@@ -31,30 +49,181 @@ public:
31 49
32 union { 50 union {
33 struct { 51 struct {
34 INSERT_PADDING_WORDS(0xAF); 52 INSERT_PADDING_WORDS(0x60);
53
54 Upload::Registers upload;
55
56 struct {
57 union {
58 BitField<0, 1, u32> linear;
59 };
60 } exec_upload;
61
62 u32 data_upload;
63
64 INSERT_PADDING_WORDS(0x3F);
65
66 struct {
67 u32 address;
68 GPUVAddr Address() const {
69 return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address) << 8));
70 }
71 } launch_desc_loc;
72
73 INSERT_PADDING_WORDS(0x1);
35 74
36 u32 launch; 75 u32 launch;
37 76
38 INSERT_PADDING_WORDS(0xC48); 77 INSERT_PADDING_WORDS(0x4A7);
78
79 struct {
80 u32 address_high;
81 u32 address_low;
82 u32 limit;
83 GPUVAddr Address() const {
84 return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
85 address_low);
86 }
87 } tsc;
88
89 INSERT_PADDING_WORDS(0x3);
90
91 struct {
92 u32 address_high;
93 u32 address_low;
94 u32 limit;
95 GPUVAddr Address() const {
96 return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
97 address_low);
98 }
99 } tic;
100
101 INSERT_PADDING_WORDS(0x22);
102
103 struct {
104 u32 address_high;
105 u32 address_low;
106 GPUVAddr Address() const {
107 return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
108 address_low);
109 }
110 } code_loc;
111
112 INSERT_PADDING_WORDS(0x3FE);
113
114 u32 texture_const_buffer_index;
115
116 INSERT_PADDING_WORDS(0x374);
39 }; 117 };
40 std::array<u32, NUM_REGS> reg_array; 118 std::array<u32, NUM_REGS> reg_array;
41 }; 119 };
42 } regs{}; 120 } regs{};
121
122 struct LaunchParams {
123 static constexpr std::size_t NUM_LAUNCH_PARAMETERS = 0x40;
124
125 INSERT_PADDING_WORDS(0x8);
126
127 u32 program_start;
128
129 INSERT_PADDING_WORDS(0x2);
130
131 BitField<30, 1, u32> linked_tsc;
132
133 BitField<0, 31, u32> grid_dim_x;
134 union {
135 BitField<0, 16, u32> grid_dim_y;
136 BitField<16, 16, u32> grid_dim_z;
137 };
138
139 INSERT_PADDING_WORDS(0x3);
140
141 BitField<0, 16, u32> shared_alloc;
142
143 BitField<0, 31, u32> block_dim_x;
144 union {
145 BitField<0, 16, u32> block_dim_y;
146 BitField<16, 16, u32> block_dim_z;
147 };
148
149 union {
150 BitField<0, 8, u32> const_buffer_enable_mask;
151 BitField<29, 2, u32> cache_layout;
152 } memory_config;
153
154 INSERT_PADDING_WORDS(0x8);
155
156 struct {
157 u32 address_low;
158 union {
159 BitField<0, 8, u32> address_high;
160 BitField<15, 17, u32> size;
161 };
162 GPUVAddr Address() const {
163 return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high.Value()) << 32) |
164 address_low);
165 }
166 } const_buffer_config[8];
167
168 union {
169 BitField<0, 20, u32> local_pos_alloc;
170 BitField<27, 5, u32> barrier_alloc;
171 };
172
173 union {
174 BitField<0, 20, u32> local_neg_alloc;
175 BitField<24, 5, u32> gpr_alloc;
176 };
177
178 INSERT_PADDING_WORDS(0x11);
179 } launch_description;
180
181 struct {
182 u32 write_offset = 0;
183 u32 copy_size = 0;
184 std::vector<u8> inner_buffer;
185 } state{};
186
43 static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32), 187 static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32),
44 "KeplerCompute Regs has wrong size"); 188 "KeplerCompute Regs has wrong size");
45 189
190 static_assert(sizeof(LaunchParams) == LaunchParams::NUM_LAUNCH_PARAMETERS * sizeof(u32),
191 "KeplerCompute LaunchParams has wrong size");
192
46 /// Write the value to the register identified by method. 193 /// Write the value to the register identified by method.
47 void CallMethod(const GPU::MethodCall& method_call); 194 void CallMethod(const GPU::MethodCall& method_call);
48 195
49private: 196private:
197 Core::System& system;
198 VideoCore::RasterizerInterface& rasterizer;
50 MemoryManager& memory_manager; 199 MemoryManager& memory_manager;
200 Upload::State upload_state;
201
202 void ProcessLaunch();
51}; 203};
52 204
53#define ASSERT_REG_POSITION(field_name, position) \ 205#define ASSERT_REG_POSITION(field_name, position) \
54 static_assert(offsetof(KeplerCompute::Regs, field_name) == position * 4, \ 206 static_assert(offsetof(KeplerCompute::Regs, field_name) == position * 4, \
55 "Field " #field_name " has invalid position") 207 "Field " #field_name " has invalid position")
56 208
209#define ASSERT_LAUNCH_PARAM_POSITION(field_name, position) \
210 static_assert(offsetof(KeplerCompute::LaunchParams, field_name) == position * 4, \
211 "Field " #field_name " has invalid position")
212
213ASSERT_REG_POSITION(upload, 0x60);
214ASSERT_REG_POSITION(exec_upload, 0x6C);
215ASSERT_REG_POSITION(data_upload, 0x6D);
57ASSERT_REG_POSITION(launch, 0xAF); 216ASSERT_REG_POSITION(launch, 0xAF);
217ASSERT_REG_POSITION(tsc, 0x557);
218ASSERT_REG_POSITION(tic, 0x55D);
219ASSERT_REG_POSITION(code_loc, 0x582);
220ASSERT_REG_POSITION(texture_const_buffer_index, 0x982);
221ASSERT_LAUNCH_PARAM_POSITION(program_start, 0x8);
222ASSERT_LAUNCH_PARAM_POSITION(grid_dim_x, 0xC);
223ASSERT_LAUNCH_PARAM_POSITION(shared_alloc, 0x11);
224ASSERT_LAUNCH_PARAM_POSITION(block_dim_x, 0x12);
225ASSERT_LAUNCH_PARAM_POSITION(memory_config, 0x14);
226ASSERT_LAUNCH_PARAM_POSITION(const_buffer_config, 0x1D);
58 227
59#undef ASSERT_REG_POSITION 228#undef ASSERT_REG_POSITION
60 229
diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp
index 7387886a3..0561f676c 100644
--- a/src/video_core/engines/kepler_memory.cpp
+++ b/src/video_core/engines/kepler_memory.cpp
@@ -14,9 +14,8 @@
14 14
15namespace Tegra::Engines { 15namespace Tegra::Engines {
16 16
17KeplerMemory::KeplerMemory(Core::System& system, VideoCore::RasterizerInterface& rasterizer, 17KeplerMemory::KeplerMemory(Core::System& system, MemoryManager& memory_manager)
18 MemoryManager& memory_manager) 18 : system{system}, memory_manager{memory_manager}, upload_state{memory_manager, regs.upload} {}
19 : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager} {}
20 19
21KeplerMemory::~KeplerMemory() = default; 20KeplerMemory::~KeplerMemory() = default;
22 21
@@ -28,46 +27,18 @@ void KeplerMemory::CallMethod(const GPU::MethodCall& method_call) {
28 27
29 switch (method_call.method) { 28 switch (method_call.method) {
30 case KEPLERMEMORY_REG_INDEX(exec): { 29 case KEPLERMEMORY_REG_INDEX(exec): {
31 ProcessExec(); 30 upload_state.ProcessExec(regs.exec.linear != 0);
32 break; 31 break;
33 } 32 }
34 case KEPLERMEMORY_REG_INDEX(data): { 33 case KEPLERMEMORY_REG_INDEX(data): {
35 ProcessData(method_call.argument, method_call.IsLastCall()); 34 const bool is_last_call = method_call.IsLastCall();
35 upload_state.ProcessData(method_call.argument, is_last_call);
36 if (is_last_call) {
37 system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite();
38 }
36 break; 39 break;
37 } 40 }
38 } 41 }
39} 42}
40 43
41void KeplerMemory::ProcessExec() {
42 state.write_offset = 0;
43 state.copy_size = regs.line_length_in * regs.line_count;
44 state.inner_buffer.resize(state.copy_size);
45}
46
47void KeplerMemory::ProcessData(u32 data, bool is_last_call) {
48 const u32 sub_copy_size = std::min(4U, state.copy_size - state.write_offset);
49 std::memcpy(&state.inner_buffer[state.write_offset], &regs.data, sub_copy_size);
50 state.write_offset += sub_copy_size;
51 if (is_last_call) {
52 const GPUVAddr address{regs.dest.Address()};
53 if (regs.exec.linear != 0) {
54 memory_manager.WriteBlock(address, state.inner_buffer.data(), state.copy_size);
55 } else {
56 UNIMPLEMENTED_IF(regs.dest.z != 0);
57 UNIMPLEMENTED_IF(regs.dest.depth != 1);
58 UNIMPLEMENTED_IF(regs.dest.BlockWidth() != 1);
59 UNIMPLEMENTED_IF(regs.dest.BlockDepth() != 1);
60 const std::size_t dst_size = Tegra::Texture::CalculateSize(
61 true, 1, regs.dest.width, regs.dest.height, 1, regs.dest.BlockHeight(), 1);
62 std::vector<u8> tmp_buffer(dst_size);
63 memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);
64 Tegra::Texture::SwizzleKepler(regs.dest.width, regs.dest.height, regs.dest.x,
65 regs.dest.y, regs.dest.BlockHeight(), state.copy_size,
66 state.inner_buffer.data(), tmp_buffer.data());
67 memory_manager.WriteBlock(address, tmp_buffer.data(), dst_size);
68 }
69 system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite();
70 }
71}
72
73} // namespace Tegra::Engines 44} // namespace Tegra::Engines
diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h
index 5f892ddad..f3bc675a9 100644
--- a/src/video_core/engines/kepler_memory.h
+++ b/src/video_core/engines/kepler_memory.h
@@ -10,6 +10,7 @@
10#include "common/bit_field.h" 10#include "common/bit_field.h"
11#include "common/common_funcs.h" 11#include "common/common_funcs.h"
12#include "common/common_types.h" 12#include "common/common_types.h"
13#include "video_core/engines/engine_upload.h"
13#include "video_core/gpu.h" 14#include "video_core/gpu.h"
14 15
15namespace Core { 16namespace Core {
@@ -20,19 +21,20 @@ namespace Tegra {
20class MemoryManager; 21class MemoryManager;
21} 22}
22 23
23namespace VideoCore {
24class RasterizerInterface;
25}
26
27namespace Tegra::Engines { 24namespace Tegra::Engines {
28 25
26/**
27 * This Engine is known as P2MF. Documentation can be found in:
28 * https://github.com/envytools/envytools/blob/master/rnndb/graph/gk104_p2mf.xml
29 * https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nvc0/nve4_p2mf.xml.h
30 */
31
29#define KEPLERMEMORY_REG_INDEX(field_name) \ 32#define KEPLERMEMORY_REG_INDEX(field_name) \
30 (offsetof(Tegra::Engines::KeplerMemory::Regs, field_name) / sizeof(u32)) 33 (offsetof(Tegra::Engines::KeplerMemory::Regs, field_name) / sizeof(u32))
31 34
32class KeplerMemory final { 35class KeplerMemory final {
33public: 36public:
34 KeplerMemory(Core::System& system, VideoCore::RasterizerInterface& rasterizer, 37 KeplerMemory(Core::System& system, MemoryManager& memory_manager);
35 MemoryManager& memory_manager);
36 ~KeplerMemory(); 38 ~KeplerMemory();
37 39
38 /// Write the value to the register identified by method. 40 /// Write the value to the register identified by method.
@@ -45,42 +47,7 @@ public:
45 struct { 47 struct {
46 INSERT_PADDING_WORDS(0x60); 48 INSERT_PADDING_WORDS(0x60);
47 49
48 u32 line_length_in; 50 Upload::Registers upload;
49 u32 line_count;
50
51 struct {
52 u32 address_high;
53 u32 address_low;
54 u32 pitch;
55 union {
56 BitField<0, 4, u32> block_width;
57 BitField<4, 4, u32> block_height;
58 BitField<8, 4, u32> block_depth;
59 };
60 u32 width;
61 u32 height;
62 u32 depth;
63 u32 z;
64 u32 x;
65 u32 y;
66
67 GPUVAddr Address() const {
68 return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
69 address_low);
70 }
71
72 u32 BlockWidth() const {
73 return 1U << block_width.Value();
74 }
75
76 u32 BlockHeight() const {
77 return 1U << block_height.Value();
78 }
79
80 u32 BlockDepth() const {
81 return 1U << block_depth.Value();
82 }
83 } dest;
84 51
85 struct { 52 struct {
86 union { 53 union {
@@ -96,28 +63,17 @@ public:
96 }; 63 };
97 } regs{}; 64 } regs{};
98 65
99 struct {
100 u32 write_offset = 0;
101 u32 copy_size = 0;
102 std::vector<u8> inner_buffer;
103 } state{};
104
105private: 66private:
106 Core::System& system; 67 Core::System& system;
107 VideoCore::RasterizerInterface& rasterizer;
108 MemoryManager& memory_manager; 68 MemoryManager& memory_manager;
109 69 Upload::State upload_state;
110 void ProcessExec();
111 void ProcessData(u32 data, bool is_last_call);
112}; 70};
113 71
114#define ASSERT_REG_POSITION(field_name, position) \ 72#define ASSERT_REG_POSITION(field_name, position) \
115 static_assert(offsetof(KeplerMemory::Regs, field_name) == position * 4, \ 73 static_assert(offsetof(KeplerMemory::Regs, field_name) == position * 4, \
116 "Field " #field_name " has invalid position") 74 "Field " #field_name " has invalid position")
117 75
118ASSERT_REG_POSITION(line_length_in, 0x60); 76ASSERT_REG_POSITION(upload, 0x60);
119ASSERT_REG_POSITION(line_count, 0x61);
120ASSERT_REG_POSITION(dest, 0x62);
121ASSERT_REG_POSITION(exec, 0x6C); 77ASSERT_REG_POSITION(exec, 0x6C);
122ASSERT_REG_POSITION(data, 0x6D); 78ASSERT_REG_POSITION(data, 0x6D);
123#undef ASSERT_REG_POSITION 79#undef ASSERT_REG_POSITION
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 9780417f2..39968d403 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -20,8 +20,8 @@ constexpr u32 MacroRegistersStart = 0xE00;
20 20
21Maxwell3D::Maxwell3D(Core::System& system, VideoCore::RasterizerInterface& rasterizer, 21Maxwell3D::Maxwell3D(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
22 MemoryManager& memory_manager) 22 MemoryManager& memory_manager)
23 : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager}, macro_interpreter{ 23 : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager},
24 *this} { 24 macro_interpreter{*this}, upload_state{memory_manager, regs.upload} {
25 InitializeRegisterDefaults(); 25 InitializeRegisterDefaults();
26} 26}
27 27
@@ -34,9 +34,9 @@ void Maxwell3D::InitializeRegisterDefaults() {
34 34
35 // Depth range near/far is not always set, but is expected to be the default 0.0f, 1.0f. This is 35 // Depth range near/far is not always set, but is expected to be the default 0.0f, 1.0f. This is
36 // needed for ARMS. 36 // needed for ARMS.
37 for (std::size_t viewport{}; viewport < Regs::NumViewports; ++viewport) { 37 for (auto& viewport : regs.viewports) {
38 regs.viewports[viewport].depth_range_near = 0.0f; 38 viewport.depth_range_near = 0.0f;
39 regs.viewports[viewport].depth_range_far = 1.0f; 39 viewport.depth_range_far = 1.0f;
40 } 40 }
41 41
42 // Doom and Bomberman seems to use the uninitialized registers and just enable blend 42 // Doom and Bomberman seems to use the uninitialized registers and just enable blend
@@ -47,13 +47,13 @@ void Maxwell3D::InitializeRegisterDefaults() {
47 regs.blend.equation_a = Regs::Blend::Equation::Add; 47 regs.blend.equation_a = Regs::Blend::Equation::Add;
48 regs.blend.factor_source_a = Regs::Blend::Factor::One; 48 regs.blend.factor_source_a = Regs::Blend::Factor::One;
49 regs.blend.factor_dest_a = Regs::Blend::Factor::Zero; 49 regs.blend.factor_dest_a = Regs::Blend::Factor::Zero;
50 for (std::size_t blend_index = 0; blend_index < Regs::NumRenderTargets; blend_index++) { 50 for (auto& blend : regs.independent_blend) {
51 regs.independent_blend[blend_index].equation_rgb = Regs::Blend::Equation::Add; 51 blend.equation_rgb = Regs::Blend::Equation::Add;
52 regs.independent_blend[blend_index].factor_source_rgb = Regs::Blend::Factor::One; 52 blend.factor_source_rgb = Regs::Blend::Factor::One;
53 regs.independent_blend[blend_index].factor_dest_rgb = Regs::Blend::Factor::Zero; 53 blend.factor_dest_rgb = Regs::Blend::Factor::Zero;
54 regs.independent_blend[blend_index].equation_a = Regs::Blend::Equation::Add; 54 blend.equation_a = Regs::Blend::Equation::Add;
55 regs.independent_blend[blend_index].factor_source_a = Regs::Blend::Factor::One; 55 blend.factor_source_a = Regs::Blend::Factor::One;
56 regs.independent_blend[blend_index].factor_dest_a = Regs::Blend::Factor::Zero; 56 blend.factor_dest_a = Regs::Blend::Factor::Zero;
57 } 57 }
58 regs.stencil_front_op_fail = Regs::StencilOp::Keep; 58 regs.stencil_front_op_fail = Regs::StencilOp::Keep;
59 regs.stencil_front_op_zfail = Regs::StencilOp::Keep; 59 regs.stencil_front_op_zfail = Regs::StencilOp::Keep;
@@ -75,11 +75,11 @@ void Maxwell3D::InitializeRegisterDefaults() {
75 75
76 // TODO(bunnei): Some games do not initialize the color masks (e.g. Sonic Mania). Assuming a 76 // TODO(bunnei): Some games do not initialize the color masks (e.g. Sonic Mania). Assuming a
77 // default of enabled fixes rendering here. 77 // default of enabled fixes rendering here.
78 for (std::size_t color_mask = 0; color_mask < Regs::NumRenderTargets; color_mask++) { 78 for (auto& color_mask : regs.color_mask) {
79 regs.color_mask[color_mask].R.Assign(1); 79 color_mask.R.Assign(1);
80 regs.color_mask[color_mask].G.Assign(1); 80 color_mask.G.Assign(1);
81 regs.color_mask[color_mask].B.Assign(1); 81 color_mask.B.Assign(1);
82 regs.color_mask[color_mask].A.Assign(1); 82 color_mask.A.Assign(1);
83 } 83 }
84 84
85 // Commercial games seem to assume this value is enabled and nouveau sets this value manually. 85 // Commercial games seem to assume this value is enabled and nouveau sets this value manually.
@@ -178,13 +178,13 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
178 178
179 // Vertex buffer 179 // Vertex buffer
180 if (method >= MAXWELL3D_REG_INDEX(vertex_array) && 180 if (method >= MAXWELL3D_REG_INDEX(vertex_array) &&
181 method < MAXWELL3D_REG_INDEX(vertex_array) + 4 * 32) { 181 method < MAXWELL3D_REG_INDEX(vertex_array) + 4 * Regs::NumVertexArrays) {
182 dirty_flags.vertex_array.set((method - MAXWELL3D_REG_INDEX(vertex_array)) >> 2); 182 dirty_flags.vertex_array.set((method - MAXWELL3D_REG_INDEX(vertex_array)) >> 2);
183 } else if (method >= MAXWELL3D_REG_INDEX(vertex_array_limit) && 183 } else if (method >= MAXWELL3D_REG_INDEX(vertex_array_limit) &&
184 method < MAXWELL3D_REG_INDEX(vertex_array_limit) + 2 * 32) { 184 method < MAXWELL3D_REG_INDEX(vertex_array_limit) + 2 * Regs::NumVertexArrays) {
185 dirty_flags.vertex_array.set((method - MAXWELL3D_REG_INDEX(vertex_array_limit)) >> 1); 185 dirty_flags.vertex_array.set((method - MAXWELL3D_REG_INDEX(vertex_array_limit)) >> 1);
186 } else if (method >= MAXWELL3D_REG_INDEX(instanced_arrays) && 186 } else if (method >= MAXWELL3D_REG_INDEX(instanced_arrays) &&
187 method < MAXWELL3D_REG_INDEX(instanced_arrays) + 32) { 187 method < MAXWELL3D_REG_INDEX(instanced_arrays) + Regs::NumVertexArrays) {
188 dirty_flags.vertex_array.set(method - MAXWELL3D_REG_INDEX(instanced_arrays)); 188 dirty_flags.vertex_array.set(method - MAXWELL3D_REG_INDEX(instanced_arrays));
189 } 189 }
190 } 190 }
@@ -253,6 +253,18 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
253 ProcessSyncPoint(); 253 ProcessSyncPoint();
254 break; 254 break;
255 } 255 }
256 case MAXWELL3D_REG_INDEX(exec_upload): {
257 upload_state.ProcessExec(regs.exec_upload.linear != 0);
258 break;
259 }
260 case MAXWELL3D_REG_INDEX(data_upload): {
261 const bool is_last_call = method_call.IsLastCall();
262 upload_state.ProcessData(method_call.argument, is_last_call);
263 if (is_last_call) {
264 dirty_flags.OnMemoryWrite();
265 }
266 break;
267 }
256 default: 268 default:
257 break; 269 break;
258 } 270 }
@@ -430,7 +442,7 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
430 const auto a_type = tic_entry.a_type.Value(); 442 const auto a_type = tic_entry.a_type.Value();
431 443
432 // TODO(Subv): Different data types for separate components are not supported 444 // TODO(Subv): Different data types for separate components are not supported
433 ASSERT(r_type == g_type && r_type == b_type && r_type == a_type); 445 DEBUG_ASSERT(r_type == g_type && r_type == b_type && r_type == a_type);
434 446
435 return tic_entry; 447 return tic_entry;
436} 448}
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index cc2424d38..f342c78e6 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -6,6 +6,7 @@
6 6
7#include <array> 7#include <array>
8#include <bitset> 8#include <bitset>
9#include <type_traits>
9#include <unordered_map> 10#include <unordered_map>
10#include <vector> 11#include <vector>
11 12
@@ -14,6 +15,7 @@
14#include "common/common_funcs.h" 15#include "common/common_funcs.h"
15#include "common/common_types.h" 16#include "common/common_types.h"
16#include "common/math_util.h" 17#include "common/math_util.h"
18#include "video_core/engines/engine_upload.h"
17#include "video_core/gpu.h" 19#include "video_core/gpu.h"
18#include "video_core/macro_interpreter.h" 20#include "video_core/macro_interpreter.h"
19#include "video_core/textures/texture.h" 21#include "video_core/textures/texture.h"
@@ -32,6 +34,12 @@ class RasterizerInterface;
32 34
33namespace Tegra::Engines { 35namespace Tegra::Engines {
34 36
37/**
38 * This Engine is known as GF100_3D. Documentation can be found in:
39 * https://github.com/envytools/envytools/blob/master/rnndb/graph/gf100_3d.xml
40 * https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nvc0/nvc0_3d.xml.h
41 */
42
35#define MAXWELL3D_REG_INDEX(field_name) \ 43#define MAXWELL3D_REG_INDEX(field_name) \
36 (offsetof(Tegra::Engines::Maxwell3D::Regs, field_name) / sizeof(u32)) 44 (offsetof(Tegra::Engines::Maxwell3D::Regs, field_name) / sizeof(u32))
37 45
@@ -51,6 +59,7 @@ public:
51 static constexpr std::size_t NumCBData = 16; 59 static constexpr std::size_t NumCBData = 16;
52 static constexpr std::size_t NumVertexArrays = 32; 60 static constexpr std::size_t NumVertexArrays = 32;
53 static constexpr std::size_t NumVertexAttributes = 32; 61 static constexpr std::size_t NumVertexAttributes = 32;
62 static constexpr std::size_t NumVaryings = 31;
54 static constexpr std::size_t NumTextureSamplers = 32; 63 static constexpr std::size_t NumTextureSamplers = 32;
55 static constexpr std::size_t NumClipDistances = 8; 64 static constexpr std::size_t NumClipDistances = 8;
56 static constexpr std::size_t MaxShaderProgram = 6; 65 static constexpr std::size_t MaxShaderProgram = 6;
@@ -243,9 +252,10 @@ public:
243 return "10_10_10_2"; 252 return "10_10_10_2";
244 case Size::Size_11_11_10: 253 case Size::Size_11_11_10:
245 return "11_11_10"; 254 return "11_11_10";
255 default:
256 UNREACHABLE();
257 return {};
246 } 258 }
247 UNREACHABLE();
248 return {};
249 } 259 }
250 260
251 std::string TypeString() const { 261 std::string TypeString() const {
@@ -579,7 +589,18 @@ public:
579 u32 bind; 589 u32 bind;
580 } macros; 590 } macros;
581 591
582 INSERT_PADDING_WORDS(0x69); 592 INSERT_PADDING_WORDS(0x17);
593
594 Upload::Registers upload;
595 struct {
596 union {
597 BitField<0, 1, u32> linear;
598 };
599 } exec_upload;
600
601 u32 data_upload;
602
603 INSERT_PADDING_WORDS(0x44);
583 604
584 struct { 605 struct {
585 union { 606 union {
@@ -1088,6 +1109,7 @@ public:
1088 } regs{}; 1109 } regs{};
1089 1110
1090 static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32), "Maxwell3D Regs has wrong size"); 1111 static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32), "Maxwell3D Regs has wrong size");
1112 static_assert(std::is_trivially_copyable_v<Regs>, "Maxwell3D Regs must be trivially copyable");
1091 1113
1092 struct State { 1114 struct State {
1093 struct ConstBufferInfo { 1115 struct ConstBufferInfo {
@@ -1175,6 +1197,8 @@ private:
1175 /// Interpreter for the macro codes uploaded to the GPU. 1197 /// Interpreter for the macro codes uploaded to the GPU.
1176 MacroInterpreter macro_interpreter; 1198 MacroInterpreter macro_interpreter;
1177 1199
1200 Upload::State upload_state;
1201
1178 /// Retrieves information about a specific TIC entry from the TIC buffer. 1202 /// Retrieves information about a specific TIC entry from the TIC buffer.
1179 Texture::TICEntry GetTICEntry(u32 tic_index) const; 1203 Texture::TICEntry GetTICEntry(u32 tic_index) const;
1180 1204
@@ -1218,6 +1242,9 @@ private:
1218 "Field " #field_name " has invalid position") 1242 "Field " #field_name " has invalid position")
1219 1243
1220ASSERT_REG_POSITION(macros, 0x45); 1244ASSERT_REG_POSITION(macros, 0x45);
1245ASSERT_REG_POSITION(upload, 0x60);
1246ASSERT_REG_POSITION(exec_upload, 0x6C);
1247ASSERT_REG_POSITION(data_upload, 0x6D);
1221ASSERT_REG_POSITION(sync_info, 0xB2); 1248ASSERT_REG_POSITION(sync_info, 0xB2);
1222ASSERT_REG_POSITION(tfb_enabled, 0x1D1); 1249ASSERT_REG_POSITION(tfb_enabled, 0x1D1);
1223ASSERT_REG_POSITION(rt, 0x200); 1250ASSERT_REG_POSITION(rt, 0x200);
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index 2426d0067..3a5dfef0c 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -83,57 +83,66 @@ void MaxwellDMA::HandleCopy() {
83 83
84 ASSERT(regs.exec.enable_2d == 1); 84 ASSERT(regs.exec.enable_2d == 1);
85 85
86 const std::size_t copy_size = regs.x_count * regs.y_count; 86 if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) {
87 ASSERT(regs.src_params.size_z == 1);
88 // If the input is tiled and the output is linear, deswizzle the input and copy it over.
89 const u32 src_bytes_per_pixel = regs.src_pitch / regs.src_params.size_x;
90 const std::size_t src_size = Texture::CalculateSize(
91 true, src_bytes_per_pixel, regs.src_params.size_x, regs.src_params.size_y,
92 regs.src_params.size_z, regs.src_params.BlockHeight(), regs.src_params.BlockDepth());
87 93
88 auto source_ptr{memory_manager.GetPointer(source)}; 94 const std::size_t dst_size = regs.dst_pitch * regs.y_count;
89 auto dst_ptr{memory_manager.GetPointer(dest)};
90 95
91 if (!source_ptr) { 96 if (read_buffer.size() < src_size) {
92 LOG_ERROR(HW_GPU, "source_ptr is invalid"); 97 read_buffer.resize(src_size);
93 return; 98 }
94 }
95 99
96 if (!dst_ptr) { 100 if (write_buffer.size() < dst_size) {
97 LOG_ERROR(HW_GPU, "dst_ptr is invalid"); 101 write_buffer.resize(dst_size);
98 return; 102 }
99 }
100 103
101 const auto FlushAndInvalidate = [&](u32 src_size, u64 dst_size) { 104 memory_manager.ReadBlock(source, read_buffer.data(), src_size);
102 // TODO(Subv): For now, manually flush the regions until we implement GPU-accelerated 105 memory_manager.ReadBlock(dest, write_buffer.data(), dst_size);
103 // copying.
104 rasterizer.FlushRegion(ToCacheAddr(source_ptr), src_size);
105 106
106 // We have to invalidate the destination region to evict any outdated surfaces from the 107 Texture::UnswizzleSubrect(regs.x_count, regs.y_count, regs.dst_pitch,
107 // cache. We do this before actually writing the new data because the destination address 108 regs.src_params.size_x, src_bytes_per_pixel, read_buffer.data(),
108 // might contain a dirty surface that will have to be written back to memory. 109 write_buffer.data(), regs.src_params.BlockHeight(),
109 rasterizer.InvalidateRegion(ToCacheAddr(dst_ptr), dst_size); 110 regs.src_params.pos_x, regs.src_params.pos_y);
110 };
111 111
112 if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) { 112 memory_manager.WriteBlock(dest, write_buffer.data(), dst_size);
113 ASSERT(regs.src_params.size_z == 1); 113 } else {
114 // If the input is tiled and the output is linear, deswizzle the input and copy it over. 114 ASSERT(regs.dst_params.BlockDepth() == 1);
115 115
116 const u32 src_bytes_per_pixel = regs.src_pitch / regs.src_params.size_x; 116 const u32 src_bytes_per_pixel = regs.src_pitch / regs.x_count;
117 117
118 FlushAndInvalidate(regs.src_pitch * regs.src_params.size_y, 118 const std::size_t dst_size = Texture::CalculateSize(
119 copy_size * src_bytes_per_pixel); 119 true, src_bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y,
120 regs.dst_params.size_z, regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth());
120 121
121 Texture::UnswizzleSubrect(regs.x_count, regs.y_count, regs.dst_pitch, 122 const std::size_t dst_layer_size = Texture::CalculateSize(
122 regs.src_params.size_x, src_bytes_per_pixel, source_ptr, dst_ptr, 123 true, src_bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y, 1,
123 regs.src_params.BlockHeight(), regs.src_params.pos_x, 124 regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth());
124 regs.src_params.pos_y);
125 } else {
126 ASSERT(regs.dst_params.size_z == 1);
127 ASSERT(regs.src_pitch == regs.x_count);
128 125
129 const u32 src_bpp = regs.src_pitch / regs.x_count; 126 const std::size_t src_size = regs.src_pitch * regs.y_count;
130 127
131 FlushAndInvalidate(regs.src_pitch * regs.y_count, 128 if (read_buffer.size() < src_size) {
132 regs.dst_params.size_x * regs.dst_params.size_y * src_bpp); 129 read_buffer.resize(src_size);
130 }
131
132 if (write_buffer.size() < dst_size) {
133 write_buffer.resize(dst_size);
134 }
135
136 memory_manager.ReadBlock(source, read_buffer.data(), src_size);
137 memory_manager.ReadBlock(dest, write_buffer.data(), dst_size);
133 138
134 // If the input is linear and the output is tiled, swizzle the input and copy it over. 139 // If the input is linear and the output is tiled, swizzle the input and copy it over.
135 Texture::SwizzleSubrect(regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x, 140 Texture::SwizzleSubrect(regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x,
136 src_bpp, dst_ptr, source_ptr, regs.dst_params.BlockHeight()); 141 src_bytes_per_pixel,
142 write_buffer.data() + dst_layer_size * regs.dst_params.pos_z,
143 read_buffer.data(), regs.dst_params.BlockHeight());
144
145 memory_manager.WriteBlock(dest, write_buffer.data(), dst_size);
137 } 146 }
138} 147}
139 148
diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h
index c6b649842..e5942f671 100644
--- a/src/video_core/engines/maxwell_dma.h
+++ b/src/video_core/engines/maxwell_dma.h
@@ -6,6 +6,7 @@
6 6
7#include <array> 7#include <array>
8#include <cstddef> 8#include <cstddef>
9#include <vector>
9#include "common/bit_field.h" 10#include "common/bit_field.h"
10#include "common/common_funcs.h" 11#include "common/common_funcs.h"
11#include "common/common_types.h" 12#include "common/common_types.h"
@@ -25,6 +26,11 @@ class RasterizerInterface;
25 26
26namespace Tegra::Engines { 27namespace Tegra::Engines {
27 28
29/**
30 * This Engine is known as GK104_Copy. Documentation can be found in:
31 * https://github.com/envytools/envytools/blob/master/rnndb/fifo/gk104_copy.xml
32 */
33
28class MaxwellDMA final { 34class MaxwellDMA final {
29public: 35public:
30 explicit MaxwellDMA(Core::System& system, VideoCore::RasterizerInterface& rasterizer, 36 explicit MaxwellDMA(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
@@ -63,6 +69,16 @@ public:
63 69
64 static_assert(sizeof(Parameters) == 24, "Parameters has wrong size"); 70 static_assert(sizeof(Parameters) == 24, "Parameters has wrong size");
65 71
72 enum class ComponentMode : u32 {
73 Src0 = 0,
74 Src1 = 1,
75 Src2 = 2,
76 Src3 = 3,
77 Const0 = 4,
78 Const1 = 5,
79 Zero = 6,
80 };
81
66 enum class CopyMode : u32 { 82 enum class CopyMode : u32 {
67 None = 0, 83 None = 0,
68 Unk1 = 1, 84 Unk1 = 1,
@@ -128,7 +144,26 @@ public:
128 u32 x_count; 144 u32 x_count;
129 u32 y_count; 145 u32 y_count;
130 146
131 INSERT_PADDING_WORDS(0xBB); 147 INSERT_PADDING_WORDS(0xB8);
148
149 u32 const0;
150 u32 const1;
151 union {
152 BitField<0, 4, ComponentMode> component0;
153 BitField<4, 4, ComponentMode> component1;
154 BitField<8, 4, ComponentMode> component2;
155 BitField<12, 4, ComponentMode> component3;
156 BitField<16, 2, u32> component_size;
157 BitField<20, 3, u32> src_num_components;
158 BitField<24, 3, u32> dst_num_components;
159
160 u32 SrcBytePerPixel() const {
161 return src_num_components.Value() * component_size.Value();
162 }
163 u32 DstBytePerPixel() const {
164 return dst_num_components.Value() * component_size.Value();
165 }
166 } swizzle_config;
132 167
133 Parameters dst_params; 168 Parameters dst_params;
134 169
@@ -149,6 +184,9 @@ private:
149 184
150 MemoryManager& memory_manager; 185 MemoryManager& memory_manager;
151 186
187 std::vector<u8> read_buffer;
188 std::vector<u8> write_buffer;
189
152 /// Performs the copy from the source buffer to the destination buffer as configured in the 190 /// Performs the copy from the source buffer to the destination buffer as configured in the
153 /// registers. 191 /// registers.
154 void HandleCopy(); 192 void HandleCopy();
@@ -165,6 +203,9 @@ ASSERT_REG_POSITION(src_pitch, 0x104);
165ASSERT_REG_POSITION(dst_pitch, 0x105); 203ASSERT_REG_POSITION(dst_pitch, 0x105);
166ASSERT_REG_POSITION(x_count, 0x106); 204ASSERT_REG_POSITION(x_count, 0x106);
167ASSERT_REG_POSITION(y_count, 0x107); 205ASSERT_REG_POSITION(y_count, 0x107);
206ASSERT_REG_POSITION(const0, 0x1C0);
207ASSERT_REG_POSITION(const1, 0x1C1);
208ASSERT_REG_POSITION(swizzle_config, 0x1C2);
168ASSERT_REG_POSITION(dst_params, 0x1C3); 209ASSERT_REG_POSITION(dst_params, 0x1C3);
169ASSERT_REG_POSITION(src_params, 0x1CA); 210ASSERT_REG_POSITION(src_params, 0x1CA);
170 211
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index e5b4eadea..e83f25fa1 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -98,6 +98,10 @@ union Attribute {
98 BitField<22, 2, u64> element; 98 BitField<22, 2, u64> element;
99 BitField<24, 6, Index> index; 99 BitField<24, 6, Index> index;
100 BitField<47, 3, AttributeSize> size; 100 BitField<47, 3, AttributeSize> size;
101
102 bool IsPhysical() const {
103 return element == 0 && static_cast<u64>(index.Value()) == 0;
104 }
101 } fmt20; 105 } fmt20;
102 106
103 union { 107 union {
@@ -499,6 +503,11 @@ enum class SystemVariable : u64 {
499 CircularQueueEntryAddressHigh = 0x63, 503 CircularQueueEntryAddressHigh = 0x63,
500}; 504};
501 505
506enum class PhysicalAttributeDirection : u64 {
507 Input = 0,
508 Output = 1,
509};
510
502union Instruction { 511union Instruction {
503 Instruction& operator=(const Instruction& instr) { 512 Instruction& operator=(const Instruction& instr) {
504 value = instr.value; 513 value = instr.value;
@@ -521,6 +530,11 @@ union Instruction {
521 BitField<48, 16, u64> opcode; 530 BitField<48, 16, u64> opcode;
522 531
523 union { 532 union {
533 BitField<8, 8, Register> gpr;
534 BitField<20, 24, s64> offset;
535 } gmem;
536
537 union {
524 BitField<20, 16, u64> imm20_16; 538 BitField<20, 16, u64> imm20_16;
525 BitField<20, 19, u64> imm20_19; 539 BitField<20, 19, u64> imm20_19;
526 BitField<20, 32, s64> imm20_32; 540 BitField<20, 32, s64> imm20_32;
@@ -587,6 +601,7 @@ union Instruction {
587 } alu; 601 } alu;
588 602
589 union { 603 union {
604 BitField<38, 1, u64> idx;
590 BitField<51, 1, u64> saturate; 605 BitField<51, 1, u64> saturate;
591 BitField<52, 2, IpaSampleMode> sample_mode; 606 BitField<52, 2, IpaSampleMode> sample_mode;
592 BitField<54, 2, IpaInterpMode> interp_mode; 607 BitField<54, 2, IpaInterpMode> interp_mode;
@@ -802,16 +817,25 @@ union Instruction {
802 union { 817 union {
803 BitField<48, 3, UniformType> type; 818 BitField<48, 3, UniformType> type;
804 BitField<46, 2, u64> cache_mode; 819 BitField<46, 2, u64> cache_mode;
805 BitField<20, 24, s64> immediate_offset;
806 } ldg; 820 } ldg;
807 821
808 union { 822 union {
809 BitField<48, 3, UniformType> type; 823 BitField<48, 3, UniformType> type;
810 BitField<46, 2, u64> cache_mode; 824 BitField<46, 2, u64> cache_mode;
811 BitField<20, 24, s64> immediate_offset;
812 } stg; 825 } stg;
813 826
814 union { 827 union {
828 BitField<32, 1, PhysicalAttributeDirection> direction;
829 BitField<47, 3, AttributeSize> size;
830 BitField<20, 11, u64> address;
831 } al2p;
832
833 union {
834 BitField<53, 3, UniformType> type;
835 BitField<52, 1, u64> extended;
836 } generic;
837
838 union {
815 BitField<0, 3, u64> pred0; 839 BitField<0, 3, u64> pred0;
816 BitField<3, 3, u64> pred3; 840 BitField<3, 3, u64> pred3;
817 BitField<7, 1, u64> abs_a; 841 BitField<7, 1, u64> abs_a;
@@ -1371,11 +1395,14 @@ public:
1371 LD_L, 1395 LD_L,
1372 LD_S, 1396 LD_S,
1373 LD_C, 1397 LD_C,
1398 LD, // Load from generic memory
1399 LDG, // Load from global memory
1374 ST_A, 1400 ST_A,
1375 ST_L, 1401 ST_L,
1376 ST_S, 1402 ST_S,
1377 LDG, // Load from global memory 1403 ST, // Store in generic memory
1378 STG, // Store in global memory 1404 STG, // Store in global memory
1405 AL2P, // Transforms attribute memory into physical memory
1379 TEX, 1406 TEX,
1380 TEX_B, // Texture Load Bindless 1407 TEX_B, // Texture Load Bindless
1381 TXQ, // Texture Query 1408 TXQ, // Texture Query
@@ -1641,11 +1668,14 @@ private:
1641 INST("1110111101001---", Id::LD_S, Type::Memory, "LD_S"), 1668 INST("1110111101001---", Id::LD_S, Type::Memory, "LD_S"),
1642 INST("1110111101000---", Id::LD_L, Type::Memory, "LD_L"), 1669 INST("1110111101000---", Id::LD_L, Type::Memory, "LD_L"),
1643 INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"), 1670 INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"),
1671 INST("100-------------", Id::LD, Type::Memory, "LD"),
1672 INST("1110111011010---", Id::LDG, Type::Memory, "LDG"),
1644 INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"), 1673 INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"),
1645 INST("1110111101011---", Id::ST_S, Type::Memory, "ST_S"), 1674 INST("1110111101011---", Id::ST_S, Type::Memory, "ST_S"),
1646 INST("1110111101010---", Id::ST_L, Type::Memory, "ST_L"), 1675 INST("1110111101010---", Id::ST_L, Type::Memory, "ST_L"),
1647 INST("1110111011010---", Id::LDG, Type::Memory, "LDG"), 1676 INST("101-------------", Id::ST, Type::Memory, "ST"),
1648 INST("1110111011011---", Id::STG, Type::Memory, "STG"), 1677 INST("1110111011011---", Id::STG, Type::Memory, "STG"),
1678 INST("1110111110100---", Id::AL2P, Type::Memory, "AL2P"),
1649 INST("110000----111---", Id::TEX, Type::Texture, "TEX"), 1679 INST("110000----111---", Id::TEX, Type::Texture, "TEX"),
1650 INST("1101111010111---", Id::TEX_B, Type::Texture, "TEX_B"), 1680 INST("1101111010111---", Id::TEX_B, Type::Texture, "TEX_B"),
1651 INST("1101111101001---", Id::TXQ, Type::Texture, "TXQ"), 1681 INST("1101111101001---", Id::TXQ, Type::Texture, "TXQ"),
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index 4461083ff..52706505b 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -35,9 +35,9 @@ GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer) : renderer{ren
35 dma_pusher = std::make_unique<Tegra::DmaPusher>(*this); 35 dma_pusher = std::make_unique<Tegra::DmaPusher>(*this);
36 maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, rasterizer, *memory_manager); 36 maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, rasterizer, *memory_manager);
37 fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager); 37 fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager);
38 kepler_compute = std::make_unique<Engines::KeplerCompute>(*memory_manager); 38 kepler_compute = std::make_unique<Engines::KeplerCompute>(system, rasterizer, *memory_manager);
39 maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, rasterizer, *memory_manager); 39 maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, rasterizer, *memory_manager);
40 kepler_memory = std::make_unique<Engines::KeplerMemory>(system, rasterizer, *memory_manager); 40 kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager);
41} 41}
42 42
43GPU::~GPU() = default; 43GPU::~GPU() = default;
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index c9a2077de..1e2ff46b0 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -44,7 +44,7 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p
44 renderer.Rasterizer().FlushRegion(data->addr, data->size); 44 renderer.Rasterizer().FlushRegion(data->addr, data->size);
45 } else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) { 45 } else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) {
46 renderer.Rasterizer().InvalidateRegion(data->addr, data->size); 46 renderer.Rasterizer().InvalidateRegion(data->addr, data->size);
47 } else if (const auto data = std::get_if<EndProcessingCommand>(&next.data)) { 47 } else if (std::holds_alternative<EndProcessingCommand>(next.data)) {
48 return; 48 return;
49 } else { 49 } else {
50 UNREACHABLE(); 50 UNREACHABLE();
@@ -118,7 +118,7 @@ void SynchState::WaitForSynchronization(u64 fence) {
118 // Wait for the GPU to be idle (all commands to be executed) 118 // Wait for the GPU to be idle (all commands to be executed)
119 { 119 {
120 MICROPROFILE_SCOPE(GPU_wait); 120 MICROPROFILE_SCOPE(GPU_wait);
121 std::unique_lock<std::mutex> lock{synchronization_mutex}; 121 std::unique_lock lock{synchronization_mutex};
122 synchronization_condition.wait(lock, [this, fence] { return signaled_fence >= fence; }); 122 synchronization_condition.wait(lock, [this, fence] { return signaled_fence >= fence; });
123 } 123 }
124} 124}
diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h
index cc14527c7..05a168a72 100644
--- a/src/video_core/gpu_thread.h
+++ b/src/video_core/gpu_thread.h
@@ -81,12 +81,6 @@ struct CommandDataContainer {
81 CommandDataContainer(CommandData&& data, u64 next_fence) 81 CommandDataContainer(CommandData&& data, u64 next_fence)
82 : data{std::move(data)}, fence{next_fence} {} 82 : data{std::move(data)}, fence{next_fence} {}
83 83
84 CommandDataContainer& operator=(const CommandDataContainer& t) {
85 data = std::move(t.data);
86 fence = t.fence;
87 return *this;
88 }
89
90 CommandData data; 84 CommandData data;
91 u64 fence{}; 85 u64 fence{};
92}; 86};
@@ -109,7 +103,7 @@ struct SynchState final {
109 103
110 void TrySynchronize() { 104 void TrySynchronize() {
111 if (IsSynchronized()) { 105 if (IsSynchronized()) {
112 std::lock_guard<std::mutex> lock{synchronization_mutex}; 106 std::lock_guard lock{synchronization_mutex};
113 synchronization_condition.notify_one(); 107 synchronization_condition.notify_one();
114 } 108 }
115 } 109 }
diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp
index 524d9ea5a..c766ed692 100644
--- a/src/video_core/macro_interpreter.cpp
+++ b/src/video_core/macro_interpreter.cpp
@@ -118,10 +118,12 @@ bool MacroInterpreter::Step(u32 offset, bool is_delay_slot) {
118 static_cast<u32>(opcode.operation.Value())); 118 static_cast<u32>(opcode.operation.Value()));
119 } 119 }
120 120
121 // An instruction with the Exit flag will not actually
122 // cause an exit if it's executed inside a delay slot.
123 // TODO(Blinkhawk): Reversed to always exit. The behavior explained above requires further
124 // testing on the MME code.
121 if (opcode.is_exit) { 125 if (opcode.is_exit) {
122 // Exit has a delay slot, execute the next instruction 126 // Exit has a delay slot, execute the next instruction
123 // Note: Executing an exit during a branch delay slot will cause the instruction at the
124 // branch target to be executed before exiting.
125 Step(offset, true); 127 Step(offset, true);
126 return false; 128 return false;
127 } 129 }
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 6c98c6701..5d8d126c1 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -25,6 +25,8 @@ MemoryManager::MemoryManager(VideoCore::RasterizerInterface& rasterizer) : raste
25 UpdatePageTableForVMA(initial_vma); 25 UpdatePageTableForVMA(initial_vma);
26} 26}
27 27
28MemoryManager::~MemoryManager() = default;
29
28GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) { 30GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) {
29 const u64 aligned_size{Common::AlignUp(size, page_size)}; 31 const u64 aligned_size{Common::AlignUp(size, page_size)};
30 const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)}; 32 const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)};
@@ -199,11 +201,11 @@ const u8* MemoryManager::GetPointer(GPUVAddr addr) const {
199 return {}; 201 return {};
200} 202}
201 203
202bool MemoryManager::IsBlockContinous(const GPUVAddr start, const std::size_t size) { 204bool MemoryManager::IsBlockContinuous(const GPUVAddr start, const std::size_t size) const {
203 const GPUVAddr end = start + size; 205 const GPUVAddr end = start + size;
204 const auto host_ptr_start = reinterpret_cast<std::uintptr_t>(GetPointer(start)); 206 const auto host_ptr_start = reinterpret_cast<std::uintptr_t>(GetPointer(start));
205 const auto host_ptr_end = reinterpret_cast<std::uintptr_t>(GetPointer(end)); 207 const auto host_ptr_end = reinterpret_cast<std::uintptr_t>(GetPointer(end));
206 const std::size_t range = static_cast<std::size_t>(host_ptr_end - host_ptr_start); 208 const auto range = static_cast<std::size_t>(host_ptr_end - host_ptr_start);
207 return range == size; 209 return range == size;
208} 210}
209 211
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index e4f0c4bd6..113f9d8f3 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -47,7 +47,8 @@ struct VirtualMemoryArea {
47 47
48class MemoryManager final { 48class MemoryManager final {
49public: 49public:
50 MemoryManager(VideoCore::RasterizerInterface& rasterizer); 50 explicit MemoryManager(VideoCore::RasterizerInterface& rasterizer);
51 ~MemoryManager();
51 52
52 GPUVAddr AllocateSpace(u64 size, u64 align); 53 GPUVAddr AllocateSpace(u64 size, u64 align);
53 GPUVAddr AllocateSpace(GPUVAddr addr, u64 size, u64 align); 54 GPUVAddr AllocateSpace(GPUVAddr addr, u64 size, u64 align);
@@ -65,18 +66,18 @@ public:
65 u8* GetPointer(GPUVAddr addr); 66 u8* GetPointer(GPUVAddr addr);
66 const u8* GetPointer(GPUVAddr addr) const; 67 const u8* GetPointer(GPUVAddr addr) const;
67 68
68 // Returns true if the block is continous in host memory, false otherwise 69 /// Returns true if the block is continuous in host memory, false otherwise
69 bool IsBlockContinous(const GPUVAddr start, const std::size_t size); 70 bool IsBlockContinuous(GPUVAddr start, std::size_t size) const;
70 71
71 /** 72 /**
72 * ReadBlock and WriteBlock are full read and write operations over virtual 73 * ReadBlock and WriteBlock are full read and write operations over virtual
73 * GPU Memory. It's important to use these when GPU memory may not be continous 74 * GPU Memory. It's important to use these when GPU memory may not be continuous
74 * in the Host Memory counterpart. Note: This functions cause Host GPU Memory 75 * in the Host Memory counterpart. Note: This functions cause Host GPU Memory
75 * Flushes and Invalidations, respectively to each operation. 76 * Flushes and Invalidations, respectively to each operation.
76 */ 77 */
77 void ReadBlock(GPUVAddr src_addr, void* dest_buffer, const std::size_t size) const; 78 void ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size) const;
78 void WriteBlock(GPUVAddr dest_addr, const void* src_buffer, const std::size_t size); 79 void WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size);
79 void CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, const std::size_t size); 80 void CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size);
80 81
81 /** 82 /**
82 * ReadBlockUnsafe and WriteBlockUnsafe are special versions of ReadBlock and 83 * ReadBlockUnsafe and WriteBlockUnsafe are special versions of ReadBlock and
@@ -88,9 +89,9 @@ public:
88 * WriteBlockUnsafe instead of WriteBlock since it shouldn't invalidate the texture 89 * WriteBlockUnsafe instead of WriteBlock since it shouldn't invalidate the texture
89 * being flushed. 90 * being flushed.
90 */ 91 */
91 void ReadBlockUnsafe(GPUVAddr src_addr, void* dest_buffer, const std::size_t size) const; 92 void ReadBlockUnsafe(GPUVAddr src_addr, void* dest_buffer, std::size_t size) const;
92 void WriteBlockUnsafe(GPUVAddr dest_addr, const void* src_buffer, const std::size_t size); 93 void WriteBlockUnsafe(GPUVAddr dest_addr, const void* src_buffer, std::size_t size);
93 void CopyBlockUnsafe(GPUVAddr dest_addr, GPUVAddr src_addr, const std::size_t size); 94 void CopyBlockUnsafe(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size);
94 95
95private: 96private:
96 using VMAMap = std::map<GPUVAddr, VirtualMemoryArea>; 97 using VMAMap = std::map<GPUVAddr, VirtualMemoryArea>;
@@ -111,10 +112,10 @@ private:
111 /** 112 /**
112 * Maps an unmanaged host memory pointer at a given address. 113 * Maps an unmanaged host memory pointer at a given address.
113 * 114 *
114 * @param target The guest address to start the mapping at. 115 * @param target The guest address to start the mapping at.
115 * @param memory The memory to be mapped. 116 * @param memory The memory to be mapped.
116 * @param size Size of the mapping. 117 * @param size Size of the mapping in bytes.
117 * @param state MemoryState tag to attach to the VMA. 118 * @param backing_addr The base address of the range to back this mapping.
118 */ 119 */
119 VMAHandle MapBackingMemory(GPUVAddr target, u8* memory, u64 size, VAddr backing_addr); 120 VMAHandle MapBackingMemory(GPUVAddr target, u8* memory, u64 size, VAddr backing_addr);
120 121
@@ -124,7 +125,7 @@ private:
124 /// Converts a VMAHandle to a mutable VMAIter. 125 /// Converts a VMAHandle to a mutable VMAIter.
125 VMAIter StripIterConstness(const VMAHandle& iter); 126 VMAIter StripIterConstness(const VMAHandle& iter);
126 127
127 /// Marks as the specfied VMA as allocated. 128 /// Marks as the specified VMA as allocated.
128 VMAIter Allocate(VMAIter vma); 129 VMAIter Allocate(VMAIter vma);
129 130
130 /** 131 /**
diff --git a/src/video_core/rasterizer_cache.h b/src/video_core/rasterizer_cache.h
index 291772186..0c4ea1494 100644
--- a/src/video_core/rasterizer_cache.h
+++ b/src/video_core/rasterizer_cache.h
@@ -37,9 +37,6 @@ public:
37 /// Gets the size of the shader in guest memory, required for cache management 37 /// Gets the size of the shader in guest memory, required for cache management
38 virtual std::size_t GetSizeInBytes() const = 0; 38 virtual std::size_t GetSizeInBytes() const = 0;
39 39
40 /// Wriets any cached resources back to memory
41 virtual void Flush() = 0;
42
43 /// Sets whether the cached object should be considered registered 40 /// Sets whether the cached object should be considered registered
44 void SetIsRegistered(bool registered) { 41 void SetIsRegistered(bool registered) {
45 is_registered = registered; 42 is_registered = registered;
@@ -147,8 +144,9 @@ protected:
147 144
148 object->SetIsRegistered(false); 145 object->SetIsRegistered(false);
149 rasterizer.UpdatePagesCachedCount(object->GetCpuAddr(), object->GetSizeInBytes(), -1); 146 rasterizer.UpdatePagesCachedCount(object->GetCpuAddr(), object->GetSizeInBytes(), -1);
147 const CacheAddr addr = object->GetCacheAddr();
150 interval_cache.subtract({GetInterval(object), ObjectSet{object}}); 148 interval_cache.subtract({GetInterval(object), ObjectSet{object}});
151 map_cache.erase(object->GetCacheAddr()); 149 map_cache.erase(addr);
152 } 150 }
153 151
154 /// Returns a ticks counter used for tracking when cached objects were last modified 152 /// Returns a ticks counter used for tracking when cached objects were last modified
@@ -158,6 +156,8 @@ protected:
158 return ++modified_ticks; 156 return ++modified_ticks;
159 } 157 }
160 158
159 virtual void FlushObjectInner(const T& object) = 0;
160
161 /// Flushes the specified object, updating appropriate cache state as needed 161 /// Flushes the specified object, updating appropriate cache state as needed
162 void FlushObject(const T& object) { 162 void FlushObject(const T& object) {
163 std::lock_guard lock{mutex}; 163 std::lock_guard lock{mutex};
@@ -165,7 +165,7 @@ protected:
165 if (!object->IsDirty()) { 165 if (!object->IsDirty()) {
166 return; 166 return;
167 } 167 }
168 object->Flush(); 168 FlushObjectInner(object);
169 object->MarkAsModified(false, *this); 169 object->MarkAsModified(false, *this);
170 } 170 }
171 171
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index fc33aa433..f9247a40e 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -42,9 +42,6 @@ public:
42 return alignment; 42 return alignment;
43 } 43 }
44 44
45 // We do not have to flush this cache as things in it are never modified by us.
46 void Flush() override {}
47
48private: 45private:
49 VAddr cpu_addr{}; 46 VAddr cpu_addr{};
50 std::size_t size{}; 47 std::size_t size{};
@@ -75,6 +72,9 @@ public:
75protected: 72protected:
76 void AlignBuffer(std::size_t alignment); 73 void AlignBuffer(std::size_t alignment);
77 74
75 // We do not have to flush this cache as things in it are never modified by us.
76 void FlushObjectInner(const std::shared_ptr<CachedBufferEntry>& object) override {}
77
78private: 78private:
79 OGLStreamBuffer stream_buffer; 79 OGLStreamBuffer stream_buffer;
80 80
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index b6d9e0ddb..1d1581f49 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -21,11 +21,21 @@ T GetInteger(GLenum pname) {
21 21
22Device::Device() { 22Device::Device() {
23 uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT); 23 uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);
24 max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS);
25 max_varyings = GetInteger<u32>(GL_MAX_VARYING_VECTORS);
24 has_variable_aoffi = TestVariableAoffi(); 26 has_variable_aoffi = TestVariableAoffi();
25} 27}
26 28
29Device::Device(std::nullptr_t) {
30 uniform_buffer_alignment = 0;
31 max_vertex_attributes = 16;
32 max_varyings = 15;
33 has_variable_aoffi = true;
34}
35
27bool Device::TestVariableAoffi() { 36bool Device::TestVariableAoffi() {
28 const GLchar* AOFFI_TEST = R"(#version 430 core 37 const GLchar* AOFFI_TEST = R"(#version 430 core
38// This is a unit test, please ignore me on apitrace bug reports.
29uniform sampler2D tex; 39uniform sampler2D tex;
30uniform ivec2 variable_offset; 40uniform ivec2 variable_offset;
31void main() { 41void main() {
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h
index 78ff5ee58..de8490682 100644
--- a/src/video_core/renderer_opengl/gl_device.h
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -5,17 +5,27 @@
5#pragma once 5#pragma once
6 6
7#include <cstddef> 7#include <cstddef>
8#include "common/common_types.h"
8 9
9namespace OpenGL { 10namespace OpenGL {
10 11
11class Device { 12class Device {
12public: 13public:
13 Device(); 14 explicit Device();
15 explicit Device(std::nullptr_t);
14 16
15 std::size_t GetUniformBufferAlignment() const { 17 std::size_t GetUniformBufferAlignment() const {
16 return uniform_buffer_alignment; 18 return uniform_buffer_alignment;
17 } 19 }
18 20
21 u32 GetMaxVertexAttributes() const {
22 return max_vertex_attributes;
23 }
24
25 u32 GetMaxVaryings() const {
26 return max_varyings;
27 }
28
19 bool HasVariableAoffi() const { 29 bool HasVariableAoffi() const {
20 return has_variable_aoffi; 30 return has_variable_aoffi;
21 } 31 }
@@ -24,6 +34,8 @@ private:
24 static bool TestVariableAoffi(); 34 static bool TestVariableAoffi();
25 35
26 std::size_t uniform_buffer_alignment{}; 36 std::size_t uniform_buffer_alignment{};
37 u32 max_vertex_attributes{};
38 u32 max_varyings{};
27 bool has_variable_aoffi{}; 39 bool has_variable_aoffi{};
28}; 40};
29 41
diff --git a/src/video_core/renderer_opengl/gl_global_cache.h b/src/video_core/renderer_opengl/gl_global_cache.h
index 196e6e278..2d467a240 100644
--- a/src/video_core/renderer_opengl/gl_global_cache.h
+++ b/src/video_core/renderer_opengl/gl_global_cache.h
@@ -46,7 +46,7 @@ public:
46 /// Reloads the global region from guest memory 46 /// Reloads the global region from guest memory
47 void Reload(u32 size_); 47 void Reload(u32 size_);
48 48
49 void Flush() override; 49 void Flush();
50 50
51private: 51private:
52 VAddr cpu_addr{}; 52 VAddr cpu_addr{};
@@ -65,6 +65,11 @@ public:
65 GlobalRegion GetGlobalRegion(const GLShader::GlobalMemoryEntry& descriptor, 65 GlobalRegion GetGlobalRegion(const GLShader::GlobalMemoryEntry& descriptor,
66 Tegra::Engines::Maxwell3D::Regs::ShaderStage stage); 66 Tegra::Engines::Maxwell3D::Regs::ShaderStage stage);
67 67
68protected:
69 void FlushObjectInner(const GlobalRegion& object) override {
70 object->Flush();
71 }
72
68private: 73private:
69 GlobalRegion TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const; 74 GlobalRegion TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const;
70 GlobalRegion GetUncachedGlobalRegion(GPUVAddr addr, u8* host_ptr, u32 size); 75 GlobalRegion GetUncachedGlobalRegion(GPUVAddr addr, u8* host_ptr, u32 size);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 9a088a503..f9b6dfeea 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -98,9 +98,11 @@ struct FramebufferCacheKey {
98 } 98 }
99}; 99};
100 100
101RasterizerOpenGL::RasterizerOpenGL(Core::System& system, ScreenInfo& info) 101RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
102 : res_cache{*this}, shader_cache{*this, system, device}, global_cache{*this}, system{system}, 102 ScreenInfo& info)
103 screen_info{info}, buffer_cache(*this, STREAM_BUFFER_SIZE) { 103 : res_cache{*this}, shader_cache{*this, system, emu_window, device},
104 global_cache{*this}, system{system}, screen_info{info},
105 buffer_cache(*this, STREAM_BUFFER_SIZE) {
104 OpenGLState::ApplyDefaultState(); 106 OpenGLState::ApplyDefaultState();
105 107
106 shader_program_manager = std::make_unique<GLShader::ProgramManager>(); 108 shader_program_manager = std::make_unique<GLShader::ProgramManager>();
@@ -261,8 +263,8 @@ DrawParameters RasterizerOpenGL::SetupDraw() {
261 // MakeQuadArray always generates u32 indexes 263 // MakeQuadArray always generates u32 indexes
262 params.index_format = GL_UNSIGNED_INT; 264 params.index_format = GL_UNSIGNED_INT;
263 params.count = (regs.vertex_buffer.count / 4) * 6; 265 params.count = (regs.vertex_buffer.count / 4) * 6;
264 params.index_buffer_offset = 266 params.index_buffer_offset = primitive_assembler.MakeQuadArray(
265 primitive_assembler.MakeQuadArray(regs.vertex_buffer.first, params.count); 267 regs.vertex_buffer.first, regs.vertex_buffer.count);
266 } 268 }
267 return params; 269 return params;
268 } 270 }
@@ -305,6 +307,8 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
305 case Maxwell::ShaderProgram::Geometry: 307 case Maxwell::ShaderProgram::Geometry:
306 shader_program_manager->UseTrivialGeometryShader(); 308 shader_program_manager->UseTrivialGeometryShader();
307 break; 309 break;
310 default:
311 break;
308 } 312 }
309 continue; 313 continue;
310 } 314 }
@@ -920,8 +924,8 @@ void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
920 viewport.y = viewport_rect.bottom; 924 viewport.y = viewport_rect.bottom;
921 viewport.width = viewport_rect.GetWidth(); 925 viewport.width = viewport_rect.GetWidth();
922 viewport.height = viewport_rect.GetHeight(); 926 viewport.height = viewport_rect.GetHeight();
923 viewport.depth_range_far = regs.viewports[i].depth_range_far; 927 viewport.depth_range_far = src.depth_range_far;
924 viewport.depth_range_near = regs.viewports[i].depth_range_near; 928 viewport.depth_range_near = src.depth_range_near;
925 } 929 }
926 state.depth_clamp.far_plane = regs.view_volume_clip_control.depth_clamp_far != 0; 930 state.depth_clamp.far_plane = regs.view_volume_clip_control.depth_clamp_far != 0;
927 state.depth_clamp.near_plane = regs.view_volume_clip_control.depth_clamp_near != 0; 931 state.depth_clamp.near_plane = regs.view_volume_clip_control.depth_clamp_near != 0;
@@ -1133,7 +1137,9 @@ void RasterizerOpenGL::SyncTransformFeedback() {
1133 1137
1134void RasterizerOpenGL::SyncPointState() { 1138void RasterizerOpenGL::SyncPointState() {
1135 const auto& regs = system.GPU().Maxwell3D().regs; 1139 const auto& regs = system.GPU().Maxwell3D().regs;
1136 state.point.size = regs.point_size; 1140 // Limit the point size to 1 since nouveau sometimes sets a point size of 0 (and that's invalid
1141 // in OpenGL).
1142 state.point.size = std::max(1.0f, regs.point_size);
1137} 1143}
1138 1144
1139void RasterizerOpenGL::SyncPolygonOffset() { 1145void RasterizerOpenGL::SyncPolygonOffset() {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 71b9c5ead..d78094138 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -48,7 +48,8 @@ struct FramebufferCacheKey;
48 48
49class RasterizerOpenGL : public VideoCore::RasterizerInterface { 49class RasterizerOpenGL : public VideoCore::RasterizerInterface {
50public: 50public:
51 explicit RasterizerOpenGL(Core::System& system, ScreenInfo& info); 51 explicit RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
52 ScreenInfo& info);
52 ~RasterizerOpenGL() override; 53 ~RasterizerOpenGL() override;
53 54
54 void DrawArrays() override; 55 void DrawArrays() override;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 5a25f5b37..a7681902e 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -628,9 +628,11 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
628} 628}
629 629
630MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 192, 64)); 630MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 192, 64));
631void CachedSurface::LoadGLBuffer() { 631void CachedSurface::LoadGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem) {
632 MICROPROFILE_SCOPE(OpenGL_SurfaceLoad); 632 MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
633 gl_buffer.resize(params.max_mip_level); 633 auto& gl_buffer = res_cache_tmp_mem.gl_buffer;
634 if (gl_buffer.size() < params.max_mip_level)
635 gl_buffer.resize(params.max_mip_level);
634 for (u32 i = 0; i < params.max_mip_level; i++) 636 for (u32 i = 0; i < params.max_mip_level; i++)
635 gl_buffer[i].resize(params.GetMipmapSizeGL(i)); 637 gl_buffer[i].resize(params.GetMipmapSizeGL(i));
636 if (params.is_tiled) { 638 if (params.is_tiled) {
@@ -671,13 +673,13 @@ void CachedSurface::LoadGLBuffer() {
671} 673}
672 674
673MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); 675MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
674void CachedSurface::FlushGLBuffer() { 676void CachedSurface::FlushGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem) {
675 MICROPROFILE_SCOPE(OpenGL_SurfaceFlush); 677 MICROPROFILE_SCOPE(OpenGL_SurfaceFlush);
676 678
677 ASSERT_MSG(!IsPixelFormatASTC(params.pixel_format), "Unimplemented"); 679 ASSERT_MSG(!IsPixelFormatASTC(params.pixel_format), "Unimplemented");
678 680
681 auto& gl_buffer = res_cache_tmp_mem.gl_buffer;
679 // OpenGL temporary buffer needs to be big enough to store raw texture size 682 // OpenGL temporary buffer needs to be big enough to store raw texture size
680 gl_buffer.resize(1);
681 gl_buffer[0].resize(GetSizeInBytes()); 683 gl_buffer[0].resize(GetSizeInBytes());
682 684
683 const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type); 685 const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
@@ -713,10 +715,12 @@ void CachedSurface::FlushGLBuffer() {
713 } 715 }
714} 716}
715 717
716void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle, 718void CachedSurface::UploadGLMipmapTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, u32 mip_map,
717 GLuint draw_fb_handle) { 719 GLuint read_fb_handle, GLuint draw_fb_handle) {
718 const auto& rect{params.GetRect(mip_map)}; 720 const auto& rect{params.GetRect(mip_map)};
719 721
722 auto& gl_buffer = res_cache_tmp_mem.gl_buffer;
723
720 // Load data from memory to the surface 724 // Load data from memory to the surface
721 const auto x0 = static_cast<GLint>(rect.left); 725 const auto x0 = static_cast<GLint>(rect.left);
722 const auto y0 = static_cast<GLint>(rect.bottom); 726 const auto y0 = static_cast<GLint>(rect.bottom);
@@ -801,7 +805,6 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
801 tuple.type, &gl_buffer[mip_map][buffer_offset]); 805 tuple.type, &gl_buffer[mip_map][buffer_offset]);
802 break; 806 break;
803 case SurfaceTarget::TextureCubemap: { 807 case SurfaceTarget::TextureCubemap: {
804 std::size_t start = buffer_offset;
805 for (std::size_t face = 0; face < params.depth; ++face) { 808 for (std::size_t face = 0; face < params.depth; ++face) {
806 glTextureSubImage3D(texture.handle, mip_map, x0, y0, static_cast<GLint>(face), 809 glTextureSubImage3D(texture.handle, mip_map, x0, y0, static_cast<GLint>(face),
807 static_cast<GLsizei>(rect.GetWidth()), 810 static_cast<GLsizei>(rect.GetWidth()),
@@ -845,11 +848,12 @@ void CachedSurface::EnsureTextureDiscrepantView() {
845} 848}
846 849
847MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64)); 850MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64));
848void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle) { 851void CachedSurface::UploadGLTexture(RasterizerTemporaryMemory& res_cache_tmp_mem,
852 GLuint read_fb_handle, GLuint draw_fb_handle) {
849 MICROPROFILE_SCOPE(OpenGL_TextureUL); 853 MICROPROFILE_SCOPE(OpenGL_TextureUL);
850 854
851 for (u32 i = 0; i < params.max_mip_level; i++) 855 for (u32 i = 0; i < params.max_mip_level; i++)
852 UploadGLMipmapTexture(i, read_fb_handle, draw_fb_handle); 856 UploadGLMipmapTexture(res_cache_tmp_mem, i, read_fb_handle, draw_fb_handle);
853} 857}
854 858
855void CachedSurface::UpdateSwizzle(Tegra::Texture::SwizzleSource swizzle_x, 859void CachedSurface::UpdateSwizzle(Tegra::Texture::SwizzleSource swizzle_x,
@@ -929,8 +933,8 @@ Surface RasterizerCacheOpenGL::GetColorBufferSurface(std::size_t index, bool pre
929} 933}
930 934
931void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) { 935void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) {
932 surface->LoadGLBuffer(); 936 surface->LoadGLBuffer(temporal_memory);
933 surface->UploadGLTexture(read_framebuffer.handle, draw_framebuffer.handle); 937 surface->UploadGLTexture(temporal_memory, read_framebuffer.handle, draw_framebuffer.handle);
934 surface->MarkAsModified(false, *this); 938 surface->MarkAsModified(false, *this);
935 surface->MarkForReload(false); 939 surface->MarkForReload(false);
936} 940}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index db280dbb3..6263ef3e7 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -355,6 +355,12 @@ namespace OpenGL {
355 355
356class RasterizerOpenGL; 356class RasterizerOpenGL;
357 357
358// This is used to store temporary big buffers,
359// instead of creating/destroying all the time
360struct RasterizerTemporaryMemory {
361 std::vector<std::vector<u8>> gl_buffer;
362};
363
358class CachedSurface final : public RasterizerCacheObject { 364class CachedSurface final : public RasterizerCacheObject {
359public: 365public:
360 explicit CachedSurface(const SurfaceParams& params); 366 explicit CachedSurface(const SurfaceParams& params);
@@ -371,10 +377,6 @@ public:
371 return memory_size; 377 return memory_size;
372 } 378 }
373 379
374 void Flush() override {
375 FlushGLBuffer();
376 }
377
378 const OGLTexture& Texture() const { 380 const OGLTexture& Texture() const {
379 return texture; 381 return texture;
380 } 382 }
@@ -397,11 +399,12 @@ public:
397 } 399 }
398 400
399 // Read/Write data in Switch memory to/from gl_buffer 401 // Read/Write data in Switch memory to/from gl_buffer
400 void LoadGLBuffer(); 402 void LoadGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem);
401 void FlushGLBuffer(); 403 void FlushGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem);
402 404
403 // Upload data in gl_buffer to this surface's texture 405 // Upload data in gl_buffer to this surface's texture
404 void UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle); 406 void UploadGLTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, GLuint read_fb_handle,
407 GLuint draw_fb_handle);
405 408
406 void UpdateSwizzle(Tegra::Texture::SwizzleSource swizzle_x, 409 void UpdateSwizzle(Tegra::Texture::SwizzleSource swizzle_x,
407 Tegra::Texture::SwizzleSource swizzle_y, 410 Tegra::Texture::SwizzleSource swizzle_y,
@@ -429,13 +432,13 @@ public:
429 } 432 }
430 433
431private: 434private:
432 void UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle, GLuint draw_fb_handle); 435 void UploadGLMipmapTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, u32 mip_map,
436 GLuint read_fb_handle, GLuint draw_fb_handle);
433 437
434 void EnsureTextureDiscrepantView(); 438 void EnsureTextureDiscrepantView();
435 439
436 OGLTexture texture; 440 OGLTexture texture;
437 OGLTexture discrepant_view; 441 OGLTexture discrepant_view;
438 std::vector<std::vector<u8>> gl_buffer;
439 SurfaceParams params{}; 442 SurfaceParams params{};
440 GLenum gl_target{}; 443 GLenum gl_target{};
441 GLenum gl_internal_format{}; 444 GLenum gl_internal_format{};
@@ -473,6 +476,11 @@ public:
473 void SignalPreDrawCall(); 476 void SignalPreDrawCall();
474 void SignalPostDrawCall(); 477 void SignalPostDrawCall();
475 478
479protected:
480 void FlushObjectInner(const Surface& object) override {
481 object->FlushGLBuffer(temporal_memory);
482 }
483
476private: 484private:
477 void LoadSurface(const Surface& surface); 485 void LoadSurface(const Surface& surface);
478 Surface GetSurface(const SurfaceParams& params, bool preserve_contents = true); 486 Surface GetSurface(const SurfaceParams& params, bool preserve_contents = true);
@@ -519,6 +527,8 @@ private:
519 std::array<Surface, Maxwell::NumRenderTargets> current_color_buffers; 527 std::array<Surface, Maxwell::NumRenderTargets> current_color_buffers;
520 Surface last_depth_buffer; 528 Surface last_depth_buffer;
521 529
530 RasterizerTemporaryMemory temporal_memory;
531
522 using SurfaceIntervalCache = boost::icl::interval_map<CacheAddr, Surface>; 532 using SurfaceIntervalCache = boost::icl::interval_map<CacheAddr, Surface>;
523 using SurfaceInterval = typename SurfaceIntervalCache::interval_type; 533 using SurfaceInterval = typename SurfaceIntervalCache::interval_type;
524 534
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 2a81b1169..d66252224 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -2,10 +2,14 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <mutex>
6#include <thread>
5#include <boost/functional/hash.hpp> 7#include <boost/functional/hash.hpp>
6#include "common/assert.h" 8#include "common/assert.h"
7#include "common/hash.h" 9#include "common/hash.h"
10#include "common/scope_exit.h"
8#include "core/core.h" 11#include "core/core.h"
12#include "core/frontend/emu_window.h"
9#include "video_core/engines/maxwell_3d.h" 13#include "video_core/engines/maxwell_3d.h"
10#include "video_core/memory_manager.h" 14#include "video_core/memory_manager.h"
11#include "video_core/renderer_opengl/gl_rasterizer.h" 15#include "video_core/renderer_opengl/gl_rasterizer.h"
@@ -166,7 +170,8 @@ GLShader::ProgramResult CreateProgram(const Device& device, Maxwell::ShaderProgr
166CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEntries& entries, 170CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEntries& entries,
167 Maxwell::ShaderProgram program_type, BaseBindings base_bindings, 171 Maxwell::ShaderProgram program_type, BaseBindings base_bindings,
168 GLenum primitive_mode, bool hint_retrievable = false) { 172 GLenum primitive_mode, bool hint_retrievable = false) {
169 std::string source = "#version 430 core\n"; 173 std::string source = "#version 430 core\n"
174 "#extension GL_ARB_separate_shader_objects : enable\n\n";
170 source += fmt::format("#define EMULATION_UBO_BINDING {}\n", base_bindings.cbuf++); 175 source += fmt::format("#define EMULATION_UBO_BINDING {}\n", base_bindings.cbuf++);
171 176
172 for (const auto& cbuf : entries.const_buffers) { 177 for (const auto& cbuf : entries.const_buffers) {
@@ -344,8 +349,8 @@ ShaderDiskCacheUsage CachedShader::GetUsage(GLenum primitive_mode,
344} 349}
345 350
346ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system, 351ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
347 const Device& device) 352 Core::Frontend::EmuWindow& emu_window, const Device& device)
348 : RasterizerCache{rasterizer}, disk_cache{system}, device{device} {} 353 : RasterizerCache{rasterizer}, emu_window{emu_window}, device{device}, disk_cache{system} {}
349 354
350void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading, 355void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
351 const VideoCore::DiskResourceLoadCallback& callback) { 356 const VideoCore::DiskResourceLoadCallback& callback) {
@@ -353,60 +358,115 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
353 if (!transferable) { 358 if (!transferable) {
354 return; 359 return;
355 } 360 }
356 const auto [raws, usages] = *transferable; 361 const auto [raws, shader_usages] = *transferable;
357 362
358 auto [decompiled, dumps] = disk_cache.LoadPrecompiled(); 363 auto [decompiled, dumps] = disk_cache.LoadPrecompiled();
359 364
360 const auto supported_formats{GetSupportedFormats()}; 365 const auto supported_formats{GetSupportedFormats()};
361 const auto unspecialized{ 366 const auto unspecialized_shaders{
362 GenerateUnspecializedShaders(stop_loading, callback, raws, decompiled)}; 367 GenerateUnspecializedShaders(stop_loading, callback, raws, decompiled)};
363 if (stop_loading) 368 if (stop_loading) {
364 return; 369 return;
370 }
365 371
366 // Build shaders 372 // Track if precompiled cache was altered during loading to know if we have to serialize the
367 if (callback) 373 // virtual precompiled cache file back to the hard drive
368 callback(VideoCore::LoadCallbackStage::Build, 0, usages.size()); 374 bool precompiled_cache_altered = false;
369 for (std::size_t i = 0; i < usages.size(); ++i) {
370 if (stop_loading)
371 return;
372 375
373 const auto& usage{usages[i]}; 376 // Inform the frontend about shader build initialization
374 LOG_INFO(Render_OpenGL, "Building shader {:016x} ({} of {})", usage.unique_identifier, 377 if (callback) {
375 i + 1, usages.size()); 378 callback(VideoCore::LoadCallbackStage::Build, 0, shader_usages.size());
379 }
376 380
377 const auto& unspec{unspecialized.at(usage.unique_identifier)}; 381 std::mutex mutex;
378 const auto dump_it = dumps.find(usage); 382 std::size_t built_shaders = 0; // It doesn't have be atomic since it's used behind a mutex
383 std::atomic_bool compilation_failed = false;
379 384
380 CachedProgram shader; 385 const auto Worker = [&](Core::Frontend::GraphicsContext* context, std::size_t begin,
381 if (dump_it != dumps.end()) { 386 std::size_t end, const std::vector<ShaderDiskCacheUsage>& shader_usages,
382 // If the shader is dumped, attempt to load it with 387 const ShaderDumpsMap& dumps) {
383 shader = GeneratePrecompiledProgram(dump_it->second, supported_formats); 388 context->MakeCurrent();
389 SCOPE_EXIT({ return context->DoneCurrent(); });
390
391 for (std::size_t i = begin; i < end; ++i) {
392 if (stop_loading || compilation_failed) {
393 return;
394 }
395 const auto& usage{shader_usages[i]};
396 LOG_INFO(Render_OpenGL, "Building shader {:016x} (index {} of {})",
397 usage.unique_identifier, i, shader_usages.size());
398
399 const auto& unspecialized{unspecialized_shaders.at(usage.unique_identifier)};
400 const auto dump{dumps.find(usage)};
401
402 CachedProgram shader;
403 if (dump != dumps.end()) {
404 // If the shader is dumped, attempt to load it with
405 shader = GeneratePrecompiledProgram(dump->second, supported_formats);
406 if (!shader) {
407 compilation_failed = true;
408 return;
409 }
410 }
384 if (!shader) { 411 if (!shader) {
385 // Invalidate the precompiled cache if a shader dumped shader was rejected 412 shader = SpecializeShader(unspecialized.code, unspecialized.entries,
386 disk_cache.InvalidatePrecompiled(); 413 unspecialized.program_type, usage.bindings,
387 dumps.clear(); 414 usage.primitive, true);
388 } 415 }
416
417 std::scoped_lock lock(mutex);
418 if (callback) {
419 callback(VideoCore::LoadCallbackStage::Build, ++built_shaders,
420 shader_usages.size());
421 }
422
423 precompiled_programs.emplace(usage, std::move(shader));
389 } 424 }
390 if (!shader) { 425 };
391 shader = SpecializeShader(unspec.code, unspec.entries, unspec.program_type, 426
392 usage.bindings, usage.primitive, true); 427 const auto num_workers{static_cast<std::size_t>(std::thread::hardware_concurrency() + 1)};
393 } 428 const std::size_t bucket_size{shader_usages.size() / num_workers};
394 precompiled_programs.insert({usage, std::move(shader)}); 429 std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> contexts(num_workers);
430 std::vector<std::thread> threads(num_workers);
431 for (std::size_t i = 0; i < num_workers; ++i) {
432 const bool is_last_worker = i + 1 == num_workers;
433 const std::size_t start{bucket_size * i};
434 const std::size_t end{is_last_worker ? shader_usages.size() : start + bucket_size};
435
436 // On some platforms the shared context has to be created from the GUI thread
437 contexts[i] = emu_window.CreateSharedContext();
438 threads[i] = std::thread(Worker, contexts[i].get(), start, end, shader_usages, dumps);
439 }
440 for (auto& thread : threads) {
441 thread.join();
442 }
395 443
396 if (callback) 444 if (compilation_failed) {
397 callback(VideoCore::LoadCallbackStage::Build, i + 1, usages.size()); 445 // Invalidate the precompiled cache if a shader dumped shader was rejected
446 disk_cache.InvalidatePrecompiled();
447 dumps.clear();
448 precompiled_cache_altered = true;
449 return;
450 }
451 if (stop_loading) {
452 return;
398 } 453 }
399 454
400 // TODO(Rodrigo): Do state tracking for transferable shaders and do a dummy draw before 455 // TODO(Rodrigo): Do state tracking for transferable shaders and do a dummy draw before
401 // precompiling them 456 // precompiling them
402 457
403 for (std::size_t i = 0; i < usages.size(); ++i) { 458 for (std::size_t i = 0; i < shader_usages.size(); ++i) {
404 const auto& usage{usages[i]}; 459 const auto& usage{shader_usages[i]};
405 if (dumps.find(usage) == dumps.end()) { 460 if (dumps.find(usage) == dumps.end()) {
406 const auto& program = precompiled_programs.at(usage); 461 const auto& program{precompiled_programs.at(usage)};
407 disk_cache.SaveDump(usage, program->handle); 462 disk_cache.SaveDump(usage, program->handle);
463 precompiled_cache_altered = true;
408 } 464 }
409 } 465 }
466
467 if (precompiled_cache_altered) {
468 disk_cache.SaveVirtualPrecompiledFile();
469 }
410} 470}
411 471
412CachedProgram ShaderCacheOpenGL::GeneratePrecompiledProgram( 472CachedProgram ShaderCacheOpenGL::GeneratePrecompiledProgram(
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index a332087f8..64e5a5594 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -22,7 +22,11 @@
22 22
23namespace Core { 23namespace Core {
24class System; 24class System;
25} // namespace Core 25}
26
27namespace Core::Frontend {
28class EmuWindow;
29}
26 30
27namespace OpenGL { 31namespace OpenGL {
28 32
@@ -57,9 +61,6 @@ public:
57 return shader_length; 61 return shader_length;
58 } 62 }
59 63
60 // We do not have to flush this cache as things in it are never modified by us.
61 void Flush() override {}
62
63 /// Gets the shader entries for the shader 64 /// Gets the shader entries for the shader
64 const GLShader::ShaderEntries& GetShaderEntries() const { 65 const GLShader::ShaderEntries& GetShaderEntries() const {
65 return entries; 66 return entries;
@@ -114,7 +115,7 @@ private:
114class ShaderCacheOpenGL final : public RasterizerCache<Shader> { 115class ShaderCacheOpenGL final : public RasterizerCache<Shader> {
115public: 116public:
116 explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system, 117 explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
117 const Device& device); 118 Core::Frontend::EmuWindow& emu_window, const Device& device);
118 119
119 /// Loads disk cache for the current game 120 /// Loads disk cache for the current game
120 void LoadDiskCache(const std::atomic_bool& stop_loading, 121 void LoadDiskCache(const std::atomic_bool& stop_loading,
@@ -123,6 +124,10 @@ public:
123 /// Gets the current specified shader stage program 124 /// Gets the current specified shader stage program
124 Shader GetStageProgram(Maxwell::ShaderProgram program); 125 Shader GetStageProgram(Maxwell::ShaderProgram program);
125 126
127protected:
128 // We do not have to flush this cache as things in it are never modified by us.
129 void FlushObjectInner(const Shader& object) override {}
130
126private: 131private:
127 std::unordered_map<u64, UnspecializedShader> GenerateUnspecializedShaders( 132 std::unordered_map<u64, UnspecializedShader> GenerateUnspecializedShaders(
128 const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback, 133 const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback,
@@ -132,13 +137,13 @@ private:
132 CachedProgram GeneratePrecompiledProgram(const ShaderDiskCacheDump& dump, 137 CachedProgram GeneratePrecompiledProgram(const ShaderDiskCacheDump& dump,
133 const std::set<GLenum>& supported_formats); 138 const std::set<GLenum>& supported_formats);
134 139
140 Core::Frontend::EmuWindow& emu_window;
135 const Device& device; 141 const Device& device;
136
137 std::array<Shader, Maxwell::MaxShaderProgram> last_shaders;
138
139 ShaderDiskCacheOpenGL disk_cache; 142 ShaderDiskCacheOpenGL disk_cache;
143
140 PrecompiledShaders precompiled_shaders; 144 PrecompiledShaders precompiled_shaders;
141 PrecompiledPrograms precompiled_programs; 145 PrecompiledPrograms precompiled_programs;
146 std::array<Shader, Maxwell::MaxShaderProgram> last_shaders;
142}; 147};
143 148
144} // namespace OpenGL 149} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index ef1a1995f..e9f8d40db 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -31,6 +31,8 @@ using Tegra::Shader::IpaInterpMode;
31using Tegra::Shader::IpaMode; 31using Tegra::Shader::IpaMode;
32using Tegra::Shader::IpaSampleMode; 32using Tegra::Shader::IpaSampleMode;
33using Tegra::Shader::Register; 33using Tegra::Shader::Register;
34
35using namespace std::string_literals;
34using namespace VideoCommon::Shader; 36using namespace VideoCommon::Shader;
35 37
36using Maxwell = Tegra::Engines::Maxwell3D::Regs; 38using Maxwell = Tegra::Engines::Maxwell3D::Regs;
@@ -57,15 +59,14 @@ public:
57 shader_source += text; 59 shader_source += text;
58 } 60 }
59 61
60 void AddLine(std::string_view text) { 62 // Forwards all arguments directly to libfmt.
61 AddExpression(text); 63 // Note that all formatting requirements for fmt must be
62 AddNewLine(); 64 // obeyed when using this function. (e.g. {{ must be used
63 } 65 // printing the character '{' is desirable. Ditto for }} and '}',
64 66 // etc).
65 void AddLine(char character) { 67 template <typename... Args>
66 DEBUG_ASSERT(scope >= 0); 68 void AddLine(std::string_view text, Args&&... args) {
67 AppendIndentation(); 69 AddExpression(fmt::format(text, std::forward<Args>(args)...));
68 shader_source += character;
69 AddNewLine(); 70 AddNewLine();
70 } 71 }
71 72
@@ -75,9 +76,7 @@ public:
75 } 76 }
76 77
77 std::string GenerateTemporary() { 78 std::string GenerateTemporary() {
78 std::string temporary = "tmp"; 79 return fmt::format("tmp{}", temporary_index++);
79 temporary += std::to_string(temporary_index++);
80 return temporary;
81 } 80 }
82 81
83 std::string GetResult() { 82 std::string GetResult() {
@@ -96,11 +95,9 @@ private:
96}; 95};
97 96
98/// Generates code to use for a swizzle operation. 97/// Generates code to use for a swizzle operation.
99std::string GetSwizzle(u32 elem) { 98constexpr const char* GetSwizzle(u32 element) {
100 ASSERT(elem <= 3); 99 constexpr std::array<const char*, 4> swizzle = {".x", ".y", ".z", ".w"};
101 std::string swizzle = "."; 100 return swizzle.at(element);
102 swizzle += "xyzw"[elem];
103 return swizzle;
104} 101}
105 102
106/// Translate topology 103/// Translate topology
@@ -134,6 +131,19 @@ bool IsPrecise(Node node) {
134 return false; 131 return false;
135} 132}
136 133
134constexpr bool IsGenericAttribute(Attribute::Index index) {
135 return index >= Attribute::Index::Attribute_0 && index <= Attribute::Index::Attribute_31;
136}
137
138constexpr Attribute::Index ToGenericAttribute(u32 value) {
139 return static_cast<Attribute::Index>(value + static_cast<u32>(Attribute::Index::Attribute_0));
140}
141
142u32 GetGenericAttributeIndex(Attribute::Index index) {
143 ASSERT(IsGenericAttribute(index));
144 return static_cast<u32>(index) - static_cast<u32>(Attribute::Index::Attribute_0);
145}
146
137class GLSLDecompiler final { 147class GLSLDecompiler final {
138public: 148public:
139 explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ShaderStage stage, 149 explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ShaderStage stage,
@@ -152,42 +162,43 @@ public:
152 DeclareConstantBuffers(); 162 DeclareConstantBuffers();
153 DeclareGlobalMemory(); 163 DeclareGlobalMemory();
154 DeclareSamplers(); 164 DeclareSamplers();
165 DeclarePhysicalAttributeReader();
155 166
156 code.AddLine("void execute_" + suffix + "() {"); 167 code.AddLine("void execute_{}() {{", suffix);
157 ++code.scope; 168 ++code.scope;
158 169
159 // VM's program counter 170 // VM's program counter
160 const auto first_address = ir.GetBasicBlocks().begin()->first; 171 const auto first_address = ir.GetBasicBlocks().begin()->first;
161 code.AddLine("uint jmp_to = " + std::to_string(first_address) + "u;"); 172 code.AddLine("uint jmp_to = {}u;", first_address);
162 173
163 // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems 174 // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems
164 // unlikely that shaders will use 20 nested SSYs and PBKs. 175 // unlikely that shaders will use 20 nested SSYs and PBKs.
165 constexpr u32 FLOW_STACK_SIZE = 20; 176 constexpr u32 FLOW_STACK_SIZE = 20;
166 code.AddLine(fmt::format("uint flow_stack[{}];", FLOW_STACK_SIZE)); 177 code.AddLine("uint flow_stack[{}];", FLOW_STACK_SIZE);
167 code.AddLine("uint flow_stack_top = 0u;"); 178 code.AddLine("uint flow_stack_top = 0u;");
168 179
169 code.AddLine("while (true) {"); 180 code.AddLine("while (true) {{");
170 ++code.scope; 181 ++code.scope;
171 182
172 code.AddLine("switch (jmp_to) {"); 183 code.AddLine("switch (jmp_to) {{");
173 184
174 for (const auto& pair : ir.GetBasicBlocks()) { 185 for (const auto& pair : ir.GetBasicBlocks()) {
175 const auto [address, bb] = pair; 186 const auto [address, bb] = pair;
176 code.AddLine(fmt::format("case 0x{:x}u: {{", address)); 187 code.AddLine("case 0x{:x}u: {{", address);
177 ++code.scope; 188 ++code.scope;
178 189
179 VisitBlock(bb); 190 VisitBlock(bb);
180 191
181 --code.scope; 192 --code.scope;
182 code.AddLine('}'); 193 code.AddLine("}}");
183 } 194 }
184 195
185 code.AddLine("default: return;"); 196 code.AddLine("default: return;");
186 code.AddLine('}'); 197 code.AddLine("}}");
187 198
188 for (std::size_t i = 0; i < 2; ++i) { 199 for (std::size_t i = 0; i < 2; ++i) {
189 --code.scope; 200 --code.scope;
190 code.AddLine('}'); 201 code.AddLine("}}");
191 } 202 }
192 } 203 }
193 204
@@ -227,12 +238,13 @@ private:
227 } 238 }
228 239
229 void DeclareGeometry() { 240 void DeclareGeometry() {
230 if (stage != ShaderStage::Geometry) 241 if (stage != ShaderStage::Geometry) {
231 return; 242 return;
243 }
232 244
233 const auto topology = GetTopologyName(header.common3.output_topology); 245 const auto topology = GetTopologyName(header.common3.output_topology);
234 const auto max_vertices = std::to_string(header.common4.max_output_vertices); 246 const auto max_vertices = header.common4.max_output_vertices.Value();
235 code.AddLine("layout (" + topology + ", max_vertices = " + max_vertices + ") out;"); 247 code.AddLine("layout ({}, max_vertices = {}) out;", topology, max_vertices);
236 code.AddNewLine(); 248 code.AddNewLine();
237 249
238 DeclareVertexRedeclarations(); 250 DeclareVertexRedeclarations();
@@ -241,7 +253,7 @@ private:
241 void DeclareVertexRedeclarations() { 253 void DeclareVertexRedeclarations() {
242 bool clip_distances_declared = false; 254 bool clip_distances_declared = false;
243 255
244 code.AddLine("out gl_PerVertex {"); 256 code.AddLine("out gl_PerVertex {{");
245 ++code.scope; 257 ++code.scope;
246 258
247 code.AddLine("vec4 gl_Position;"); 259 code.AddLine("vec4 gl_Position;");
@@ -257,122 +269,143 @@ private:
257 } 269 }
258 270
259 --code.scope; 271 --code.scope;
260 code.AddLine("};"); 272 code.AddLine("}};");
261 code.AddNewLine(); 273 code.AddNewLine();
262 } 274 }
263 275
264 void DeclareRegisters() { 276 void DeclareRegisters() {
265 const auto& registers = ir.GetRegisters(); 277 const auto& registers = ir.GetRegisters();
266 for (const u32 gpr : registers) { 278 for (const u32 gpr : registers) {
267 code.AddLine("float " + GetRegister(gpr) + " = 0;"); 279 code.AddLine("float {} = 0;", GetRegister(gpr));
268 } 280 }
269 if (!registers.empty()) 281 if (!registers.empty()) {
270 code.AddNewLine(); 282 code.AddNewLine();
283 }
271 } 284 }
272 285
273 void DeclarePredicates() { 286 void DeclarePredicates() {
274 const auto& predicates = ir.GetPredicates(); 287 const auto& predicates = ir.GetPredicates();
275 for (const auto pred : predicates) { 288 for (const auto pred : predicates) {
276 code.AddLine("bool " + GetPredicate(pred) + " = false;"); 289 code.AddLine("bool {} = false;", GetPredicate(pred));
277 } 290 }
278 if (!predicates.empty()) 291 if (!predicates.empty()) {
279 code.AddNewLine(); 292 code.AddNewLine();
293 }
280 } 294 }
281 295
282 void DeclareLocalMemory() { 296 void DeclareLocalMemory() {
283 if (const u64 local_memory_size = header.GetLocalMemorySize(); local_memory_size > 0) { 297 if (const u64 local_memory_size = header.GetLocalMemorySize(); local_memory_size > 0) {
284 const auto element_count = Common::AlignUp(local_memory_size, 4) / 4; 298 const auto element_count = Common::AlignUp(local_memory_size, 4) / 4;
285 code.AddLine("float " + GetLocalMemory() + '[' + std::to_string(element_count) + "];"); 299 code.AddLine("float {}[{}];", GetLocalMemory(), element_count);
286 code.AddNewLine(); 300 code.AddNewLine();
287 } 301 }
288 } 302 }
289 303
290 void DeclareInternalFlags() { 304 void DeclareInternalFlags() {
291 for (u32 flag = 0; flag < static_cast<u32>(InternalFlag::Amount); flag++) { 305 for (u32 flag = 0; flag < static_cast<u32>(InternalFlag::Amount); flag++) {
292 const InternalFlag flag_code = static_cast<InternalFlag>(flag); 306 const auto flag_code = static_cast<InternalFlag>(flag);
293 code.AddLine("bool " + GetInternalFlag(flag_code) + " = false;"); 307 code.AddLine("bool {} = false;", GetInternalFlag(flag_code));
294 } 308 }
295 code.AddNewLine(); 309 code.AddNewLine();
296 } 310 }
297 311
298 std::string GetInputFlags(AttributeUse attribute) { 312 std::string GetInputFlags(AttributeUse attribute) {
299 std::string out;
300
301 switch (attribute) { 313 switch (attribute) {
302 case AttributeUse::Constant:
303 out += "flat ";
304 break;
305 case AttributeUse::ScreenLinear:
306 out += "noperspective ";
307 break;
308 case AttributeUse::Perspective: 314 case AttributeUse::Perspective:
309 // Default, Smooth 315 // Default, Smooth
310 break; 316 return {};
317 case AttributeUse::Constant:
318 return "flat ";
319 case AttributeUse::ScreenLinear:
320 return "noperspective ";
311 default: 321 default:
312 LOG_CRITICAL(HW_GPU, "Unused attribute being fetched"); 322 case AttributeUse::Unused:
313 UNREACHABLE(); 323 UNREACHABLE_MSG("Unused attribute being fetched");
324 return {};
325 UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<u32>(attribute));
326 return {};
314 } 327 }
315 return out;
316 } 328 }
317 329
318 void DeclareInputAttributes() { 330 void DeclareInputAttributes() {
319 const auto& attributes = ir.GetInputAttributes(); 331 if (ir.HasPhysicalAttributes()) {
320 for (const auto element : attributes) { 332 const u32 num_inputs{GetNumPhysicalInputAttributes()};
321 const Attribute::Index index = element.first; 333 for (u32 i = 0; i < num_inputs; ++i) {
322 if (index < Attribute::Index::Attribute_0 || index > Attribute::Index::Attribute_31) { 334 DeclareInputAttribute(ToGenericAttribute(i), true);
323 // Skip when it's not a generic attribute
324 continue;
325 } 335 }
336 code.AddNewLine();
337 return;
338 }
326 339
327 // TODO(bunnei): Use proper number of elements for these 340 const auto& attributes = ir.GetInputAttributes();
328 u32 idx = static_cast<u32>(index) - static_cast<u32>(Attribute::Index::Attribute_0); 341 for (const auto index : attributes) {
329 if (stage != ShaderStage::Vertex) { 342 if (IsGenericAttribute(index)) {
330 // If inputs are varyings, add an offset 343 DeclareInputAttribute(index, false);
331 idx += GENERIC_VARYING_START_LOCATION;
332 } 344 }
345 }
346 if (!attributes.empty()) {
347 code.AddNewLine();
348 }
349 }
333 350
334 std::string attr = GetInputAttribute(index); 351 void DeclareInputAttribute(Attribute::Index index, bool skip_unused) {
335 if (stage == ShaderStage::Geometry) { 352 const u32 generic_index{GetGenericAttributeIndex(index)};
336 attr = "gs_" + attr + "[]"; 353
337 } 354 std::string name{GetInputAttribute(index)};
338 std::string suffix; 355 if (stage == ShaderStage::Geometry) {
339 if (stage == ShaderStage::Fragment) { 356 name = "gs_" + name + "[]";
340 const auto input_mode = 357 }
341 header.ps.GetAttributeUse(idx - GENERIC_VARYING_START_LOCATION); 358
342 suffix = GetInputFlags(input_mode); 359 std::string suffix;
360 if (stage == ShaderStage::Fragment) {
361 const auto input_mode{header.ps.GetAttributeUse(generic_index)};
362 if (skip_unused && input_mode == AttributeUse::Unused) {
363 return;
343 } 364 }
344 code.AddLine("layout (location = " + std::to_string(idx) + ") " + suffix + "in vec4 " + 365 suffix = GetInputFlags(input_mode);
345 attr + ';');
346 } 366 }
347 if (!attributes.empty()) 367
348 code.AddNewLine(); 368 u32 location = generic_index;
369 if (stage != ShaderStage::Vertex) {
370 // If inputs are varyings, add an offset
371 location += GENERIC_VARYING_START_LOCATION;
372 }
373
374 code.AddLine("layout (location = {}) {} in vec4 {};", location, suffix, name);
349 } 375 }
350 376
351 void DeclareOutputAttributes() { 377 void DeclareOutputAttributes() {
378 if (ir.HasPhysicalAttributes() && stage != ShaderStage::Fragment) {
379 for (u32 i = 0; i < GetNumPhysicalVaryings(); ++i) {
380 DeclareOutputAttribute(ToGenericAttribute(i));
381 }
382 code.AddNewLine();
383 return;
384 }
385
352 const auto& attributes = ir.GetOutputAttributes(); 386 const auto& attributes = ir.GetOutputAttributes();
353 for (const auto index : attributes) { 387 for (const auto index : attributes) {
354 if (index < Attribute::Index::Attribute_0 || index > Attribute::Index::Attribute_31) { 388 if (IsGenericAttribute(index)) {
355 // Skip when it's not a generic attribute 389 DeclareOutputAttribute(index);
356 continue;
357 } 390 }
358 // TODO(bunnei): Use proper number of elements for these 391 }
359 const auto idx = static_cast<u32>(index) - 392 if (!attributes.empty()) {
360 static_cast<u32>(Attribute::Index::Attribute_0) +
361 GENERIC_VARYING_START_LOCATION;
362 code.AddLine("layout (location = " + std::to_string(idx) + ") out vec4 " +
363 GetOutputAttribute(index) + ';');
364 }
365 if (!attributes.empty())
366 code.AddNewLine(); 393 code.AddNewLine();
394 }
395 }
396
397 void DeclareOutputAttribute(Attribute::Index index) {
398 const u32 location{GetGenericAttributeIndex(index) + GENERIC_VARYING_START_LOCATION};
399 code.AddLine("layout (location = {}) out vec4 {};", location, GetOutputAttribute(index));
367 } 400 }
368 401
369 void DeclareConstantBuffers() { 402 void DeclareConstantBuffers() {
370 for (const auto& entry : ir.GetConstantBuffers()) { 403 for (const auto& entry : ir.GetConstantBuffers()) {
371 const auto [index, size] = entry; 404 const auto [index, size] = entry;
372 code.AddLine("layout (std140, binding = CBUF_BINDING_" + std::to_string(index) + 405 code.AddLine("layout (std140, binding = CBUF_BINDING_{}) uniform {} {{", index,
373 ") uniform " + GetConstBufferBlock(index) + " {"); 406 GetConstBufferBlock(index));
374 code.AddLine(" vec4 " + GetConstBuffer(index) + "[MAX_CONSTBUFFER_ELEMENTS];"); 407 code.AddLine(" vec4 {}[MAX_CONSTBUFFER_ELEMENTS];", GetConstBuffer(index));
375 code.AddLine("};"); 408 code.AddLine("}};");
376 code.AddNewLine(); 409 code.AddNewLine();
377 } 410 }
378 } 411 }
@@ -384,17 +417,16 @@ private:
384 // Since we don't know how the shader will use the shader, hint the driver to disable as 417 // Since we don't know how the shader will use the shader, hint the driver to disable as
385 // much optimizations as possible 418 // much optimizations as possible
386 std::string qualifier = "coherent volatile"; 419 std::string qualifier = "coherent volatile";
387 if (usage.is_read && !usage.is_written) 420 if (usage.is_read && !usage.is_written) {
388 qualifier += " readonly"; 421 qualifier += " readonly";
389 else if (usage.is_written && !usage.is_read) 422 } else if (usage.is_written && !usage.is_read) {
390 qualifier += " writeonly"; 423 qualifier += " writeonly";
424 }
391 425
392 const std::string binding = 426 code.AddLine("layout (std430, binding = GMEM_BINDING_{}_{}) {} buffer {} {{",
393 fmt::format("GMEM_BINDING_{}_{}", base.cbuf_index, base.cbuf_offset); 427 base.cbuf_index, base.cbuf_offset, qualifier, GetGlobalMemoryBlock(base));
394 code.AddLine("layout (std430, binding = " + binding + ") " + qualifier + " buffer " + 428 code.AddLine(" float {}[];", GetGlobalMemory(base));
395 GetGlobalMemoryBlock(base) + " {"); 429 code.AddLine("}};");
396 code.AddLine(" float " + GetGlobalMemory(base) + "[];");
397 code.AddLine("};");
398 code.AddNewLine(); 430 code.AddNewLine();
399 } 431 }
400 } 432 }
@@ -402,7 +434,7 @@ private:
402 void DeclareSamplers() { 434 void DeclareSamplers() {
403 const auto& samplers = ir.GetSamplers(); 435 const auto& samplers = ir.GetSamplers();
404 for (const auto& sampler : samplers) { 436 for (const auto& sampler : samplers) {
405 std::string sampler_type = [&]() { 437 std::string sampler_type = [&sampler] {
406 switch (sampler.GetType()) { 438 switch (sampler.GetType()) {
407 case Tegra::Shader::TextureType::Texture1D: 439 case Tegra::Shader::TextureType::Texture1D:
408 return "sampler1D"; 440 return "sampler1D";
@@ -417,16 +449,52 @@ private:
417 return "sampler2D"; 449 return "sampler2D";
418 } 450 }
419 }(); 451 }();
420 if (sampler.IsArray()) 452 if (sampler.IsArray()) {
421 sampler_type += "Array"; 453 sampler_type += "Array";
422 if (sampler.IsShadow()) 454 }
455 if (sampler.IsShadow()) {
423 sampler_type += "Shadow"; 456 sampler_type += "Shadow";
457 }
424 458
425 code.AddLine("layout (binding = SAMPLER_BINDING_" + std::to_string(sampler.GetIndex()) + 459 code.AddLine("layout (binding = SAMPLER_BINDING_{}) uniform {} {};", sampler.GetIndex(),
426 ") uniform " + sampler_type + ' ' + GetSampler(sampler) + ';'); 460 sampler_type, GetSampler(sampler));
427 } 461 }
428 if (!samplers.empty()) 462 if (!samplers.empty()) {
429 code.AddNewLine(); 463 code.AddNewLine();
464 }
465 }
466
467 void DeclarePhysicalAttributeReader() {
468 if (!ir.HasPhysicalAttributes()) {
469 return;
470 }
471 code.AddLine("float readPhysicalAttribute(uint physical_address) {{");
472 ++code.scope;
473 code.AddLine("switch (physical_address) {{");
474
475 // Just declare generic attributes for now.
476 const auto num_attributes{static_cast<u32>(GetNumPhysicalInputAttributes())};
477 for (u32 index = 0; index < num_attributes; ++index) {
478 const auto attribute{ToGenericAttribute(index)};
479 for (u32 element = 0; element < 4; ++element) {
480 constexpr u32 generic_base{0x80};
481 constexpr u32 generic_stride{16};
482 constexpr u32 element_stride{4};
483 const u32 address{generic_base + index * generic_stride + element * element_stride};
484
485 const bool declared{stage != ShaderStage::Fragment ||
486 header.ps.GetAttributeUse(index) != AttributeUse::Unused};
487 const std::string value{declared ? ReadAttribute(attribute, element) : "0"};
488 code.AddLine("case 0x{:x}: return {};", address, value);
489 }
490 }
491
492 code.AddLine("default: return 0;");
493
494 code.AddLine("}}");
495 --code.scope;
496 code.AddLine("}}");
497 code.AddNewLine();
430 } 498 }
431 499
432 void VisitBlock(const NodeBlock& bb) { 500 void VisitBlock(const NodeBlock& bb) {
@@ -450,23 +518,26 @@ private:
450 return {}; 518 return {};
451 } 519 }
452 return (this->*decompiler)(*operation); 520 return (this->*decompiler)(*operation);
521 }
453 522
454 } else if (const auto gpr = std::get_if<GprNode>(node)) { 523 if (const auto gpr = std::get_if<GprNode>(node)) {
455 const u32 index = gpr->GetIndex(); 524 const u32 index = gpr->GetIndex();
456 if (index == Register::ZeroIndex) { 525 if (index == Register::ZeroIndex) {
457 return "0"; 526 return "0";
458 } 527 }
459 return GetRegister(index); 528 return GetRegister(index);
529 }
460 530
461 } else if (const auto immediate = std::get_if<ImmediateNode>(node)) { 531 if (const auto immediate = std::get_if<ImmediateNode>(node)) {
462 const u32 value = immediate->GetValue(); 532 const u32 value = immediate->GetValue();
463 if (value < 10) { 533 if (value < 10) {
464 // For eyecandy avoid using hex numbers on single digits 534 // For eyecandy avoid using hex numbers on single digits
465 return fmt::format("utof({}u)", immediate->GetValue()); 535 return fmt::format("utof({}u)", immediate->GetValue());
466 } 536 }
467 return fmt::format("utof(0x{:x}u)", immediate->GetValue()); 537 return fmt::format("utof(0x{:x}u)", immediate->GetValue());
538 }
468 539
469 } else if (const auto predicate = std::get_if<PredicateNode>(node)) { 540 if (const auto predicate = std::get_if<PredicateNode>(node)) {
470 const auto value = [&]() -> std::string { 541 const auto value = [&]() -> std::string {
471 switch (const auto index = predicate->GetIndex(); index) { 542 switch (const auto index = predicate->GetIndex(); index) {
472 case Tegra::Shader::Pred::UnusedIndex: 543 case Tegra::Shader::Pred::UnusedIndex:
@@ -478,77 +549,22 @@ private:
478 } 549 }
479 }(); 550 }();
480 if (predicate->IsNegated()) { 551 if (predicate->IsNegated()) {
481 return "!(" + value + ')'; 552 return fmt::format("!({})", value);
482 } 553 }
483 return value; 554 return value;
555 }
484 556
485 } else if (const auto abuf = std::get_if<AbufNode>(node)) { 557 if (const auto abuf = std::get_if<AbufNode>(node)) {
486 const auto attribute = abuf->GetIndex(); 558 UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ShaderStage::Geometry,
487 const auto element = abuf->GetElement(); 559 "Physical attributes in geometry shaders are not implemented");
488 560 if (abuf->IsPhysicalBuffer()) {
489 const auto GeometryPass = [&](const std::string& name) { 561 return fmt::format("readPhysicalAttribute(ftou({}))",
490 if (stage == ShaderStage::Geometry && abuf->GetBuffer()) { 562 Visit(abuf->GetPhysicalAddress()));
491 // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games
492 // set an 0x80000000 index for those and the shader fails to build. Find out why
493 // this happens and what's its intent.
494 return "gs_" + name + "[ftou(" + Visit(abuf->GetBuffer()) +
495 ") % MAX_VERTEX_INPUT]";
496 }
497 return name;
498 };
499
500 switch (attribute) {
501 case Attribute::Index::Position:
502 if (stage != ShaderStage::Fragment) {
503 return GeometryPass("position") + GetSwizzle(element);
504 } else {
505 return element == 3 ? "1.0f" : "gl_FragCoord" + GetSwizzle(element);
506 }
507 case Attribute::Index::PointCoord:
508 switch (element) {
509 case 0:
510 return "gl_PointCoord.x";
511 case 1:
512 return "gl_PointCoord.y";
513 case 2:
514 case 3:
515 return "0";
516 }
517 UNREACHABLE();
518 return "0";
519 case Attribute::Index::TessCoordInstanceIDVertexID:
520 // TODO(Subv): Find out what the values are for the first two elements when inside a
521 // vertex shader, and what's the value of the fourth element when inside a Tess Eval
522 // shader.
523 ASSERT(stage == ShaderStage::Vertex);
524 switch (element) {
525 case 2:
526 // Config pack's first value is instance_id.
527 return "uintBitsToFloat(config_pack[0])";
528 case 3:
529 return "uintBitsToFloat(gl_VertexID)";
530 }
531 UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element);
532 return "0";
533 case Attribute::Index::FrontFacing:
534 // TODO(Subv): Find out what the values are for the other elements.
535 ASSERT(stage == ShaderStage::Fragment);
536 switch (element) {
537 case 3:
538 return "itof(gl_FrontFacing ? -1 : 0)";
539 }
540 UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element);
541 return "0";
542 default:
543 if (attribute >= Attribute::Index::Attribute_0 &&
544 attribute <= Attribute::Index::Attribute_31) {
545 return GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element);
546 }
547 break;
548 } 563 }
549 UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); 564 return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer());
565 }
550 566
551 } else if (const auto cbuf = std::get_if<CbufNode>(node)) { 567 if (const auto cbuf = std::get_if<CbufNode>(node)) {
552 const Node offset = cbuf->GetOffset(); 568 const Node offset = cbuf->GetOffset();
553 if (const auto immediate = std::get_if<ImmediateNode>(offset)) { 569 if (const auto immediate = std::get_if<ImmediateNode>(offset)) {
554 // Direct access 570 // Direct access
@@ -556,48 +572,117 @@ private:
556 ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access"); 572 ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access");
557 return fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), 573 return fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()),
558 offset_imm / (4 * 4), (offset_imm / 4) % 4); 574 offset_imm / (4 * 4), (offset_imm / 4) % 4);
575 }
559 576
560 } else if (std::holds_alternative<OperationNode>(*offset)) { 577 if (std::holds_alternative<OperationNode>(*offset)) {
561 // Indirect access 578 // Indirect access
562 const std::string final_offset = code.GenerateTemporary(); 579 const std::string final_offset = code.GenerateTemporary();
563 code.AddLine("uint " + final_offset + " = (ftou(" + Visit(offset) + ") / 4);"); 580 code.AddLine("uint {} = (ftou({}) / 4);", final_offset, Visit(offset));
564 return fmt::format("{}[{} / 4][{} % 4]", GetConstBuffer(cbuf->GetIndex()), 581 return fmt::format("{}[{} / 4][{} % 4]", GetConstBuffer(cbuf->GetIndex()),
565 final_offset, final_offset); 582 final_offset, final_offset);
566
567 } else {
568 UNREACHABLE_MSG("Unmanaged offset node type");
569 } 583 }
570 584
571 } else if (const auto gmem = std::get_if<GmemNode>(node)) { 585 UNREACHABLE_MSG("Unmanaged offset node type");
586 }
587
588 if (const auto gmem = std::get_if<GmemNode>(node)) {
572 const std::string real = Visit(gmem->GetRealAddress()); 589 const std::string real = Visit(gmem->GetRealAddress());
573 const std::string base = Visit(gmem->GetBaseAddress()); 590 const std::string base = Visit(gmem->GetBaseAddress());
574 const std::string final_offset = "(ftou(" + real + ") - ftou(" + base + ")) / 4"; 591 const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base);
575 return fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); 592 return fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset);
593 }
576 594
577 } else if (const auto lmem = std::get_if<LmemNode>(node)) { 595 if (const auto lmem = std::get_if<LmemNode>(node)) {
578 return fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); 596 return fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress()));
597 }
579 598
580 } else if (const auto internal_flag = std::get_if<InternalFlagNode>(node)) { 599 if (const auto internal_flag = std::get_if<InternalFlagNode>(node)) {
581 return GetInternalFlag(internal_flag->GetFlag()); 600 return GetInternalFlag(internal_flag->GetFlag());
601 }
582 602
583 } else if (const auto conditional = std::get_if<ConditionalNode>(node)) { 603 if (const auto conditional = std::get_if<ConditionalNode>(node)) {
584 // It's invalid to call conditional on nested nodes, use an operation instead 604 // It's invalid to call conditional on nested nodes, use an operation instead
585 code.AddLine("if (" + Visit(conditional->GetCondition()) + ") {"); 605 code.AddLine("if ({}) {{", Visit(conditional->GetCondition()));
586 ++code.scope; 606 ++code.scope;
587 607
588 VisitBlock(conditional->GetCode()); 608 VisitBlock(conditional->GetCode());
589 609
590 --code.scope; 610 --code.scope;
591 code.AddLine('}'); 611 code.AddLine("}}");
592 return {}; 612 return {};
613 }
593 614
594 } else if (const auto comment = std::get_if<CommentNode>(node)) { 615 if (const auto comment = std::get_if<CommentNode>(node)) {
595 return "// " + comment->GetText(); 616 return "// " + comment->GetText();
596 } 617 }
618
597 UNREACHABLE(); 619 UNREACHABLE();
598 return {}; 620 return {};
599 } 621 }
600 622
623 std::string ReadAttribute(Attribute::Index attribute, u32 element, Node buffer = {}) {
624 const auto GeometryPass = [&](std::string_view name) {
625 if (stage == ShaderStage::Geometry && buffer) {
626 // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games
627 // set an 0x80000000 index for those and the shader fails to build. Find out why
628 // this happens and what's its intent.
629 return fmt::format("gs_{}[ftou({}) % MAX_VERTEX_INPUT]", name, Visit(buffer));
630 }
631 return std::string(name);
632 };
633
634 switch (attribute) {
635 case Attribute::Index::Position:
636 if (stage != ShaderStage::Fragment) {
637 return GeometryPass("position") + GetSwizzle(element);
638 } else {
639 return element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element));
640 }
641 case Attribute::Index::PointCoord:
642 switch (element) {
643 case 0:
644 return "gl_PointCoord.x";
645 case 1:
646 return "gl_PointCoord.y";
647 case 2:
648 case 3:
649 return "0";
650 }
651 UNREACHABLE();
652 return "0";
653 case Attribute::Index::TessCoordInstanceIDVertexID:
654 // TODO(Subv): Find out what the values are for the first two elements when inside a
655 // vertex shader, and what's the value of the fourth element when inside a Tess Eval
656 // shader.
657 ASSERT(stage == ShaderStage::Vertex);
658 switch (element) {
659 case 2:
660 // Config pack's first value is instance_id.
661 return "uintBitsToFloat(config_pack[0])";
662 case 3:
663 return "uintBitsToFloat(gl_VertexID)";
664 }
665 UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element);
666 return "0";
667 case Attribute::Index::FrontFacing:
668 // TODO(Subv): Find out what the values are for the other elements.
669 ASSERT(stage == ShaderStage::Fragment);
670 switch (element) {
671 case 3:
672 return "itof(gl_FrontFacing ? -1 : 0)";
673 }
674 UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element);
675 return "0";
676 default:
677 if (IsGenericAttribute(attribute)) {
678 return GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element);
679 }
680 break;
681 }
682 UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute));
683 return "0";
684 }
685
601 std::string ApplyPrecise(Operation operation, const std::string& value) { 686 std::string ApplyPrecise(Operation operation, const std::string& value) {
602 if (!IsPrecise(operation)) { 687 if (!IsPrecise(operation)) {
603 return value; 688 return value;
@@ -606,7 +691,7 @@ private:
606 const std::string precise = stage != ShaderStage::Fragment ? "precise " : ""; 691 const std::string precise = stage != ShaderStage::Fragment ? "precise " : "";
607 692
608 const std::string temporary = code.GenerateTemporary(); 693 const std::string temporary = code.GenerateTemporary();
609 code.AddLine(precise + "float " + temporary + " = " + value + ';'); 694 code.AddLine("{}float {} = {};", precise, temporary, value);
610 return temporary; 695 return temporary;
611 } 696 }
612 697
@@ -620,7 +705,7 @@ private:
620 } 705 }
621 706
622 const std::string temporary = code.GenerateTemporary(); 707 const std::string temporary = code.GenerateTemporary();
623 code.AddLine("float " + temporary + " = " + Visit(operand) + ';'); 708 code.AddLine("float {} = {};", temporary, Visit(operand));
624 return temporary; 709 return temporary;
625 } 710 }
626 711
@@ -635,31 +720,32 @@ private:
635 case Type::Float: 720 case Type::Float:
636 return value; 721 return value;
637 case Type::Int: 722 case Type::Int:
638 return "ftoi(" + value + ')'; 723 return fmt::format("ftoi({})", value);
639 case Type::Uint: 724 case Type::Uint:
640 return "ftou(" + value + ')'; 725 return fmt::format("ftou({})", value);
641 case Type::HalfFloat: 726 case Type::HalfFloat:
642 return "toHalf2(" + value + ')'; 727 return fmt::format("toHalf2({})", value);
643 } 728 }
644 UNREACHABLE(); 729 UNREACHABLE();
645 return value; 730 return value;
646 } 731 }
647 732
648 std::string BitwiseCastResult(std::string value, Type type, bool needs_parenthesis = false) { 733 std::string BitwiseCastResult(const std::string& value, Type type,
734 bool needs_parenthesis = false) {
649 switch (type) { 735 switch (type) {
650 case Type::Bool: 736 case Type::Bool:
651 case Type::Bool2: 737 case Type::Bool2:
652 case Type::Float: 738 case Type::Float:
653 if (needs_parenthesis) { 739 if (needs_parenthesis) {
654 return '(' + value + ')'; 740 return fmt::format("({})", value);
655 } 741 }
656 return value; 742 return value;
657 case Type::Int: 743 case Type::Int:
658 return "itof(" + value + ')'; 744 return fmt::format("itof({})", value);
659 case Type::Uint: 745 case Type::Uint:
660 return "utof(" + value + ')'; 746 return fmt::format("utof({})", value);
661 case Type::HalfFloat: 747 case Type::HalfFloat:
662 return "fromHalf2(" + value + ')'; 748 return fmt::format("fromHalf2({})", value);
663 } 749 }
664 UNREACHABLE(); 750 UNREACHABLE();
665 return value; 751 return value;
@@ -667,27 +753,27 @@ private:
667 753
668 std::string GenerateUnary(Operation operation, const std::string& func, Type result_type, 754 std::string GenerateUnary(Operation operation, const std::string& func, Type result_type,
669 Type type_a, bool needs_parenthesis = true) { 755 Type type_a, bool needs_parenthesis = true) {
670 return ApplyPrecise(operation, 756 const std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0, type_a));
671 BitwiseCastResult(func + '(' + VisitOperand(operation, 0, type_a) + ')', 757
672 result_type, needs_parenthesis)); 758 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type, needs_parenthesis));
673 } 759 }
674 760
675 std::string GenerateBinaryInfix(Operation operation, const std::string& func, Type result_type, 761 std::string GenerateBinaryInfix(Operation operation, const std::string& func, Type result_type,
676 Type type_a, Type type_b) { 762 Type type_a, Type type_b) {
677 const std::string op_a = VisitOperand(operation, 0, type_a); 763 const std::string op_a = VisitOperand(operation, 0, type_a);
678 const std::string op_b = VisitOperand(operation, 1, type_b); 764 const std::string op_b = VisitOperand(operation, 1, type_b);
765 const std::string op_str = fmt::format("({} {} {})", op_a, func, op_b);
679 766
680 return ApplyPrecise( 767 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type));
681 operation, BitwiseCastResult('(' + op_a + ' ' + func + ' ' + op_b + ')', result_type));
682 } 768 }
683 769
684 std::string GenerateBinaryCall(Operation operation, const std::string& func, Type result_type, 770 std::string GenerateBinaryCall(Operation operation, const std::string& func, Type result_type,
685 Type type_a, Type type_b) { 771 Type type_a, Type type_b) {
686 const std::string op_a = VisitOperand(operation, 0, type_a); 772 const std::string op_a = VisitOperand(operation, 0, type_a);
687 const std::string op_b = VisitOperand(operation, 1, type_b); 773 const std::string op_b = VisitOperand(operation, 1, type_b);
774 const std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b);
688 775
689 return ApplyPrecise(operation, 776 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type));
690 BitwiseCastResult(func + '(' + op_a + ", " + op_b + ')', result_type));
691 } 777 }
692 778
693 std::string GenerateTernary(Operation operation, const std::string& func, Type result_type, 779 std::string GenerateTernary(Operation operation, const std::string& func, Type result_type,
@@ -695,10 +781,9 @@ private:
695 const std::string op_a = VisitOperand(operation, 0, type_a); 781 const std::string op_a = VisitOperand(operation, 0, type_a);
696 const std::string op_b = VisitOperand(operation, 1, type_b); 782 const std::string op_b = VisitOperand(operation, 1, type_b);
697 const std::string op_c = VisitOperand(operation, 2, type_c); 783 const std::string op_c = VisitOperand(operation, 2, type_c);
784 const std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c);
698 785
699 return ApplyPrecise( 786 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type));
700 operation,
701 BitwiseCastResult(func + '(' + op_a + ", " + op_b + ", " + op_c + ')', result_type));
702 } 787 }
703 788
704 std::string GenerateQuaternary(Operation operation, const std::string& func, Type result_type, 789 std::string GenerateQuaternary(Operation operation, const std::string& func, Type result_type,
@@ -707,10 +792,9 @@ private:
707 const std::string op_b = VisitOperand(operation, 1, type_b); 792 const std::string op_b = VisitOperand(operation, 1, type_b);
708 const std::string op_c = VisitOperand(operation, 2, type_c); 793 const std::string op_c = VisitOperand(operation, 2, type_c);
709 const std::string op_d = VisitOperand(operation, 3, type_d); 794 const std::string op_d = VisitOperand(operation, 3, type_d);
795 const std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d);
710 796
711 return ApplyPrecise(operation, BitwiseCastResult(func + '(' + op_a + ", " + op_b + ", " + 797 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type));
712 op_c + ", " + op_d + ')',
713 result_type));
714 } 798 }
715 799
716 std::string GenerateTexture(Operation operation, const std::string& function_suffix, 800 std::string GenerateTexture(Operation operation, const std::string& function_suffix,
@@ -773,7 +857,7 @@ private:
773 // required to be constant) 857 // required to be constant)
774 expr += std::to_string(static_cast<s32>(immediate->GetValue())); 858 expr += std::to_string(static_cast<s32>(immediate->GetValue()));
775 } else { 859 } else {
776 expr += "ftoi(" + Visit(operand) + ')'; 860 expr += fmt::format("ftoi({})", Visit(operand));
777 } 861 }
778 break; 862 break;
779 case Type::Float: 863 case Type::Float:
@@ -806,7 +890,7 @@ private:
806 expr += std::to_string(static_cast<s32>(immediate->GetValue())); 890 expr += std::to_string(static_cast<s32>(immediate->GetValue()));
807 } else if (device.HasVariableAoffi()) { 891 } else if (device.HasVariableAoffi()) {
808 // Avoid using variable AOFFI on unsupported devices. 892 // Avoid using variable AOFFI on unsupported devices.
809 expr += "ftoi(" + Visit(operand) + ')'; 893 expr += fmt::format("ftoi({})", Visit(operand));
810 } else { 894 } else {
811 // Insert 0 on devices not supporting variable AOFFI. 895 // Insert 0 on devices not supporting variable AOFFI.
812 expr += '0'; 896 expr += '0';
@@ -831,21 +915,21 @@ private:
831 return {}; 915 return {};
832 } 916 }
833 target = GetRegister(gpr->GetIndex()); 917 target = GetRegister(gpr->GetIndex());
834
835 } else if (const auto abuf = std::get_if<AbufNode>(dest)) { 918 } else if (const auto abuf = std::get_if<AbufNode>(dest)) {
919 UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer());
920
836 target = [&]() -> std::string { 921 target = [&]() -> std::string {
837 switch (const auto attribute = abuf->GetIndex(); abuf->GetIndex()) { 922 switch (const auto attribute = abuf->GetIndex(); abuf->GetIndex()) {
838 case Attribute::Index::Position: 923 case Attribute::Index::Position:
839 return "position" + GetSwizzle(abuf->GetElement()); 924 return "position"s + GetSwizzle(abuf->GetElement());
840 case Attribute::Index::PointSize: 925 case Attribute::Index::PointSize:
841 return "gl_PointSize"; 926 return "gl_PointSize";
842 case Attribute::Index::ClipDistances0123: 927 case Attribute::Index::ClipDistances0123:
843 return "gl_ClipDistance[" + std::to_string(abuf->GetElement()) + ']'; 928 return fmt::format("gl_ClipDistance[{}]", abuf->GetElement());
844 case Attribute::Index::ClipDistances4567: 929 case Attribute::Index::ClipDistances4567:
845 return "gl_ClipDistance[" + std::to_string(abuf->GetElement() + 4) + ']'; 930 return fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4);
846 default: 931 default:
847 if (attribute >= Attribute::Index::Attribute_0 && 932 if (IsGenericAttribute(attribute)) {
848 attribute <= Attribute::Index::Attribute_31) {
849 return GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()); 933 return GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement());
850 } 934 }
851 UNIMPLEMENTED_MSG("Unhandled output attribute: {}", 935 UNIMPLEMENTED_MSG("Unhandled output attribute: {}",
@@ -853,35 +937,21 @@ private:
853 return "0"; 937 return "0";
854 } 938 }
855 }(); 939 }();
856
857 } else if (const auto lmem = std::get_if<LmemNode>(dest)) { 940 } else if (const auto lmem = std::get_if<LmemNode>(dest)) {
858 target = GetLocalMemory() + "[ftou(" + Visit(lmem->GetAddress()) + ") / 4]"; 941 target = fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress()));
859
860 } else if (const auto gmem = std::get_if<GmemNode>(dest)) { 942 } else if (const auto gmem = std::get_if<GmemNode>(dest)) {
861 const std::string real = Visit(gmem->GetRealAddress()); 943 const std::string real = Visit(gmem->GetRealAddress());
862 const std::string base = Visit(gmem->GetBaseAddress()); 944 const std::string base = Visit(gmem->GetBaseAddress());
863 const std::string final_offset = "(ftou(" + real + ") - ftou(" + base + ")) / 4"; 945 const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base);
864 target = fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); 946 target = fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset);
865
866 } else { 947 } else {
867 UNREACHABLE_MSG("Assign called without a proper target"); 948 UNREACHABLE_MSG("Assign called without a proper target");
868 } 949 }
869 950
870 code.AddLine(target + " = " + Visit(src) + ';'); 951 code.AddLine("{} = {};", target, Visit(src));
871 return {}; 952 return {};
872 } 953 }
873 954
874 std::string Composite(Operation operation) {
875 std::string value = "vec4(";
876 for (std::size_t i = 0; i < 4; ++i) {
877 value += Visit(operation[i]);
878 if (i < 3)
879 value += ", ";
880 }
881 value += ')';
882 return value;
883 }
884
885 template <Type type> 955 template <Type type>
886 std::string Add(Operation operation) { 956 std::string Add(Operation operation) {
887 return GenerateBinaryInfix(operation, "+", type, type, type); 957 return GenerateBinaryInfix(operation, "+", type, type, type);
@@ -931,8 +1001,9 @@ private:
931 const std::string condition = Visit(operation[0]); 1001 const std::string condition = Visit(operation[0]);
932 const std::string true_case = Visit(operation[1]); 1002 const std::string true_case = Visit(operation[1]);
933 const std::string false_case = Visit(operation[2]); 1003 const std::string false_case = Visit(operation[2]);
934 return ApplyPrecise(operation, 1004 const std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case);
935 '(' + condition + " ? " + true_case + " : " + false_case + ')'); 1005
1006 return ApplyPrecise(operation, op_str);
936 } 1007 }
937 1008
938 std::string FCos(Operation operation) { 1009 std::string FCos(Operation operation) {
@@ -996,9 +1067,9 @@ private:
996 std::string ILogicalShiftRight(Operation operation) { 1067 std::string ILogicalShiftRight(Operation operation) {
997 const std::string op_a = VisitOperand(operation, 0, Type::Uint); 1068 const std::string op_a = VisitOperand(operation, 0, Type::Uint);
998 const std::string op_b = VisitOperand(operation, 1, Type::Uint); 1069 const std::string op_b = VisitOperand(operation, 1, Type::Uint);
1070 const std::string op_str = fmt::format("int({} >> {})", op_a, op_b);
999 1071
1000 return ApplyPrecise(operation, 1072 return ApplyPrecise(operation, BitwiseCastResult(op_str, Type::Int));
1001 BitwiseCastResult("int(" + op_a + " >> " + op_b + ')', Type::Int));
1002 } 1073 }
1003 1074
1004 std::string IArithmeticShiftRight(Operation operation) { 1075 std::string IArithmeticShiftRight(Operation operation) {
@@ -1054,11 +1125,12 @@ private:
1054 } 1125 }
1055 1126
1056 std::string HNegate(Operation operation) { 1127 std::string HNegate(Operation operation) {
1057 const auto GetNegate = [&](std::size_t index) -> std::string { 1128 const auto GetNegate = [&](std::size_t index) {
1058 return VisitOperand(operation, index, Type::Bool) + " ? -1 : 1"; 1129 return VisitOperand(operation, index, Type::Bool) + " ? -1 : 1";
1059 }; 1130 };
1060 const std::string value = '(' + VisitOperand(operation, 0, Type::HalfFloat) + " * vec2(" + 1131 const std::string value =
1061 GetNegate(1) + ", " + GetNegate(2) + "))"; 1132 fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0, Type::HalfFloat),
1133 GetNegate(1), GetNegate(2));
1062 return BitwiseCastResult(value, Type::HalfFloat); 1134 return BitwiseCastResult(value, Type::HalfFloat);
1063 } 1135 }
1064 1136
@@ -1066,7 +1138,8 @@ private:
1066 const std::string value = VisitOperand(operation, 0, Type::HalfFloat); 1138 const std::string value = VisitOperand(operation, 0, Type::HalfFloat);
1067 const std::string min = VisitOperand(operation, 1, Type::Float); 1139 const std::string min = VisitOperand(operation, 1, Type::Float);
1068 const std::string max = VisitOperand(operation, 2, Type::Float); 1140 const std::string max = VisitOperand(operation, 2, Type::Float);
1069 const std::string clamped = "clamp(" + value + ", vec2(" + min + "), vec2(" + max + "))"; 1141 const std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max);
1142
1070 return ApplyPrecise(operation, BitwiseCastResult(clamped, Type::HalfFloat)); 1143 return ApplyPrecise(operation, BitwiseCastResult(clamped, Type::HalfFloat));
1071 } 1144 }
1072 1145
@@ -1077,34 +1150,35 @@ private:
1077 case Tegra::Shader::HalfType::H0_H1: 1150 case Tegra::Shader::HalfType::H0_H1:
1078 return operand; 1151 return operand;
1079 case Tegra::Shader::HalfType::F32: 1152 case Tegra::Shader::HalfType::F32:
1080 return "vec2(fromHalf2(" + operand + "))"; 1153 return fmt::format("vec2(fromHalf2({}))", operand);
1081 case Tegra::Shader::HalfType::H0_H0: 1154 case Tegra::Shader::HalfType::H0_H0:
1082 return "vec2(" + operand + "[0])"; 1155 return fmt::format("vec2({}[0])", operand);
1083 case Tegra::Shader::HalfType::H1_H1: 1156 case Tegra::Shader::HalfType::H1_H1:
1084 return "vec2(" + operand + "[1])"; 1157 return fmt::format("vec2({}[1])", operand);
1085 } 1158 }
1086 UNREACHABLE(); 1159 UNREACHABLE();
1087 return "0"; 1160 return "0";
1088 }(); 1161 }();
1089 return "fromHalf2(" + value + ')'; 1162 return fmt::format("fromHalf2({})", value);
1090 } 1163 }
1091 1164
1092 std::string HMergeF32(Operation operation) { 1165 std::string HMergeF32(Operation operation) {
1093 return "float(toHalf2(" + Visit(operation[0]) + ")[0])"; 1166 return fmt::format("float(toHalf2({})[0])", Visit(operation[0]));
1094 } 1167 }
1095 1168
1096 std::string HMergeH0(Operation operation) { 1169 std::string HMergeH0(Operation operation) {
1097 return "fromHalf2(vec2(toHalf2(" + Visit(operation[1]) + ")[0], toHalf2(" + 1170 return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[1]),
1098 Visit(operation[0]) + ")[1]))"; 1171 Visit(operation[0]));
1099 } 1172 }
1100 1173
1101 std::string HMergeH1(Operation operation) { 1174 std::string HMergeH1(Operation operation) {
1102 return "fromHalf2(vec2(toHalf2(" + Visit(operation[0]) + ")[0], toHalf2(" + 1175 return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[0]),
1103 Visit(operation[1]) + ")[1]))"; 1176 Visit(operation[1]));
1104 } 1177 }
1105 1178
1106 std::string HPack2(Operation operation) { 1179 std::string HPack2(Operation operation) {
1107 return "utof(packHalf2x16(vec2(" + Visit(operation[0]) + ", " + Visit(operation[1]) + ")))"; 1180 return fmt::format("utof(packHalf2x16(vec2({}, {})))", Visit(operation[0]),
1181 Visit(operation[1]));
1108 } 1182 }
1109 1183
1110 template <Type type> 1184 template <Type type>
@@ -1162,7 +1236,7 @@ private:
1162 target = GetInternalFlag(flag->GetFlag()); 1236 target = GetInternalFlag(flag->GetFlag());
1163 } 1237 }
1164 1238
1165 code.AddLine(target + " = " + Visit(src) + ';'); 1239 code.AddLine("{} = {};", target, Visit(src));
1166 return {}; 1240 return {};
1167 } 1241 }
1168 1242
@@ -1184,7 +1258,7 @@ private:
1184 1258
1185 std::string LogicalPick2(Operation operation) { 1259 std::string LogicalPick2(Operation operation) {
1186 const std::string pair = VisitOperand(operation, 0, Type::Bool2); 1260 const std::string pair = VisitOperand(operation, 0, Type::Bool2);
1187 return pair + '[' + VisitOperand(operation, 1, Type::Uint) + ']'; 1261 return fmt::format("{}[{}]", pair, VisitOperand(operation, 1, Type::Uint));
1188 } 1262 }
1189 1263
1190 std::string LogicalAll2(Operation operation) { 1264 std::string LogicalAll2(Operation operation) {
@@ -1196,15 +1270,15 @@ private:
1196 } 1270 }
1197 1271
1198 template <bool with_nan> 1272 template <bool with_nan>
1199 std::string GenerateHalfComparison(Operation operation, std::string compare_op) { 1273 std::string GenerateHalfComparison(Operation operation, const std::string& compare_op) {
1200 std::string comparison{GenerateBinaryCall(operation, compare_op, Type::Bool2, 1274 const std::string comparison{GenerateBinaryCall(operation, compare_op, Type::Bool2,
1201 Type::HalfFloat, Type::HalfFloat)}; 1275 Type::HalfFloat, Type::HalfFloat)};
1202 if constexpr (!with_nan) { 1276 if constexpr (!with_nan) {
1203 return comparison; 1277 return comparison;
1204 } 1278 }
1205 return "halfFloatNanComparison(" + comparison + ", " + 1279 return fmt::format("halfFloatNanComparison({}, {}, {})", comparison,
1206 VisitOperand(operation, 0, Type::HalfFloat) + ", " + 1280 VisitOperand(operation, 0, Type::HalfFloat),
1207 VisitOperand(operation, 1, Type::HalfFloat) + ')'; 1281 VisitOperand(operation, 1, Type::HalfFloat));
1208 } 1282 }
1209 1283
1210 template <bool with_nan> 1284 template <bool with_nan>
@@ -1281,12 +1355,12 @@ private:
1281 switch (meta->element) { 1355 switch (meta->element) {
1282 case 0: 1356 case 0:
1283 case 1: 1357 case 1:
1284 return "itof(int(textureSize(" + sampler + ", " + lod + ')' + 1358 return fmt::format("itof(int(textureSize({}, {}){}))", sampler, lod,
1285 GetSwizzle(meta->element) + "))"; 1359 GetSwizzle(meta->element));
1286 case 2: 1360 case 2:
1287 return "0"; 1361 return "0";
1288 case 3: 1362 case 3:
1289 return "itof(textureQueryLevels(" + sampler + "))"; 1363 return fmt::format("itof(textureQueryLevels({}))", sampler);
1290 } 1364 }
1291 UNREACHABLE(); 1365 UNREACHABLE();
1292 return "0"; 1366 return "0";
@@ -1297,8 +1371,9 @@ private:
1297 ASSERT(meta); 1371 ASSERT(meta);
1298 1372
1299 if (meta->element < 2) { 1373 if (meta->element < 2) {
1300 return "itof(int((" + GenerateTexture(operation, "QueryLod", {}) + " * vec2(256))" + 1374 return fmt::format("itof(int(({} * vec2(256)){}))",
1301 GetSwizzle(meta->element) + "))"; 1375 GenerateTexture(operation, "QueryLod", {}),
1376 GetSwizzle(meta->element));
1302 } 1377 }
1303 return "0"; 1378 return "0";
1304 } 1379 }
@@ -1337,7 +1412,7 @@ private:
1337 const auto target = std::get_if<ImmediateNode>(operation[0]); 1412 const auto target = std::get_if<ImmediateNode>(operation[0]);
1338 UNIMPLEMENTED_IF(!target); 1413 UNIMPLEMENTED_IF(!target);
1339 1414
1340 code.AddLine(fmt::format("jmp_to = 0x{:x}u;", target->GetValue())); 1415 code.AddLine("jmp_to = 0x{:x}u;", target->GetValue());
1341 code.AddLine("break;"); 1416 code.AddLine("break;");
1342 return {}; 1417 return {};
1343 } 1418 }
@@ -1346,7 +1421,7 @@ private:
1346 const auto target = std::get_if<ImmediateNode>(operation[0]); 1421 const auto target = std::get_if<ImmediateNode>(operation[0]);
1347 UNIMPLEMENTED_IF(!target); 1422 UNIMPLEMENTED_IF(!target);
1348 1423
1349 code.AddLine(fmt::format("flow_stack[flow_stack_top++] = 0x{:x}u;", target->GetValue())); 1424 code.AddLine("flow_stack[flow_stack_top++] = 0x{:x}u;", target->GetValue());
1350 return {}; 1425 return {};
1351 } 1426 }
1352 1427
@@ -1372,7 +1447,7 @@ private:
1372 1447
1373 UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented"); 1448 UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented");
1374 1449
1375 code.AddLine("if (alpha_test[0] != 0) {"); 1450 code.AddLine("if (alpha_test[0] != 0) {{");
1376 ++code.scope; 1451 ++code.scope;
1377 // We start on the register containing the alpha value in the first RT. 1452 // We start on the register containing the alpha value in the first RT.
1378 u32 current_reg = 3; 1453 u32 current_reg = 3;
@@ -1383,13 +1458,12 @@ private:
1383 header.ps.IsColorComponentOutputEnabled(render_target, 1) || 1458 header.ps.IsColorComponentOutputEnabled(render_target, 1) ||
1384 header.ps.IsColorComponentOutputEnabled(render_target, 2) || 1459 header.ps.IsColorComponentOutputEnabled(render_target, 2) ||
1385 header.ps.IsColorComponentOutputEnabled(render_target, 3)) { 1460 header.ps.IsColorComponentOutputEnabled(render_target, 3)) {
1386 code.AddLine( 1461 code.AddLine("if (!AlphaFunc({})) discard;", SafeGetRegister(current_reg));
1387 fmt::format("if (!AlphaFunc({})) discard;", SafeGetRegister(current_reg)));
1388 current_reg += 4; 1462 current_reg += 4;
1389 } 1463 }
1390 } 1464 }
1391 --code.scope; 1465 --code.scope;
1392 code.AddLine('}'); 1466 code.AddLine("}}");
1393 1467
1394 // Write the color outputs using the data in the shader registers, disabled 1468 // Write the color outputs using the data in the shader registers, disabled
1395 // rendertargets/components are skipped in the register assignment. 1469 // rendertargets/components are skipped in the register assignment.
@@ -1398,8 +1472,8 @@ private:
1398 // TODO(Subv): Figure out how dual-source blending is configured in the Switch. 1472 // TODO(Subv): Figure out how dual-source blending is configured in the Switch.
1399 for (u32 component = 0; component < 4; ++component) { 1473 for (u32 component = 0; component < 4; ++component) {
1400 if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { 1474 if (header.ps.IsColorComponentOutputEnabled(render_target, component)) {
1401 code.AddLine(fmt::format("FragColor{}[{}] = {};", render_target, component, 1475 code.AddLine("FragColor{}[{}] = {};", render_target, component,
1402 SafeGetRegister(current_reg))); 1476 SafeGetRegister(current_reg));
1403 ++current_reg; 1477 ++current_reg;
1404 } 1478 }
1405 } 1479 }
@@ -1408,7 +1482,7 @@ private:
1408 if (header.ps.omap.depth) { 1482 if (header.ps.omap.depth) {
1409 // The depth output is always 2 registers after the last color output, and current_reg 1483 // The depth output is always 2 registers after the last color output, and current_reg
1410 // already contains one past the last color register. 1484 // already contains one past the last color register.
1411 code.AddLine("gl_FragDepth = " + SafeGetRegister(current_reg + 1) + ';'); 1485 code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1));
1412 } 1486 }
1413 1487
1414 code.AddLine("return;"); 1488 code.AddLine("return;");
@@ -1418,11 +1492,11 @@ private:
1418 std::string Discard(Operation operation) { 1492 std::string Discard(Operation operation) {
1419 // Enclose "discard" in a conditional, so that GLSL compilation does not complain 1493 // Enclose "discard" in a conditional, so that GLSL compilation does not complain
1420 // about unexecuted instructions that may follow this. 1494 // about unexecuted instructions that may follow this.
1421 code.AddLine("if (true) {"); 1495 code.AddLine("if (true) {{");
1422 ++code.scope; 1496 ++code.scope;
1423 code.AddLine("discard;"); 1497 code.AddLine("discard;");
1424 --code.scope; 1498 --code.scope;
1425 code.AddLine("}"); 1499 code.AddLine("}}");
1426 return {}; 1500 return {};
1427 } 1501 }
1428 1502
@@ -1452,6 +1526,16 @@ private:
1452 return "uintBitsToFloat(config_pack[2])"; 1526 return "uintBitsToFloat(config_pack[2])";
1453 } 1527 }
1454 1528
1529 template <u32 element>
1530 std::string LocalInvocationId(Operation) {
1531 return "utof(gl_LocalInvocationID"s + GetSwizzle(element) + ')';
1532 }
1533
1534 template <u32 element>
1535 std::string WorkGroupId(Operation) {
1536 return "utof(gl_WorkGroupID"s + GetSwizzle(element) + ')';
1537 }
1538
1455 static constexpr OperationDecompilersArray operation_decompilers = { 1539 static constexpr OperationDecompilersArray operation_decompilers = {
1456 &GLSLDecompiler::Assign, 1540 &GLSLDecompiler::Assign,
1457 1541
@@ -1591,6 +1675,12 @@ private:
1591 &GLSLDecompiler::EndPrimitive, 1675 &GLSLDecompiler::EndPrimitive,
1592 1676
1593 &GLSLDecompiler::YNegate, 1677 &GLSLDecompiler::YNegate,
1678 &GLSLDecompiler::LocalInvocationId<0>,
1679 &GLSLDecompiler::LocalInvocationId<1>,
1680 &GLSLDecompiler::LocalInvocationId<2>,
1681 &GLSLDecompiler::WorkGroupId<0>,
1682 &GLSLDecompiler::WorkGroupId<1>,
1683 &GLSLDecompiler::WorkGroupId<2>,
1594 }; 1684 };
1595 1685
1596 std::string GetRegister(u32 index) const { 1686 std::string GetRegister(u32 index) const {
@@ -1602,15 +1692,11 @@ private:
1602 } 1692 }
1603 1693
1604 std::string GetInputAttribute(Attribute::Index attribute) const { 1694 std::string GetInputAttribute(Attribute::Index attribute) const {
1605 const auto index{static_cast<u32>(attribute) - 1695 return GetDeclarationWithSuffix(GetGenericAttributeIndex(attribute), "input_attr");
1606 static_cast<u32>(Attribute::Index::Attribute_0)};
1607 return GetDeclarationWithSuffix(index, "input_attr");
1608 } 1696 }
1609 1697
1610 std::string GetOutputAttribute(Attribute::Index attribute) const { 1698 std::string GetOutputAttribute(Attribute::Index attribute) const {
1611 const auto index{static_cast<u32>(attribute) - 1699 return GetDeclarationWithSuffix(GetGenericAttributeIndex(attribute), "output_attr");
1612 static_cast<u32>(Attribute::Index::Attribute_0)};
1613 return GetDeclarationWithSuffix(index, "output_attr");
1614 } 1700 }
1615 1701
1616 std::string GetConstBuffer(u32 index) const { 1702 std::string GetConstBuffer(u32 index) const {
@@ -1640,7 +1726,7 @@ private:
1640 const auto index = static_cast<u32>(flag); 1726 const auto index = static_cast<u32>(flag);
1641 ASSERT(index < static_cast<u32>(InternalFlag::Amount)); 1727 ASSERT(index < static_cast<u32>(InternalFlag::Amount));
1642 1728
1643 return std::string(InternalFlagNames[index]) + '_' + suffix; 1729 return fmt::format("{}_{}", InternalFlagNames[index], suffix);
1644 } 1730 }
1645 1731
1646 std::string GetSampler(const Sampler& sampler) const { 1732 std::string GetSampler(const Sampler& sampler) const {
@@ -1648,7 +1734,20 @@ private:
1648 } 1734 }
1649 1735
1650 std::string GetDeclarationWithSuffix(u32 index, const std::string& name) const { 1736 std::string GetDeclarationWithSuffix(u32 index, const std::string& name) const {
1651 return name + '_' + std::to_string(index) + '_' + suffix; 1737 return fmt::format("{}_{}_{}", name, index, suffix);
1738 }
1739
1740 u32 GetNumPhysicalInputAttributes() const {
1741 return stage == ShaderStage::Vertex ? GetNumPhysicalAttributes() : GetNumPhysicalVaryings();
1742 }
1743
1744 u32 GetNumPhysicalAttributes() const {
1745 return std::min<u32>(device.GetMaxVertexAttributes(), Maxwell::NumVertexAttributes);
1746 }
1747
1748 u32 GetNumPhysicalVaryings() const {
1749 return std::min<u32>(device.GetMaxVaryings() - GENERIC_VARYING_START_LOCATION,
1750 Maxwell::NumVaryings);
1652 } 1751 }
1653 1752
1654 const Device& device; 1753 const Device& device;
@@ -1663,24 +1762,25 @@ private:
1663} // Anonymous namespace 1762} // Anonymous namespace
1664 1763
1665std::string GetCommonDeclarations() { 1764std::string GetCommonDeclarations() {
1666 const auto cbuf = std::to_string(MAX_CONSTBUFFER_ELEMENTS); 1765 return fmt::format(
1667 return "#define MAX_CONSTBUFFER_ELEMENTS " + cbuf + "\n" + 1766 "#define MAX_CONSTBUFFER_ELEMENTS {}\n"
1668 "#define ftoi floatBitsToInt\n" 1767 "#define ftoi floatBitsToInt\n"
1669 "#define ftou floatBitsToUint\n" 1768 "#define ftou floatBitsToUint\n"
1670 "#define itof intBitsToFloat\n" 1769 "#define itof intBitsToFloat\n"
1671 "#define utof uintBitsToFloat\n\n" 1770 "#define utof uintBitsToFloat\n\n"
1672 "float fromHalf2(vec2 pair) {\n" 1771 "float fromHalf2(vec2 pair) {{\n"
1673 " return utof(packHalf2x16(pair));\n" 1772 " return utof(packHalf2x16(pair));\n"
1674 "}\n\n" 1773 "}}\n\n"
1675 "vec2 toHalf2(float value) {\n" 1774 "vec2 toHalf2(float value) {{\n"
1676 " return unpackHalf2x16(ftou(value));\n" 1775 " return unpackHalf2x16(ftou(value));\n"
1677 "}\n\n" 1776 "}}\n\n"
1678 "bvec2 halfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {\n" 1777 "bvec2 halfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{\n"
1679 " bvec2 is_nan1 = isnan(pair1);\n" 1778 " bvec2 is_nan1 = isnan(pair1);\n"
1680 " bvec2 is_nan2 = isnan(pair2);\n" 1779 " bvec2 is_nan2 = isnan(pair2);\n"
1681 " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || " 1780 " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || "
1682 "is_nan2.y);\n" 1781 "is_nan2.y);\n"
1683 "}\n"; 1782 "}}\n",
1783 MAX_CONSTBUFFER_ELEMENTS);
1684} 1784}
1685 1785
1686ProgramResult Decompile(const Device& device, const ShaderIR& ir, Maxwell::ShaderStage stage, 1786ProgramResult Decompile(const Device& device, const ShaderIR& ir, Maxwell::ShaderStage stage,
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
index 53752b38d..ee4a45ca2 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -106,6 +106,8 @@ bool ShaderDiskCacheRaw::Save(FileUtil::IOFile& file) const {
106 106
107ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL(Core::System& system) : system{system} {} 107ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL(Core::System& system) : system{system} {}
108 108
109ShaderDiskCacheOpenGL::~ShaderDiskCacheOpenGL() = default;
110
109std::optional<std::pair<std::vector<ShaderDiskCacheRaw>, std::vector<ShaderDiskCacheUsage>>> 111std::optional<std::pair<std::vector<ShaderDiskCacheRaw>, std::vector<ShaderDiskCacheUsage>>>
110ShaderDiskCacheOpenGL::LoadTransferable() { 112ShaderDiskCacheOpenGL::LoadTransferable() {
111 // Skip games without title id 113 // Skip games without title id
@@ -177,11 +179,11 @@ ShaderDiskCacheOpenGL::LoadTransferable() {
177 return {}; 179 return {};
178 } 180 }
179 } 181 }
182
180 return {{raws, usages}}; 183 return {{raws, usages}};
181} 184}
182 185
183std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, 186std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>
184 std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>>
185ShaderDiskCacheOpenGL::LoadPrecompiled() { 187ShaderDiskCacheOpenGL::LoadPrecompiled() {
186 if (!IsUsable()) 188 if (!IsUsable())
187 return {}; 189 return {};
@@ -205,62 +207,66 @@ ShaderDiskCacheOpenGL::LoadPrecompiled() {
205 return *result; 207 return *result;
206} 208}
207 209
208std::optional<std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, 210std::optional<std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>>
209 std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>>>
210ShaderDiskCacheOpenGL::LoadPrecompiledFile(FileUtil::IOFile& file) { 211ShaderDiskCacheOpenGL::LoadPrecompiledFile(FileUtil::IOFile& file) {
212 // Read compressed file from disk and decompress to virtual precompiled cache file
213 std::vector<u8> compressed(file.GetSize());
214 file.ReadBytes(compressed.data(), compressed.size());
215 const std::vector<u8> decompressed = Common::Compression::DecompressDataZSTD(compressed);
216 SaveArrayToPrecompiled(decompressed.data(), decompressed.size());
217 precompiled_cache_virtual_file_offset = 0;
218
211 ShaderCacheVersionHash file_hash{}; 219 ShaderCacheVersionHash file_hash{};
212 if (file.ReadArray(file_hash.data(), file_hash.size()) != file_hash.size()) { 220 if (!LoadArrayFromPrecompiled(file_hash.data(), file_hash.size())) {
221 precompiled_cache_virtual_file_offset = 0;
213 return {}; 222 return {};
214 } 223 }
215 if (GetShaderCacheVersionHash() != file_hash) { 224 if (GetShaderCacheVersionHash() != file_hash) {
216 LOG_INFO(Render_OpenGL, "Precompiled cache is from another version of the emulator"); 225 LOG_INFO(Render_OpenGL, "Precompiled cache is from another version of the emulator");
226 precompiled_cache_virtual_file_offset = 0;
217 return {}; 227 return {};
218 } 228 }
219 229
220 std::unordered_map<u64, ShaderDiskCacheDecompiled> decompiled; 230 std::unordered_map<u64, ShaderDiskCacheDecompiled> decompiled;
221 std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump> dumps; 231 ShaderDumpsMap dumps;
222 while (file.Tell() < file.GetSize()) { 232 while (precompiled_cache_virtual_file_offset < precompiled_cache_virtual_file.GetSize()) {
223 PrecompiledEntryKind kind{}; 233 PrecompiledEntryKind kind{};
224 if (file.ReadBytes(&kind, sizeof(u32)) != sizeof(u32)) { 234 if (!LoadObjectFromPrecompiled(kind)) {
225 return {}; 235 return {};
226 } 236 }
227 237
228 switch (kind) { 238 switch (kind) {
229 case PrecompiledEntryKind::Decompiled: { 239 case PrecompiledEntryKind::Decompiled: {
230 u64 unique_identifier{}; 240 u64 unique_identifier{};
231 if (file.ReadBytes(&unique_identifier, sizeof(u64)) != sizeof(u64)) 241 if (!LoadObjectFromPrecompiled(unique_identifier)) {
232 return {}; 242 return {};
243 }
233 244
234 const auto entry = LoadDecompiledEntry(file); 245 auto entry = LoadDecompiledEntry();
235 if (!entry) 246 if (!entry) {
236 return {}; 247 return {};
248 }
237 decompiled.insert({unique_identifier, std::move(*entry)}); 249 decompiled.insert({unique_identifier, std::move(*entry)});
238 break; 250 break;
239 } 251 }
240 case PrecompiledEntryKind::Dump: { 252 case PrecompiledEntryKind::Dump: {
241 ShaderDiskCacheUsage usage; 253 ShaderDiskCacheUsage usage;
242 if (file.ReadBytes(&usage, sizeof(usage)) != sizeof(usage)) 254 if (!LoadObjectFromPrecompiled(usage)) {
243 return {}; 255 return {};
256 }
244 257
245 ShaderDiskCacheDump dump; 258 ShaderDiskCacheDump dump;
246 if (file.ReadBytes(&dump.binary_format, sizeof(u32)) != sizeof(u32)) 259 if (!LoadObjectFromPrecompiled(dump.binary_format)) {
247 return {};
248
249 u32 binary_length{};
250 u32 compressed_size{};
251 if (file.ReadBytes(&binary_length, sizeof(u32)) != sizeof(u32) ||
252 file.ReadBytes(&compressed_size, sizeof(u32)) != sizeof(u32)) {
253 return {}; 260 return {};
254 } 261 }
255 262
256 std::vector<u8> compressed_binary(compressed_size); 263 u32 binary_length{};
257 if (file.ReadArray(compressed_binary.data(), compressed_binary.size()) != 264 if (!LoadObjectFromPrecompiled(binary_length)) {
258 compressed_binary.size()) {
259 return {}; 265 return {};
260 } 266 }
261 267
262 dump.binary = Common::Compression::DecompressDataZSTD(compressed_binary); 268 dump.binary.resize(binary_length);
263 if (dump.binary.empty()) { 269 if (!LoadArrayFromPrecompiled(dump.binary.data(), dump.binary.size())) {
264 return {}; 270 return {};
265 } 271 }
266 272
@@ -274,154 +280,151 @@ ShaderDiskCacheOpenGL::LoadPrecompiledFile(FileUtil::IOFile& file) {
274 return {{decompiled, dumps}}; 280 return {{decompiled, dumps}};
275} 281}
276 282
277std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEntry( 283std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEntry() {
278 FileUtil::IOFile& file) {
279 u32 code_size{}; 284 u32 code_size{};
280 u32 compressed_code_size{}; 285 if (!LoadObjectFromPrecompiled(code_size)) {
281 if (file.ReadBytes(&code_size, sizeof(u32)) != sizeof(u32) ||
282 file.ReadBytes(&compressed_code_size, sizeof(u32)) != sizeof(u32)) {
283 return {}; 286 return {};
284 } 287 }
285 288
286 std::vector<u8> compressed_code(compressed_code_size); 289 std::string code(code_size, '\0');
287 if (file.ReadArray(compressed_code.data(), compressed_code.size()) != compressed_code.size()) { 290 if (!LoadArrayFromPrecompiled(code.data(), code.size())) {
288 return {}; 291 return {};
289 } 292 }
290 293
291 const std::vector<u8> code = Common::Compression::DecompressDataZSTD(compressed_code);
292 if (code.empty()) {
293 return {};
294 }
295 ShaderDiskCacheDecompiled entry; 294 ShaderDiskCacheDecompiled entry;
296 entry.code = std::string(reinterpret_cast<const char*>(code.data()), code_size); 295 entry.code = std::move(code);
297 296
298 u32 const_buffers_count{}; 297 u32 const_buffers_count{};
299 if (file.ReadBytes(&const_buffers_count, sizeof(u32)) != sizeof(u32)) 298 if (!LoadObjectFromPrecompiled(const_buffers_count)) {
300 return {}; 299 return {};
300 }
301
301 for (u32 i = 0; i < const_buffers_count; ++i) { 302 for (u32 i = 0; i < const_buffers_count; ++i) {
302 u32 max_offset{}; 303 u32 max_offset{};
303 u32 index{}; 304 u32 index{};
304 u8 is_indirect{}; 305 bool is_indirect{};
305 if (file.ReadBytes(&max_offset, sizeof(u32)) != sizeof(u32) || 306 if (!LoadObjectFromPrecompiled(max_offset) || !LoadObjectFromPrecompiled(index) ||
306 file.ReadBytes(&index, sizeof(u32)) != sizeof(u32) || 307 !LoadObjectFromPrecompiled(is_indirect)) {
307 file.ReadBytes(&is_indirect, sizeof(u8)) != sizeof(u8)) {
308 return {}; 308 return {};
309 } 309 }
310 entry.entries.const_buffers.emplace_back(max_offset, is_indirect != 0, index); 310 entry.entries.const_buffers.emplace_back(max_offset, is_indirect, index);
311 } 311 }
312 312
313 u32 samplers_count{}; 313 u32 samplers_count{};
314 if (file.ReadBytes(&samplers_count, sizeof(u32)) != sizeof(u32)) 314 if (!LoadObjectFromPrecompiled(samplers_count)) {
315 return {}; 315 return {};
316 }
317
316 for (u32 i = 0; i < samplers_count; ++i) { 318 for (u32 i = 0; i < samplers_count; ++i) {
317 u64 offset{}; 319 u64 offset{};
318 u64 index{}; 320 u64 index{};
319 u32 type{}; 321 u32 type{};
320 u8 is_array{}; 322 bool is_array{};
321 u8 is_shadow{}; 323 bool is_shadow{};
322 u8 is_bindless{}; 324 bool is_bindless{};
323 if (file.ReadBytes(&offset, sizeof(u64)) != sizeof(u64) || 325 if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) ||
324 file.ReadBytes(&index, sizeof(u64)) != sizeof(u64) || 326 !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_array) ||
325 file.ReadBytes(&type, sizeof(u32)) != sizeof(u32) || 327 !LoadObjectFromPrecompiled(is_shadow) || !LoadObjectFromPrecompiled(is_bindless)) {
326 file.ReadBytes(&is_array, sizeof(u8)) != sizeof(u8) ||
327 file.ReadBytes(&is_shadow, sizeof(u8)) != sizeof(u8) ||
328 file.ReadBytes(&is_bindless, sizeof(u8)) != sizeof(u8)) {
329 return {}; 328 return {};
330 } 329 }
331 entry.entries.samplers.emplace_back(static_cast<std::size_t>(offset), 330 entry.entries.samplers.emplace_back(
332 static_cast<std::size_t>(index), 331 static_cast<std::size_t>(offset), static_cast<std::size_t>(index),
333 static_cast<Tegra::Shader::TextureType>(type), 332 static_cast<Tegra::Shader::TextureType>(type), is_array, is_shadow, is_bindless);
334 is_array != 0, is_shadow != 0, is_bindless != 0);
335 } 333 }
336 334
337 u32 global_memory_count{}; 335 u32 global_memory_count{};
338 if (file.ReadBytes(&global_memory_count, sizeof(u32)) != sizeof(u32)) 336 if (!LoadObjectFromPrecompiled(global_memory_count)) {
339 return {}; 337 return {};
338 }
339
340 for (u32 i = 0; i < global_memory_count; ++i) { 340 for (u32 i = 0; i < global_memory_count; ++i) {
341 u32 cbuf_index{}; 341 u32 cbuf_index{};
342 u32 cbuf_offset{}; 342 u32 cbuf_offset{};
343 u8 is_read{}; 343 bool is_read{};
344 u8 is_written{}; 344 bool is_written{};
345 if (file.ReadBytes(&cbuf_index, sizeof(u32)) != sizeof(u32) || 345 if (!LoadObjectFromPrecompiled(cbuf_index) || !LoadObjectFromPrecompiled(cbuf_offset) ||
346 file.ReadBytes(&cbuf_offset, sizeof(u32)) != sizeof(u32) || 346 !LoadObjectFromPrecompiled(is_read) || !LoadObjectFromPrecompiled(is_written)) {
347 file.ReadBytes(&is_read, sizeof(u8)) != sizeof(u8) ||
348 file.ReadBytes(&is_written, sizeof(u8)) != sizeof(u8)) {
349 return {}; 347 return {};
350 } 348 }
351 entry.entries.global_memory_entries.emplace_back(cbuf_index, cbuf_offset, is_read != 0, 349 entry.entries.global_memory_entries.emplace_back(cbuf_index, cbuf_offset, is_read,
352 is_written != 0); 350 is_written);
353 } 351 }
354 352
355 for (auto& clip_distance : entry.entries.clip_distances) { 353 for (auto& clip_distance : entry.entries.clip_distances) {
356 u8 clip_distance_raw{}; 354 if (!LoadObjectFromPrecompiled(clip_distance)) {
357 if (file.ReadBytes(&clip_distance_raw, sizeof(u8)) != sizeof(u8))
358 return {}; 355 return {};
359 clip_distance = clip_distance_raw != 0; 356 }
360 } 357 }
361 358
362 u64 shader_length{}; 359 u64 shader_length{};
363 if (file.ReadBytes(&shader_length, sizeof(u64)) != sizeof(u64)) 360 if (!LoadObjectFromPrecompiled(shader_length)) {
364 return {}; 361 return {};
362 }
363
365 entry.entries.shader_length = static_cast<std::size_t>(shader_length); 364 entry.entries.shader_length = static_cast<std::size_t>(shader_length);
366 365
367 return entry; 366 return entry;
368} 367}
369 368
370bool ShaderDiskCacheOpenGL::SaveDecompiledFile(FileUtil::IOFile& file, u64 unique_identifier, 369bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std::string& code,
371 const std::string& code,
372 const std::vector<u8>& compressed_code,
373 const GLShader::ShaderEntries& entries) { 370 const GLShader::ShaderEntries& entries) {
374 if (file.WriteObject(static_cast<u32>(PrecompiledEntryKind::Decompiled)) != 1 || 371 if (!SaveObjectToPrecompiled(static_cast<u32>(PrecompiledEntryKind::Decompiled)) ||
375 file.WriteObject(unique_identifier) != 1 || 372 !SaveObjectToPrecompiled(unique_identifier) ||
376 file.WriteObject(static_cast<u32>(code.size())) != 1 || 373 !SaveObjectToPrecompiled(static_cast<u32>(code.size())) ||
377 file.WriteObject(static_cast<u32>(compressed_code.size())) != 1 || 374 !SaveArrayToPrecompiled(code.data(), code.size())) {
378 file.WriteArray(compressed_code.data(), compressed_code.size()) != compressed_code.size()) {
379 return false; 375 return false;
380 } 376 }
381 377
382 if (file.WriteObject(static_cast<u32>(entries.const_buffers.size())) != 1) 378 if (!SaveObjectToPrecompiled(static_cast<u32>(entries.const_buffers.size()))) {
383 return false; 379 return false;
380 }
384 for (const auto& cbuf : entries.const_buffers) { 381 for (const auto& cbuf : entries.const_buffers) {
385 if (file.WriteObject(static_cast<u32>(cbuf.GetMaxOffset())) != 1 || 382 if (!SaveObjectToPrecompiled(static_cast<u32>(cbuf.GetMaxOffset())) ||
386 file.WriteObject(static_cast<u32>(cbuf.GetIndex())) != 1 || 383 !SaveObjectToPrecompiled(static_cast<u32>(cbuf.GetIndex())) ||
387 file.WriteObject(static_cast<u8>(cbuf.IsIndirect() ? 1 : 0)) != 1) { 384 !SaveObjectToPrecompiled(cbuf.IsIndirect())) {
388 return false; 385 return false;
389 } 386 }
390 } 387 }
391 388
392 if (file.WriteObject(static_cast<u32>(entries.samplers.size())) != 1) 389 if (!SaveObjectToPrecompiled(static_cast<u32>(entries.samplers.size()))) {
393 return false; 390 return false;
391 }
394 for (const auto& sampler : entries.samplers) { 392 for (const auto& sampler : entries.samplers) {
395 if (file.WriteObject(static_cast<u64>(sampler.GetOffset())) != 1 || 393 if (!SaveObjectToPrecompiled(static_cast<u64>(sampler.GetOffset())) ||
396 file.WriteObject(static_cast<u64>(sampler.GetIndex())) != 1 || 394 !SaveObjectToPrecompiled(static_cast<u64>(sampler.GetIndex())) ||
397 file.WriteObject(static_cast<u32>(sampler.GetType())) != 1 || 395 !SaveObjectToPrecompiled(static_cast<u32>(sampler.GetType())) ||
398 file.WriteObject(static_cast<u8>(sampler.IsArray() ? 1 : 0)) != 1 || 396 !SaveObjectToPrecompiled(sampler.IsArray()) ||
399 file.WriteObject(static_cast<u8>(sampler.IsShadow() ? 1 : 0)) != 1 || 397 !SaveObjectToPrecompiled(sampler.IsShadow()) ||
400 file.WriteObject(static_cast<u8>(sampler.IsBindless() ? 1 : 0)) != 1) { 398 !SaveObjectToPrecompiled(sampler.IsBindless())) {
401 return false; 399 return false;
402 } 400 }
403 } 401 }
404 402
405 if (file.WriteObject(static_cast<u32>(entries.global_memory_entries.size())) != 1) 403 if (!SaveObjectToPrecompiled(static_cast<u32>(entries.global_memory_entries.size()))) {
406 return false; 404 return false;
405 }
407 for (const auto& gmem : entries.global_memory_entries) { 406 for (const auto& gmem : entries.global_memory_entries) {
408 if (file.WriteObject(static_cast<u32>(gmem.GetCbufIndex())) != 1 || 407 if (!SaveObjectToPrecompiled(static_cast<u32>(gmem.GetCbufIndex())) ||
409 file.WriteObject(static_cast<u32>(gmem.GetCbufOffset())) != 1 || 408 !SaveObjectToPrecompiled(static_cast<u32>(gmem.GetCbufOffset())) ||
410 file.WriteObject(static_cast<u8>(gmem.IsRead() ? 1 : 0)) != 1 || 409 !SaveObjectToPrecompiled(gmem.IsRead()) || !SaveObjectToPrecompiled(gmem.IsWritten())) {
411 file.WriteObject(static_cast<u8>(gmem.IsWritten() ? 1 : 0)) != 1) {
412 return false; 410 return false;
413 } 411 }
414 } 412 }
415 413
416 for (const bool clip_distance : entries.clip_distances) { 414 for (const bool clip_distance : entries.clip_distances) {
417 if (file.WriteObject(static_cast<u8>(clip_distance ? 1 : 0)) != 1) 415 if (!SaveObjectToPrecompiled(clip_distance)) {
418 return false; 416 return false;
417 }
419 } 418 }
420 419
421 return file.WriteObject(static_cast<u64>(entries.shader_length)) == 1; 420 if (!SaveObjectToPrecompiled(static_cast<u64>(entries.shader_length))) {
421 return false;
422 }
423
424 return true;
422} 425}
423 426
424void ShaderDiskCacheOpenGL::InvalidateTransferable() const { 427void ShaderDiskCacheOpenGL::InvalidateTransferable() {
425 if (!FileUtil::Delete(GetTransferablePath())) { 428 if (!FileUtil::Delete(GetTransferablePath())) {
426 LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}", 429 LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}",
427 GetTransferablePath()); 430 GetTransferablePath());
@@ -429,7 +432,10 @@ void ShaderDiskCacheOpenGL::InvalidateTransferable() const {
429 InvalidatePrecompiled(); 432 InvalidatePrecompiled();
430} 433}
431 434
432void ShaderDiskCacheOpenGL::InvalidatePrecompiled() const { 435void ShaderDiskCacheOpenGL::InvalidatePrecompiled() {
436 // Clear virtaul precompiled cache file
437 precompiled_cache_virtual_file.Resize(0);
438
433 if (!FileUtil::Delete(GetPrecompiledPath())) { 439 if (!FileUtil::Delete(GetPrecompiledPath())) {
434 LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath()); 440 LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath());
435 } 441 }
@@ -465,7 +471,10 @@ void ShaderDiskCacheOpenGL::SaveUsage(const ShaderDiskCacheUsage& usage) {
465 ASSERT_MSG(it != transferable.end(), "Saving shader usage without storing raw previously"); 471 ASSERT_MSG(it != transferable.end(), "Saving shader usage without storing raw previously");
466 472
467 auto& usages{it->second}; 473 auto& usages{it->second};
468 ASSERT(usages.find(usage) == usages.end()); 474 if (usages.find(usage) != usages.end()) {
475 // Skip this variant since the shader is already stored.
476 return;
477 }
469 usages.insert(usage); 478 usages.insert(usage);
470 479
471 FileUtil::IOFile file = AppendTransferableFile(); 480 FileUtil::IOFile file = AppendTransferableFile();
@@ -485,22 +494,13 @@ void ShaderDiskCacheOpenGL::SaveDecompiled(u64 unique_identifier, const std::str
485 if (!IsUsable()) 494 if (!IsUsable())
486 return; 495 return;
487 496
488 const std::vector<u8> compressed_code{Common::Compression::CompressDataZSTDDefault( 497 if (precompiled_cache_virtual_file.GetSize() == 0) {
489 reinterpret_cast<const u8*>(code.data()), code.size())}; 498 SavePrecompiledHeaderToVirtualPrecompiledCache();
490 if (compressed_code.empty()) {
491 LOG_ERROR(Render_OpenGL, "Failed to compress GLSL code - skipping shader {:016x}",
492 unique_identifier);
493 return;
494 } 499 }
495 500
496 FileUtil::IOFile file = AppendPrecompiledFile(); 501 if (!SaveDecompiledFile(unique_identifier, code, entries)) {
497 if (!file.IsOpen())
498 return;
499
500 if (!SaveDecompiledFile(file, unique_identifier, code, compressed_code, entries)) {
501 LOG_ERROR(Render_OpenGL, 502 LOG_ERROR(Render_OpenGL,
502 "Failed to save decompiled entry to the precompiled file - removing"); 503 "Failed to save decompiled entry to the precompiled file - removing");
503 file.Close();
504 InvalidatePrecompiled(); 504 InvalidatePrecompiled();
505 } 505 }
506} 506}
@@ -516,28 +516,13 @@ void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint p
516 std::vector<u8> binary(binary_length); 516 std::vector<u8> binary(binary_length);
517 glGetProgramBinary(program, binary_length, nullptr, &binary_format, binary.data()); 517 glGetProgramBinary(program, binary_length, nullptr, &binary_format, binary.data());
518 518
519 const std::vector<u8> compressed_binary = 519 if (!SaveObjectToPrecompiled(static_cast<u32>(PrecompiledEntryKind::Dump)) ||
520 Common::Compression::CompressDataZSTDDefault(binary.data(), binary.size()); 520 !SaveObjectToPrecompiled(usage) ||
521 521 !SaveObjectToPrecompiled(static_cast<u32>(binary_format)) ||
522 if (compressed_binary.empty()) { 522 !SaveObjectToPrecompiled(static_cast<u32>(binary_length)) ||
523 LOG_ERROR(Render_OpenGL, "Failed to compress binary program in shader={:016x}", 523 !SaveArrayToPrecompiled(binary.data(), binary.size())) {
524 usage.unique_identifier);
525 return;
526 }
527
528 FileUtil::IOFile file = AppendPrecompiledFile();
529 if (!file.IsOpen())
530 return;
531
532 if (file.WriteObject(static_cast<u32>(PrecompiledEntryKind::Dump)) != 1 ||
533 file.WriteObject(usage) != 1 || file.WriteObject(static_cast<u32>(binary_format)) != 1 ||
534 file.WriteObject(static_cast<u32>(binary_length)) != 1 ||
535 file.WriteObject(static_cast<u32>(compressed_binary.size())) != 1 ||
536 file.WriteArray(compressed_binary.data(), compressed_binary.size()) !=
537 compressed_binary.size()) {
538 LOG_ERROR(Render_OpenGL, "Failed to save binary program file in shader={:016x} - removing", 524 LOG_ERROR(Render_OpenGL, "Failed to save binary program file in shader={:016x} - removing",
539 usage.unique_identifier); 525 usage.unique_identifier);
540 file.Close();
541 InvalidatePrecompiled(); 526 InvalidatePrecompiled();
542 return; 527 return;
543 } 528 }
@@ -570,28 +555,33 @@ FileUtil::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const {
570 return file; 555 return file;
571} 556}
572 557
573FileUtil::IOFile ShaderDiskCacheOpenGL::AppendPrecompiledFile() const { 558void ShaderDiskCacheOpenGL::SavePrecompiledHeaderToVirtualPrecompiledCache() {
574 if (!EnsureDirectories()) 559 const auto hash{GetShaderCacheVersionHash()};
575 return {}; 560 if (!SaveArrayToPrecompiled(hash.data(), hash.size())) {
561 LOG_ERROR(
562 Render_OpenGL,
563 "Failed to write precompiled cache version hash to virtual precompiled cache file");
564 }
565}
566
567void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() {
568 precompiled_cache_virtual_file_offset = 0;
569 const std::vector<u8>& uncompressed = precompiled_cache_virtual_file.ReadAllBytes();
570 const std::vector<u8>& compressed =
571 Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size());
576 572
577 const auto precompiled_path{GetPrecompiledPath()}; 573 const auto precompiled_path{GetPrecompiledPath()};
578 const bool existed = FileUtil::Exists(precompiled_path); 574 FileUtil::IOFile file(precompiled_path, "wb");
579 575
580 FileUtil::IOFile file(precompiled_path, "ab");
581 if (!file.IsOpen()) { 576 if (!file.IsOpen()) {
582 LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path); 577 LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path);
583 return {}; 578 return;
584 } 579 }
585 580 if (file.WriteBytes(compressed.data(), compressed.size()) != compressed.size()) {
586 if (!existed || file.GetSize() == 0) { 581 LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}",
587 const auto hash{GetShaderCacheVersionHash()}; 582 precompiled_path);
588 if (file.WriteArray(hash.data(), hash.size()) != hash.size()) { 583 return;
589 LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version hash in path={}",
590 precompiled_path);
591 return {};
592 }
593 } 584 }
594 return file;
595} 585}
596 586
597bool ShaderDiskCacheOpenGL::EnsureDirectories() const { 587bool ShaderDiskCacheOpenGL::EnsureDirectories() const {
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
index 6be0c0547..ecd72ba58 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
@@ -16,6 +16,7 @@
16 16
17#include "common/assert.h" 17#include "common/assert.h"
18#include "common/common_types.h" 18#include "common/common_types.h"
19#include "core/file_sys/vfs_vector.h"
19#include "video_core/engines/maxwell_3d.h" 20#include "video_core/engines/maxwell_3d.h"
20#include "video_core/renderer_opengl/gl_shader_gen.h" 21#include "video_core/renderer_opengl/gl_shader_gen.h"
21 22
@@ -32,6 +33,11 @@ namespace OpenGL {
32using ProgramCode = std::vector<u64>; 33using ProgramCode = std::vector<u64>;
33using Maxwell = Tegra::Engines::Maxwell3D::Regs; 34using Maxwell = Tegra::Engines::Maxwell3D::Regs;
34 35
36struct ShaderDiskCacheUsage;
37struct ShaderDiskCacheDump;
38
39using ShaderDumpsMap = std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>;
40
35/// Allocated bindings used by an OpenGL shader program 41/// Allocated bindings used by an OpenGL shader program
36struct BaseBindings { 42struct BaseBindings {
37 u32 cbuf{}; 43 u32 cbuf{};
@@ -69,14 +75,14 @@ namespace std {
69 75
70template <> 76template <>
71struct hash<OpenGL::BaseBindings> { 77struct hash<OpenGL::BaseBindings> {
72 std::size_t operator()(const OpenGL::BaseBindings& bindings) const { 78 std::size_t operator()(const OpenGL::BaseBindings& bindings) const noexcept {
73 return bindings.cbuf | bindings.gmem << 8 | bindings.sampler << 16; 79 return bindings.cbuf | bindings.gmem << 8 | bindings.sampler << 16;
74 } 80 }
75}; 81};
76 82
77template <> 83template <>
78struct hash<OpenGL::ShaderDiskCacheUsage> { 84struct hash<OpenGL::ShaderDiskCacheUsage> {
79 std::size_t operator()(const OpenGL::ShaderDiskCacheUsage& usage) const { 85 std::size_t operator()(const OpenGL::ShaderDiskCacheUsage& usage) const noexcept {
80 return static_cast<std::size_t>(usage.unique_identifier) ^ 86 return static_cast<std::size_t>(usage.unique_identifier) ^
81 std::hash<OpenGL::BaseBindings>()(usage.bindings) ^ usage.primitive << 16; 87 std::hash<OpenGL::BaseBindings>()(usage.bindings) ^ usage.primitive << 16;
82 } 88 }
@@ -161,6 +167,7 @@ struct ShaderDiskCacheDump {
161class ShaderDiskCacheOpenGL { 167class ShaderDiskCacheOpenGL {
162public: 168public:
163 explicit ShaderDiskCacheOpenGL(Core::System& system); 169 explicit ShaderDiskCacheOpenGL(Core::System& system);
170 ~ShaderDiskCacheOpenGL();
164 171
165 /// Loads transferable cache. If file has a old version or on failure, it deletes the file. 172 /// Loads transferable cache. If file has a old version or on failure, it deletes the file.
166 std::optional<std::pair<std::vector<ShaderDiskCacheRaw>, std::vector<ShaderDiskCacheUsage>>> 173 std::optional<std::pair<std::vector<ShaderDiskCacheRaw>, std::vector<ShaderDiskCacheUsage>>>
@@ -172,10 +179,10 @@ public:
172 LoadPrecompiled(); 179 LoadPrecompiled();
173 180
174 /// Removes the transferable (and precompiled) cache file. 181 /// Removes the transferable (and precompiled) cache file.
175 void InvalidateTransferable() const; 182 void InvalidateTransferable();
176 183
177 /// Removes the precompiled cache file. 184 /// Removes the precompiled cache file and clears virtual precompiled cache file.
178 void InvalidatePrecompiled() const; 185 void InvalidatePrecompiled();
179 186
180 /// Saves a raw dump to the transferable file. Checks for collisions. 187 /// Saves a raw dump to the transferable file. Checks for collisions.
181 void SaveRaw(const ShaderDiskCacheRaw& entry); 188 void SaveRaw(const ShaderDiskCacheRaw& entry);
@@ -190,18 +197,21 @@ public:
190 /// Saves a dump entry to the precompiled file. Does not check for collisions. 197 /// Saves a dump entry to the precompiled file. Does not check for collisions.
191 void SaveDump(const ShaderDiskCacheUsage& usage, GLuint program); 198 void SaveDump(const ShaderDiskCacheUsage& usage, GLuint program);
192 199
200 /// Serializes virtual precompiled shader cache file to real file
201 void SaveVirtualPrecompiledFile();
202
193private: 203private:
194 /// Loads the transferable cache. Returns empty on failure. 204 /// Loads the transferable cache. Returns empty on failure.
195 std::optional<std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, 205 std::optional<std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>,
196 std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>>> 206 std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>>>
197 LoadPrecompiledFile(FileUtil::IOFile& file); 207 LoadPrecompiledFile(FileUtil::IOFile& file);
198 208
199 /// Loads a decompiled cache entry from the passed file. Returns empty on failure. 209 /// Loads a decompiled cache entry from m_precompiled_cache_virtual_file. Returns empty on
200 std::optional<ShaderDiskCacheDecompiled> LoadDecompiledEntry(FileUtil::IOFile& file); 210 /// failure.
211 std::optional<ShaderDiskCacheDecompiled> LoadDecompiledEntry();
201 212
202 /// Saves a decompiled entry to the passed file. Returns true on success. 213 /// Saves a decompiled entry to the passed file. Returns true on success.
203 bool SaveDecompiledFile(FileUtil::IOFile& file, u64 unique_identifier, const std::string& code, 214 bool SaveDecompiledFile(u64 unique_identifier, const std::string& code,
204 const std::vector<u8>& compressed_code,
205 const GLShader::ShaderEntries& entries); 215 const GLShader::ShaderEntries& entries);
206 216
207 /// Returns if the cache can be used 217 /// Returns if the cache can be used
@@ -210,8 +220,8 @@ private:
210 /// Opens current game's transferable file and write it's header if it doesn't exist 220 /// Opens current game's transferable file and write it's header if it doesn't exist
211 FileUtil::IOFile AppendTransferableFile() const; 221 FileUtil::IOFile AppendTransferableFile() const;
212 222
213 /// Opens current game's precompiled file and write it's header if it doesn't exist 223 /// Save precompiled header to precompiled_cache_in_memory
214 FileUtil::IOFile AppendPrecompiledFile() const; 224 void SavePrecompiledHeaderToVirtualPrecompiledCache();
215 225
216 /// Create shader disk cache directories. Returns true on success. 226 /// Create shader disk cache directories. Returns true on success.
217 bool EnsureDirectories() const; 227 bool EnsureDirectories() const;
@@ -234,12 +244,59 @@ private:
234 /// Get current game's title id 244 /// Get current game's title id
235 std::string GetTitleID() const; 245 std::string GetTitleID() const;
236 246
237 // Copre system 247 template <typename T>
248 bool SaveArrayToPrecompiled(const T* data, std::size_t length) {
249 const std::size_t write_length = precompiled_cache_virtual_file.WriteArray(
250 data, length, precompiled_cache_virtual_file_offset);
251 precompiled_cache_virtual_file_offset += write_length;
252 return write_length == sizeof(T) * length;
253 }
254
255 template <typename T>
256 bool LoadArrayFromPrecompiled(T* data, std::size_t length) {
257 const std::size_t read_length = precompiled_cache_virtual_file.ReadArray(
258 data, length, precompiled_cache_virtual_file_offset);
259 precompiled_cache_virtual_file_offset += read_length;
260 return read_length == sizeof(T) * length;
261 }
262
263 template <typename T>
264 bool SaveObjectToPrecompiled(const T& object) {
265 return SaveArrayToPrecompiled(&object, 1);
266 }
267
268 bool SaveObjectToPrecompiled(bool object) {
269 const auto value = static_cast<u8>(object);
270 return SaveArrayToPrecompiled(&value, 1);
271 }
272
273 template <typename T>
274 bool LoadObjectFromPrecompiled(T& object) {
275 return LoadArrayFromPrecompiled(&object, 1);
276 }
277
278 bool LoadObjectFromPrecompiled(bool& object) {
279 u8 value;
280 const bool read_ok = LoadArrayFromPrecompiled(&value, 1);
281 if (!read_ok) {
282 return false;
283 }
284
285 object = value != 0;
286 return true;
287 }
288
289 // Core system
238 Core::System& system; 290 Core::System& system;
239 // Stored transferable shaders 291 // Stored transferable shaders
240 std::map<u64, std::unordered_set<ShaderDiskCacheUsage>> transferable; 292 std::map<u64, std::unordered_set<ShaderDiskCacheUsage>> transferable;
293 // Stores whole precompiled cache which will be read from/saved to the precompiled cache file
294 FileSys::VectorVfsFile precompiled_cache_virtual_file;
295 // Stores the current offset of the precompiled cache file for IO purposes
296 std::size_t precompiled_cache_virtual_file_offset = 0;
297
241 // The cache has been loaded at boot 298 // The cache has been loaded at boot
242 bool tried_to_load{}; 299 bool tried_to_load{};
243}; 300};
244 301
245} // namespace OpenGL \ No newline at end of file 302} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 6abf948f8..d2bb705a9 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -19,8 +19,7 @@ static constexpr u32 PROGRAM_OFFSET{10};
19ProgramResult GenerateVertexShader(const Device& device, const ShaderSetup& setup) { 19ProgramResult GenerateVertexShader(const Device& device, const ShaderSetup& setup) {
20 const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); 20 const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
21 21
22 std::string out = "#extension GL_ARB_separate_shader_objects : enable\n\n"; 22 std::string out = "// Shader Unique Id: VS" + id + "\n\n";
23 out += "// Shader Unique Id: VS" + id + "\n\n";
24 out += GetCommonDeclarations(); 23 out += GetCommonDeclarations();
25 24
26 out += R"( 25 out += R"(
@@ -33,14 +32,14 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform vs_config {
33}; 32};
34 33
35)"; 34)";
36 ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET); 35 const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET);
37 ProgramResult program = 36 ProgramResult program =
38 Decompile(device, program_ir, Maxwell3D::Regs::ShaderStage::Vertex, "vertex"); 37 Decompile(device, program_ir, Maxwell3D::Regs::ShaderStage::Vertex, "vertex");
39 38
40 out += program.first; 39 out += program.first;
41 40
42 if (setup.IsDualProgram()) { 41 if (setup.IsDualProgram()) {
43 ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET); 42 const ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET);
44 ProgramResult program_b = 43 ProgramResult program_b =
45 Decompile(device, program_ir_b, Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b"); 44 Decompile(device, program_ir_b, Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b");
46 45
@@ -76,14 +75,13 @@ void main() {
76 } 75 }
77})"; 76})";
78 77
79 return {out, program.second}; 78 return {std::move(out), std::move(program.second)};
80} 79}
81 80
82ProgramResult GenerateGeometryShader(const Device& device, const ShaderSetup& setup) { 81ProgramResult GenerateGeometryShader(const Device& device, const ShaderSetup& setup) {
83 const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); 82 const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
84 83
85 std::string out = "#extension GL_ARB_separate_shader_objects : enable\n\n"; 84 std::string out = "// Shader Unique Id: GS" + id + "\n\n";
86 out += "// Shader Unique Id: GS" + id + "\n\n";
87 out += GetCommonDeclarations(); 85 out += GetCommonDeclarations();
88 86
89 out += R"( 87 out += R"(
@@ -97,7 +95,7 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform gs_config {
97}; 95};
98 96
99)"; 97)";
100 ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET); 98 const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET);
101 ProgramResult program = 99 ProgramResult program =
102 Decompile(device, program_ir, Maxwell3D::Regs::ShaderStage::Geometry, "geometry"); 100 Decompile(device, program_ir, Maxwell3D::Regs::ShaderStage::Geometry, "geometry");
103 out += program.first; 101 out += program.first;
@@ -107,14 +105,13 @@ void main() {
107 execute_geometry(); 105 execute_geometry();
108};)"; 106};)";
109 107
110 return {out, program.second}; 108 return {std::move(out), std::move(program.second)};
111} 109}
112 110
113ProgramResult GenerateFragmentShader(const Device& device, const ShaderSetup& setup) { 111ProgramResult GenerateFragmentShader(const Device& device, const ShaderSetup& setup) {
114 const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); 112 const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
115 113
116 std::string out = "#extension GL_ARB_separate_shader_objects : enable\n\n"; 114 std::string out = "// Shader Unique Id: FS" + id + "\n\n";
117 out += "// Shader Unique Id: FS" + id + "\n\n";
118 out += GetCommonDeclarations(); 115 out += GetCommonDeclarations();
119 116
120 out += R"( 117 out += R"(
@@ -160,7 +157,7 @@ bool AlphaFunc(in float value) {
160} 157}
161 158
162)"; 159)";
163 ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET); 160 const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET);
164 ProgramResult program = 161 ProgramResult program =
165 Decompile(device, program_ir, Maxwell3D::Regs::ShaderStage::Fragment, "fragment"); 162 Decompile(device, program_ir, Maxwell3D::Regs::ShaderStage::Fragment, "fragment");
166 163
@@ -172,7 +169,7 @@ void main() {
172} 169}
173 170
174)"; 171)";
175 return {out, program.second}; 172 return {std::move(out), std::move(program.second)};
176} 173}
177 174
178} // namespace OpenGL::GLShader 175} // namespace OpenGL::GLShader
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index a8833c06e..ed7b5cff0 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -27,8 +27,7 @@ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
27inline GLenum VertexType(Maxwell::VertexAttribute attrib) { 27inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
28 switch (attrib.type) { 28 switch (attrib.type) {
29 case Maxwell::VertexAttribute::Type::UnsignedInt: 29 case Maxwell::VertexAttribute::Type::UnsignedInt:
30 case Maxwell::VertexAttribute::Type::UnsignedNorm: { 30 case Maxwell::VertexAttribute::Type::UnsignedNorm:
31
32 switch (attrib.size) { 31 switch (attrib.size) {
33 case Maxwell::VertexAttribute::Size::Size_8: 32 case Maxwell::VertexAttribute::Size::Size_8:
34 case Maxwell::VertexAttribute::Size::Size_8_8: 33 case Maxwell::VertexAttribute::Size::Size_8_8:
@@ -47,16 +46,13 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
47 return GL_UNSIGNED_INT; 46 return GL_UNSIGNED_INT;
48 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 47 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
49 return GL_UNSIGNED_INT_2_10_10_10_REV; 48 return GL_UNSIGNED_INT_2_10_10_10_REV;
49 default:
50 LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
51 UNREACHABLE();
52 return {};
50 } 53 }
51
52 LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
53 UNREACHABLE();
54 return {};
55 }
56
57 case Maxwell::VertexAttribute::Type::SignedInt: 54 case Maxwell::VertexAttribute::Type::SignedInt:
58 case Maxwell::VertexAttribute::Type::SignedNorm: { 55 case Maxwell::VertexAttribute::Type::SignedNorm:
59
60 switch (attrib.size) { 56 switch (attrib.size) {
61 case Maxwell::VertexAttribute::Size::Size_8: 57 case Maxwell::VertexAttribute::Size::Size_8:
62 case Maxwell::VertexAttribute::Size::Size_8_8: 58 case Maxwell::VertexAttribute::Size::Size_8_8:
@@ -75,14 +71,12 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
75 return GL_INT; 71 return GL_INT;
76 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 72 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
77 return GL_INT_2_10_10_10_REV; 73 return GL_INT_2_10_10_10_REV;
74 default:
75 LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
76 UNREACHABLE();
77 return {};
78 } 78 }
79 79 case Maxwell::VertexAttribute::Type::Float:
80 LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
81 UNREACHABLE();
82 return {};
83 }
84
85 case Maxwell::VertexAttribute::Type::Float: {
86 switch (attrib.size) { 80 switch (attrib.size) {
87 case Maxwell::VertexAttribute::Size::Size_16: 81 case Maxwell::VertexAttribute::Size::Size_16:
88 case Maxwell::VertexAttribute::Size::Size_16_16: 82 case Maxwell::VertexAttribute::Size::Size_16_16:
@@ -94,13 +88,16 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
94 case Maxwell::VertexAttribute::Size::Size_32_32_32: 88 case Maxwell::VertexAttribute::Size::Size_32_32_32:
95 case Maxwell::VertexAttribute::Size::Size_32_32_32_32: 89 case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
96 return GL_FLOAT; 90 return GL_FLOAT;
91 default:
92 LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
93 UNREACHABLE();
94 return {};
97 } 95 }
96 default:
97 LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type={}", attrib.TypeString());
98 UNREACHABLE();
99 return {};
98 } 100 }
99 }
100
101 LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type={}", attrib.TypeString());
102 UNREACHABLE();
103 return {};
104} 101}
105 102
106inline GLenum IndexFormat(Maxwell::IndexFormat index_format) { 103inline GLenum IndexFormat(Maxwell::IndexFormat index_format) {
@@ -129,10 +126,13 @@ inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
129 return GL_TRIANGLES; 126 return GL_TRIANGLES;
130 case Maxwell::PrimitiveTopology::TriangleStrip: 127 case Maxwell::PrimitiveTopology::TriangleStrip:
131 return GL_TRIANGLE_STRIP; 128 return GL_TRIANGLE_STRIP;
129 case Maxwell::PrimitiveTopology::TriangleFan:
130 return GL_TRIANGLE_FAN;
131 default:
132 LOG_CRITICAL(Render_OpenGL, "Unimplemented topology={}", static_cast<u32>(topology));
133 UNREACHABLE();
134 return {};
132 } 135 }
133 LOG_CRITICAL(Render_OpenGL, "Unimplemented topology={}", static_cast<u32>(topology));
134 UNREACHABLE();
135 return {};
136} 136}
137 137
138inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode, 138inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode,
@@ -186,9 +186,10 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) {
186 } else { 186 } else {
187 return GL_MIRROR_CLAMP_TO_EDGE; 187 return GL_MIRROR_CLAMP_TO_EDGE;
188 } 188 }
189 default:
190 LOG_ERROR(Render_OpenGL, "Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode));
191 return GL_REPEAT;
189 } 192 }
190 LOG_ERROR(Render_OpenGL, "Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode));
191 return GL_REPEAT;
192} 193}
193 194
194inline GLenum DepthCompareFunc(Tegra::Texture::DepthCompareFunc func) { 195inline GLenum DepthCompareFunc(Tegra::Texture::DepthCompareFunc func) {
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index d69cba9c3..3451d321d 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -97,8 +97,8 @@ static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, cons
97 return matrix; 97 return matrix;
98} 98}
99 99
100RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& window, Core::System& system) 100RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system)
101 : VideoCore::RendererBase{window}, system{system} {} 101 : VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system} {}
102 102
103RendererOpenGL::~RendererOpenGL() = default; 103RendererOpenGL::~RendererOpenGL() = default;
104 104
@@ -265,7 +265,7 @@ void RendererOpenGL::CreateRasterizer() {
265 } 265 }
266 // Initialize sRGB Usage 266 // Initialize sRGB Usage
267 OpenGLState::ClearsRGBUsed(); 267 OpenGLState::ClearsRGBUsed();
268 rasterizer = std::make_unique<RasterizerOpenGL>(system, screen_info); 268 rasterizer = std::make_unique<RasterizerOpenGL>(system, emu_window, screen_info);
269} 269}
270 270
271void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, 271void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 6cbf9d2cb..4aebf2321 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -45,7 +45,7 @@ struct ScreenInfo {
45 45
46class RendererOpenGL : public VideoCore::RendererBase { 46class RendererOpenGL : public VideoCore::RendererBase {
47public: 47public:
48 explicit RendererOpenGL(Core::Frontend::EmuWindow& window, Core::System& system); 48 explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system);
49 ~RendererOpenGL() override; 49 ~RendererOpenGL() override;
50 50
51 /// Swap buffers (render frame) 51 /// Swap buffers (render frame)
@@ -77,6 +77,7 @@ private:
77 void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a, 77 void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a,
78 const TextureInfo& texture); 78 const TextureInfo& texture);
79 79
80 Core::Frontend::EmuWindow& emu_window;
80 Core::System& system; 81 Core::System& system;
81 82
82 OpenGLState state; 83 OpenGLState state;
diff --git a/src/video_core/renderer_opengl/utils.cpp b/src/video_core/renderer_opengl/utils.cpp
index 84a987371..f23fc9f9d 100644
--- a/src/video_core/renderer_opengl/utils.cpp
+++ b/src/video_core/renderer_opengl/utils.cpp
@@ -38,27 +38,27 @@ void BindBuffersRangePushBuffer::Bind() const {
38 sizes.data()); 38 sizes.data());
39} 39}
40 40
41void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string extra_info) { 41void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_view extra_info) {
42 if (!GLAD_GL_KHR_debug) { 42 if (!GLAD_GL_KHR_debug) {
43 return; // We don't need to throw an error as this is just for debugging 43 // We don't need to throw an error as this is just for debugging
44 return;
44 } 45 }
45 const std::string nice_addr = fmt::format("0x{:016x}", addr);
46 std::string object_label;
47 46
47 std::string object_label;
48 if (extra_info.empty()) { 48 if (extra_info.empty()) {
49 switch (identifier) { 49 switch (identifier) {
50 case GL_TEXTURE: 50 case GL_TEXTURE:
51 object_label = "Texture@" + nice_addr; 51 object_label = fmt::format("Texture@0x{:016X}", addr);
52 break; 52 break;
53 case GL_PROGRAM: 53 case GL_PROGRAM:
54 object_label = "Shader@" + nice_addr; 54 object_label = fmt::format("Shader@0x{:016X}", addr);
55 break; 55 break;
56 default: 56 default:
57 object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr); 57 object_label = fmt::format("Object(0x{:X})@0x{:016X}", identifier, addr);
58 break; 58 break;
59 } 59 }
60 } else { 60 } else {
61 object_label = extra_info + '@' + nice_addr; 61 object_label = fmt::format("{}@0x{:016X}", extra_info, addr);
62 } 62 }
63 glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str())); 63 glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str()));
64} 64}
diff --git a/src/video_core/renderer_opengl/utils.h b/src/video_core/renderer_opengl/utils.h
index aef45c9dc..b3e9fc499 100644
--- a/src/video_core/renderer_opengl/utils.h
+++ b/src/video_core/renderer_opengl/utils.h
@@ -4,7 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <string> 7#include <string_view>
8#include <vector> 8#include <vector>
9#include <glad/glad.h> 9#include <glad/glad.h>
10#include "common/common_types.h" 10#include "common/common_types.h"
@@ -30,6 +30,6 @@ private:
30 std::vector<GLsizeiptr> sizes; 30 std::vector<GLsizeiptr> sizes;
31}; 31};
32 32
33void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string extra_info = ""); 33void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_view extra_info = {});
34 34
35} // namespace OpenGL \ No newline at end of file 35} // namespace OpenGL \ No newline at end of file
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 34bf26ff2..9fe1e3280 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -62,9 +62,10 @@ vk::SamplerAddressMode WrapMode(Tegra::Texture::WrapMode wrap_mode) {
62 case Tegra::Texture::WrapMode::MirrorOnceBorder: 62 case Tegra::Texture::WrapMode::MirrorOnceBorder:
63 UNIMPLEMENTED(); 63 UNIMPLEMENTED();
64 return vk::SamplerAddressMode::eMirrorClampToEdge; 64 return vk::SamplerAddressMode::eMirrorClampToEdge;
65 default:
66 UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", static_cast<u32>(wrap_mode));
67 return {};
65 } 68 }
66 UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", static_cast<u32>(wrap_mode));
67 return {};
68} 69}
69 70
70vk::CompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func) { 71vk::CompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func) {
@@ -225,9 +226,10 @@ vk::PrimitiveTopology PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
225 return vk::PrimitiveTopology::eTriangleList; 226 return vk::PrimitiveTopology::eTriangleList;
226 case Maxwell::PrimitiveTopology::TriangleStrip: 227 case Maxwell::PrimitiveTopology::TriangleStrip:
227 return vk::PrimitiveTopology::eTriangleStrip; 228 return vk::PrimitiveTopology::eTriangleStrip;
229 default:
230 UNIMPLEMENTED_MSG("Unimplemented topology={}", static_cast<u32>(topology));
231 return {};
228 } 232 }
229 UNIMPLEMENTED_MSG("Unimplemented topology={}", static_cast<u32>(topology));
230 return {};
231} 233}
232 234
233vk::Format VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) { 235vk::Format VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) {
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 08b786aad..3edf460df 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -49,9 +49,6 @@ public:
49 return alignment; 49 return alignment;
50 } 50 }
51 51
52 // We do not have to flush this cache as things in it are never modified by us.
53 void Flush() override {}
54
55private: 52private:
56 VAddr cpu_addr{}; 53 VAddr cpu_addr{};
57 std::size_t size{}; 54 std::size_t size{};
@@ -87,6 +84,10 @@ public:
87 return buffer_handle; 84 return buffer_handle;
88 } 85 }
89 86
87protected:
88 // We do not have to flush this cache as things in it are never modified by us.
89 void FlushObjectInner(const std::shared_ptr<CachedBufferEntry>& object) override {}
90
90private: 91private:
91 void AlignBuffer(std::size_t alignment); 92 void AlignBuffer(std::size_t alignment);
92 93
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
index 23d9b10db..a5b25aeff 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
@@ -194,8 +194,8 @@ public:
194 for (const auto& sampler : ir.GetSamplers()) { 194 for (const auto& sampler : ir.GetSamplers()) {
195 entries.samplers.emplace_back(sampler); 195 entries.samplers.emplace_back(sampler);
196 } 196 }
197 for (const auto& attr : ir.GetInputAttributes()) { 197 for (const auto& attribute : ir.GetInputAttributes()) {
198 entries.attributes.insert(GetGenericAttributeLocation(attr.first)); 198 entries.attributes.insert(GetGenericAttributeLocation(attribute));
199 } 199 }
200 entries.clip_distances = ir.GetClipDistances(); 200 entries.clip_distances = ir.GetClipDistances();
201 entries.shader_length = ir.GetLength(); 201 entries.shader_length = ir.GetLength();
@@ -315,15 +315,13 @@ private:
315 constexpr std::array<const char*, INTERNAL_FLAGS_COUNT> names = {"zero", "sign", "carry", 315 constexpr std::array<const char*, INTERNAL_FLAGS_COUNT> names = {"zero", "sign", "carry",
316 "overflow"}; 316 "overflow"};
317 for (std::size_t flag = 0; flag < INTERNAL_FLAGS_COUNT; ++flag) { 317 for (std::size_t flag = 0; flag < INTERNAL_FLAGS_COUNT; ++flag) {
318 const auto flag_code = static_cast<InternalFlag>(flag);
319 const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false); 318 const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
320 internal_flags[flag] = AddGlobalVariable(Name(id, names[flag])); 319 internal_flags[flag] = AddGlobalVariable(Name(id, names[flag]));
321 } 320 }
322 } 321 }
323 322
324 void DeclareInputAttributes() { 323 void DeclareInputAttributes() {
325 for (const auto element : ir.GetInputAttributes()) { 324 for (const auto index : ir.GetInputAttributes()) {
326 const Attribute::Index index = element.first;
327 if (!IsGenericAttribute(index)) { 325 if (!IsGenericAttribute(index)) {
328 continue; 326 continue;
329 } 327 }
@@ -1037,6 +1035,18 @@ private:
1037 return {}; 1035 return {};
1038 } 1036 }
1039 1037
1038 template <u32 element>
1039 Id LocalInvocationId(Operation) {
1040 UNIMPLEMENTED();
1041 return {};
1042 }
1043
1044 template <u32 element>
1045 Id WorkGroupId(Operation) {
1046 UNIMPLEMENTED();
1047 return {};
1048 }
1049
1040 Id DeclareBuiltIn(spv::BuiltIn builtin, spv::StorageClass storage, Id type, 1050 Id DeclareBuiltIn(spv::BuiltIn builtin, spv::StorageClass storage, Id type,
1041 const std::string& name) { 1051 const std::string& name) {
1042 const Id id = OpVariable(type, storage); 1052 const Id id = OpVariable(type, storage);
@@ -1293,6 +1303,12 @@ private:
1293 &SPIRVDecompiler::EndPrimitive, 1303 &SPIRVDecompiler::EndPrimitive,
1294 1304
1295 &SPIRVDecompiler::YNegate, 1305 &SPIRVDecompiler::YNegate,
1306 &SPIRVDecompiler::LocalInvocationId<0>,
1307 &SPIRVDecompiler::LocalInvocationId<1>,
1308 &SPIRVDecompiler::LocalInvocationId<2>,
1309 &SPIRVDecompiler::WorkGroupId<0>,
1310 &SPIRVDecompiler::WorkGroupId<1>,
1311 &SPIRVDecompiler::WorkGroupId<2>,
1296 }; 1312 };
1297 1313
1298 const ShaderIR& ir; 1314 const ShaderIR& ir;
diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp
index e4c438792..2da595c0d 100644
--- a/src/video_core/shader/decode.cpp
+++ b/src/video_core/shader/decode.cpp
@@ -116,6 +116,8 @@ ExitMethod ShaderIR::Scan(u32 begin, u32 end, std::set<u32>& labels) {
116 // Continue scanning for an exit method. 116 // Continue scanning for an exit method.
117 break; 117 break;
118 } 118 }
119 default:
120 break;
119 } 121 }
120 } 122 }
121 return exit_method = ExitMethod::AlwaysReturn; 123 return exit_method = ExitMethod::AlwaysReturn;
@@ -206,4 +208,4 @@ u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) {
206 return pc + 1; 208 return pc + 1;
207} 209}
208 210
209} // namespace VideoCommon::Shader \ No newline at end of file 211} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/arithmetic.cpp b/src/video_core/shader/decode/arithmetic.cpp
index 3190e2d7c..b4859bc1e 100644
--- a/src/video_core/shader/decode/arithmetic.cpp
+++ b/src/video_core/shader/decode/arithmetic.cpp
@@ -4,6 +4,7 @@
4 4
5#include "common/assert.h" 5#include "common/assert.h"
6#include "common/common_types.h" 6#include "common/common_types.h"
7#include "common/logging/log.h"
7#include "video_core/engines/shader_bytecode.h" 8#include "video_core/engines/shader_bytecode.h"
8#include "video_core/shader/shader_ir.h" 9#include "video_core/shader/shader_ir.h"
9 10
@@ -152,4 +153,4 @@ u32 ShaderIR::DecodeArithmetic(NodeBlock& bb, u32 pc) {
152 return pc; 153 return pc;
153} 154}
154 155
155} // namespace VideoCommon::Shader \ No newline at end of file 156} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/arithmetic_half.cpp b/src/video_core/shader/decode/arithmetic_half.cpp
index 9467f9417..3a29c4a46 100644
--- a/src/video_core/shader/decode/arithmetic_half.cpp
+++ b/src/video_core/shader/decode/arithmetic_half.cpp
@@ -4,11 +4,13 @@
4 4
5#include "common/assert.h" 5#include "common/assert.h"
6#include "common/common_types.h" 6#include "common/common_types.h"
7#include "common/logging/log.h"
7#include "video_core/engines/shader_bytecode.h" 8#include "video_core/engines/shader_bytecode.h"
8#include "video_core/shader/shader_ir.h" 9#include "video_core/shader/shader_ir.h"
9 10
10namespace VideoCommon::Shader { 11namespace VideoCommon::Shader {
11 12
13using Tegra::Shader::HalfType;
12using Tegra::Shader::Instruction; 14using Tegra::Shader::Instruction;
13using Tegra::Shader::OpCode; 15using Tegra::Shader::OpCode;
14 16
@@ -22,7 +24,6 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) {
22 LOG_WARNING(HW_GPU, "{} FTZ not implemented", opcode->get().GetName()); 24 LOG_WARNING(HW_GPU, "{} FTZ not implemented", opcode->get().GetName());
23 } 25 }
24 } 26 }
25 UNIMPLEMENTED_IF_MSG(instr.alu_half.saturate != 0, "Half float saturation not implemented");
26 27
27 const bool negate_a = 28 const bool negate_a =
28 opcode->get().GetId() != OpCode::Id::HMUL2_R && instr.alu_half.negate_a != 0; 29 opcode->get().GetId() != OpCode::Id::HMUL2_R && instr.alu_half.negate_a != 0;
@@ -32,35 +33,37 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) {
32 Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.alu_half.type_a); 33 Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.alu_half.type_a);
33 op_a = GetOperandAbsNegHalf(op_a, instr.alu_half.abs_a, negate_a); 34 op_a = GetOperandAbsNegHalf(op_a, instr.alu_half.abs_a, negate_a);
34 35
35 Node op_b = [&]() { 36 auto [type_b, op_b] = [&]() -> std::tuple<HalfType, Node> {
36 switch (opcode->get().GetId()) { 37 switch (opcode->get().GetId()) {
37 case OpCode::Id::HADD2_C: 38 case OpCode::Id::HADD2_C:
38 case OpCode::Id::HMUL2_C: 39 case OpCode::Id::HMUL2_C:
39 return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()); 40 return {HalfType::F32, GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())};
40 case OpCode::Id::HADD2_R: 41 case OpCode::Id::HADD2_R:
41 case OpCode::Id::HMUL2_R: 42 case OpCode::Id::HMUL2_R:
42 return GetRegister(instr.gpr20); 43 return {instr.alu_half.type_b, GetRegister(instr.gpr20)};
43 default: 44 default:
44 UNREACHABLE(); 45 UNREACHABLE();
45 return Immediate(0); 46 return {HalfType::F32, Immediate(0)};
46 } 47 }
47 }(); 48 }();
48 op_b = UnpackHalfFloat(op_b, instr.alu_half.type_b); 49 op_b = UnpackHalfFloat(op_b, type_b);
49 op_b = GetOperandAbsNegHalf(op_b, instr.alu_half.abs_b, negate_b); 50 // redeclaration to avoid a bug in clang with reusing local bindings in lambdas
51 Node op_b_alt = GetOperandAbsNegHalf(op_b, instr.alu_half.abs_b, negate_b);
50 52
51 Node value = [&]() { 53 Node value = [&]() {
52 switch (opcode->get().GetId()) { 54 switch (opcode->get().GetId()) {
53 case OpCode::Id::HADD2_C: 55 case OpCode::Id::HADD2_C:
54 case OpCode::Id::HADD2_R: 56 case OpCode::Id::HADD2_R:
55 return Operation(OperationCode::HAdd, PRECISE, op_a, op_b); 57 return Operation(OperationCode::HAdd, PRECISE, op_a, op_b_alt);
56 case OpCode::Id::HMUL2_C: 58 case OpCode::Id::HMUL2_C:
57 case OpCode::Id::HMUL2_R: 59 case OpCode::Id::HMUL2_R:
58 return Operation(OperationCode::HMul, PRECISE, op_a, op_b); 60 return Operation(OperationCode::HMul, PRECISE, op_a, op_b_alt);
59 default: 61 default:
60 UNIMPLEMENTED_MSG("Unhandled half float instruction: {}", opcode->get().GetName()); 62 UNIMPLEMENTED_MSG("Unhandled half float instruction: {}", opcode->get().GetName());
61 return Immediate(0); 63 return Immediate(0);
62 } 64 }
63 }(); 65 }();
66 value = GetSaturatedHalfFloat(value, instr.alu_half.saturate);
64 value = HalfMerge(GetRegister(instr.gpr0), value, instr.alu_half.merge); 67 value = HalfMerge(GetRegister(instr.gpr0), value, instr.alu_half.merge);
65 68
66 SetRegister(bb, instr.gpr0, value); 69 SetRegister(bb, instr.gpr0, value);
@@ -68,4 +71,4 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) {
68 return pc; 71 return pc;
69} 72}
70 73
71} // namespace VideoCommon::Shader \ No newline at end of file 74} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/arithmetic_half_immediate.cpp b/src/video_core/shader/decode/arithmetic_half_immediate.cpp
index fbcd35b18..5341e460f 100644
--- a/src/video_core/shader/decode/arithmetic_half_immediate.cpp
+++ b/src/video_core/shader/decode/arithmetic_half_immediate.cpp
@@ -4,6 +4,7 @@
4 4
5#include "common/assert.h" 5#include "common/assert.h"
6#include "common/common_types.h" 6#include "common/common_types.h"
7#include "common/logging/log.h"
7#include "video_core/engines/shader_bytecode.h" 8#include "video_core/engines/shader_bytecode.h"
8#include "video_core/shader/shader_ir.h" 9#include "video_core/shader/shader_ir.h"
9 10
@@ -47,4 +48,4 @@ u32 ShaderIR::DecodeArithmeticHalfImmediate(NodeBlock& bb, u32 pc) {
47 return pc; 48 return pc;
48} 49}
49 50
50} // namespace VideoCommon::Shader \ No newline at end of file 51} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/arithmetic_immediate.cpp b/src/video_core/shader/decode/arithmetic_immediate.cpp
index 0d139c0d2..3095f2fd4 100644
--- a/src/video_core/shader/decode/arithmetic_immediate.cpp
+++ b/src/video_core/shader/decode/arithmetic_immediate.cpp
@@ -49,4 +49,4 @@ u32 ShaderIR::DecodeArithmeticImmediate(NodeBlock& bb, u32 pc) {
49 return pc; 49 return pc;
50} 50}
51 51
52} // namespace VideoCommon::Shader \ No newline at end of file 52} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/arithmetic_integer_immediate.cpp b/src/video_core/shader/decode/arithmetic_integer_immediate.cpp
index 3ed5ccc5a..679ac0d4e 100644
--- a/src/video_core/shader/decode/arithmetic_integer_immediate.cpp
+++ b/src/video_core/shader/decode/arithmetic_integer_immediate.cpp
@@ -93,4 +93,4 @@ void ShaderIR::WriteLogicOperation(NodeBlock& bb, Register dest, LogicOperation
93 } 93 }
94} 94}
95 95
96} // namespace VideoCommon::Shader \ No newline at end of file 96} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/bfe.cpp b/src/video_core/shader/decode/bfe.cpp
index 6a95dc928..1ae192c6a 100644
--- a/src/video_core/shader/decode/bfe.cpp
+++ b/src/video_core/shader/decode/bfe.cpp
@@ -46,4 +46,4 @@ u32 ShaderIR::DecodeBfe(NodeBlock& bb, u32 pc) {
46 return pc; 46 return pc;
47} 47}
48 48
49} // namespace VideoCommon::Shader \ No newline at end of file 49} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/bfi.cpp b/src/video_core/shader/decode/bfi.cpp
index 601d66f1f..0b12a0d08 100644
--- a/src/video_core/shader/decode/bfi.cpp
+++ b/src/video_core/shader/decode/bfi.cpp
@@ -38,4 +38,4 @@ u32 ShaderIR::DecodeBfi(NodeBlock& bb, u32 pc) {
38 return pc; 38 return pc;
39} 39}
40 40
41} // namespace VideoCommon::Shader \ No newline at end of file 41} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/conversion.cpp b/src/video_core/shader/decode/conversion.cpp
index ba15b1115..b5ec9a6f5 100644
--- a/src/video_core/shader/decode/conversion.cpp
+++ b/src/video_core/shader/decode/conversion.cpp
@@ -120,10 +120,11 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
120 return Operation(OperationCode::FCeil, PRECISE, value); 120 return Operation(OperationCode::FCeil, PRECISE, value);
121 case Tegra::Shader::F2fRoundingOp::Trunc: 121 case Tegra::Shader::F2fRoundingOp::Trunc:
122 return Operation(OperationCode::FTrunc, PRECISE, value); 122 return Operation(OperationCode::FTrunc, PRECISE, value);
123 default:
124 UNIMPLEMENTED_MSG("Unimplemented F2F rounding mode {}",
125 static_cast<u32>(instr.conversion.f2f.rounding.Value()));
126 return Immediate(0);
123 } 127 }
124 UNIMPLEMENTED_MSG("Unimplemented F2F rounding mode {}",
125 static_cast<u32>(instr.conversion.f2f.rounding.Value()));
126 return Immediate(0);
127 }(); 128 }();
128 value = GetSaturatedFloat(value, instr.alu.saturate_d); 129 value = GetSaturatedFloat(value, instr.alu.saturate_d);
129 130
diff --git a/src/video_core/shader/decode/ffma.cpp b/src/video_core/shader/decode/ffma.cpp
index 0559cc8de..a1d04c6e5 100644
--- a/src/video_core/shader/decode/ffma.cpp
+++ b/src/video_core/shader/decode/ffma.cpp
@@ -56,4 +56,4 @@ u32 ShaderIR::DecodeFfma(NodeBlock& bb, u32 pc) {
56 return pc; 56 return pc;
57} 57}
58 58
59} // namespace VideoCommon::Shader \ No newline at end of file 59} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/float_set.cpp b/src/video_core/shader/decode/float_set.cpp
index 1bd6755dd..cc522f1de 100644
--- a/src/video_core/shader/decode/float_set.cpp
+++ b/src/video_core/shader/decode/float_set.cpp
@@ -55,4 +55,4 @@ u32 ShaderIR::DecodeFloatSet(NodeBlock& bb, u32 pc) {
55 return pc; 55 return pc;
56} 56}
57 57
58} // namespace VideoCommon::Shader \ No newline at end of file 58} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/float_set_predicate.cpp b/src/video_core/shader/decode/float_set_predicate.cpp
index 9285b8d05..9d2322a1d 100644
--- a/src/video_core/shader/decode/float_set_predicate.cpp
+++ b/src/video_core/shader/decode/float_set_predicate.cpp
@@ -53,4 +53,4 @@ u32 ShaderIR::DecodeFloatSetPredicate(NodeBlock& bb, u32 pc) {
53 return pc; 53 return pc;
54} 54}
55 55
56} // namespace VideoCommon::Shader \ No newline at end of file 56} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/half_set.cpp b/src/video_core/shader/decode/half_set.cpp
index 1dd94bf9d..755f2ec44 100644
--- a/src/video_core/shader/decode/half_set.cpp
+++ b/src/video_core/shader/decode/half_set.cpp
@@ -6,6 +6,7 @@
6 6
7#include "common/assert.h" 7#include "common/assert.h"
8#include "common/common_types.h" 8#include "common/common_types.h"
9#include "common/logging/log.h"
9#include "video_core/engines/shader_bytecode.h" 10#include "video_core/engines/shader_bytecode.h"
10#include "video_core/shader/shader_ir.h" 11#include "video_core/shader/shader_ir.h"
11 12
@@ -64,4 +65,4 @@ u32 ShaderIR::DecodeHalfSet(NodeBlock& bb, u32 pc) {
64 return pc; 65 return pc;
65} 66}
66 67
67} // namespace VideoCommon::Shader \ No newline at end of file 68} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/half_set_predicate.cpp b/src/video_core/shader/decode/half_set_predicate.cpp
index 6e59eb650..fba44d714 100644
--- a/src/video_core/shader/decode/half_set_predicate.cpp
+++ b/src/video_core/shader/decode/half_set_predicate.cpp
@@ -59,4 +59,4 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
59 return pc; 59 return pc;
60} 60}
61 61
62} // namespace VideoCommon::Shader \ No newline at end of file 62} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/hfma2.cpp b/src/video_core/shader/decode/hfma2.cpp
index 5c1becce5..a425f9eb7 100644
--- a/src/video_core/shader/decode/hfma2.cpp
+++ b/src/video_core/shader/decode/hfma2.cpp
@@ -34,15 +34,14 @@ u32 ShaderIR::DecodeHfma2(NodeBlock& bb, u32 pc) {
34 case OpCode::Id::HFMA2_CR: 34 case OpCode::Id::HFMA2_CR:
35 neg_b = instr.hfma2.negate_b; 35 neg_b = instr.hfma2.negate_b;
36 neg_c = instr.hfma2.negate_c; 36 neg_c = instr.hfma2.negate_c;
37 return {instr.hfma2.saturate, instr.hfma2.type_b, 37 return {instr.hfma2.saturate, HalfType::F32,
38 GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()), 38 GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()),
39 instr.hfma2.type_reg39, GetRegister(instr.gpr39)}; 39 instr.hfma2.type_reg39, GetRegister(instr.gpr39)};
40 case OpCode::Id::HFMA2_RC: 40 case OpCode::Id::HFMA2_RC:
41 neg_b = instr.hfma2.negate_b; 41 neg_b = instr.hfma2.negate_b;
42 neg_c = instr.hfma2.negate_c; 42 neg_c = instr.hfma2.negate_c;
43 return {instr.hfma2.saturate, instr.hfma2.type_reg39, GetRegister(instr.gpr39), 43 return {instr.hfma2.saturate, instr.hfma2.type_reg39, GetRegister(instr.gpr39),
44 instr.hfma2.type_b, 44 HalfType::F32, GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())};
45 GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())};
46 case OpCode::Id::HFMA2_RR: 45 case OpCode::Id::HFMA2_RR:
47 neg_b = instr.hfma2.rr.negate_b; 46 neg_b = instr.hfma2.rr.negate_b;
48 neg_c = instr.hfma2.rr.negate_c; 47 neg_c = instr.hfma2.rr.negate_c;
@@ -56,13 +55,13 @@ u32 ShaderIR::DecodeHfma2(NodeBlock& bb, u32 pc) {
56 return {false, identity, Immediate(0), identity, Immediate(0)}; 55 return {false, identity, Immediate(0), identity, Immediate(0)};
57 } 56 }
58 }(); 57 }();
59 UNIMPLEMENTED_IF_MSG(saturate, "HFMA2 saturation is not implemented");
60 58
61 const Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hfma2.type_a); 59 const Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hfma2.type_a);
62 op_b = GetOperandAbsNegHalf(UnpackHalfFloat(op_b, type_b), false, neg_b); 60 op_b = GetOperandAbsNegHalf(UnpackHalfFloat(op_b, type_b), false, neg_b);
63 op_c = GetOperandAbsNegHalf(UnpackHalfFloat(op_c, type_c), false, neg_c); 61 op_c = GetOperandAbsNegHalf(UnpackHalfFloat(op_c, type_c), false, neg_c);
64 62
65 Node value = Operation(OperationCode::HFma, PRECISE, op_a, op_b, op_c); 63 Node value = Operation(OperationCode::HFma, PRECISE, op_a, op_b, op_c);
64 value = GetSaturatedHalfFloat(value, saturate);
66 value = HalfMerge(GetRegister(instr.gpr0), value, instr.hfma2.merge); 65 value = HalfMerge(GetRegister(instr.gpr0), value, instr.hfma2.merge);
67 66
68 SetRegister(bb, instr.gpr0, value); 67 SetRegister(bb, instr.gpr0, value);
@@ -70,4 +69,4 @@ u32 ShaderIR::DecodeHfma2(NodeBlock& bb, u32 pc) {
70 return pc; 69 return pc;
71} 70}
72 71
73} // namespace VideoCommon::Shader \ No newline at end of file 72} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/integer_set.cpp b/src/video_core/shader/decode/integer_set.cpp
index a3bf17eba..a4cdaf74d 100644
--- a/src/video_core/shader/decode/integer_set.cpp
+++ b/src/video_core/shader/decode/integer_set.cpp
@@ -2,7 +2,6 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "common/assert.h"
6#include "common/common_types.h" 5#include "common/common_types.h"
7#include "video_core/engines/shader_bytecode.h" 6#include "video_core/engines/shader_bytecode.h"
8#include "video_core/shader/shader_ir.h" 7#include "video_core/shader/shader_ir.h"
@@ -47,4 +46,4 @@ u32 ShaderIR::DecodeIntegerSet(NodeBlock& bb, u32 pc) {
47 return pc; 46 return pc;
48} 47}
49 48
50} // namespace VideoCommon::Shader \ No newline at end of file 49} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/integer_set_predicate.cpp b/src/video_core/shader/decode/integer_set_predicate.cpp
index aad836d24..a6a1fb632 100644
--- a/src/video_core/shader/decode/integer_set_predicate.cpp
+++ b/src/video_core/shader/decode/integer_set_predicate.cpp
@@ -50,4 +50,4 @@ u32 ShaderIR::DecodeIntegerSetPredicate(NodeBlock& bb, u32 pc) {
50 return pc; 50 return pc;
51} 51}
52 52
53} // namespace VideoCommon::Shader \ No newline at end of file 53} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp
index ea1092db1..e6a010a7d 100644
--- a/src/video_core/shader/decode/memory.cpp
+++ b/src/video_core/shader/decode/memory.cpp
@@ -47,17 +47,20 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
47 "Indirect attribute loads are not supported"); 47 "Indirect attribute loads are not supported");
48 UNIMPLEMENTED_IF_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) != 0, 48 UNIMPLEMENTED_IF_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) != 0,
49 "Unaligned attribute loads are not supported"); 49 "Unaligned attribute loads are not supported");
50 UNIMPLEMENTED_IF_MSG(instr.attribute.fmt20.IsPhysical() &&
51 instr.attribute.fmt20.size != Tegra::Shader::AttributeSize::Word,
52 "Non-32 bits PHYS reads are not implemented");
50 53
51 Tegra::Shader::IpaMode input_mode{Tegra::Shader::IpaInterpMode::Pass, 54 const Node buffer{GetRegister(instr.gpr39)};
52 Tegra::Shader::IpaSampleMode::Default};
53 55
54 u64 next_element = instr.attribute.fmt20.element; 56 u64 next_element = instr.attribute.fmt20.element;
55 auto next_index = static_cast<u64>(instr.attribute.fmt20.index.Value()); 57 auto next_index = static_cast<u64>(instr.attribute.fmt20.index.Value());
56 58
57 const auto LoadNextElement = [&](u32 reg_offset) { 59 const auto LoadNextElement = [&](u32 reg_offset) {
58 const Node buffer = GetRegister(instr.gpr39); 60 const Node attribute{instr.attribute.fmt20.IsPhysical()
59 const Node attribute = GetInputAttribute(static_cast<Attribute::Index>(next_index), 61 ? GetPhysicalInputAttribute(instr.gpr8, buffer)
60 next_element, input_mode, buffer); 62 : GetInputAttribute(static_cast<Attribute::Index>(next_index),
63 next_element, buffer)};
61 64
62 SetRegister(bb, instr.gpr0.Value() + reg_offset, attribute); 65 SetRegister(bb, instr.gpr0.Value() + reg_offset, attribute);
63 66
@@ -143,12 +146,25 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
143 } 146 }
144 break; 147 break;
145 } 148 }
149 case OpCode::Id::LD:
146 case OpCode::Id::LDG: { 150 case OpCode::Id::LDG: {
151 const auto type = [instr, &opcode]() -> Tegra::Shader::UniformType {
152 switch (opcode->get().GetId()) {
153 case OpCode::Id::LD:
154 UNIMPLEMENTED_IF_MSG(!instr.generic.extended, "Unextended LD is not implemented");
155 return instr.generic.type;
156 case OpCode::Id::LDG:
157 return instr.ldg.type;
158 default:
159 UNREACHABLE();
160 return {};
161 }
162 }();
163
147 const auto [real_address_base, base_address, descriptor] = 164 const auto [real_address_base, base_address, descriptor] =
148 TrackAndGetGlobalMemory(bb, GetRegister(instr.gpr8), 165 TrackAndGetGlobalMemory(bb, instr, false);
149 static_cast<u32>(instr.ldg.immediate_offset.Value()), false);
150 166
151 const u32 count = GetUniformTypeElementsCount(instr.ldg.type); 167 const u32 count = GetUniformTypeElementsCount(type);
152 for (u32 i = 0; i < count; ++i) { 168 for (u32 i = 0; i < count; ++i) {
153 const Node it_offset = Immediate(i * 4); 169 const Node it_offset = Immediate(i * 4);
154 const Node real_address = 170 const Node real_address =
@@ -162,28 +178,6 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
162 } 178 }
163 break; 179 break;
164 } 180 }
165 case OpCode::Id::STG: {
166 const auto [real_address_base, base_address, descriptor] =
167 TrackAndGetGlobalMemory(bb, GetRegister(instr.gpr8),
168 static_cast<u32>(instr.stg.immediate_offset.Value()), true);
169
170 // Encode in temporary registers like this: real_base_address, {registers_to_be_written...}
171 SetTemporal(bb, 0, real_address_base);
172
173 const u32 count = GetUniformTypeElementsCount(instr.stg.type);
174 for (u32 i = 0; i < count; ++i) {
175 SetTemporal(bb, i + 1, GetRegister(instr.gpr0.Value() + i));
176 }
177 for (u32 i = 0; i < count; ++i) {
178 const Node it_offset = Immediate(i * 4);
179 const Node real_address =
180 Operation(OperationCode::UAdd, NO_PRECISE, real_address_base, it_offset);
181 const Node gmem = StoreNode(GmemNode(real_address, base_address, descriptor));
182
183 bb.push_back(Operation(OperationCode::Assign, gmem, GetTemporal(i + 1)));
184 }
185 break;
186 }
187 case OpCode::Id::ST_A: { 181 case OpCode::Id::ST_A: {
188 UNIMPLEMENTED_IF_MSG(instr.gpr8.Value() != Register::ZeroIndex, 182 UNIMPLEMENTED_IF_MSG(instr.gpr8.Value() != Register::ZeroIndex,
189 "Indirect attribute loads are not supported"); 183 "Indirect attribute loads are not supported");
@@ -239,6 +233,56 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
239 } 233 }
240 break; 234 break;
241 } 235 }
236 case OpCode::Id::ST:
237 case OpCode::Id::STG: {
238 const auto type = [instr, &opcode]() -> Tegra::Shader::UniformType {
239 switch (opcode->get().GetId()) {
240 case OpCode::Id::ST:
241 UNIMPLEMENTED_IF_MSG(!instr.generic.extended, "Unextended ST is not implemented");
242 return instr.generic.type;
243 case OpCode::Id::STG:
244 return instr.stg.type;
245 default:
246 UNREACHABLE();
247 return {};
248 }
249 }();
250
251 const auto [real_address_base, base_address, descriptor] =
252 TrackAndGetGlobalMemory(bb, instr, true);
253
254 // Encode in temporary registers like this: real_base_address, {registers_to_be_written...}
255 SetTemporal(bb, 0, real_address_base);
256
257 const u32 count = GetUniformTypeElementsCount(type);
258 for (u32 i = 0; i < count; ++i) {
259 SetTemporal(bb, i + 1, GetRegister(instr.gpr0.Value() + i));
260 }
261 for (u32 i = 0; i < count; ++i) {
262 const Node it_offset = Immediate(i * 4);
263 const Node real_address =
264 Operation(OperationCode::UAdd, NO_PRECISE, real_address_base, it_offset);
265 const Node gmem = StoreNode(GmemNode(real_address, base_address, descriptor));
266
267 bb.push_back(Operation(OperationCode::Assign, gmem, GetTemporal(i + 1)));
268 }
269 break;
270 }
271 case OpCode::Id::AL2P: {
272 // Ignore al2p.direction since we don't care about it.
273
274 // Calculate emulation fake physical address.
275 const Node fixed_address{Immediate(static_cast<u32>(instr.al2p.address))};
276 const Node reg{GetRegister(instr.gpr8)};
277 const Node fake_address{Operation(OperationCode::IAdd, NO_PRECISE, reg, fixed_address)};
278
279 // Set the fake address to target register.
280 SetRegister(bb, instr.gpr0, fake_address);
281
282 // Signal the shader IR to declare all possible attributes and varyings
283 uses_physical_attributes = true;
284 break;
285 }
242 default: 286 default:
243 UNIMPLEMENTED_MSG("Unhandled memory instruction: {}", opcode->get().GetName()); 287 UNIMPLEMENTED_MSG("Unhandled memory instruction: {}", opcode->get().GetName());
244 } 288 }
@@ -247,9 +291,11 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
247} 291}
248 292
249std::tuple<Node, Node, GlobalMemoryBase> ShaderIR::TrackAndGetGlobalMemory(NodeBlock& bb, 293std::tuple<Node, Node, GlobalMemoryBase> ShaderIR::TrackAndGetGlobalMemory(NodeBlock& bb,
250 Node addr_register, 294 Instruction instr,
251 u32 immediate_offset,
252 bool is_write) { 295 bool is_write) {
296 const auto addr_register{GetRegister(instr.gmem.gpr)};
297 const auto immediate_offset{static_cast<u32>(instr.gmem.offset)};
298
253 const Node base_address{ 299 const Node base_address{
254 TrackCbuf(addr_register, global_code, static_cast<s64>(global_code.size()))}; 300 TrackCbuf(addr_register, global_code, static_cast<s64>(global_code.size()))};
255 const auto cbuf = std::get_if<CbufNode>(base_address); 301 const auto cbuf = std::get_if<CbufNode>(base_address);
diff --git a/src/video_core/shader/decode/other.cpp b/src/video_core/shader/decode/other.cpp
index d750a2936..a6c123573 100644
--- a/src/video_core/shader/decode/other.cpp
+++ b/src/video_core/shader/decode/other.cpp
@@ -4,6 +4,7 @@
4 4
5#include "common/assert.h" 5#include "common/assert.h"
6#include "common/common_types.h" 6#include "common/common_types.h"
7#include "common/logging/log.h"
7#include "video_core/engines/shader_bytecode.h" 8#include "video_core/engines/shader_bytecode.h"
8#include "video_core/shader/shader_ir.h" 9#include "video_core/shader/shader_ir.h"
9 10
@@ -13,6 +14,7 @@ using Tegra::Shader::ConditionCode;
13using Tegra::Shader::Instruction; 14using Tegra::Shader::Instruction;
14using Tegra::Shader::OpCode; 15using Tegra::Shader::OpCode;
15using Tegra::Shader::Register; 16using Tegra::Shader::Register;
17using Tegra::Shader::SystemVariable;
16 18
17u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { 19u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
18 const Instruction instr = {program_code[pc]}; 20 const Instruction instr = {program_code[pc]};
@@ -58,20 +60,33 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
58 break; 60 break;
59 } 61 }
60 case OpCode::Id::MOV_SYS: { 62 case OpCode::Id::MOV_SYS: {
61 switch (instr.sys20) { 63 const Node value = [&]() {
62 case Tegra::Shader::SystemVariable::InvocationInfo: { 64 switch (instr.sys20) {
63 LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete"); 65 case SystemVariable::Ydirection:
64 SetRegister(bb, instr.gpr0, Immediate(0u)); 66 return Operation(OperationCode::YNegate);
65 break; 67 case SystemVariable::InvocationInfo:
66 } 68 LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete");
67 case Tegra::Shader::SystemVariable::Ydirection: { 69 return Immediate(0u);
68 // Config pack's third value is Y_NEGATE's state. 70 case SystemVariable::TidX:
69 SetRegister(bb, instr.gpr0, Operation(OperationCode::YNegate)); 71 return Operation(OperationCode::LocalInvocationIdX);
70 break; 72 case SystemVariable::TidY:
71 } 73 return Operation(OperationCode::LocalInvocationIdY);
72 default: 74 case SystemVariable::TidZ:
73 UNIMPLEMENTED_MSG("Unhandled system move: {}", static_cast<u32>(instr.sys20.Value())); 75 return Operation(OperationCode::LocalInvocationIdZ);
74 } 76 case SystemVariable::CtaIdX:
77 return Operation(OperationCode::WorkGroupIdX);
78 case SystemVariable::CtaIdY:
79 return Operation(OperationCode::WorkGroupIdY);
80 case SystemVariable::CtaIdZ:
81 return Operation(OperationCode::WorkGroupIdZ);
82 default:
83 UNIMPLEMENTED_MSG("Unhandled system move: {}",
84 static_cast<u32>(instr.sys20.Value()));
85 return Immediate(0u);
86 }
87 }();
88 SetRegister(bb, instr.gpr0, value);
89
75 break; 90 break;
76 } 91 }
77 case OpCode::Id::BRA: { 92 case OpCode::Id::BRA: {
@@ -130,15 +145,18 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
130 break; 145 break;
131 } 146 }
132 case OpCode::Id::IPA: { 147 case OpCode::Id::IPA: {
133 const auto& attribute = instr.attribute.fmt28; 148 const bool is_physical = instr.ipa.idx && instr.gpr8.Value() != 0xff;
149
150 const auto attribute = instr.attribute.fmt28;
134 const Tegra::Shader::IpaMode input_mode{instr.ipa.interp_mode.Value(), 151 const Tegra::Shader::IpaMode input_mode{instr.ipa.interp_mode.Value(),
135 instr.ipa.sample_mode.Value()}; 152 instr.ipa.sample_mode.Value()};
136 153
137 const Node attr = GetInputAttribute(attribute.index, attribute.element, input_mode); 154 Node value = is_physical ? GetPhysicalInputAttribute(instr.gpr8)
138 Node value = attr; 155 : GetInputAttribute(attribute.index, attribute.element);
139 const Tegra::Shader::Attribute::Index index = attribute.index.Value(); 156 const Tegra::Shader::Attribute::Index index = attribute.index.Value();
140 if (index >= Tegra::Shader::Attribute::Index::Attribute_0 && 157 const bool is_generic = index >= Tegra::Shader::Attribute::Index::Attribute_0 &&
141 index <= Tegra::Shader::Attribute::Index::Attribute_31) { 158 index <= Tegra::Shader::Attribute::Index::Attribute_31;
159 if (is_generic || is_physical) {
142 // TODO(Blinkhawk): There are cases where a perspective attribute use PASS. 160 // TODO(Blinkhawk): There are cases where a perspective attribute use PASS.
143 // In theory by setting them as perspective, OpenGL does the perspective correction. 161 // In theory by setting them as perspective, OpenGL does the perspective correction.
144 // A way must figured to reverse the last step of it. 162 // A way must figured to reverse the last step of it.
diff --git a/src/video_core/shader/decode/predicate_set_predicate.cpp b/src/video_core/shader/decode/predicate_set_predicate.cpp
index 83c61680e..71844c42b 100644
--- a/src/video_core/shader/decode/predicate_set_predicate.cpp
+++ b/src/video_core/shader/decode/predicate_set_predicate.cpp
@@ -64,4 +64,4 @@ u32 ShaderIR::DecodePredicateSetPredicate(NodeBlock& bb, u32 pc) {
64 return pc; 64 return pc;
65} 65}
66 66
67} // namespace VideoCommon::Shader \ No newline at end of file 67} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/predicate_set_register.cpp b/src/video_core/shader/decode/predicate_set_register.cpp
index d0495995d..387491bd3 100644
--- a/src/video_core/shader/decode/predicate_set_register.cpp
+++ b/src/video_core/shader/decode/predicate_set_register.cpp
@@ -43,4 +43,4 @@ u32 ShaderIR::DecodePredicateSetRegister(NodeBlock& bb, u32 pc) {
43 return pc; 43 return pc;
44} 44}
45 45
46} // namespace VideoCommon::Shader \ No newline at end of file 46} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/register_set_predicate.cpp b/src/video_core/shader/decode/register_set_predicate.cpp
index f070e8912..f8659e48e 100644
--- a/src/video_core/shader/decode/register_set_predicate.cpp
+++ b/src/video_core/shader/decode/register_set_predicate.cpp
@@ -48,4 +48,4 @@ u32 ShaderIR::DecodeRegisterSetPredicate(NodeBlock& bb, u32 pc) {
48 return pc; 48 return pc;
49} 49}
50 50
51} // namespace VideoCommon::Shader \ No newline at end of file 51} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/shift.cpp b/src/video_core/shader/decode/shift.cpp
index 951e85f44..44ae87ece 100644
--- a/src/video_core/shader/decode/shift.cpp
+++ b/src/video_core/shader/decode/shift.cpp
@@ -52,4 +52,4 @@ u32 ShaderIR::DecodeShift(NodeBlock& bb, u32 pc) {
52 return pc; 52 return pc;
53} 53}
54 54
55} // namespace VideoCommon::Shader \ No newline at end of file 55} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp
index fa65ac9a9..5b033126d 100644
--- a/src/video_core/shader/decode/texture.cpp
+++ b/src/video_core/shader/decode/texture.cpp
@@ -296,7 +296,7 @@ const Sampler& ShaderIR::GetBindlessSampler(const Tegra::Shader::Register& reg,
296 ASSERT(cbuf_offset_imm != nullptr); 296 ASSERT(cbuf_offset_imm != nullptr);
297 const auto cbuf_offset = cbuf_offset_imm->GetValue(); 297 const auto cbuf_offset = cbuf_offset_imm->GetValue();
298 const auto cbuf_index = cbuf->GetIndex(); 298 const auto cbuf_index = cbuf->GetIndex();
299 const u64 cbuf_key = (cbuf_index << 32) | cbuf_offset; 299 const auto cbuf_key = (static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset);
300 300
301 // If this sampler has already been used, return the existing mapping. 301 // If this sampler has already been used, return the existing mapping.
302 const auto itr = 302 const auto itr =
@@ -540,8 +540,6 @@ Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
540Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool depth_compare, 540Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool depth_compare,
541 bool is_array, bool is_aoffi) { 541 bool is_array, bool is_aoffi) {
542 const std::size_t coord_count = GetCoordCount(texture_type); 542 const std::size_t coord_count = GetCoordCount(texture_type);
543 const std::size_t total_coord_count = coord_count + (is_array ? 1 : 0);
544 const std::size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0);
545 543
546 // If enabled arrays index is always stored in the gpr8 field 544 // If enabled arrays index is always stored in the gpr8 field
547 const u64 array_register = instr.gpr8.Value(); 545 const u64 array_register = instr.gpr8.Value();
diff --git a/src/video_core/shader/decode/video.cpp b/src/video_core/shader/decode/video.cpp
index 956c01d9b..cb9ab72b1 100644
--- a/src/video_core/shader/decode/video.cpp
+++ b/src/video_core/shader/decode/video.cpp
@@ -108,4 +108,4 @@ Node ShaderIR::GetVideoOperand(Node op, bool is_chunk, bool is_signed,
108 } 108 }
109} 109}
110 110
111} // namespace VideoCommon::Shader \ No newline at end of file 111} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/xmad.cpp b/src/video_core/shader/decode/xmad.cpp
index db15c0718..04a776398 100644
--- a/src/video_core/shader/decode/xmad.cpp
+++ b/src/video_core/shader/decode/xmad.cpp
@@ -56,9 +56,10 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
56 instr.xmad.mode, 56 instr.xmad.mode,
57 Immediate(static_cast<u32>(instr.xmad.imm20_16)), 57 Immediate(static_cast<u32>(instr.xmad.imm20_16)),
58 GetRegister(instr.gpr39)}; 58 GetRegister(instr.gpr39)};
59 default:
60 UNIMPLEMENTED_MSG("Unhandled XMAD instruction: {}", opcode->get().GetName());
61 return {false, false, false, Tegra::Shader::XmadMode::None, Immediate(0), Immediate(0)};
59 } 62 }
60 UNIMPLEMENTED_MSG("Unhandled XMAD instruction: {}", opcode->get().GetName());
61 return {false, false, false, Tegra::Shader::XmadMode::None, Immediate(0), Immediate(0)};
62 }(); 63 }();
63 64
64 op_a = BitfieldExtract(op_a, instr.xmad.high_a ? 16 : 0, 16); 65 op_a = BitfieldExtract(op_a, instr.xmad.high_a ? 16 : 0, 16);
diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp
index 17f2f711c..8a6ee5cf5 100644
--- a/src/video_core/shader/shader_ir.cpp
+++ b/src/video_core/shader/shader_ir.cpp
@@ -21,6 +21,13 @@ using Tegra::Shader::PredCondition;
21using Tegra::Shader::PredOperation; 21using Tegra::Shader::PredOperation;
22using Tegra::Shader::Register; 22using Tegra::Shader::Register;
23 23
24ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset)
25 : program_code{program_code}, main_offset{main_offset} {
26 Decode();
27}
28
29ShaderIR::~ShaderIR() = default;
30
24Node ShaderIR::StoreNode(NodeData&& node_data) { 31Node ShaderIR::StoreNode(NodeData&& node_data) {
25 auto store = std::make_unique<NodeData>(node_data); 32 auto store = std::make_unique<NodeData>(node_data);
26 const Node node = store.get(); 33 const Node node = store.get();
@@ -32,8 +39,8 @@ Node ShaderIR::Conditional(Node condition, std::vector<Node>&& code) {
32 return StoreNode(ConditionalNode(condition, std::move(code))); 39 return StoreNode(ConditionalNode(condition, std::move(code)));
33} 40}
34 41
35Node ShaderIR::Comment(const std::string& text) { 42Node ShaderIR::Comment(std::string text) {
36 return StoreNode(CommentNode(text)); 43 return StoreNode(CommentNode(std::move(text)));
37} 44}
38 45
39Node ShaderIR::Immediate(u32 value) { 46Node ShaderIR::Immediate(u32 value) {
@@ -89,13 +96,14 @@ Node ShaderIR::GetPredicate(bool immediate) {
89 return GetPredicate(static_cast<u64>(immediate ? Pred::UnusedIndex : Pred::NeverExecute)); 96 return GetPredicate(static_cast<u64>(immediate ? Pred::UnusedIndex : Pred::NeverExecute));
90} 97}
91 98
92Node ShaderIR::GetInputAttribute(Attribute::Index index, u64 element, 99Node ShaderIR::GetInputAttribute(Attribute::Index index, u64 element, Node buffer) {
93 const Tegra::Shader::IpaMode& input_mode, Node buffer) { 100 used_input_attributes.emplace(index);
94 const auto [entry, is_new] = 101 return StoreNode(AbufNode(index, static_cast<u32>(element), buffer));
95 used_input_attributes.emplace(std::make_pair(index, std::set<Tegra::Shader::IpaMode>{})); 102}
96 entry->second.insert(input_mode);
97 103
98 return StoreNode(AbufNode(index, static_cast<u32>(element), input_mode, buffer)); 104Node ShaderIR::GetPhysicalInputAttribute(Tegra::Shader::Register physical_address, Node buffer) {
105 uses_physical_attributes = true;
106 return StoreNode(AbufNode(GetRegister(physical_address), buffer));
99} 107}
100 108
101Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buffer) { 109Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buffer) {
@@ -439,11 +447,14 @@ Node ShaderIR::BitfieldExtract(Node value, u32 offset, u32 bits) {
439 return OperationCode::LogicalUGreaterEqual; 447 return OperationCode::LogicalUGreaterEqual;
440 case OperationCode::INegate: 448 case OperationCode::INegate:
441 UNREACHABLE_MSG("Can't negate an unsigned integer"); 449 UNREACHABLE_MSG("Can't negate an unsigned integer");
450 return {};
442 case OperationCode::IAbsolute: 451 case OperationCode::IAbsolute:
443 UNREACHABLE_MSG("Can't apply absolute to an unsigned integer"); 452 UNREACHABLE_MSG("Can't apply absolute to an unsigned integer");
453 return {};
454 default:
455 UNREACHABLE_MSG("Unknown signed operation with code={}", static_cast<u32>(operation_code));
456 return {};
444 } 457 }
445 UNREACHABLE_MSG("Unknown signed operation with code={}", static_cast<u32>(operation_code));
446 return {};
447} 458}
448 459
449} // namespace VideoCommon::Shader \ No newline at end of file 460} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index 81278fb33..ff7472e30 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -181,7 +181,13 @@ enum class OperationCode {
181 EmitVertex, /// () -> void 181 EmitVertex, /// () -> void
182 EndPrimitive, /// () -> void 182 EndPrimitive, /// () -> void
183 183
184 YNegate, /// () -> float 184 YNegate, /// () -> float
185 LocalInvocationIdX, /// () -> uint
186 LocalInvocationIdY, /// () -> uint
187 LocalInvocationIdZ, /// () -> uint
188 WorkGroupIdX, /// () -> uint
189 WorkGroupIdY, /// () -> uint
190 WorkGroupIdZ, /// () -> uint
185 191
186 Amount, 192 Amount,
187}; 193};
@@ -251,8 +257,9 @@ public:
251 } 257 }
252 258
253 bool operator<(const Sampler& rhs) const { 259 bool operator<(const Sampler& rhs) const {
254 return std::tie(offset, index, type, is_array, is_shadow) < 260 return std::tie(index, offset, type, is_array, is_shadow, is_bindless) <
255 std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_array, rhs.is_shadow); 261 std::tie(rhs.index, rhs.offset, rhs.type, rhs.is_array, rhs.is_shadow,
262 rhs.is_bindless);
256 } 263 }
257 264
258private: 265private:
@@ -327,40 +334,31 @@ struct MetaTexture {
327 u32 element{}; 334 u32 element{};
328}; 335};
329 336
330inline constexpr MetaArithmetic PRECISE = {true}; 337constexpr MetaArithmetic PRECISE = {true};
331inline constexpr MetaArithmetic NO_PRECISE = {false}; 338constexpr MetaArithmetic NO_PRECISE = {false};
332 339
333using Meta = std::variant<MetaArithmetic, MetaTexture, Tegra::Shader::HalfType>; 340using Meta = std::variant<MetaArithmetic, MetaTexture, Tegra::Shader::HalfType>;
334 341
335/// Holds any kind of operation that can be done in the IR 342/// Holds any kind of operation that can be done in the IR
336class OperationNode final { 343class OperationNode final {
337public: 344public:
338 template <typename... T> 345 explicit OperationNode(OperationCode code) : code{code} {}
339 explicit constexpr OperationNode(OperationCode code) : code{code}, meta{} {}
340 346
341 template <typename... T> 347 explicit OperationNode(OperationCode code, Meta&& meta) : code{code}, meta{std::move(meta)} {}
342 explicit constexpr OperationNode(OperationCode code, Meta&& meta)
343 : code{code}, meta{std::move(meta)} {}
344 348
345 template <typename... T> 349 template <typename... T>
346 explicit constexpr OperationNode(OperationCode code, const T*... operands) 350 explicit OperationNode(OperationCode code, const T*... operands)
347 : OperationNode(code, {}, operands...) {} 351 : OperationNode(code, {}, operands...) {}
348 352
349 template <typename... T> 353 template <typename... T>
350 explicit constexpr OperationNode(OperationCode code, Meta&& meta, const T*... operands_) 354 explicit OperationNode(OperationCode code, Meta&& meta, const T*... operands_)
351 : code{code}, meta{std::move(meta)} { 355 : code{code}, meta{std::move(meta)}, operands{operands_...} {}
352
353 auto operands_list = {operands_...};
354 for (auto& operand : operands_list) {
355 operands.push_back(operand);
356 }
357 }
358 356
359 explicit OperationNode(OperationCode code, Meta&& meta, std::vector<Node>&& operands) 357 explicit OperationNode(OperationCode code, Meta&& meta, std::vector<Node>&& operands)
360 : code{code}, meta{meta}, operands{std::move(operands)} {} 358 : code{code}, meta{meta}, operands{std::move(operands)} {}
361 359
362 explicit OperationNode(OperationCode code, std::vector<Node>&& operands) 360 explicit OperationNode(OperationCode code, std::vector<Node>&& operands)
363 : code{code}, meta{}, operands{std::move(operands)} {} 361 : code{code}, operands{std::move(operands)} {}
364 362
365 OperationCode GetCode() const { 363 OperationCode GetCode() const {
366 return code; 364 return code;
@@ -464,17 +462,14 @@ private:
464/// Attribute buffer memory (known as attributes or varyings in GLSL terms) 462/// Attribute buffer memory (known as attributes or varyings in GLSL terms)
465class AbufNode final { 463class AbufNode final {
466public: 464public:
467 explicit constexpr AbufNode(Tegra::Shader::Attribute::Index index, u32 element, 465 // Initialize for standard attributes (index is explicit).
468 const Tegra::Shader::IpaMode& input_mode, Node buffer = {})
469 : input_mode{input_mode}, buffer{buffer}, index{index}, element{element} {}
470
471 explicit constexpr AbufNode(Tegra::Shader::Attribute::Index index, u32 element, 466 explicit constexpr AbufNode(Tegra::Shader::Attribute::Index index, u32 element,
472 Node buffer = {}) 467 Node buffer = {})
473 : input_mode{}, buffer{buffer}, index{index}, element{element} {} 468 : buffer{buffer}, index{index}, element{element} {}
474 469
475 Tegra::Shader::IpaMode GetInputMode() const { 470 // Initialize for physical attributes (index is a variable value).
476 return input_mode; 471 explicit constexpr AbufNode(Node physical_address, Node buffer = {})
477 } 472 : physical_address{physical_address}, buffer{buffer} {}
478 473
479 Tegra::Shader::Attribute::Index GetIndex() const { 474 Tegra::Shader::Attribute::Index GetIndex() const {
480 return index; 475 return index;
@@ -488,11 +483,19 @@ public:
488 return buffer; 483 return buffer;
489 } 484 }
490 485
486 bool IsPhysicalBuffer() const {
487 return physical_address != nullptr;
488 }
489
490 Node GetPhysicalAddress() const {
491 return physical_address;
492 }
493
491private: 494private:
492 const Tegra::Shader::IpaMode input_mode; 495 Node physical_address{};
493 const Node buffer; 496 Node buffer{};
494 const Tegra::Shader::Attribute::Index index; 497 Tegra::Shader::Attribute::Index index{};
495 const u32 element; 498 u32 element{};
496}; 499};
497 500
498/// Constant buffer node, usually mapped to uniform buffers in GLSL 501/// Constant buffer node, usually mapped to uniform buffers in GLSL
@@ -566,11 +569,8 @@ private:
566 569
567class ShaderIR final { 570class ShaderIR final {
568public: 571public:
569 explicit ShaderIR(const ProgramCode& program_code, u32 main_offset) 572 explicit ShaderIR(const ProgramCode& program_code, u32 main_offset);
570 : program_code{program_code}, main_offset{main_offset} { 573 ~ShaderIR();
571
572 Decode();
573 }
574 574
575 const std::map<u32, NodeBlock>& GetBasicBlocks() const { 575 const std::map<u32, NodeBlock>& GetBasicBlocks() const {
576 return basic_blocks; 576 return basic_blocks;
@@ -584,8 +584,7 @@ public:
584 return used_predicates; 584 return used_predicates;
585 } 585 }
586 586
587 const std::map<Tegra::Shader::Attribute::Index, std::set<Tegra::Shader::IpaMode>>& 587 const std::set<Tegra::Shader::Attribute::Index>& GetInputAttributes() const {
588 GetInputAttributes() const {
589 return used_input_attributes; 588 return used_input_attributes;
590 } 589 }
591 590
@@ -614,6 +613,10 @@ public:
614 return static_cast<std::size_t>(coverage_end * sizeof(u64)); 613 return static_cast<std::size_t>(coverage_end * sizeof(u64));
615 } 614 }
616 615
616 bool HasPhysicalAttributes() const {
617 return uses_physical_attributes;
618 }
619
617 const Tegra::Shader::Header& GetHeader() const { 620 const Tegra::Shader::Header& GetHeader() const {
618 return header; 621 return header;
619 } 622 }
@@ -666,7 +669,7 @@ private:
666 /// Creates a conditional node 669 /// Creates a conditional node
667 Node Conditional(Node condition, std::vector<Node>&& code); 670 Node Conditional(Node condition, std::vector<Node>&& code);
668 /// Creates a commentary 671 /// Creates a commentary
669 Node Comment(const std::string& text); 672 Node Comment(std::string text);
670 /// Creates an u32 immediate 673 /// Creates an u32 immediate
671 Node Immediate(u32 value); 674 Node Immediate(u32 value);
672 /// Creates a s32 immediate 675 /// Creates a s32 immediate
@@ -695,8 +698,9 @@ private:
695 /// Generates a predicate node for an immediate true or false value 698 /// Generates a predicate node for an immediate true or false value
696 Node GetPredicate(bool immediate); 699 Node GetPredicate(bool immediate);
697 /// Generates a node representing an input attribute. Keeps track of used attributes. 700 /// Generates a node representing an input attribute. Keeps track of used attributes.
698 Node GetInputAttribute(Tegra::Shader::Attribute::Index index, u64 element, 701 Node GetInputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer = {});
699 const Tegra::Shader::IpaMode& input_mode, Node buffer = {}); 702 /// Generates a node representing a physical input attribute.
703 Node GetPhysicalInputAttribute(Tegra::Shader::Register physical_address, Node buffer = {});
700 /// Generates a node representing an output attribute. Keeps track of used attributes. 704 /// Generates a node representing an output attribute. Keeps track of used attributes.
701 Node GetOutputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer); 705 Node GetOutputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer);
702 /// Generates a node representing an internal flag 706 /// Generates a node representing an internal flag
@@ -813,16 +817,15 @@ private:
813 void WriteLop3Instruction(NodeBlock& bb, Tegra::Shader::Register dest, Node op_a, Node op_b, 817 void WriteLop3Instruction(NodeBlock& bb, Tegra::Shader::Register dest, Node op_a, Node op_b,
814 Node op_c, Node imm_lut, bool sets_cc); 818 Node op_c, Node imm_lut, bool sets_cc);
815 819
816 Node TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor); 820 Node TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) const;
817 821
818 std::optional<u32> TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor); 822 std::optional<u32> TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor) const;
819 823
820 std::pair<Node, s64> TrackRegister(const GprNode* tracked, const NodeBlock& code, s64 cursor); 824 std::pair<Node, s64> TrackRegister(const GprNode* tracked, const NodeBlock& code,
825 s64 cursor) const;
821 826
822 std::tuple<Node, Node, GlobalMemoryBase> TrackAndGetGlobalMemory(NodeBlock& bb, 827 std::tuple<Node, Node, GlobalMemoryBase> TrackAndGetGlobalMemory(
823 Node addr_register, 828 NodeBlock& bb, Tegra::Shader::Instruction instr, bool is_write);
824 u32 immediate_offset,
825 bool is_write);
826 829
827 template <typename... T> 830 template <typename... T>
828 Node Operation(OperationCode code, const T*... operands) { 831 Node Operation(OperationCode code, const T*... operands) {
@@ -834,12 +837,10 @@ private:
834 return StoreNode(OperationNode(code, std::move(meta), operands...)); 837 return StoreNode(OperationNode(code, std::move(meta), operands...));
835 } 838 }
836 839
837 template <typename... T>
838 Node Operation(OperationCode code, std::vector<Node>&& operands) { 840 Node Operation(OperationCode code, std::vector<Node>&& operands) {
839 return StoreNode(OperationNode(code, std::move(operands))); 841 return StoreNode(OperationNode(code, std::move(operands)));
840 } 842 }
841 843
842 template <typename... T>
843 Node Operation(OperationCode code, Meta&& meta, std::vector<Node>&& operands) { 844 Node Operation(OperationCode code, Meta&& meta, std::vector<Node>&& operands) {
844 return StoreNode(OperationNode(code, std::move(meta), std::move(operands))); 845 return StoreNode(OperationNode(code, std::move(meta), std::move(operands)));
845 } 846 }
@@ -871,13 +872,13 @@ private:
871 872
872 std::set<u32> used_registers; 873 std::set<u32> used_registers;
873 std::set<Tegra::Shader::Pred> used_predicates; 874 std::set<Tegra::Shader::Pred> used_predicates;
874 std::map<Tegra::Shader::Attribute::Index, std::set<Tegra::Shader::IpaMode>> 875 std::set<Tegra::Shader::Attribute::Index> used_input_attributes;
875 used_input_attributes;
876 std::set<Tegra::Shader::Attribute::Index> used_output_attributes; 876 std::set<Tegra::Shader::Attribute::Index> used_output_attributes;
877 std::map<u32, ConstBuffer> used_cbufs; 877 std::map<u32, ConstBuffer> used_cbufs;
878 std::set<Sampler> used_samplers; 878 std::set<Sampler> used_samplers;
879 std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{}; 879 std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{};
880 std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory; 880 std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory;
881 bool uses_physical_attributes{}; // Shader uses AL2P or physical attribute read/writes
881 882
882 Tegra::Shader::Header header; 883 Tegra::Shader::Header header;
883}; 884};
diff --git a/src/video_core/shader/track.cpp b/src/video_core/shader/track.cpp
index 4505667ff..19ede1eb9 100644
--- a/src/video_core/shader/track.cpp
+++ b/src/video_core/shader/track.cpp
@@ -17,22 +17,24 @@ std::pair<Node, s64> FindOperation(const NodeBlock& code, s64 cursor,
17 for (; cursor >= 0; --cursor) { 17 for (; cursor >= 0; --cursor) {
18 const Node node = code.at(cursor); 18 const Node node = code.at(cursor);
19 if (const auto operation = std::get_if<OperationNode>(node)) { 19 if (const auto operation = std::get_if<OperationNode>(node)) {
20 if (operation->GetCode() == operation_code) 20 if (operation->GetCode() == operation_code) {
21 return {node, cursor}; 21 return {node, cursor};
22 }
22 } 23 }
23 if (const auto conditional = std::get_if<ConditionalNode>(node)) { 24 if (const auto conditional = std::get_if<ConditionalNode>(node)) {
24 const auto& conditional_code = conditional->GetCode(); 25 const auto& conditional_code = conditional->GetCode();
25 const auto [found, internal_cursor] = FindOperation( 26 const auto [found, internal_cursor] = FindOperation(
26 conditional_code, static_cast<s64>(conditional_code.size() - 1), operation_code); 27 conditional_code, static_cast<s64>(conditional_code.size() - 1), operation_code);
27 if (found) 28 if (found) {
28 return {found, cursor}; 29 return {found, cursor};
30 }
29 } 31 }
30 } 32 }
31 return {}; 33 return {};
32} 34}
33} // namespace 35} // namespace
34 36
35Node ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) { 37Node ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) const {
36 if (const auto cbuf = std::get_if<CbufNode>(tracked)) { 38 if (const auto cbuf = std::get_if<CbufNode>(tracked)) {
37 // Cbuf found, but it has to be immediate 39 // Cbuf found, but it has to be immediate
38 return std::holds_alternative<ImmediateNode>(*cbuf->GetOffset()) ? tracked : nullptr; 40 return std::holds_alternative<ImmediateNode>(*cbuf->GetOffset()) ? tracked : nullptr;
@@ -65,7 +67,7 @@ Node ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) {
65 return nullptr; 67 return nullptr;
66} 68}
67 69
68std::optional<u32> ShaderIR::TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor) { 70std::optional<u32> ShaderIR::TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor) const {
69 // Reduce the cursor in one to avoid infinite loops when the instruction sets the same register 71 // Reduce the cursor in one to avoid infinite loops when the instruction sets the same register
70 // that it uses as operand 72 // that it uses as operand
71 const auto [found, found_cursor] = 73 const auto [found, found_cursor] =
@@ -80,7 +82,7 @@ std::optional<u32> ShaderIR::TrackImmediate(Node tracked, const NodeBlock& code,
80} 82}
81 83
82std::pair<Node, s64> ShaderIR::TrackRegister(const GprNode* tracked, const NodeBlock& code, 84std::pair<Node, s64> ShaderIR::TrackRegister(const GprNode* tracked, const NodeBlock& code,
83 s64 cursor) { 85 s64 cursor) const {
84 for (; cursor >= 0; --cursor) { 86 for (; cursor >= 0; --cursor) {
85 const auto [found_node, new_cursor] = FindOperation(code, cursor, OperationCode::Assign); 87 const auto [found_node, new_cursor] = FindOperation(code, cursor, OperationCode::Assign);
86 if (!found_node) { 88 if (!found_node) {
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index 3b022a456..6384fa8d2 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -178,39 +178,44 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
178 return PixelFormat::ABGR8S; 178 return PixelFormat::ABGR8S;
179 case Tegra::Texture::ComponentType::UINT: 179 case Tegra::Texture::ComponentType::UINT:
180 return PixelFormat::ABGR8UI; 180 return PixelFormat::ABGR8UI;
181 default:
182 break;
181 } 183 }
182 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 184 break;
183 UNREACHABLE();
184 case Tegra::Texture::TextureFormat::B5G6R5: 185 case Tegra::Texture::TextureFormat::B5G6R5:
185 switch (component_type) { 186 switch (component_type) {
186 case Tegra::Texture::ComponentType::UNORM: 187 case Tegra::Texture::ComponentType::UNORM:
187 return PixelFormat::B5G6R5U; 188 return PixelFormat::B5G6R5U;
189 default:
190 break;
188 } 191 }
189 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 192 break;
190 UNREACHABLE();
191 case Tegra::Texture::TextureFormat::A2B10G10R10: 193 case Tegra::Texture::TextureFormat::A2B10G10R10:
192 switch (component_type) { 194 switch (component_type) {
193 case Tegra::Texture::ComponentType::UNORM: 195 case Tegra::Texture::ComponentType::UNORM:
194 return PixelFormat::A2B10G10R10U; 196 return PixelFormat::A2B10G10R10U;
197 default:
198 break;
195 } 199 }
196 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 200 break;
197 UNREACHABLE();
198 case Tegra::Texture::TextureFormat::A1B5G5R5: 201 case Tegra::Texture::TextureFormat::A1B5G5R5:
199 switch (component_type) { 202 switch (component_type) {
200 case Tegra::Texture::ComponentType::UNORM: 203 case Tegra::Texture::ComponentType::UNORM:
201 return PixelFormat::A1B5G5R5U; 204 return PixelFormat::A1B5G5R5U;
205 default:
206 break;
202 } 207 }
203 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 208 break;
204 UNREACHABLE();
205 case Tegra::Texture::TextureFormat::R8: 209 case Tegra::Texture::TextureFormat::R8:
206 switch (component_type) { 210 switch (component_type) {
207 case Tegra::Texture::ComponentType::UNORM: 211 case Tegra::Texture::ComponentType::UNORM:
208 return PixelFormat::R8U; 212 return PixelFormat::R8U;
209 case Tegra::Texture::ComponentType::UINT: 213 case Tegra::Texture::ComponentType::UINT:
210 return PixelFormat::R8UI; 214 return PixelFormat::R8UI;
215 default:
216 break;
211 } 217 }
212 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 218 break;
213 UNREACHABLE();
214 case Tegra::Texture::TextureFormat::G8R8: 219 case Tegra::Texture::TextureFormat::G8R8:
215 // TextureFormat::G8R8 is actually ordered red then green, as such we can use 220 // TextureFormat::G8R8 is actually ordered red then green, as such we can use
216 // PixelFormat::RG8U and PixelFormat::RG8S. This was tested with The Legend of Zelda: Breath 221 // PixelFormat::RG8U and PixelFormat::RG8S. This was tested with The Legend of Zelda: Breath
@@ -220,50 +225,55 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
220 return PixelFormat::RG8U; 225 return PixelFormat::RG8U;
221 case Tegra::Texture::ComponentType::SNORM: 226 case Tegra::Texture::ComponentType::SNORM:
222 return PixelFormat::RG8S; 227 return PixelFormat::RG8S;
228 default:
229 break;
223 } 230 }
224 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 231 break;
225 UNREACHABLE();
226 case Tegra::Texture::TextureFormat::R16_G16_B16_A16: 232 case Tegra::Texture::TextureFormat::R16_G16_B16_A16:
227 switch (component_type) { 233 switch (component_type) {
228 case Tegra::Texture::ComponentType::UNORM: 234 case Tegra::Texture::ComponentType::UNORM:
229 return PixelFormat::RGBA16U; 235 return PixelFormat::RGBA16U;
230 case Tegra::Texture::ComponentType::FLOAT: 236 case Tegra::Texture::ComponentType::FLOAT:
231 return PixelFormat::RGBA16F; 237 return PixelFormat::RGBA16F;
238 default:
239 break;
232 } 240 }
233 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 241 break;
234 UNREACHABLE();
235 case Tegra::Texture::TextureFormat::BF10GF11RF11: 242 case Tegra::Texture::TextureFormat::BF10GF11RF11:
236 switch (component_type) { 243 switch (component_type) {
237 case Tegra::Texture::ComponentType::FLOAT: 244 case Tegra::Texture::ComponentType::FLOAT:
238 return PixelFormat::R11FG11FB10F; 245 return PixelFormat::R11FG11FB10F;
246 default:
247 break;
239 } 248 }
240 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
241 UNREACHABLE();
242 case Tegra::Texture::TextureFormat::R32_G32_B32_A32: 249 case Tegra::Texture::TextureFormat::R32_G32_B32_A32:
243 switch (component_type) { 250 switch (component_type) {
244 case Tegra::Texture::ComponentType::FLOAT: 251 case Tegra::Texture::ComponentType::FLOAT:
245 return PixelFormat::RGBA32F; 252 return PixelFormat::RGBA32F;
246 case Tegra::Texture::ComponentType::UINT: 253 case Tegra::Texture::ComponentType::UINT:
247 return PixelFormat::RGBA32UI; 254 return PixelFormat::RGBA32UI;
255 default:
256 break;
248 } 257 }
249 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 258 break;
250 UNREACHABLE();
251 case Tegra::Texture::TextureFormat::R32_G32: 259 case Tegra::Texture::TextureFormat::R32_G32:
252 switch (component_type) { 260 switch (component_type) {
253 case Tegra::Texture::ComponentType::FLOAT: 261 case Tegra::Texture::ComponentType::FLOAT:
254 return PixelFormat::RG32F; 262 return PixelFormat::RG32F;
255 case Tegra::Texture::ComponentType::UINT: 263 case Tegra::Texture::ComponentType::UINT:
256 return PixelFormat::RG32UI; 264 return PixelFormat::RG32UI;
265 default:
266 break;
257 } 267 }
258 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 268 break;
259 UNREACHABLE();
260 case Tegra::Texture::TextureFormat::R32_G32_B32: 269 case Tegra::Texture::TextureFormat::R32_G32_B32:
261 switch (component_type) { 270 switch (component_type) {
262 case Tegra::Texture::ComponentType::FLOAT: 271 case Tegra::Texture::ComponentType::FLOAT:
263 return PixelFormat::RGB32F; 272 return PixelFormat::RGB32F;
273 default:
274 break;
264 } 275 }
265 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 276 break;
266 UNREACHABLE();
267 case Tegra::Texture::TextureFormat::R16: 277 case Tegra::Texture::TextureFormat::R16:
268 switch (component_type) { 278 switch (component_type) {
269 case Tegra::Texture::ComponentType::FLOAT: 279 case Tegra::Texture::ComponentType::FLOAT:
@@ -276,18 +286,20 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
276 return PixelFormat::R16UI; 286 return PixelFormat::R16UI;
277 case Tegra::Texture::ComponentType::SINT: 287 case Tegra::Texture::ComponentType::SINT:
278 return PixelFormat::R16I; 288 return PixelFormat::R16I;
289 default:
290 break;
279 } 291 }
280 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 292 break;
281 UNREACHABLE();
282 case Tegra::Texture::TextureFormat::R32: 293 case Tegra::Texture::TextureFormat::R32:
283 switch (component_type) { 294 switch (component_type) {
284 case Tegra::Texture::ComponentType::FLOAT: 295 case Tegra::Texture::ComponentType::FLOAT:
285 return PixelFormat::R32F; 296 return PixelFormat::R32F;
286 case Tegra::Texture::ComponentType::UINT: 297 case Tegra::Texture::ComponentType::UINT:
287 return PixelFormat::R32UI; 298 return PixelFormat::R32UI;
299 default:
300 break;
288 } 301 }
289 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 302 break;
290 UNREACHABLE();
291 case Tegra::Texture::TextureFormat::ZF32: 303 case Tegra::Texture::TextureFormat::ZF32:
292 return PixelFormat::Z32F; 304 return PixelFormat::Z32F;
293 case Tegra::Texture::TextureFormat::Z16: 305 case Tegra::Texture::TextureFormat::Z16:
@@ -310,9 +322,10 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
310 return PixelFormat::DXN2UNORM; 322 return PixelFormat::DXN2UNORM;
311 case Tegra::Texture::ComponentType::SNORM: 323 case Tegra::Texture::ComponentType::SNORM:
312 return PixelFormat::DXN2SNORM; 324 return PixelFormat::DXN2SNORM;
325 default:
326 break;
313 } 327 }
314 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 328 break;
315 UNREACHABLE();
316 case Tegra::Texture::TextureFormat::BC7U: 329 case Tegra::Texture::TextureFormat::BC7U:
317 return is_srgb ? PixelFormat::BC7U_SRGB : PixelFormat::BC7U; 330 return is_srgb ? PixelFormat::BC7U_SRGB : PixelFormat::BC7U;
318 case Tegra::Texture::TextureFormat::BC6H_UF16: 331 case Tegra::Texture::TextureFormat::BC6H_UF16:
@@ -343,15 +356,17 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
343 return PixelFormat::RG16UI; 356 return PixelFormat::RG16UI;
344 case Tegra::Texture::ComponentType::SINT: 357 case Tegra::Texture::ComponentType::SINT:
345 return PixelFormat::RG16I; 358 return PixelFormat::RG16I;
359 default:
360 break;
346 } 361 }
347 LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); 362 break;
348 UNREACHABLE();
349 default: 363 default:
350 LOG_CRITICAL(HW_GPU, "Unimplemented format={}, component_type={}", static_cast<u32>(format), 364 break;
351 static_cast<u32>(component_type));
352 UNREACHABLE();
353 return PixelFormat::ABGR8U;
354 } 365 }
366 LOG_CRITICAL(HW_GPU, "Unimplemented format={}, component_type={}", static_cast<u32>(format),
367 static_cast<u32>(component_type));
368 UNREACHABLE();
369 return PixelFormat::ABGR8U;
355} 370}
356 371
357ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type) { 372ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type) {
@@ -513,8 +528,9 @@ bool IsFormatBCn(PixelFormat format) {
513 case PixelFormat::DXT45_SRGB: 528 case PixelFormat::DXT45_SRGB:
514 case PixelFormat::BC7U_SRGB: 529 case PixelFormat::BC7U_SRGB:
515 return true; 530 return true;
531 default:
532 return false;
516 } 533 }
517 return false;
518} 534}
519 535
520} // namespace VideoCore::Surface 536} // namespace VideoCore::Surface
diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp
index b508d64e9..a9b8f69af 100644
--- a/src/video_core/textures/astc.cpp
+++ b/src/video_core/textures/astc.cpp
@@ -25,8 +25,8 @@
25 25
26class InputBitStream { 26class InputBitStream {
27public: 27public:
28 explicit InputBitStream(const unsigned char* ptr, int nBits = 0, int start_offset = 0) 28 explicit InputBitStream(const unsigned char* ptr, int start_offset = 0)
29 : m_NumBits(nBits), m_CurByte(ptr), m_NextBit(start_offset % 8) {} 29 : m_CurByte(ptr), m_NextBit(start_offset % 8) {}
30 30
31 ~InputBitStream() = default; 31 ~InputBitStream() = default;
32 32
@@ -55,12 +55,9 @@ public:
55 } 55 }
56 56
57private: 57private:
58 const int m_NumBits;
59 const unsigned char* m_CurByte; 58 const unsigned char* m_CurByte;
60 int m_NextBit = 0; 59 int m_NextBit = 0;
61 int m_BitsRead = 0; 60 int m_BitsRead = 0;
62
63 bool done = false;
64}; 61};
65 62
66class OutputBitStream { 63class OutputBitStream {
@@ -114,7 +111,6 @@ private:
114 const int m_NumBits; 111 const int m_NumBits;
115 unsigned char* m_CurByte; 112 unsigned char* m_CurByte;
116 int m_NextBit = 0; 113 int m_NextBit = 0;
117 int m_BitsRead = 0;
118 114
119 bool done = false; 115 bool done = false;
120}; 116};
@@ -1616,6 +1612,7 @@ namespace Tegra::Texture::ASTC {
1616std::vector<uint8_t> Decompress(const uint8_t* data, uint32_t width, uint32_t height, 1612std::vector<uint8_t> Decompress(const uint8_t* data, uint32_t width, uint32_t height,
1617 uint32_t depth, uint32_t block_width, uint32_t block_height) { 1613 uint32_t depth, uint32_t block_width, uint32_t block_height) {
1618 uint32_t blockIdx = 0; 1614 uint32_t blockIdx = 0;
1615 std::size_t depth_offset = 0;
1619 std::vector<uint8_t> outData(height * width * depth * 4); 1616 std::vector<uint8_t> outData(height * width * depth * 4);
1620 for (uint32_t k = 0; k < depth; k++) { 1617 for (uint32_t k = 0; k < depth; k++) {
1621 for (uint32_t j = 0; j < height; j += block_height) { 1618 for (uint32_t j = 0; j < height; j += block_height) {
@@ -1630,7 +1627,7 @@ std::vector<uint8_t> Decompress(const uint8_t* data, uint32_t width, uint32_t he
1630 uint32_t decompWidth = std::min(block_width, width - i); 1627 uint32_t decompWidth = std::min(block_width, width - i);
1631 uint32_t decompHeight = std::min(block_height, height - j); 1628 uint32_t decompHeight = std::min(block_height, height - j);
1632 1629
1633 uint8_t* outRow = outData.data() + (j * width + i) * 4; 1630 uint8_t* outRow = depth_offset + outData.data() + (j * width + i) * 4;
1634 for (uint32_t jj = 0; jj < decompHeight; jj++) { 1631 for (uint32_t jj = 0; jj < decompHeight; jj++) {
1635 memcpy(outRow + jj * width * 4, uncompData + jj * block_width, decompWidth * 4); 1632 memcpy(outRow + jj * width * 4, uncompData + jj * block_width, decompWidth * 4);
1636 } 1633 }
@@ -1638,6 +1635,7 @@ std::vector<uint8_t> Decompress(const uint8_t* data, uint32_t width, uint32_t he
1638 blockIdx++; 1635 blockIdx++;
1639 } 1636 }
1640 } 1637 }
1638 depth_offset += height * width * 4;
1641 } 1639 }
1642 1640
1643 return outData; 1641 return outData;
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 5138bd9a3..3ea7b55d0 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -82,8 +82,6 @@ add_executable(yuzu
82 util/limitable_input_dialog.h 82 util/limitable_input_dialog.h
83 util/sequence_dialog/sequence_dialog.cpp 83 util/sequence_dialog/sequence_dialog.cpp
84 util/sequence_dialog/sequence_dialog.h 84 util/sequence_dialog/sequence_dialog.h
85 util/spinbox.cpp
86 util/spinbox.h
87 util/util.cpp 85 util/util.cpp
88 util/util.h 86 util/util.h
89 compatdb.cpp 87 compatdb.cpp
@@ -157,6 +155,10 @@ target_compile_definitions(yuzu PRIVATE
157 # Use QStringBuilder for string concatenation to reduce 155 # Use QStringBuilder for string concatenation to reduce
158 # the overall number of temporary strings created. 156 # the overall number of temporary strings created.
159 -DQT_USE_QSTRINGBUILDER 157 -DQT_USE_QSTRINGBUILDER
158
159 # Disable implicit conversions from/to C strings
160 -DQT_NO_CAST_FROM_ASCII
161 -DQT_NO_CAST_TO_ASCII
160) 162)
161 163
162if (YUZU_ENABLE_COMPATIBILITY_REPORTING) 164if (YUZU_ENABLE_COMPATIBILITY_REPORTING)
diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp
index 3efa65a38..d39b3f07a 100644
--- a/src/yuzu/about_dialog.cpp
+++ b/src/yuzu/about_dialog.cpp
@@ -9,10 +9,10 @@
9 9
10AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) { 10AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) {
11 ui->setupUi(this); 11 ui->setupUi(this);
12 ui->labelLogo->setPixmap(QIcon::fromTheme("yuzu").pixmap(200)); 12 ui->labelLogo->setPixmap(QIcon::fromTheme(QStringLiteral("yuzu")).pixmap(200));
13 ui->labelBuildInfo->setText( 13 ui->labelBuildInfo->setText(ui->labelBuildInfo->text().arg(
14 ui->labelBuildInfo->text().arg(Common::g_build_fullname, Common::g_scm_branch, 14 QString::fromUtf8(Common::g_build_fullname), QString::fromUtf8(Common::g_scm_branch),
15 Common::g_scm_desc, QString(Common::g_build_date).left(10))); 15 QString::fromUtf8(Common::g_scm_desc), QString::fromUtf8(Common::g_build_date).left(10)));
16} 16}
17 17
18AboutDialog::~AboutDialog() = default; 18AboutDialog::~AboutDialog() = default;
diff --git a/src/yuzu/applets/error.cpp b/src/yuzu/applets/error.cpp
index 1fb2fe277..08ed57355 100644
--- a/src/yuzu/applets/error.cpp
+++ b/src/yuzu/applets/error.cpp
@@ -29,11 +29,13 @@ void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished)
29void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, 29void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
30 std::function<void()> finished) const { 30 std::function<void()> finished) const {
31 this->callback = std::move(finished); 31 this->callback = std::move(finished);
32
33 const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count());
32 emit MainWindowDisplayError( 34 emit MainWindowDisplayError(
33 tr("An error occured on %1 at %2.\nPlease try again or contact the " 35 tr("An error occured on %1 at %2.\nPlease try again or contact the "
34 "developer of the software.\n\nError Code: %3-%4 (0x%5)") 36 "developer of the software.\n\nError Code: %3-%4 (0x%5)")
35 .arg(QDateTime::fromSecsSinceEpoch(time.count()).toString("dddd, MMMM d, yyyy")) 37 .arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy")))
36 .arg(QDateTime::fromSecsSinceEpoch(time.count()).toString("h:mm:ss A")) 38 .arg(date_time.toString(QStringLiteral("h:mm:ss A")))
37 .arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) 39 .arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
38 .arg(error.description, 4, 10, QChar::fromLatin1('0')) 40 .arg(error.description, 4, 10, QChar::fromLatin1('0'))
39 .arg(error.raw, 8, 16, QChar::fromLatin1('0'))); 41 .arg(error.raw, 8, 16, QChar::fromLatin1('0')));
@@ -54,6 +56,6 @@ void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_te
54 56
55void QtErrorDisplay::MainWindowFinishedError() { 57void QtErrorDisplay::MainWindowFinishedError() {
56 // Acquire the HLE mutex 58 // Acquire the HLE mutex
57 std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); 59 std::lock_guard lock{HLE::g_hle_lock};
58 callback(); 60 callback();
59} 61}
diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp
index 812b0142f..42e26b978 100644
--- a/src/yuzu/applets/profile_select.cpp
+++ b/src/yuzu/applets/profile_select.cpp
@@ -84,10 +84,10 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
84 tree_view->setContextMenuPolicy(Qt::NoContextMenu); 84 tree_view->setContextMenuPolicy(Qt::NoContextMenu);
85 85
86 item_model->insertColumns(0, 1); 86 item_model->insertColumns(0, 1);
87 item_model->setHeaderData(0, Qt::Horizontal, "Users"); 87 item_model->setHeaderData(0, Qt::Horizontal, tr("Users"));
88 88
89 // We must register all custom types with the Qt Automoc system so that we are able to use it 89 // We must register all custom types with the Qt Automoc system so that we are able to use it
90 // with signals/slots. In this case, QList falls under the umbrells of custom types. 90 // with signals/slots. In this case, QList falls under the umbrella of custom types.
91 qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); 91 qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
92 92
93 layout->setContentsMargins(0, 0, 0, 0); 93 layout->setContentsMargins(0, 0, 0, 0);
diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp
index f3eb29b25..5223ec977 100644
--- a/src/yuzu/applets/software_keyboard.cpp
+++ b/src/yuzu/applets/software_keyboard.cpp
@@ -18,23 +18,30 @@ QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator(
18 : parameters(std::move(parameters)) {} 18 : parameters(std::move(parameters)) {}
19 19
20QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const { 20QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const {
21 if (input.size() > parameters.max_length) 21 if (input.size() > static_cast<s64>(parameters.max_length)) {
22 return Invalid; 22 return Invalid;
23 if (parameters.disable_space && input.contains(' ')) 23 }
24 if (parameters.disable_space && input.contains(QLatin1Char{' '})) {
24 return Invalid; 25 return Invalid;
25 if (parameters.disable_address && input.contains('@')) 26 }
27 if (parameters.disable_address && input.contains(QLatin1Char{'@'})) {
26 return Invalid; 28 return Invalid;
27 if (parameters.disable_percent && input.contains('%')) 29 }
30 if (parameters.disable_percent && input.contains(QLatin1Char{'%'})) {
28 return Invalid; 31 return Invalid;
29 if (parameters.disable_slash && (input.contains('/') || input.contains('\\'))) 32 }
33 if (parameters.disable_slash &&
34 (input.contains(QLatin1Char{'/'}) || input.contains(QLatin1Char{'\\'}))) {
30 return Invalid; 35 return Invalid;
36 }
31 if (parameters.disable_number && 37 if (parameters.disable_number &&
32 std::any_of(input.begin(), input.end(), [](QChar c) { return c.isDigit(); })) { 38 std::any_of(input.begin(), input.end(), [](QChar c) { return c.isDigit(); })) {
33 return Invalid; 39 return Invalid;
34 } 40 }
35 41
36 if (parameters.disable_download_code && 42 if (parameters.disable_download_code && std::any_of(input.begin(), input.end(), [](QChar c) {
37 std::any_of(input.begin(), input.end(), [](QChar c) { return c == 'O' || c == 'I'; })) { 43 return c == QLatin1Char{'O'} || c == QLatin1Char{'I'};
44 })) {
38 return Invalid; 45 return Invalid;
39 } 46 }
40 47
@@ -142,7 +149,7 @@ void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message,
142void QtSoftwareKeyboard::MainWindowFinishedText(std::optional<std::u16string> text) { 149void QtSoftwareKeyboard::MainWindowFinishedText(std::optional<std::u16string> text) {
143 // Acquire the HLE mutex 150 // Acquire the HLE mutex
144 std::lock_guard lock{HLE::g_hle_lock}; 151 std::lock_guard lock{HLE::g_hle_lock};
145 text_output(text); 152 text_output(std::move(text));
146} 153}
147 154
148void QtSoftwareKeyboard::MainWindowFinishedCheckDialog() { 155void QtSoftwareKeyboard::MainWindowFinishedCheckDialog() {
diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h
index c63720ba4..78c5a042b 100644
--- a/src/yuzu/applets/software_keyboard.h
+++ b/src/yuzu/applets/software_keyboard.h
@@ -6,7 +6,6 @@
6 6
7#include <QDialog> 7#include <QDialog>
8#include <QValidator> 8#include <QValidator>
9#include "common/assert.h"
10#include "core/frontend/applets/software_keyboard.h" 9#include "core/frontend/applets/software_keyboard.h"
11 10
12class GMainWindow; 11class GMainWindow;
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 5c98636c5..9e420b359 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -91,25 +91,25 @@ void EmuThread::run() {
91 91
92class GGLContext : public Core::Frontend::GraphicsContext { 92class GGLContext : public Core::Frontend::GraphicsContext {
93public: 93public:
94 explicit GGLContext(QOpenGLContext* shared_context) 94 explicit GGLContext(QOpenGLContext* shared_context) : shared_context{shared_context} {
95 : context{std::make_unique<QOpenGLContext>(shared_context)} { 95 context.setFormat(shared_context->format());
96 surface.setFormat(shared_context->format()); 96 context.setShareContext(shared_context);
97 surface.create(); 97 context.create();
98 } 98 }
99 99
100 void MakeCurrent() override { 100 void MakeCurrent() override {
101 context->makeCurrent(&surface); 101 context.makeCurrent(shared_context->surface());
102 } 102 }
103 103
104 void DoneCurrent() override { 104 void DoneCurrent() override {
105 context->doneCurrent(); 105 context.doneCurrent();
106 } 106 }
107 107
108 void SwapBuffers() override {} 108 void SwapBuffers() override {}
109 109
110private: 110private:
111 std::unique_ptr<QOpenGLContext> context; 111 QOpenGLContext* shared_context;
112 QOffscreenSurface surface; 112 QOpenGLContext context;
113}; 113};
114 114
115// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL 115// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
@@ -188,7 +188,9 @@ private:
188GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) 188GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
189 : QWidget(parent), emu_thread(emu_thread) { 189 : QWidget(parent), emu_thread(emu_thread) {
190 setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") 190 setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
191 .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); 191 .arg(QString::fromUtf8(Common::g_build_name),
192 QString::fromUtf8(Common::g_scm_branch),
193 QString::fromUtf8(Common::g_scm_desc)));
192 setAttribute(Qt::WA_AcceptTouchEvents); 194 setAttribute(Qt::WA_AcceptTouchEvents);
193 195
194 InputCommon::Init(); 196 InputCommon::Init();
@@ -217,7 +219,7 @@ void GRenderWindow::SwapBuffers() {
217 // However: 219 // However:
218 // - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called 220 // - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called
219 // since the last time `swapBuffers` was executed; 221 // since the last time `swapBuffers` was executed;
220 // - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks. 222 // - On macOS, if `makeCurrent` isn't called explicitly, resizing the buffer breaks.
221 context->makeCurrent(child); 223 context->makeCurrent(child);
222 224
223 context->swapBuffers(child); 225 context->swapBuffers(child);
@@ -356,7 +358,7 @@ void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
356} 358}
357 359
358std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { 360std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
359 return std::make_unique<GGLContext>(shared_context.get()); 361 return std::make_unique<GGLContext>(context.get());
360} 362}
361 363
362void GRenderWindow::InitRenderTarget() { 364void GRenderWindow::InitRenderTarget() {
@@ -379,6 +381,7 @@ void GRenderWindow::InitRenderTarget() {
379 fmt.setVersion(4, 3); 381 fmt.setVersion(4, 3);
380 if (Settings::values.use_compatibility_profile) { 382 if (Settings::values.use_compatibility_profile) {
381 fmt.setProfile(QSurfaceFormat::CompatibilityProfile); 383 fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
384 fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
382 } else { 385 } else {
383 fmt.setProfile(QSurfaceFormat::CoreProfile); 386 fmt.setProfile(QSurfaceFormat::CoreProfile);
384 } 387 }
@@ -437,8 +440,7 @@ void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_p
437 layout); 440 layout);
438} 441}
439 442
440void GRenderWindow::OnMinimalClientAreaChangeRequest( 443void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) {
441 const std::pair<unsigned, unsigned>& minimal_size) {
442 setMinimumSize(minimal_size.first, minimal_size.second); 444 setMinimumSize(minimal_size.first, minimal_size.second);
443} 445}
444 446
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 3df33aca1..7f9f8e8e3 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -162,8 +162,7 @@ private:
162 void TouchUpdateEvent(const QTouchEvent* event); 162 void TouchUpdateEvent(const QTouchEvent* event);
163 void TouchEndEvent(); 163 void TouchEndEvent();
164 164
165 void OnMinimalClientAreaChangeRequest( 165 void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) override;
166 const std::pair<unsigned, unsigned>& minimal_size) override;
167 166
168 QWidget* container = nullptr; 167 QWidget* container = nullptr;
169 GGLWidgetInternal* child = nullptr; 168 GGLWidgetInternal* child = nullptr;
diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp
index c8b0a5ec0..5477f050c 100644
--- a/src/yuzu/compatdb.cpp
+++ b/src/yuzu/compatdb.cpp
@@ -58,7 +58,7 @@ void CompatDB::Submit() {
58 58
59 button(NextButton)->setEnabled(false); 59 button(NextButton)->setEnabled(false);
60 button(NextButton)->setText(tr("Submitting")); 60 button(NextButton)->setText(tr("Submitting"));
61 button(QWizard::CancelButton)->setVisible(false); 61 button(CancelButton)->setVisible(false);
62 62
63 testcase_watcher.setFuture(QtConcurrent::run( 63 testcase_watcher.setFuture(QtConcurrent::run(
64 [] { return Core::System::GetInstance().TelemetrySession().SubmitTestcase(); })); 64 [] { return Core::System::GetInstance().TelemetrySession().SubmitTestcase(); }));
@@ -74,12 +74,12 @@ void CompatDB::OnTestcaseSubmitted() {
74 tr("An error occured while sending the Testcase")); 74 tr("An error occured while sending the Testcase"));
75 button(NextButton)->setEnabled(true); 75 button(NextButton)->setEnabled(true);
76 button(NextButton)->setText(tr("Next")); 76 button(NextButton)->setText(tr("Next"));
77 button(QWizard::CancelButton)->setVisible(true); 77 button(CancelButton)->setVisible(true);
78 } else { 78 } else {
79 next(); 79 next();
80 // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a 80 // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a
81 // workaround 81 // workaround
82 button(QWizard::CancelButton)->setVisible(false); 82 button(CancelButton)->setVisible(false);
83 } 83 }
84} 84}
85 85
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 6c6f047d8..b1942bedc 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -11,6 +11,7 @@
11#include "core/hle/service/hid/controllers/npad.h" 11#include "core/hle/service/hid/controllers/npad.h"
12#include "input_common/main.h" 12#include "input_common/main.h"
13#include "yuzu/configuration/config.h" 13#include "yuzu/configuration/config.h"
14#include "yuzu/ui_settings.h"
14 15
15Config::Config() { 16Config::Config() {
16 // TODO: Don't hardcode the path; let the frontend decide where to put the config files. 17 // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
@@ -206,79 +207,91 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
206}; 207};
207 208
208// This shouldn't have anything except static initializers (no functions). So 209// This shouldn't have anything except static initializers (no functions). So
209// QKeySequnce(...).toString() is NOT ALLOWED HERE. 210// QKeySequence(...).toString() is NOT ALLOWED HERE.
210// This must be in alphabetical order according to action name as it must have the same order as 211// This must be in alphabetical order according to action name as it must have the same order as
211// UISetting::values.shortcuts, which is alphabetically ordered. 212// UISetting::values.shortcuts, which is alphabetically ordered.
212const std::array<UISettings::Shortcut, 15> Config::default_hotkeys{ 213// clang-format off
213 {{"Capture Screenshot", "Main Window", {"Ctrl+P", Qt::ApplicationShortcut}}, 214const std::array<UISettings::Shortcut, 15> default_hotkeys{{
214 {"Continue/Pause Emulation", "Main Window", {"F4", Qt::WindowShortcut}}, 215 {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}},
215 {"Decrease Speed Limit", "Main Window", {"-", Qt::ApplicationShortcut}}, 216 {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
216 {"Exit yuzu", "Main Window", {"Ctrl+Q", Qt::WindowShortcut}}, 217 {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}},
217 {"Exit Fullscreen", "Main Window", {"Esc", Qt::WindowShortcut}}, 218 {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},
218 {"Fullscreen", "Main Window", {"F11", Qt::WindowShortcut}}, 219 {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), Qt::WindowShortcut}},
219 {"Increase Speed Limit", "Main Window", {"+", Qt::ApplicationShortcut}}, 220 {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}},
220 {"Load Amiibo", "Main Window", {"F2", Qt::ApplicationShortcut}}, 221 {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}},
221 {"Load File", "Main Window", {"Ctrl+O", Qt::WindowShortcut}}, 222 {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::ApplicationShortcut}},
222 {"Restart Emulation", "Main Window", {"F6", Qt::WindowShortcut}}, 223 {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WindowShortcut}},
223 {"Stop Emulation", "Main Window", {"F5", Qt::WindowShortcut}}, 224 {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
224 {"Toggle Filter Bar", "Main Window", {"Ctrl+F", Qt::WindowShortcut}}, 225 {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
225 {"Toggle Speed Limit", "Main Window", {"Ctrl+Z", Qt::ApplicationShortcut}}, 226 {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
226 {"Toggle Status Bar", "Main Window", {"Ctrl+S", Qt::WindowShortcut}}, 227 {QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
227 {"Change Docked Mode", "Main Window", {"F10", Qt::ApplicationShortcut}}}}; 228 {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
229 {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
230}};
231// clang-format on
228 232
229void Config::ReadPlayerValues() { 233void Config::ReadPlayerValues() {
230 for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { 234 for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
231 auto& player = Settings::values.players[p]; 235 auto& player = Settings::values.players[p];
232 236
233 player.connected = ReadSetting(QString("player_%1_connected").arg(p), false).toBool(); 237 player.connected =
238 ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool();
234 239
235 player.type = static_cast<Settings::ControllerType>( 240 player.type = static_cast<Settings::ControllerType>(
236 qt_config 241 qt_config
237 ->value(QString("player_%1_type").arg(p), 242 ->value(QStringLiteral("player_%1_type").arg(p),
238 static_cast<u8>(Settings::ControllerType::DualJoycon)) 243 static_cast<u8>(Settings::ControllerType::DualJoycon))
239 .toUInt()); 244 .toUInt());
240 245
241 player.body_color_left = qt_config 246 player.body_color_left = qt_config
242 ->value(QString("player_%1_body_color_left").arg(p), 247 ->value(QStringLiteral("player_%1_body_color_left").arg(p),
243 Settings::JOYCON_BODY_NEON_BLUE) 248 Settings::JOYCON_BODY_NEON_BLUE)
244 .toUInt(); 249 .toUInt();
245 player.body_color_right = qt_config 250 player.body_color_right = qt_config
246 ->value(QString("player_%1_body_color_right").arg(p), 251 ->value(QStringLiteral("player_%1_body_color_right").arg(p),
247 Settings::JOYCON_BODY_NEON_RED) 252 Settings::JOYCON_BODY_NEON_RED)
248 .toUInt(); 253 .toUInt();
249 player.button_color_left = qt_config 254 player.button_color_left = qt_config
250 ->value(QString("player_%1_button_color_left").arg(p), 255 ->value(QStringLiteral("player_%1_button_color_left").arg(p),
251 Settings::JOYCON_BUTTONS_NEON_BLUE) 256 Settings::JOYCON_BUTTONS_NEON_BLUE)
252 .toUInt(); 257 .toUInt();
253 player.button_color_right = qt_config 258 player.button_color_right =
254 ->value(QString("player_%1_button_color_right").arg(p), 259 qt_config
255 Settings::JOYCON_BUTTONS_NEON_RED) 260 ->value(QStringLiteral("player_%1_button_color_right").arg(p),
256 .toUInt(); 261 Settings::JOYCON_BUTTONS_NEON_RED)
262 .toUInt();
257 263
258 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { 264 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
259 std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); 265 const std::string default_param =
260 player.buttons[i] = 266 InputCommon::GenerateKeyboardParam(default_buttons[i]);
261 qt_config 267 auto& player_buttons = player.buttons[i];
262 ->value(QString("player_%1_").arg(p) + Settings::NativeButton::mapping[i], 268
263 QString::fromStdString(default_param)) 269 player_buttons = qt_config
264 .toString() 270 ->value(QStringLiteral("player_%1_").arg(p) +
265 .toStdString(); 271 QString::fromUtf8(Settings::NativeButton::mapping[i]),
266 if (player.buttons[i].empty()) 272 QString::fromStdString(default_param))
267 player.buttons[i] = default_param; 273 .toString()
274 .toStdString();
275 if (player_buttons.empty()) {
276 player_buttons = default_param;
277 }
268 } 278 }
269 279
270 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 280 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
271 std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 281 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
272 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 282 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
273 default_analogs[i][3], default_analogs[i][4], 0.5f); 283 default_analogs[i][3], default_analogs[i][4], 0.5f);
274 player.analogs[i] = 284 auto& player_analogs = player.analogs[i];
275 qt_config 285
276 ->value(QString("player_%1_").arg(p) + Settings::NativeAnalog::mapping[i], 286 player_analogs = qt_config
277 QString::fromStdString(default_param)) 287 ->value(QStringLiteral("player_%1_").arg(p) +
278 .toString() 288 QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
279 .toStdString(); 289 QString::fromStdString(default_param))
280 if (player.analogs[i].empty()) 290 .toString()
281 player.analogs[i] = default_param; 291 .toStdString();
292 if (player_analogs.empty()) {
293 player_analogs = default_param;
294 }
282 } 295 }
283 } 296 }
284 297
@@ -290,36 +303,45 @@ void Config::ReadPlayerValues() {
290} 303}
291 304
292void Config::ReadDebugValues() { 305void Config::ReadDebugValues() {
293 Settings::values.debug_pad_enabled = ReadSetting("debug_pad_enabled", false).toBool(); 306 Settings::values.debug_pad_enabled =
307 ReadSetting(QStringLiteral("debug_pad_enabled"), false).toBool();
308
294 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { 309 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
295 std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); 310 const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
296 Settings::values.debug_pad_buttons[i] = 311 auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i];
297 qt_config 312
298 ->value(QString("debug_pad_") + Settings::NativeButton::mapping[i], 313 debug_pad_buttons = qt_config
299 QString::fromStdString(default_param)) 314 ->value(QStringLiteral("debug_pad_") +
300 .toString() 315 QString::fromUtf8(Settings::NativeButton::mapping[i]),
301 .toStdString(); 316 QString::fromStdString(default_param))
302 if (Settings::values.debug_pad_buttons[i].empty()) 317 .toString()
303 Settings::values.debug_pad_buttons[i] = default_param; 318 .toStdString();
319 if (debug_pad_buttons.empty()) {
320 debug_pad_buttons = default_param;
321 }
304 } 322 }
305 323
306 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 324 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
307 std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 325 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
308 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 326 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
309 default_analogs[i][3], default_analogs[i][4], 0.5f); 327 default_analogs[i][3], default_analogs[i][4], 0.5f);
310 Settings::values.debug_pad_analogs[i] = 328 auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];
311 qt_config 329
312 ->value(QString("debug_pad_") + Settings::NativeAnalog::mapping[i], 330 debug_pad_analogs = qt_config
313 QString::fromStdString(default_param)) 331 ->value(QStringLiteral("debug_pad_") +
314 .toString() 332 QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
315 .toStdString(); 333 QString::fromStdString(default_param))
316 if (Settings::values.debug_pad_analogs[i].empty()) 334 .toString()
317 Settings::values.debug_pad_analogs[i] = default_param; 335 .toStdString();
336 if (debug_pad_analogs.empty()) {
337 debug_pad_analogs = default_param;
338 }
318 } 339 }
319} 340}
320 341
321void Config::ReadKeyboardValues() { 342void Config::ReadKeyboardValues() {
322 Settings::values.keyboard_enabled = ReadSetting("keyboard_enabled", false).toBool(); 343 Settings::values.keyboard_enabled =
344 ReadSetting(QStringLiteral("keyboard_enabled"), false).toBool();
323 345
324 std::transform(default_keyboard_keys.begin(), default_keyboard_keys.end(), 346 std::transform(default_keyboard_keys.begin(), default_keyboard_keys.end(),
325 Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); 347 Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam);
@@ -332,31 +354,41 @@ void Config::ReadKeyboardValues() {
332} 354}
333 355
334void Config::ReadMouseValues() { 356void Config::ReadMouseValues() {
335 Settings::values.mouse_enabled = ReadSetting("mouse_enabled", false).toBool(); 357 Settings::values.mouse_enabled = ReadSetting(QStringLiteral("mouse_enabled"), false).toBool();
336 358
337 for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { 359 for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
338 std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); 360 const std::string default_param =
339 Settings::values.mouse_buttons[i] = 361 InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
340 qt_config 362 auto& mouse_buttons = Settings::values.mouse_buttons[i];
341 ->value(QString("mouse_") + Settings::NativeMouseButton::mapping[i], 363
342 QString::fromStdString(default_param)) 364 mouse_buttons = qt_config
343 .toString() 365 ->value(QStringLiteral("mouse_") +
344 .toStdString(); 366 QString::fromUtf8(Settings::NativeMouseButton::mapping[i]),
345 if (Settings::values.mouse_buttons[i].empty()) 367 QString::fromStdString(default_param))
346 Settings::values.mouse_buttons[i] = default_param; 368 .toString()
369 .toStdString();
370 if (mouse_buttons.empty()) {
371 mouse_buttons = default_param;
372 }
347 } 373 }
348} 374}
349 375
350void Config::ReadTouchscreenValues() { 376void Config::ReadTouchscreenValues() {
351 Settings::values.touchscreen.enabled = ReadSetting("touchscreen_enabled", true).toBool(); 377 Settings::values.touchscreen.enabled =
378 ReadSetting(QStringLiteral("touchscreen_enabled"), true).toBool();
352 Settings::values.touchscreen.device = 379 Settings::values.touchscreen.device =
353 ReadSetting("touchscreen_device", "engine:emu_window").toString().toStdString(); 380 ReadSetting(QStringLiteral("touchscreen_device"), QStringLiteral("engine:emu_window"))
381 .toString()
382 .toStdString();
354 383
355 Settings::values.touchscreen.finger = ReadSetting("touchscreen_finger", 0).toUInt(); 384 Settings::values.touchscreen.finger =
356 Settings::values.touchscreen.rotation_angle = ReadSetting("touchscreen_angle", 0).toUInt(); 385 ReadSetting(QStringLiteral("touchscreen_finger"), 0).toUInt();
357 Settings::values.touchscreen.diameter_x = ReadSetting("touchscreen_diameter_x", 15).toUInt(); 386 Settings::values.touchscreen.rotation_angle =
358 Settings::values.touchscreen.diameter_y = ReadSetting("touchscreen_diameter_y", 15).toUInt(); 387 ReadSetting(QStringLiteral("touchscreen_angle"), 0).toUInt();
359 qt_config->endGroup(); 388 Settings::values.touchscreen.diameter_x =
389 ReadSetting(QStringLiteral("touchscreen_diameter_x"), 15).toUInt();
390 Settings::values.touchscreen.diameter_y =
391 ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt();
360} 392}
361 393
362void Config::ApplyDefaultProfileIfInputInvalid() { 394void Config::ApplyDefaultProfileIfInputInvalid() {
@@ -366,8 +398,25 @@ void Config::ApplyDefaultProfileIfInputInvalid() {
366 } 398 }
367} 399}
368 400
369void Config::ReadValues() { 401void Config::ReadAudioValues() {
370 qt_config->beginGroup("Controls"); 402 qt_config->beginGroup(QStringLiteral("Audio"));
403
404 Settings::values.sink_id = ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto"))
405 .toString()
406 .toStdString();
407 Settings::values.enable_audio_stretching =
408 ReadSetting(QStringLiteral("enable_audio_stretching"), true).toBool();
409 Settings::values.audio_device_id =
410 ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto"))
411 .toString()
412 .toStdString();
413 Settings::values.volume = ReadSetting(QStringLiteral("volume"), 1).toFloat();
414
415 qt_config->endGroup();
416}
417
418void Config::ReadControlValues() {
419 qt_config->beginGroup(QStringLiteral("Controls"));
371 420
372 ReadPlayerValues(); 421 ReadPlayerValues();
373 ReadDebugValues(); 422 ReadDebugValues();
@@ -376,220 +425,310 @@ void Config::ReadValues() {
376 ReadTouchscreenValues(); 425 ReadTouchscreenValues();
377 426
378 Settings::values.motion_device = 427 Settings::values.motion_device =
379 ReadSetting("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01") 428 ReadSetting(QStringLiteral("motion_device"),
429 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
380 .toString() 430 .toString()
381 .toStdString(); 431 .toStdString();
382 432
383 qt_config->beginGroup("Core");
384 Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool();
385 Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool();
386 qt_config->endGroup(); 433 qt_config->endGroup();
434}
387 435
388 qt_config->beginGroup("Renderer"); 436void Config::ReadCoreValues() {
389 Settings::values.resolution_factor = ReadSetting("resolution_factor", 1.0).toFloat(); 437 qt_config->beginGroup(QStringLiteral("Core"));
390 Settings::values.use_frame_limit = ReadSetting("use_frame_limit", true).toBool();
391 Settings::values.frame_limit = ReadSetting("frame_limit", 100).toInt();
392 Settings::values.use_compatibility_profile =
393 ReadSetting("use_compatibility_profile", true).toBool();
394 Settings::values.use_disk_shader_cache = ReadSetting("use_disk_shader_cache", true).toBool();
395 Settings::values.use_accurate_gpu_emulation =
396 ReadSetting("use_accurate_gpu_emulation", false).toBool();
397 Settings::values.use_asynchronous_gpu_emulation =
398 ReadSetting("use_asynchronous_gpu_emulation", false).toBool();
399 Settings::values.force_30fps_mode = ReadSetting("force_30fps_mode", false).toBool();
400 438
401 Settings::values.bg_red = ReadSetting("bg_red", 0.0).toFloat(); 439 Settings::values.use_cpu_jit = ReadSetting(QStringLiteral("use_cpu_jit"), true).toBool();
402 Settings::values.bg_green = ReadSetting("bg_green", 0.0).toFloat(); 440 Settings::values.use_multi_core = ReadSetting(QStringLiteral("use_multi_core"), false).toBool();
403 Settings::values.bg_blue = ReadSetting("bg_blue", 0.0).toFloat();
404 qt_config->endGroup();
405 441
406 qt_config->beginGroup("Audio");
407 Settings::values.sink_id = ReadSetting("output_engine", "auto").toString().toStdString();
408 Settings::values.enable_audio_stretching =
409 ReadSetting("enable_audio_stretching", true).toBool();
410 Settings::values.audio_device_id =
411 ReadSetting("output_device", "auto").toString().toStdString();
412 Settings::values.volume = ReadSetting("volume", 1).toFloat();
413 qt_config->endGroup(); 442 qt_config->endGroup();
443}
444
445void Config::ReadDataStorageValues() {
446 qt_config->beginGroup(QStringLiteral("Data Storage"));
414 447
415 qt_config->beginGroup("Data Storage"); 448 Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool();
416 Settings::values.use_virtual_sd = ReadSetting("use_virtual_sd", true).toBool();
417 FileUtil::GetUserPath( 449 FileUtil::GetUserPath(
418 FileUtil::UserPath::NANDDir, 450 FileUtil::UserPath::NANDDir,
419 qt_config 451 qt_config
420 ->value("nand_directory", 452 ->value(QStringLiteral("nand_directory"),
421 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))) 453 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)))
422 .toString() 454 .toString()
423 .toStdString()); 455 .toStdString());
424 FileUtil::GetUserPath( 456 FileUtil::GetUserPath(
425 FileUtil::UserPath::SDMCDir, 457 FileUtil::UserPath::SDMCDir,
426 qt_config 458 qt_config
427 ->value("sdmc_directory", 459 ->value(QStringLiteral("sdmc_directory"),
428 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))) 460 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)))
429 .toString() 461 .toString()
430 .toStdString()); 462 .toStdString());
431 qt_config->endGroup();
432 463
433 qt_config->beginGroup("Core");
434 Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool();
435 Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool();
436 qt_config->endGroup(); 464 qt_config->endGroup();
465}
437 466
438 qt_config->beginGroup("System"); 467void Config::ReadDebuggingValues() {
439 Settings::values.use_docked_mode = ReadSetting("use_docked_mode", false).toBool(); 468 qt_config->beginGroup(QStringLiteral("Debugging"));
440
441 Settings::values.current_user =
442 std::clamp<int>(ReadSetting("current_user", 0).toInt(), 0, Service::Account::MAX_USERS - 1);
443
444 Settings::values.language_index = ReadSetting("language_index", 1).toInt();
445
446 const auto rng_seed_enabled = ReadSetting("rng_seed_enabled", false).toBool();
447 if (rng_seed_enabled) {
448 Settings::values.rng_seed = ReadSetting("rng_seed", 0).toULongLong();
449 } else {
450 Settings::values.rng_seed = std::nullopt;
451 }
452
453 const auto custom_rtc_enabled = ReadSetting("custom_rtc_enabled", false).toBool();
454 if (custom_rtc_enabled) {
455 Settings::values.custom_rtc =
456 std::chrono::seconds(ReadSetting("custom_rtc", 0).toULongLong());
457 } else {
458 Settings::values.custom_rtc = std::nullopt;
459 }
460
461 qt_config->endGroup();
462 469
463 qt_config->beginGroup("Miscellaneous"); 470 Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
464 Settings::values.log_filter = ReadSetting("log_filter", "*:Info").toString().toStdString(); 471 Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
465 Settings::values.use_dev_keys = ReadSetting("use_dev_keys", false).toBool(); 472 Settings::values.program_args =
466 qt_config->endGroup(); 473 ReadSetting(QStringLiteral("program_args"), QStringLiteral("")).toString().toStdString();
474 Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool();
475 Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool();
467 476
468 qt_config->beginGroup("Debugging");
469 Settings::values.use_gdbstub = ReadSetting("use_gdbstub", false).toBool();
470 Settings::values.gdbstub_port = ReadSetting("gdbstub_port", 24689).toInt();
471 Settings::values.program_args = ReadSetting("program_args", "").toString().toStdString();
472 Settings::values.dump_exefs = ReadSetting("dump_exefs", false).toBool();
473 Settings::values.dump_nso = ReadSetting("dump_nso", false).toBool();
474 qt_config->endGroup(); 477 qt_config->endGroup();
478}
475 479
476 qt_config->beginGroup("WebService"); 480void Config::ReadDisabledAddOnValues() {
477 Settings::values.enable_telemetry = ReadSetting("enable_telemetry", true).toBool(); 481 const auto size = qt_config->beginReadArray(QStringLiteral("DisabledAddOns"));
478 Settings::values.web_api_url =
479 ReadSetting("web_api_url", "https://api.yuzu-emu.org").toString().toStdString();
480 Settings::values.yuzu_username = ReadSetting("yuzu_username").toString().toStdString();
481 Settings::values.yuzu_token = ReadSetting("yuzu_token").toString().toStdString();
482 qt_config->endGroup();
483 482
484 const auto size = qt_config->beginReadArray("DisabledAddOns");
485 for (int i = 0; i < size; ++i) { 483 for (int i = 0; i < size; ++i) {
486 qt_config->setArrayIndex(i); 484 qt_config->setArrayIndex(i);
487 const auto title_id = ReadSetting("title_id", 0).toULongLong(); 485 const auto title_id = ReadSetting(QStringLiteral("title_id"), 0).toULongLong();
488 std::vector<std::string> out; 486 std::vector<std::string> out;
489 const auto d_size = qt_config->beginReadArray("disabled"); 487 const auto d_size = qt_config->beginReadArray(QStringLiteral("disabled"));
490 for (int j = 0; j < d_size; ++j) { 488 for (int j = 0; j < d_size; ++j) {
491 qt_config->setArrayIndex(j); 489 qt_config->setArrayIndex(j);
492 out.push_back(ReadSetting("d", "").toString().toStdString()); 490 out.push_back(
491 ReadSetting(QStringLiteral("d"), QStringLiteral("")).toString().toStdString());
493 } 492 }
494 qt_config->endArray(); 493 qt_config->endArray();
495 Settings::values.disabled_addons.insert_or_assign(title_id, out); 494 Settings::values.disabled_addons.insert_or_assign(title_id, out);
496 } 495 }
496
497 qt_config->endArray(); 497 qt_config->endArray();
498}
499
500void Config::ReadMiscellaneousValues() {
501 qt_config->beginGroup(QStringLiteral("Miscellaneous"));
502
503 Settings::values.log_filter =
504 ReadSetting(QStringLiteral("log_filter"), QStringLiteral("*:Info"))
505 .toString()
506 .toStdString();
507 Settings::values.use_dev_keys = ReadSetting(QStringLiteral("use_dev_keys"), false).toBool();
498 508
499 qt_config->beginGroup("UI");
500 UISettings::values.theme = ReadSetting("theme", UISettings::themes[0].second).toString();
501 UISettings::values.enable_discord_presence =
502 ReadSetting("enable_discord_presence", true).toBool();
503 UISettings::values.screenshot_resolution_factor =
504 static_cast<u16>(ReadSetting("screenshot_resolution_factor", 0).toUInt());
505 UISettings::values.select_user_on_boot = ReadSetting("select_user_on_boot", false).toBool();
506
507 qt_config->beginGroup("UIGameList");
508 UISettings::values.show_unknown = ReadSetting("show_unknown", true).toBool();
509 UISettings::values.show_add_ons = ReadSetting("show_add_ons", true).toBool();
510 UISettings::values.icon_size = ReadSetting("icon_size", 64).toUInt();
511 UISettings::values.row_1_text_id = ReadSetting("row_1_text_id", 3).toUInt();
512 UISettings::values.row_2_text_id = ReadSetting("row_2_text_id", 2).toUInt();
513 qt_config->endGroup(); 509 qt_config->endGroup();
510}
511
512void Config::ReadPathValues() {
513 qt_config->beginGroup(QStringLiteral("Paths"));
514
515 UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString();
516 UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString();
517 UISettings::values.game_directory_path =
518 ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();
519 UISettings::values.game_directory_deepscan =
520 ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool();
521 UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
514 522
515 qt_config->beginGroup("UILayout");
516 UISettings::values.geometry = ReadSetting("geometry").toByteArray();
517 UISettings::values.state = ReadSetting("state").toByteArray();
518 UISettings::values.renderwindow_geometry = ReadSetting("geometryRenderWindow").toByteArray();
519 UISettings::values.gamelist_header_state = ReadSetting("gameListHeaderState").toByteArray();
520 UISettings::values.microprofile_geometry =
521 ReadSetting("microProfileDialogGeometry").toByteArray();
522 UISettings::values.microprofile_visible =
523 ReadSetting("microProfileDialogVisible", false).toBool();
524 qt_config->endGroup(); 523 qt_config->endGroup();
524}
525
526void Config::ReadRendererValues() {
527 qt_config->beginGroup(QStringLiteral("Renderer"));
528
529 Settings::values.resolution_factor =
530 ReadSetting(QStringLiteral("resolution_factor"), 1.0).toFloat();
531 Settings::values.use_frame_limit =
532 ReadSetting(QStringLiteral("use_frame_limit"), true).toBool();
533 Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt();
534 Settings::values.use_compatibility_profile =
535 ReadSetting(QStringLiteral("use_compatibility_profile"), true).toBool();
536 Settings::values.use_disk_shader_cache =
537 ReadSetting(QStringLiteral("use_disk_shader_cache"), true).toBool();
538 Settings::values.use_accurate_gpu_emulation =
539 ReadSetting(QStringLiteral("use_accurate_gpu_emulation"), false).toBool();
540 Settings::values.use_asynchronous_gpu_emulation =
541 ReadSetting(QStringLiteral("use_asynchronous_gpu_emulation"), false).toBool();
542 Settings::values.force_30fps_mode =
543 ReadSetting(QStringLiteral("force_30fps_mode"), false).toBool();
544
545 Settings::values.bg_red = ReadSetting(QStringLiteral("bg_red"), 0.0).toFloat();
546 Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 0.0).toFloat();
547 Settings::values.bg_blue = ReadSetting(QStringLiteral("bg_blue"), 0.0).toFloat();
525 548
526 qt_config->beginGroup("Paths");
527 UISettings::values.roms_path = ReadSetting("romsPath").toString();
528 UISettings::values.symbols_path = ReadSetting("symbolsPath").toString();
529 UISettings::values.game_directory_path = ReadSetting("gameListRootDir", ".").toString();
530 UISettings::values.game_directory_deepscan = ReadSetting("gameListDeepScan", false).toBool();
531 UISettings::values.recent_files = ReadSetting("recentFiles").toStringList();
532 qt_config->endGroup(); 549 qt_config->endGroup();
550}
551
552void Config::ReadShortcutValues() {
553 qt_config->beginGroup(QStringLiteral("Shortcuts"));
533 554
534 qt_config->beginGroup("Shortcuts"); 555 for (const auto& [name, group, shortcut] : default_hotkeys) {
535 for (auto [name, group, shortcut] : default_hotkeys) { 556 const auto& [keyseq, context] = shortcut;
536 auto [keyseq, context] = shortcut;
537 qt_config->beginGroup(group); 557 qt_config->beginGroup(group);
538 qt_config->beginGroup(name); 558 qt_config->beginGroup(name);
539 UISettings::values.shortcuts.push_back( 559 UISettings::values.shortcuts.push_back(
540 {name, 560 {name,
541 group, 561 group,
542 {ReadSetting("KeySeq", keyseq).toString(), ReadSetting("Context", context).toInt()}}); 562 {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(),
563 ReadSetting(QStringLiteral("Context"), context).toInt()}});
543 qt_config->endGroup(); 564 qt_config->endGroup();
544 qt_config->endGroup(); 565 qt_config->endGroup();
545 } 566 }
567
568 qt_config->endGroup();
569}
570
571void Config::ReadSystemValues() {
572 qt_config->beginGroup(QStringLiteral("System"));
573
574 Settings::values.use_docked_mode =
575 ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
576
577 Settings::values.current_user = std::clamp<int>(
578 ReadSetting(QStringLiteral("current_user"), 0).toInt(), 0, Service::Account::MAX_USERS - 1);
579
580 Settings::values.language_index = ReadSetting(QStringLiteral("language_index"), 1).toInt();
581
582 const auto rng_seed_enabled = ReadSetting(QStringLiteral("rng_seed_enabled"), false).toBool();
583 if (rng_seed_enabled) {
584 Settings::values.rng_seed = ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong();
585 } else {
586 Settings::values.rng_seed = std::nullopt;
587 }
588
589 const auto custom_rtc_enabled =
590 ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool();
591 if (custom_rtc_enabled) {
592 Settings::values.custom_rtc =
593 std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong());
594 } else {
595 Settings::values.custom_rtc = std::nullopt;
596 }
597
546 qt_config->endGroup(); 598 qt_config->endGroup();
599}
600
601void Config::ReadUIValues() {
602 qt_config->beginGroup(QStringLiteral("UI"));
547 603
548 UISettings::values.single_window_mode = ReadSetting("singleWindowMode", true).toBool(); 604 UISettings::values.theme =
549 UISettings::values.fullscreen = ReadSetting("fullscreen", false).toBool(); 605 ReadSetting(QStringLiteral("theme"), QString::fromUtf8(UISettings::themes[0].second))
550 UISettings::values.display_titlebar = ReadSetting("displayTitleBars", true).toBool(); 606 .toString();
551 UISettings::values.show_filter_bar = ReadSetting("showFilterBar", true).toBool(); 607 UISettings::values.enable_discord_presence =
552 UISettings::values.show_status_bar = ReadSetting("showStatusBar", true).toBool(); 608 ReadSetting(QStringLiteral("enable_discord_presence"), true).toBool();
553 UISettings::values.confirm_before_closing = ReadSetting("confirmClose", true).toBool(); 609 UISettings::values.screenshot_resolution_factor =
554 UISettings::values.first_start = ReadSetting("firstStart", true).toBool(); 610 static_cast<u16>(ReadSetting(QStringLiteral("screenshot_resolution_factor"), 0).toUInt());
555 UISettings::values.callout_flags = ReadSetting("calloutFlags", 0).toUInt(); 611 UISettings::values.select_user_on_boot =
556 UISettings::values.show_console = ReadSetting("showConsole", false).toBool(); 612 ReadSetting(QStringLiteral("select_user_on_boot"), false).toBool();
557 UISettings::values.profile_index = ReadSetting("profileIndex", 0).toUInt(); 613
614 ReadUIGamelistValues();
615 ReadUILayoutValues();
616 ReadPathValues();
617 ReadShortcutValues();
618
619 UISettings::values.single_window_mode =
620 ReadSetting(QStringLiteral("singleWindowMode"), true).toBool();
621 UISettings::values.fullscreen = ReadSetting(QStringLiteral("fullscreen"), false).toBool();
622 UISettings::values.display_titlebar =
623 ReadSetting(QStringLiteral("displayTitleBars"), true).toBool();
624 UISettings::values.show_filter_bar =
625 ReadSetting(QStringLiteral("showFilterBar"), true).toBool();
626 UISettings::values.show_status_bar =
627 ReadSetting(QStringLiteral("showStatusBar"), true).toBool();
628 UISettings::values.confirm_before_closing =
629 ReadSetting(QStringLiteral("confirmClose"), true).toBool();
630 UISettings::values.first_start = ReadSetting(QStringLiteral("firstStart"), true).toBool();
631 UISettings::values.callout_flags = ReadSetting(QStringLiteral("calloutFlags"), 0).toUInt();
632 UISettings::values.show_console = ReadSetting(QStringLiteral("showConsole"), false).toBool();
633 UISettings::values.profile_index = ReadSetting(QStringLiteral("profileIndex"), 0).toUInt();
558 634
559 ApplyDefaultProfileIfInputInvalid(); 635 ApplyDefaultProfileIfInputInvalid();
560 636
561 qt_config->endGroup(); 637 qt_config->endGroup();
562} 638}
563 639
640void Config::ReadUIGamelistValues() {
641 qt_config->beginGroup(QStringLiteral("UIGameList"));
642
643 UISettings::values.show_unknown = ReadSetting(QStringLiteral("show_unknown"), true).toBool();
644 UISettings::values.show_add_ons = ReadSetting(QStringLiteral("show_add_ons"), true).toBool();
645 UISettings::values.icon_size = ReadSetting(QStringLiteral("icon_size"), 64).toUInt();
646 UISettings::values.row_1_text_id = ReadSetting(QStringLiteral("row_1_text_id"), 3).toUInt();
647 UISettings::values.row_2_text_id = ReadSetting(QStringLiteral("row_2_text_id"), 2).toUInt();
648 UISettings::values.cache_game_list =
649 ReadSetting(QStringLiteral("cache_game_list"), true).toBool();
650
651 qt_config->endGroup();
652}
653
654void Config::ReadUILayoutValues() {
655 qt_config->beginGroup(QStringLiteral("UILayout"));
656
657 UISettings::values.geometry = ReadSetting(QStringLiteral("geometry")).toByteArray();
658 UISettings::values.state = ReadSetting(QStringLiteral("state")).toByteArray();
659 UISettings::values.renderwindow_geometry =
660 ReadSetting(QStringLiteral("geometryRenderWindow")).toByteArray();
661 UISettings::values.gamelist_header_state =
662 ReadSetting(QStringLiteral("gameListHeaderState")).toByteArray();
663 UISettings::values.microprofile_geometry =
664 ReadSetting(QStringLiteral("microProfileDialogGeometry")).toByteArray();
665 UISettings::values.microprofile_visible =
666 ReadSetting(QStringLiteral("microProfileDialogVisible"), false).toBool();
667
668 qt_config->endGroup();
669}
670
671void Config::ReadWebServiceValues() {
672 qt_config->beginGroup(QStringLiteral("WebService"));
673
674 Settings::values.enable_telemetry =
675 ReadSetting(QStringLiteral("enable_telemetry"), true).toBool();
676 Settings::values.web_api_url =
677 ReadSetting(QStringLiteral("web_api_url"), QStringLiteral("https://api.yuzu-emu.org"))
678 .toString()
679 .toStdString();
680 Settings::values.yuzu_username =
681 ReadSetting(QStringLiteral("yuzu_username")).toString().toStdString();
682 Settings::values.yuzu_token =
683 ReadSetting(QStringLiteral("yuzu_token")).toString().toStdString();
684
685 qt_config->endGroup();
686}
687
688void Config::ReadValues() {
689 ReadControlValues();
690 ReadCoreValues();
691 ReadRendererValues();
692 ReadAudioValues();
693 ReadDataStorageValues();
694 ReadSystemValues();
695 ReadMiscellaneousValues();
696 ReadDebugValues();
697 ReadWebServiceValues();
698 ReadDisabledAddOnValues();
699 ReadUIValues();
700}
701
564void Config::SavePlayerValues() { 702void Config::SavePlayerValues() {
565 for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { 703 for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
566 const auto& player = Settings::values.players[p]; 704 const auto& player = Settings::values.players[p];
567 705
568 WriteSetting(QString("player_%1_connected").arg(p), player.connected, false); 706 WriteSetting(QStringLiteral("player_%1_connected").arg(p), player.connected, false);
569 WriteSetting(QString("player_%1_type").arg(p), static_cast<u8>(player.type), 707 WriteSetting(QStringLiteral("player_%1_type").arg(p), static_cast<u8>(player.type),
570 static_cast<u8>(Settings::ControllerType::DualJoycon)); 708 static_cast<u8>(Settings::ControllerType::DualJoycon));
571 709
572 WriteSetting(QString("player_%1_body_color_left").arg(p), player.body_color_left, 710 WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left,
573 Settings::JOYCON_BODY_NEON_BLUE); 711 Settings::JOYCON_BODY_NEON_BLUE);
574 WriteSetting(QString("player_%1_body_color_right").arg(p), player.body_color_right, 712 WriteSetting(QStringLiteral("player_%1_body_color_right").arg(p), player.body_color_right,
575 Settings::JOYCON_BODY_NEON_RED); 713 Settings::JOYCON_BODY_NEON_RED);
576 WriteSetting(QString("player_%1_button_color_left").arg(p), player.button_color_left, 714 WriteSetting(QStringLiteral("player_%1_button_color_left").arg(p), player.button_color_left,
577 Settings::JOYCON_BUTTONS_NEON_BLUE); 715 Settings::JOYCON_BUTTONS_NEON_BLUE);
578 WriteSetting(QString("player_%1_button_color_right").arg(p), player.button_color_right, 716 WriteSetting(QStringLiteral("player_%1_button_color_right").arg(p),
579 Settings::JOYCON_BUTTONS_NEON_RED); 717 player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED);
580 718
581 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { 719 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
582 std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); 720 const std::string default_param =
583 WriteSetting(QString("player_%1_").arg(p) + 721 InputCommon::GenerateKeyboardParam(default_buttons[i]);
722 WriteSetting(QStringLiteral("player_%1_").arg(p) +
584 QString::fromStdString(Settings::NativeButton::mapping[i]), 723 QString::fromStdString(Settings::NativeButton::mapping[i]),
585 QString::fromStdString(player.buttons[i]), 724 QString::fromStdString(player.buttons[i]),
586 QString::fromStdString(default_param)); 725 QString::fromStdString(default_param));
587 } 726 }
588 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 727 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
589 std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 728 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
590 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 729 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
591 default_analogs[i][3], default_analogs[i][4], 0.5f); 730 default_analogs[i][3], default_analogs[i][4], 0.5f);
592 WriteSetting(QString("player_%1_").arg(p) + 731 WriteSetting(QStringLiteral("player_%1_").arg(p) +
593 QString::fromStdString(Settings::NativeAnalog::mapping[i]), 732 QString::fromStdString(Settings::NativeAnalog::mapping[i]),
594 QString::fromStdString(player.analogs[i]), 733 QString::fromStdString(player.analogs[i]),
595 QString::fromStdString(default_param)); 734 QString::fromStdString(default_param));
@@ -598,19 +737,19 @@ void Config::SavePlayerValues() {
598} 737}
599 738
600void Config::SaveDebugValues() { 739void Config::SaveDebugValues() {
601 WriteSetting("debug_pad_enabled", Settings::values.debug_pad_enabled, false); 740 WriteSetting(QStringLiteral("debug_pad_enabled"), Settings::values.debug_pad_enabled, false);
602 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { 741 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
603 std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); 742 const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
604 WriteSetting(QString("debug_pad_") + 743 WriteSetting(QStringLiteral("debug_pad_") +
605 QString::fromStdString(Settings::NativeButton::mapping[i]), 744 QString::fromStdString(Settings::NativeButton::mapping[i]),
606 QString::fromStdString(Settings::values.debug_pad_buttons[i]), 745 QString::fromStdString(Settings::values.debug_pad_buttons[i]),
607 QString::fromStdString(default_param)); 746 QString::fromStdString(default_param));
608 } 747 }
609 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 748 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
610 std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 749 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
611 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 750 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
612 default_analogs[i][3], default_analogs[i][4], 0.5f); 751 default_analogs[i][3], default_analogs[i][4], 0.5f);
613 WriteSetting(QString("debug_pad_") + 752 WriteSetting(QStringLiteral("debug_pad_") +
614 QString::fromStdString(Settings::NativeAnalog::mapping[i]), 753 QString::fromStdString(Settings::NativeAnalog::mapping[i]),
615 QString::fromStdString(Settings::values.debug_pad_analogs[i]), 754 QString::fromStdString(Settings::values.debug_pad_analogs[i]),
616 QString::fromStdString(default_param)); 755 QString::fromStdString(default_param));
@@ -618,11 +757,12 @@ void Config::SaveDebugValues() {
618} 757}
619 758
620void Config::SaveMouseValues() { 759void Config::SaveMouseValues() {
621 WriteSetting("mouse_enabled", Settings::values.mouse_enabled, false); 760 WriteSetting(QStringLiteral("mouse_enabled"), Settings::values.mouse_enabled, false);
622 761
623 for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { 762 for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
624 std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); 763 const std::string default_param =
625 WriteSetting(QString("mouse_") + 764 InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
765 WriteSetting(QStringLiteral("mouse_") +
626 QString::fromStdString(Settings::NativeMouseButton::mapping[i]), 766 QString::fromStdString(Settings::NativeMouseButton::mapping[i]),
627 QString::fromStdString(Settings::values.mouse_buttons[i]), 767 QString::fromStdString(Settings::values.mouse_buttons[i]),
628 QString::fromStdString(default_param)); 768 QString::fromStdString(default_param));
@@ -630,178 +770,278 @@ void Config::SaveMouseValues() {
630} 770}
631 771
632void Config::SaveTouchscreenValues() { 772void Config::SaveTouchscreenValues() {
633 WriteSetting("touchscreen_enabled", Settings::values.touchscreen.enabled, true); 773 const auto& touchscreen = Settings::values.touchscreen;
634 WriteSetting("touchscreen_device", QString::fromStdString(Settings::values.touchscreen.device), 774
635 "engine:emu_window"); 775 WriteSetting(QStringLiteral("touchscreen_enabled"), touchscreen.enabled, true);
776 WriteSetting(QStringLiteral("touchscreen_device"), QString::fromStdString(touchscreen.device),
777 QStringLiteral("engine:emu_window"));
636 778
637 WriteSetting("touchscreen_finger", Settings::values.touchscreen.finger, 0); 779 WriteSetting(QStringLiteral("touchscreen_finger"), touchscreen.finger, 0);
638 WriteSetting("touchscreen_angle", Settings::values.touchscreen.rotation_angle, 0); 780 WriteSetting(QStringLiteral("touchscreen_angle"), touchscreen.rotation_angle, 0);
639 WriteSetting("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x, 15); 781 WriteSetting(QStringLiteral("touchscreen_diameter_x"), touchscreen.diameter_x, 15);
640 WriteSetting("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y, 15); 782 WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
641} 783}
642 784
643void Config::SaveValues() { 785void Config::SaveValues() {
644 qt_config->beginGroup("Controls"); 786 SaveControlValues();
787 SaveCoreValues();
788 SaveRendererValues();
789 SaveAudioValues();
790 SaveDataStorageValues();
791 SaveSystemValues();
792 SaveMiscellaneousValues();
793 SaveDebuggingValues();
794 SaveWebServiceValues();
795 SaveDisabledAddOnValues();
796 SaveUIValues();
797}
798
799void Config::SaveAudioValues() {
800 qt_config->beginGroup(QStringLiteral("Audio"));
801
802 WriteSetting(QStringLiteral("output_engine"), QString::fromStdString(Settings::values.sink_id),
803 QStringLiteral("auto"));
804 WriteSetting(QStringLiteral("enable_audio_stretching"),
805 Settings::values.enable_audio_stretching, true);
806 WriteSetting(QStringLiteral("output_device"),
807 QString::fromStdString(Settings::values.audio_device_id), QStringLiteral("auto"));
808 WriteSetting(QStringLiteral("volume"), Settings::values.volume, 1.0f);
809
810 qt_config->endGroup();
811}
812
813void Config::SaveControlValues() {
814 qt_config->beginGroup(QStringLiteral("Controls"));
645 815
646 SavePlayerValues(); 816 SavePlayerValues();
647 SaveDebugValues(); 817 SaveDebugValues();
648 SaveMouseValues(); 818 SaveMouseValues();
649 SaveTouchscreenValues(); 819 SaveTouchscreenValues();
650 820
651 WriteSetting("motion_device", QString::fromStdString(Settings::values.motion_device), 821 WriteSetting(QStringLiteral("motion_device"),
652 "engine:motion_emu,update_period:100,sensitivity:0.01"); 822 QString::fromStdString(Settings::values.motion_device),
653 WriteSetting("keyboard_enabled", Settings::values.keyboard_enabled, false); 823 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
824 WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
654 825
655 qt_config->endGroup(); 826 qt_config->endGroup();
827}
656 828
657 qt_config->beginGroup("Core"); 829void Config::SaveCoreValues() {
658 WriteSetting("use_cpu_jit", Settings::values.use_cpu_jit, true); 830 qt_config->beginGroup(QStringLiteral("Core"));
659 WriteSetting("use_multi_core", Settings::values.use_multi_core, false);
660 qt_config->endGroup();
661 831
662 qt_config->beginGroup("Renderer"); 832 WriteSetting(QStringLiteral("use_cpu_jit"), Settings::values.use_cpu_jit, true);
663 WriteSetting("resolution_factor", (double)Settings::values.resolution_factor, 1.0); 833 WriteSetting(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false);
664 WriteSetting("use_frame_limit", Settings::values.use_frame_limit, true);
665 WriteSetting("frame_limit", Settings::values.frame_limit, 100);
666 WriteSetting("use_compatibility_profile", Settings::values.use_compatibility_profile, true);
667 WriteSetting("use_disk_shader_cache", Settings::values.use_disk_shader_cache, true);
668 WriteSetting("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation, false);
669 WriteSetting("use_asynchronous_gpu_emulation", Settings::values.use_asynchronous_gpu_emulation,
670 false);
671 WriteSetting("force_30fps_mode", Settings::values.force_30fps_mode, false);
672 834
673 // Cast to double because Qt's written float values are not human-readable
674 WriteSetting("bg_red", (double)Settings::values.bg_red, 0.0);
675 WriteSetting("bg_green", (double)Settings::values.bg_green, 0.0);
676 WriteSetting("bg_blue", (double)Settings::values.bg_blue, 0.0);
677 qt_config->endGroup(); 835 qt_config->endGroup();
836}
678 837
679 qt_config->beginGroup("Audio"); 838void Config::SaveDataStorageValues() {
680 WriteSetting("output_engine", QString::fromStdString(Settings::values.sink_id), "auto"); 839 qt_config->beginGroup(QStringLiteral("Data Storage"));
681 WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true);
682 WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto");
683 WriteSetting("volume", Settings::values.volume, 1.0f);
684 qt_config->endGroup();
685 840
686 qt_config->beginGroup("Data Storage"); 841 WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true);
687 WriteSetting("use_virtual_sd", Settings::values.use_virtual_sd, true); 842 WriteSetting(QStringLiteral("nand_directory"),
688 WriteSetting("nand_directory",
689 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)), 843 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
690 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); 844 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
691 WriteSetting("sdmc_directory", 845 WriteSetting(QStringLiteral("sdmc_directory"),
692 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)), 846 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
693 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); 847 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
694 qt_config->endGroup();
695
696 qt_config->beginGroup("System");
697 WriteSetting("use_docked_mode", Settings::values.use_docked_mode, false);
698 WriteSetting("current_user", Settings::values.current_user, 0);
699 WriteSetting("language_index", Settings::values.language_index, 1);
700
701 WriteSetting("rng_seed_enabled", Settings::values.rng_seed.has_value(), false);
702 WriteSetting("rng_seed", Settings::values.rng_seed.value_or(0), 0);
703
704 WriteSetting("custom_rtc_enabled", Settings::values.custom_rtc.has_value(), false);
705 WriteSetting("custom_rtc",
706 QVariant::fromValue<long long>(
707 Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()),
708 0);
709 848
710 qt_config->endGroup(); 849 qt_config->endGroup();
850}
711 851
712 qt_config->beginGroup("Miscellaneous"); 852void Config::SaveDebuggingValues() {
713 WriteSetting("log_filter", QString::fromStdString(Settings::values.log_filter), "*:Info"); 853 qt_config->beginGroup(QStringLiteral("Debugging"));
714 WriteSetting("use_dev_keys", Settings::values.use_dev_keys, false);
715 qt_config->endGroup();
716 854
717 qt_config->beginGroup("Debugging"); 855 WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
718 WriteSetting("use_gdbstub", Settings::values.use_gdbstub, false); 856 WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
719 WriteSetting("gdbstub_port", Settings::values.gdbstub_port, 24689); 857 WriteSetting(QStringLiteral("program_args"),
720 WriteSetting("program_args", QString::fromStdString(Settings::values.program_args), ""); 858 QString::fromStdString(Settings::values.program_args), QStringLiteral(""));
721 WriteSetting("dump_exefs", Settings::values.dump_exefs, false); 859 WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false);
722 WriteSetting("dump_nso", Settings::values.dump_nso, false); 860 WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false);
723 qt_config->endGroup();
724 861
725 qt_config->beginGroup("WebService");
726 WriteSetting("enable_telemetry", Settings::values.enable_telemetry, true);
727 WriteSetting("web_api_url", QString::fromStdString(Settings::values.web_api_url),
728 "https://api.yuzu-emu.org");
729 WriteSetting("yuzu_username", QString::fromStdString(Settings::values.yuzu_username));
730 WriteSetting("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
731 qt_config->endGroup(); 862 qt_config->endGroup();
863}
864
865void Config::SaveDisabledAddOnValues() {
866 qt_config->beginWriteArray(QStringLiteral("DisabledAddOns"));
732 867
733 qt_config->beginWriteArray("DisabledAddOns");
734 int i = 0; 868 int i = 0;
735 for (const auto& elem : Settings::values.disabled_addons) { 869 for (const auto& elem : Settings::values.disabled_addons) {
736 qt_config->setArrayIndex(i); 870 qt_config->setArrayIndex(i);
737 WriteSetting("title_id", QVariant::fromValue<u64>(elem.first), 0); 871 WriteSetting(QStringLiteral("title_id"), QVariant::fromValue<u64>(elem.first), 0);
738 qt_config->beginWriteArray("disabled"); 872 qt_config->beginWriteArray(QStringLiteral("disabled"));
739 for (std::size_t j = 0; j < elem.second.size(); ++j) { 873 for (std::size_t j = 0; j < elem.second.size(); ++j) {
740 qt_config->setArrayIndex(static_cast<int>(j)); 874 qt_config->setArrayIndex(static_cast<int>(j));
741 WriteSetting("d", QString::fromStdString(elem.second[j]), ""); 875 WriteSetting(QStringLiteral("d"), QString::fromStdString(elem.second[j]),
876 QStringLiteral(""));
742 } 877 }
743 qt_config->endArray(); 878 qt_config->endArray();
744 ++i; 879 ++i;
745 } 880 }
881
746 qt_config->endArray(); 882 qt_config->endArray();
883}
884
885void Config::SaveMiscellaneousValues() {
886 qt_config->beginGroup(QStringLiteral("Miscellaneous"));
887
888 WriteSetting(QStringLiteral("log_filter"), QString::fromStdString(Settings::values.log_filter),
889 QStringLiteral("*:Info"));
890 WriteSetting(QStringLiteral("use_dev_keys"), Settings::values.use_dev_keys, false);
747 891
748 qt_config->beginGroup("UI");
749 WriteSetting("theme", UISettings::values.theme, UISettings::themes[0].second);
750 WriteSetting("enable_discord_presence", UISettings::values.enable_discord_presence, true);
751 WriteSetting("screenshot_resolution_factor", UISettings::values.screenshot_resolution_factor,
752 0);
753 WriteSetting("select_user_on_boot", UISettings::values.select_user_on_boot, false);
754
755 qt_config->beginGroup("UIGameList");
756 WriteSetting("show_unknown", UISettings::values.show_unknown, true);
757 WriteSetting("show_add_ons", UISettings::values.show_add_ons, true);
758 WriteSetting("icon_size", UISettings::values.icon_size, 64);
759 WriteSetting("row_1_text_id", UISettings::values.row_1_text_id, 3);
760 WriteSetting("row_2_text_id", UISettings::values.row_2_text_id, 2);
761 qt_config->endGroup(); 892 qt_config->endGroup();
893}
894
895void Config::SavePathValues() {
896 qt_config->beginGroup(QStringLiteral("Paths"));
897
898 WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
899 WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path);
900 WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path);
901 WriteSetting(QStringLiteral("gameListRootDir"), UISettings::values.game_directory_path,
902 QStringLiteral("."));
903 WriteSetting(QStringLiteral("gameListDeepScan"), UISettings::values.game_directory_deepscan,
904 false);
905 WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
762 906
763 qt_config->beginGroup("UILayout");
764 WriteSetting("geometry", UISettings::values.geometry);
765 WriteSetting("state", UISettings::values.state);
766 WriteSetting("geometryRenderWindow", UISettings::values.renderwindow_geometry);
767 WriteSetting("gameListHeaderState", UISettings::values.gamelist_header_state);
768 WriteSetting("microProfileDialogGeometry", UISettings::values.microprofile_geometry);
769 WriteSetting("microProfileDialogVisible", UISettings::values.microprofile_visible, false);
770 qt_config->endGroup(); 907 qt_config->endGroup();
908}
909
910void Config::SaveRendererValues() {
911 qt_config->beginGroup(QStringLiteral("Renderer"));
912
913 WriteSetting(QStringLiteral("resolution_factor"),
914 static_cast<double>(Settings::values.resolution_factor), 1.0);
915 WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true);
916 WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100);
917 WriteSetting(QStringLiteral("use_compatibility_profile"),
918 Settings::values.use_compatibility_profile, true);
919 WriteSetting(QStringLiteral("use_disk_shader_cache"), Settings::values.use_disk_shader_cache,
920 true);
921 WriteSetting(QStringLiteral("use_accurate_gpu_emulation"),
922 Settings::values.use_accurate_gpu_emulation, false);
923 WriteSetting(QStringLiteral("use_asynchronous_gpu_emulation"),
924 Settings::values.use_asynchronous_gpu_emulation, false);
925 WriteSetting(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, false);
926
927 // Cast to double because Qt's written float values are not human-readable
928 WriteSetting(QStringLiteral("bg_red"), static_cast<double>(Settings::values.bg_red), 0.0);
929 WriteSetting(QStringLiteral("bg_green"), static_cast<double>(Settings::values.bg_green), 0.0);
930 WriteSetting(QStringLiteral("bg_blue"), static_cast<double>(Settings::values.bg_blue), 0.0);
771 931
772 qt_config->beginGroup("Paths");
773 WriteSetting("romsPath", UISettings::values.roms_path);
774 WriteSetting("symbolsPath", UISettings::values.symbols_path);
775 WriteSetting("screenshotPath", UISettings::values.screenshot_path);
776 WriteSetting("gameListRootDir", UISettings::values.game_directory_path, ".");
777 WriteSetting("gameListDeepScan", UISettings::values.game_directory_deepscan, false);
778 WriteSetting("recentFiles", UISettings::values.recent_files);
779 qt_config->endGroup(); 932 qt_config->endGroup();
933}
934
935void Config::SaveShortcutValues() {
936 qt_config->beginGroup(QStringLiteral("Shortcuts"));
780 937
781 qt_config->beginGroup("Shortcuts");
782 // Lengths of UISettings::values.shortcuts & default_hotkeys are same. 938 // Lengths of UISettings::values.shortcuts & default_hotkeys are same.
783 // However, their ordering must also be the same. 939 // However, their ordering must also be the same.
784 for (std::size_t i = 0; i < default_hotkeys.size(); i++) { 940 for (std::size_t i = 0; i < default_hotkeys.size(); i++) {
785 auto [name, group, shortcut] = UISettings::values.shortcuts[i]; 941 const auto& [name, group, shortcut] = UISettings::values.shortcuts[i];
942 const auto& default_hotkey = default_hotkeys[i].shortcut;
943
786 qt_config->beginGroup(group); 944 qt_config->beginGroup(group);
787 qt_config->beginGroup(name); 945 qt_config->beginGroup(name);
788 WriteSetting("KeySeq", shortcut.first, default_hotkeys[i].shortcut.first); 946 WriteSetting(QStringLiteral("KeySeq"), shortcut.first, default_hotkey.first);
789 WriteSetting("Context", shortcut.second, default_hotkeys[i].shortcut.second); 947 WriteSetting(QStringLiteral("Context"), shortcut.second, default_hotkey.second);
790 qt_config->endGroup(); 948 qt_config->endGroup();
791 qt_config->endGroup(); 949 qt_config->endGroup();
792 } 950 }
951
952 qt_config->endGroup();
953}
954
955void Config::SaveSystemValues() {
956 qt_config->beginGroup(QStringLiteral("System"));
957
958 WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
959 WriteSetting(QStringLiteral("current_user"), Settings::values.current_user, 0);
960 WriteSetting(QStringLiteral("language_index"), Settings::values.language_index, 1);
961
962 WriteSetting(QStringLiteral("rng_seed_enabled"), Settings::values.rng_seed.has_value(), false);
963 WriteSetting(QStringLiteral("rng_seed"), Settings::values.rng_seed.value_or(0), 0);
964
965 WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(),
966 false);
967 WriteSetting(QStringLiteral("custom_rtc"),
968 QVariant::fromValue<long long>(
969 Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()),
970 0);
971
972 qt_config->endGroup();
973}
974
975void Config::SaveUIValues() {
976 qt_config->beginGroup(QStringLiteral("UI"));
977
978 WriteSetting(QStringLiteral("theme"), UISettings::values.theme,
979 QString::fromUtf8(UISettings::themes[0].second));
980 WriteSetting(QStringLiteral("enable_discord_presence"),
981 UISettings::values.enable_discord_presence, true);
982 WriteSetting(QStringLiteral("screenshot_resolution_factor"),
983 UISettings::values.screenshot_resolution_factor, 0);
984 WriteSetting(QStringLiteral("select_user_on_boot"), UISettings::values.select_user_on_boot,
985 false);
986
987 SaveUIGamelistValues();
988 SaveUILayoutValues();
989 SavePathValues();
990 SaveShortcutValues();
991
992 WriteSetting(QStringLiteral("singleWindowMode"), UISettings::values.single_window_mode, true);
993 WriteSetting(QStringLiteral("fullscreen"), UISettings::values.fullscreen, false);
994 WriteSetting(QStringLiteral("displayTitleBars"), UISettings::values.display_titlebar, true);
995 WriteSetting(QStringLiteral("showFilterBar"), UISettings::values.show_filter_bar, true);
996 WriteSetting(QStringLiteral("showStatusBar"), UISettings::values.show_status_bar, true);
997 WriteSetting(QStringLiteral("confirmClose"), UISettings::values.confirm_before_closing, true);
998 WriteSetting(QStringLiteral("firstStart"), UISettings::values.first_start, true);
999 WriteSetting(QStringLiteral("calloutFlags"), UISettings::values.callout_flags, 0);
1000 WriteSetting(QStringLiteral("showConsole"), UISettings::values.show_console, false);
1001 WriteSetting(QStringLiteral("profileIndex"), UISettings::values.profile_index, 0);
1002
1003 qt_config->endGroup();
1004}
1005
1006void Config::SaveUIGamelistValues() {
1007 qt_config->beginGroup(QStringLiteral("UIGameList"));
1008
1009 WriteSetting(QStringLiteral("show_unknown"), UISettings::values.show_unknown, true);
1010 WriteSetting(QStringLiteral("show_add_ons"), UISettings::values.show_add_ons, true);
1011 WriteSetting(QStringLiteral("icon_size"), UISettings::values.icon_size, 64);
1012 WriteSetting(QStringLiteral("row_1_text_id"), UISettings::values.row_1_text_id, 3);
1013 WriteSetting(QStringLiteral("row_2_text_id"), UISettings::values.row_2_text_id, 2);
1014 WriteSetting(QStringLiteral("cache_game_list"), UISettings::values.cache_game_list, true);
1015
1016 qt_config->endGroup();
1017}
1018
1019void Config::SaveUILayoutValues() {
1020 qt_config->beginGroup(QStringLiteral("UILayout"));
1021
1022 WriteSetting(QStringLiteral("geometry"), UISettings::values.geometry);
1023 WriteSetting(QStringLiteral("state"), UISettings::values.state);
1024 WriteSetting(QStringLiteral("geometryRenderWindow"), UISettings::values.renderwindow_geometry);
1025 WriteSetting(QStringLiteral("gameListHeaderState"), UISettings::values.gamelist_header_state);
1026 WriteSetting(QStringLiteral("microProfileDialogGeometry"),
1027 UISettings::values.microprofile_geometry);
1028 WriteSetting(QStringLiteral("microProfileDialogVisible"),
1029 UISettings::values.microprofile_visible, false);
1030
793 qt_config->endGroup(); 1031 qt_config->endGroup();
1032}
1033
1034void Config::SaveWebServiceValues() {
1035 qt_config->beginGroup(QStringLiteral("WebService"));
1036
1037 WriteSetting(QStringLiteral("enable_telemetry"), Settings::values.enable_telemetry, true);
1038 WriteSetting(QStringLiteral("web_api_url"),
1039 QString::fromStdString(Settings::values.web_api_url),
1040 QStringLiteral("https://api.yuzu-emu.org"));
1041 WriteSetting(QStringLiteral("yuzu_username"),
1042 QString::fromStdString(Settings::values.yuzu_username));
1043 WriteSetting(QStringLiteral("yuzu_token"), QString::fromStdString(Settings::values.yuzu_token));
794 1044
795 WriteSetting("singleWindowMode", UISettings::values.single_window_mode, true);
796 WriteSetting("fullscreen", UISettings::values.fullscreen, false);
797 WriteSetting("displayTitleBars", UISettings::values.display_titlebar, true);
798 WriteSetting("showFilterBar", UISettings::values.show_filter_bar, true);
799 WriteSetting("showStatusBar", UISettings::values.show_status_bar, true);
800 WriteSetting("confirmClose", UISettings::values.confirm_before_closing, true);
801 WriteSetting("firstStart", UISettings::values.first_start, true);
802 WriteSetting("calloutFlags", UISettings::values.callout_flags, 0);
803 WriteSetting("showConsole", UISettings::values.show_console, false);
804 WriteSetting("profileIndex", UISettings::values.profile_index, 0);
805 qt_config->endGroup(); 1045 qt_config->endGroup();
806} 1046}
807 1047
@@ -811,7 +1051,7 @@ QVariant Config::ReadSetting(const QString& name) const {
811 1051
812QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) const { 1052QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) const {
813 QVariant result; 1053 QVariant result;
814 if (qt_config->value(name + "/default", false).toBool()) { 1054 if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
815 result = default_value; 1055 result = default_value;
816 } else { 1056 } else {
817 result = qt_config->value(name, default_value); 1057 result = qt_config->value(name, default_value);
@@ -825,7 +1065,7 @@ void Config::WriteSetting(const QString& name, const QVariant& value) {
825 1065
826void Config::WriteSetting(const QString& name, const QVariant& value, 1066void Config::WriteSetting(const QString& name, const QVariant& value,
827 const QVariant& default_value) { 1067 const QVariant& default_value) {
828 qt_config->setValue(name + "/default", value == default_value); 1068 qt_config->setValue(name + QStringLiteral("/default"), value == default_value);
829 qt_config->setValue(name, value); 1069 qt_config->setValue(name, value);
830} 1070}
831 1071
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 221d2364c..6b523ecdd 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -9,7 +9,6 @@
9#include <string> 9#include <string>
10#include <QVariant> 10#include <QVariant>
11#include "core/settings.h" 11#include "core/settings.h"
12#include "yuzu/ui_settings.h"
13 12
14class QSettings; 13class QSettings;
15 14
@@ -37,19 +36,51 @@ private:
37 void ReadTouchscreenValues(); 36 void ReadTouchscreenValues();
38 void ApplyDefaultProfileIfInputInvalid(); 37 void ApplyDefaultProfileIfInputInvalid();
39 38
39 // Read functions bases off the respective config section names.
40 void ReadAudioValues();
41 void ReadControlValues();
42 void ReadCoreValues();
43 void ReadDataStorageValues();
44 void ReadDebuggingValues();
45 void ReadDisabledAddOnValues();
46 void ReadMiscellaneousValues();
47 void ReadPathValues();
48 void ReadRendererValues();
49 void ReadShortcutValues();
50 void ReadSystemValues();
51 void ReadUIValues();
52 void ReadUIGamelistValues();
53 void ReadUILayoutValues();
54 void ReadWebServiceValues();
55
40 void SaveValues(); 56 void SaveValues();
41 void SavePlayerValues(); 57 void SavePlayerValues();
42 void SaveDebugValues(); 58 void SaveDebugValues();
43 void SaveMouseValues(); 59 void SaveMouseValues();
44 void SaveTouchscreenValues(); 60 void SaveTouchscreenValues();
45 61
62 // Save functions based off the respective config section names.
63 void SaveAudioValues();
64 void SaveControlValues();
65 void SaveCoreValues();
66 void SaveDataStorageValues();
67 void SaveDebuggingValues();
68 void SaveDisabledAddOnValues();
69 void SaveMiscellaneousValues();
70 void SavePathValues();
71 void SaveRendererValues();
72 void SaveShortcutValues();
73 void SaveSystemValues();
74 void SaveUIValues();
75 void SaveUIGamelistValues();
76 void SaveUILayoutValues();
77 void SaveWebServiceValues();
78
46 QVariant ReadSetting(const QString& name) const; 79 QVariant ReadSetting(const QString& name) const;
47 QVariant ReadSetting(const QString& name, const QVariant& default_value) const; 80 QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
48 void WriteSetting(const QString& name, const QVariant& value); 81 void WriteSetting(const QString& name, const QVariant& value);
49 void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value); 82 void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
50 83
51 static const std::array<UISettings::Shortcut, 15> default_hotkeys;
52
53 std::unique_ptr<QSettings> qt_config; 84 std::unique_ptr<QSettings> qt_config;
54 std::string qt_config_loc; 85 std::string qt_config_loc;
55}; 86};
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index 5d9ccc6e8..b0f9b814d 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -16,21 +16,21 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
16 ui->setupUi(this); 16 ui->setupUi(this);
17 17
18 ui->output_sink_combo_box->clear(); 18 ui->output_sink_combo_box->clear();
19 ui->output_sink_combo_box->addItem("auto"); 19 ui->output_sink_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));
20 for (const char* id : AudioCore::GetSinkIDs()) { 20 for (const char* id : AudioCore::GetSinkIDs()) {
21 ui->output_sink_combo_box->addItem(id); 21 ui->output_sink_combo_box->addItem(QString::fromUtf8(id));
22 } 22 }
23 23
24 connect(ui->volume_slider, &QSlider::valueChanged, this, 24 connect(ui->volume_slider, &QSlider::valueChanged, this,
25 &ConfigureAudio::setVolumeIndicatorText); 25 &ConfigureAudio::setVolumeIndicatorText);
26 26
27 this->setConfiguration(); 27 this->setConfiguration();
28 connect(ui->output_sink_combo_box, 28 connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
29 static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
30 &ConfigureAudio::updateAudioDevices); 29 &ConfigureAudio::updateAudioDevices);
31 30
32 ui->output_sink_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn()); 31 const bool is_powered_on = Core::System::GetInstance().IsPoweredOn();
33 ui->audio_device_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn()); 32 ui->output_sink_combo_box->setEnabled(!is_powered_on);
33 ui->audio_device_combo_box->setEnabled(!is_powered_on);
34} 34}
35 35
36ConfigureAudio::~ConfigureAudio() = default; 36ConfigureAudio::~ConfigureAudio() = default;
@@ -94,7 +94,7 @@ void ConfigureAudio::applyConfiguration() {
94 94
95void ConfigureAudio::updateAudioDevices(int sink_index) { 95void ConfigureAudio::updateAudioDevices(int sink_index) {
96 ui->audio_device_combo_box->clear(); 96 ui->audio_device_combo_box->clear();
97 ui->audio_device_combo_box->addItem(AudioCore::auto_device_name); 97 ui->audio_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));
98 98
99 const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); 99 const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
100 for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) { 100 for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) {
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index a5218b051..8086f9d6b 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -17,13 +17,14 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
17 ui->hotkeysTab->Populate(registry); 17 ui->hotkeysTab->Populate(registry);
18 this->setConfiguration(); 18 this->setConfiguration();
19 this->PopulateSelectionList(); 19 this->PopulateSelectionList();
20
21 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
22
20 connect(ui->selectorList, &QListWidget::itemSelectionChanged, this, 23 connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
21 &ConfigureDialog::UpdateVisibleTabs); 24 &ConfigureDialog::UpdateVisibleTabs);
25
22 adjustSize(); 26 adjustSize();
23 ui->selectorList->setCurrentRow(0); 27 ui->selectorList->setCurrentRow(0);
24
25 // Synchronise lists upon initialisation
26 ui->hotkeysTab->EmitHotkeysChanged();
27} 28}
28 29
29ConfigureDialog::~ConfigureDialog() = default; 30ConfigureDialog::~ConfigureDialog() = default;
diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp
index ae8cac243..6f0d75605 100644
--- a/src/yuzu/configuration/configure_gamelist.cpp
+++ b/src/yuzu/configuration/configure_gamelist.cpp
@@ -100,13 +100,15 @@ void ConfigureGameList::RetranslateUI() {
100 100
101void ConfigureGameList::InitializeIconSizeComboBox() { 101void ConfigureGameList::InitializeIconSizeComboBox() {
102 for (const auto& size : default_icon_sizes) { 102 for (const auto& size : default_icon_sizes) {
103 ui->icon_size_combobox->addItem(size.second, size.first); 103 ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first);
104 } 104 }
105} 105}
106 106
107void ConfigureGameList::InitializeRowComboBoxes() { 107void ConfigureGameList::InitializeRowComboBoxes() {
108 for (std::size_t i = 0; i < row_text_names.size(); ++i) { 108 for (std::size_t i = 0; i < row_text_names.size(); ++i) {
109 ui->row_1_text_combobox->addItem(row_text_names[i], QVariant::fromValue(i)); 109 const QString row_text_name = QString::fromUtf8(row_text_names[i]);
110 ui->row_2_text_combobox->addItem(row_text_names[i], QVariant::fromValue(i)); 110
111 ui->row_1_text_combobox->addItem(row_text_name, QVariant::fromValue(i));
112 ui->row_2_text_combobox->addItem(row_text_name, QVariant::fromValue(i));
111 } 113 }
112} 114}
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index e48f4f5a3..dd25dc6e1 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -14,7 +14,8 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
14 ui->setupUi(this); 14 ui->setupUi(this);
15 15
16 for (const auto& theme : UISettings::themes) { 16 for (const auto& theme : UISettings::themes) {
17 ui->theme_combobox->addItem(theme.first, theme.second); 17 ui->theme_combobox->addItem(QString::fromUtf8(theme.first),
18 QString::fromUtf8(theme.second));
18 } 19 }
19 20
20 this->setConfiguration(); 21 this->setConfiguration();
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index c299c0b5b..08ea41b0f 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -69,16 +69,20 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
69ConfigureGraphics::~ConfigureGraphics() = default; 69ConfigureGraphics::~ConfigureGraphics() = default;
70 70
71void ConfigureGraphics::setConfiguration() { 71void ConfigureGraphics::setConfiguration() {
72 const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
73
72 ui->resolution_factor_combobox->setCurrentIndex( 74 ui->resolution_factor_combobox->setCurrentIndex(
73 static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); 75 static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
74 ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); 76 ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
75 ui->frame_limit->setValue(Settings::values.frame_limit); 77 ui->frame_limit->setValue(Settings::values.frame_limit);
78 ui->use_compatibility_profile->setEnabled(runtime_lock);
76 ui->use_compatibility_profile->setChecked(Settings::values.use_compatibility_profile); 79 ui->use_compatibility_profile->setChecked(Settings::values.use_compatibility_profile);
80 ui->use_disk_shader_cache->setEnabled(runtime_lock);
77 ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache); 81 ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
78 ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); 82 ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
79 ui->use_asynchronous_gpu_emulation->setEnabled(!Core::System::GetInstance().IsPoweredOn()); 83 ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);
80 ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation); 84 ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation);
81 ui->force_30fps_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn()); 85 ui->force_30fps_mode->setEnabled(runtime_lock);
82 ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode); 86 ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);
83 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, 87 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
84 Settings::values.bg_blue)); 88 Settings::values.bg_blue));
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
index a7a8752e5..9fb970c21 100644
--- a/src/yuzu/configuration/configure_hotkeys.cpp
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -31,22 +31,6 @@ ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
31 31
32ConfigureHotkeys::~ConfigureHotkeys() = default; 32ConfigureHotkeys::~ConfigureHotkeys() = default;
33 33
34void ConfigureHotkeys::EmitHotkeysChanged() {
35 emit HotkeysChanged(GetUsedKeyList());
36}
37
38QList<QKeySequence> ConfigureHotkeys::GetUsedKeyList() const {
39 QList<QKeySequence> list;
40 for (int r = 0; r < model->rowCount(); r++) {
41 const QStandardItem* parent = model->item(r, 0);
42 for (int r2 = 0; r2 < parent->rowCount(); r2++) {
43 const QStandardItem* keyseq = parent->child(r2, 1);
44 list << QKeySequence::fromString(keyseq->text(), QKeySequence::NativeText);
45 }
46 }
47 return list;
48}
49
50void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) { 34void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
51 for (const auto& group : registry.hotkey_groups) { 35 for (const auto& group : registry.hotkey_groups) {
52 auto* parent_item = new QStandardItem(group.first); 36 auto* parent_item = new QStandardItem(group.first);
@@ -83,16 +67,29 @@ void ConfigureHotkeys::Configure(QModelIndex index) {
83 } 67 }
84 68
85 if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) { 69 if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) {
86 QMessageBox::critical(this, tr("Error in inputted key"), 70 QMessageBox::warning(this, tr("Conflicting Key Sequence"),
87 tr("You're using a key that's already bound.")); 71 tr("The entered key sequence is already assigned to another hotkey."));
88 } else { 72 } else {
89 model->setData(index, key_sequence.toString(QKeySequence::NativeText)); 73 model->setData(index, key_sequence.toString(QKeySequence::NativeText));
90 EmitHotkeysChanged();
91 } 74 }
92} 75}
93 76
94bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const { 77bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const {
95 return GetUsedKeyList().contains(key_sequence); 78 for (int r = 0; r < model->rowCount(); r++) {
79 const QStandardItem* const parent = model->item(r, 0);
80
81 for (int r2 = 0; r2 < parent->rowCount(); r2++) {
82 const QStandardItem* const key_seq_item = parent->child(r2, 1);
83 const auto key_seq_str = key_seq_item->text();
84 const auto key_seq = QKeySequence::fromString(key_seq_str, QKeySequence::NativeText);
85
86 if (key_sequence == key_seq) {
87 return true;
88 }
89 }
90 }
91
92 return false;
96} 93}
97 94
98void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) { 95void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) {
@@ -114,7 +111,6 @@ void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) {
114 } 111 }
115 112
116 registry.SaveHotkeys(); 113 registry.SaveHotkeys();
117 Settings::Apply();
118} 114}
119 115
120void ConfigureHotkeys::retranslateUi() { 116void ConfigureHotkeys::retranslateUi() {
diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h
index 73fb8a175..e77d73c35 100644
--- a/src/yuzu/configuration/configure_hotkeys.h
+++ b/src/yuzu/configuration/configure_hotkeys.h
@@ -24,8 +24,6 @@ public:
24 void applyConfiguration(HotkeyRegistry& registry); 24 void applyConfiguration(HotkeyRegistry& registry);
25 void retranslateUi(); 25 void retranslateUi();
26 26
27 void EmitHotkeysChanged();
28
29 /** 27 /**
30 * Populates the hotkey list widget using data from the provided registry. 28 * Populates the hotkey list widget using data from the provided registry.
31 * Called everytime the Configure dialog is opened. 29 * Called everytime the Configure dialog is opened.
@@ -33,13 +31,9 @@ public:
33 */ 31 */
34 void Populate(const HotkeyRegistry& registry); 32 void Populate(const HotkeyRegistry& registry);
35 33
36signals:
37 void HotkeysChanged(QList<QKeySequence> new_key_list);
38
39private: 34private:
40 void Configure(QModelIndex index); 35 void Configure(QModelIndex index);
41 bool IsUsedKey(QKeySequence key_sequence) const; 36 bool IsUsedKey(QKeySequence key_sequence) const;
42 QList<QKeySequence> GetUsedKeyList() const;
43 37
44 std::unique_ptr<Ui::ConfigureHotkeys> ui; 38 std::unique_ptr<Ui::ConfigureHotkeys> ui;
45 39
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index f39d57998..87e459714 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -75,8 +75,8 @@ ConfigureInput::ConfigureInput(QWidget* parent)
75 }; 75 };
76 76
77 for (auto* controller_box : players_controller) { 77 for (auto* controller_box : players_controller) {
78 controller_box->addItems({"None", "Pro Controller", "Dual Joycons", "Single Right Joycon", 78 controller_box->addItems({tr("None"), tr("Pro Controller"), tr("Dual Joycons"),
79 "Single Left Joycon"}); 79 tr("Single Right Joycon"), tr("Single Left Joycon")});
80 } 80 }
81 81
82 this->loadConfiguration(); 82 this->loadConfiguration();
@@ -85,9 +85,10 @@ ConfigureInput::ConfigureInput(QWidget* parent)
85 connect(ui->restore_defaults_button, &QPushButton::pressed, this, 85 connect(ui->restore_defaults_button, &QPushButton::pressed, this,
86 &ConfigureInput::restoreDefaults); 86 &ConfigureInput::restoreDefaults);
87 87
88 for (auto* enabled : players_controller) 88 for (auto* enabled : players_controller) {
89 connect(enabled, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 89 connect(enabled, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
90 &ConfigureInput::updateUIEnabled); 90 &ConfigureInput::updateUIEnabled);
91 }
91 connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); 92 connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled);
92 connect(ui->handheld_connected, &QCheckBox::stateChanged, this, 93 connect(ui->handheld_connected, &QCheckBox::stateChanged, this,
93 &ConfigureInput::updateUIEnabled); 94 &ConfigureInput::updateUIEnabled);
@@ -147,10 +148,12 @@ void ConfigureInput::updateUIEnabled() {
147 bool hit_disabled = false; 148 bool hit_disabled = false;
148 for (auto* player : players_controller) { 149 for (auto* player : players_controller) {
149 player->setDisabled(hit_disabled); 150 player->setDisabled(hit_disabled);
150 if (hit_disabled) 151 if (hit_disabled) {
151 player->setCurrentIndex(0); 152 player->setCurrentIndex(0);
152 if (!hit_disabled && player->currentIndex() == 0) 153 }
154 if (!hit_disabled && player->currentIndex() == 0) {
153 hit_disabled = true; 155 hit_disabled = true;
156 }
154 } 157 }
155 158
156 for (std::size_t i = 0; i < players_controller.size(); ++i) { 159 for (std::size_t i = 0; i < players_controller.size(); ++i) {
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index c5a245ebe..95b0a656a 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -45,7 +45,7 @@ static QString GetKeyName(int key_code) {
45 case Qt::Key_Alt: 45 case Qt::Key_Alt:
46 return QObject::tr("Alt"); 46 return QObject::tr("Alt");
47 case Qt::Key_Meta: 47 case Qt::Key_Meta:
48 return ""; 48 return {};
49 default: 49 default:
50 return QKeySequence(key_code).toString(); 50 return QKeySequence(key_code).toString();
51 } 51 }
@@ -65,46 +65,70 @@ static void SetAnalogButton(const Common::ParamPackage& input_param,
65static QString ButtonToText(const Common::ParamPackage& param) { 65static QString ButtonToText(const Common::ParamPackage& param) {
66 if (!param.Has("engine")) { 66 if (!param.Has("engine")) {
67 return QObject::tr("[not set]"); 67 return QObject::tr("[not set]");
68 } else if (param.Get("engine", "") == "keyboard") { 68 }
69
70 if (param.Get("engine", "") == "keyboard") {
69 return GetKeyName(param.Get("code", 0)); 71 return GetKeyName(param.Get("code", 0));
70 } else if (param.Get("engine", "") == "sdl") { 72 }
73
74 if (param.Get("engine", "") == "sdl") {
71 if (param.Has("hat")) { 75 if (param.Has("hat")) {
72 return QString(QObject::tr("Hat %1 %2")) 76 const QString hat_str = QString::fromStdString(param.Get("hat", ""));
73 .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); 77 const QString direction_str = QString::fromStdString(param.Get("direction", ""));
78
79 return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
74 } 80 }
81
75 if (param.Has("axis")) { 82 if (param.Has("axis")) {
76 return QString(QObject::tr("Axis %1%2")) 83 const QString axis_str = QString::fromStdString(param.Get("axis", ""));
77 .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); 84 const QString direction_str = QString::fromStdString(param.Get("direction", ""));
85
86 return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
78 } 87 }
88
79 if (param.Has("button")) { 89 if (param.Has("button")) {
80 return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); 90 const QString button_str = QString::fromStdString(param.Get("button", ""));
91
92 return QObject::tr("Button %1").arg(button_str);
81 } 93 }
82 return QString(); 94
83 } else { 95 return {};
84 return QObject::tr("[unknown]");
85 } 96 }
86}; 97
98 return QObject::tr("[unknown]");
99}
87 100
88static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) { 101static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {
89 if (!param.Has("engine")) { 102 if (!param.Has("engine")) {
90 return QObject::tr("[not set]"); 103 return QObject::tr("[not set]");
91 } else if (param.Get("engine", "") == "analog_from_button") { 104 }
105
106 if (param.Get("engine", "") == "analog_from_button") {
92 return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); 107 return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
93 } else if (param.Get("engine", "") == "sdl") { 108 }
109
110 if (param.Get("engine", "") == "sdl") {
94 if (dir == "modifier") { 111 if (dir == "modifier") {
95 return QString(QObject::tr("[unused]")); 112 return QObject::tr("[unused]");
96 } 113 }
97 114
98 if (dir == "left" || dir == "right") { 115 if (dir == "left" || dir == "right") {
99 return QString(QObject::tr("Axis %1")).arg(param.Get("axis_x", "").c_str()); 116 const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
100 } else if (dir == "up" || dir == "down") { 117
101 return QString(QObject::tr("Axis %1")).arg(param.Get("axis_y", "").c_str()); 118 return QObject::tr("Axis %1").arg(axis_x_str);
102 } 119 }
103 return QString(); 120
104 } else { 121 if (dir == "up" || dir == "down") {
105 return QObject::tr("[unknown]"); 122 const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
123
124 return QObject::tr("Axis %1").arg(axis_y_str);
125 }
126
127 return {};
106 } 128 }
107}; 129
130 return QObject::tr("[unknown]");
131}
108 132
109ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug) 133ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug)
110 : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index), 134 : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
@@ -214,38 +238,42 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
214 analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog}; 238 analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
215 239
216 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { 240 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
217 if (!button_map[button_id]) 241 auto* const button = button_map[button_id];
242 if (button == nullptr) {
218 continue; 243 continue;
219 button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); 244 }
220 connect(button_map[button_id], &QPushButton::released, [=]() { 245
246 button->setContextMenuPolicy(Qt::CustomContextMenu);
247 connect(button, &QPushButton::released, [=] {
221 handleClick( 248 handleClick(
222 button_map[button_id], 249 button_map[button_id],
223 [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, 250 [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
224 InputCommon::Polling::DeviceType::Button); 251 InputCommon::Polling::DeviceType::Button);
225 }); 252 });
226 connect(button_map[button_id], &QPushButton::customContextMenuRequested, 253 connect(button, &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
227 [=](const QPoint& menu_location) { 254 QMenu context_menu;
228 QMenu context_menu; 255 context_menu.addAction(tr("Clear"), [&] {
229 context_menu.addAction(tr("Clear"), [&] { 256 buttons_param[button_id].Clear();
230 buttons_param[button_id].Clear(); 257 button_map[button_id]->setText(tr("[not set]"));
231 button_map[button_id]->setText(tr("[not set]")); 258 });
232 }); 259 context_menu.addAction(tr("Restore Default"), [&] {
233 context_menu.addAction(tr("Restore Default"), [&] { 260 buttons_param[button_id] = Common::ParamPackage{
234 buttons_param[button_id] = Common::ParamPackage{ 261 InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
235 InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; 262 button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
236 button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); 263 });
237 }); 264 context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
238 context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); 265 });
239 });
240 } 266 }
241 267
242 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { 268 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
243 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { 269 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
244 if (!analog_map_buttons[analog_id][sub_button_id]) 270 auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
271 if (analog_button == nullptr) {
245 continue; 272 continue;
246 analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy( 273 }
247 Qt::CustomContextMenu); 274
248 connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() { 275 analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
276 connect(analog_button, &QPushButton::released, [=]() {
249 handleClick(analog_map_buttons[analog_id][sub_button_id], 277 handleClick(analog_map_buttons[analog_id][sub_button_id],
250 [=](const Common::ParamPackage& params) { 278 [=](const Common::ParamPackage& params) {
251 SetAnalogButton(params, analogs_param[analog_id], 279 SetAnalogButton(params, analogs_param[analog_id],
@@ -253,8 +281,8 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
253 }, 281 },
254 InputCommon::Polling::DeviceType::Button); 282 InputCommon::Polling::DeviceType::Button);
255 }); 283 });
256 connect(analog_map_buttons[analog_id][sub_button_id], 284 connect(analog_button, &QPushButton::customContextMenuRequested,
257 &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { 285 [=](const QPoint& menu_location) {
258 QMenu context_menu; 286 QMenu context_menu;
259 context_menu.addAction(tr("Clear"), [&] { 287 context_menu.addAction(tr("Clear"), [&] {
260 analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); 288 analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
@@ -272,7 +300,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
272 menu_location)); 300 menu_location));
273 }); 301 });
274 } 302 }
275 connect(analog_map_stick[analog_id], &QPushButton::released, [=]() { 303 connect(analog_map_stick[analog_id], &QPushButton::released, [=] {
276 QMessageBox::information(this, tr("Information"), 304 QMessageBox::information(this, tr("Information"),
277 tr("After pressing OK, first move your joystick horizontally, " 305 tr("After pressing OK, first move your joystick horizontally, "
278 "and then vertically.")); 306 "and then vertically."));
@@ -351,7 +379,7 @@ void ConfigureInputPlayer::OnControllerButtonClick(int i) {
351 return; 379 return;
352 controller_colors[i] = new_bg_color; 380 controller_colors[i] = new_bg_color;
353 controller_color_buttons[i]->setStyleSheet( 381 controller_color_buttons[i]->setStyleSheet(
354 QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name())); 382 QStringLiteral("QPushButton { background-color: %1 }").arg(controller_colors[i].name()));
355} 383}
356 384
357void ConfigureInputPlayer::loadConfiguration() { 385void ConfigureInputPlayer::loadConfiguration() {
@@ -388,7 +416,8 @@ void ConfigureInputPlayer::loadConfiguration() {
388 416
389 for (std::size_t i = 0; i < colors.size(); ++i) { 417 for (std::size_t i = 0; i < colors.size(); ++i) {
390 controller_color_buttons[i]->setStyleSheet( 418 controller_color_buttons[i]->setStyleSheet(
391 QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name())); 419 QStringLiteral("QPushButton { background-color: %1 }")
420 .arg(controller_colors[i].name()));
392 } 421 }
393} 422}
394 423
@@ -410,14 +439,22 @@ void ConfigureInputPlayer::restoreDefaults() {
410 439
411void ConfigureInputPlayer::ClearAll() { 440void ConfigureInputPlayer::ClearAll() {
412 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { 441 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
413 if (button_map[button_id] && button_map[button_id]->isEnabled()) 442 const auto* const button = button_map[button_id];
414 buttons_param[button_id].Clear(); 443 if (button == nullptr || !button->isEnabled()) {
444 continue;
445 }
446
447 buttons_param[button_id].Clear();
415 } 448 }
449
416 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { 450 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
417 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { 451 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
418 if (analog_map_buttons[analog_id][sub_button_id] && 452 const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
419 analog_map_buttons[analog_id][sub_button_id]->isEnabled()) 453 if (analog_button == nullptr || !analog_button->isEnabled()) {
420 analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); 454 continue;
455 }
456
457 analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
421 } 458 }
422 } 459 }
423 460
@@ -431,10 +468,14 @@ void ConfigureInputPlayer::updateButtonLabels() {
431 468
432 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { 469 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
433 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { 470 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
434 if (analog_map_buttons[analog_id][sub_button_id]) { 471 auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
435 analog_map_buttons[analog_id][sub_button_id]->setText( 472
436 AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); 473 if (analog_button == nullptr) {
474 continue;
437 } 475 }
476
477 analog_button->setText(
478 AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
438 } 479 }
439 analog_map_stick[analog_id]->setText(tr("Set Analog Stick")); 480 analog_map_stick[analog_id]->setText(tr("Set Analog Stick"));
440 } 481 }
diff --git a/src/yuzu/configuration/configure_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp
index ef857035e..a14bb1475 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.cpp
+++ b/src/yuzu/configuration/configure_mouse_advanced.cpp
@@ -25,7 +25,7 @@ static QString GetKeyName(int key_code) {
25 case Qt::Key_Alt: 25 case Qt::Key_Alt:
26 return QObject::tr("Alt"); 26 return QObject::tr("Alt");
27 case Qt::Key_Meta: 27 case Qt::Key_Meta:
28 return ""; 28 return {};
29 default: 29 default:
30 return QKeySequence(key_code).toString(); 30 return QKeySequence(key_code).toString();
31 } 31 }
@@ -34,24 +34,36 @@ static QString GetKeyName(int key_code) {
34static QString ButtonToText(const Common::ParamPackage& param) { 34static QString ButtonToText(const Common::ParamPackage& param) {
35 if (!param.Has("engine")) { 35 if (!param.Has("engine")) {
36 return QObject::tr("[not set]"); 36 return QObject::tr("[not set]");
37 } else if (param.Get("engine", "") == "keyboard") { 37 }
38
39 if (param.Get("engine", "") == "keyboard") {
38 return GetKeyName(param.Get("code", 0)); 40 return GetKeyName(param.Get("code", 0));
39 } else if (param.Get("engine", "") == "sdl") { 41 }
42
43 if (param.Get("engine", "") == "sdl") {
40 if (param.Has("hat")) { 44 if (param.Has("hat")) {
41 return QString(QObject::tr("Hat %1 %2")) 45 const QString hat_str = QString::fromStdString(param.Get("hat", ""));
42 .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); 46 const QString direction_str = QString::fromStdString(param.Get("direction", ""));
47
48 return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
43 } 49 }
50
44 if (param.Has("axis")) { 51 if (param.Has("axis")) {
45 return QString(QObject::tr("Axis %1%2")) 52 const QString axis_str = QString::fromStdString(param.Get("axis", ""));
46 .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); 53 const QString direction_str = QString::fromStdString(param.Get("direction", ""));
54
55 return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
47 } 56 }
57
48 if (param.Has("button")) { 58 if (param.Has("button")) {
49 return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); 59 const QString button_str = QString::fromStdString(param.Get("button", ""));
60
61 return QObject::tr("Button %1").arg(button_str);
50 } 62 }
51 return QString(); 63 return {};
52 } else {
53 return QObject::tr("[unknown]");
54 } 64 }
65
66 return QObject::tr("[unknown]");
55} 67}
56 68
57ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent) 69ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)
@@ -65,30 +77,31 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)
65 }; 77 };
66 78
67 for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) { 79 for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) {
68 if (!button_map[button_id]) 80 auto* const button = button_map[button_id];
81 if (button == nullptr) {
69 continue; 82 continue;
70 button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); 83 }
71 connect(button_map[button_id], &QPushButton::released, [=]() { 84
85 button->setContextMenuPolicy(Qt::CustomContextMenu);
86 connect(button, &QPushButton::released, [=] {
72 handleClick( 87 handleClick(
73 button_map[button_id], 88 button_map[button_id],
74 [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, 89 [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
75 InputCommon::Polling::DeviceType::Button); 90 InputCommon::Polling::DeviceType::Button);
76 }); 91 });
77 connect(button_map[button_id], &QPushButton::customContextMenuRequested, 92 connect(button, &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
78 [=](const QPoint& menu_location) { 93 QMenu context_menu;
79 QMenu context_menu; 94 context_menu.addAction(tr("Clear"), [&] {
80 context_menu.addAction(tr("Clear"), [&] { 95 buttons_param[button_id].Clear();
81 buttons_param[button_id].Clear(); 96 button_map[button_id]->setText(tr("[not set]"));
82 button_map[button_id]->setText(tr("[not set]")); 97 });
83 }); 98 context_menu.addAction(tr("Restore Default"), [&] {
84 context_menu.addAction(tr("Restore Default"), [&] { 99 buttons_param[button_id] = Common::ParamPackage{
85 buttons_param[button_id] = 100 InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])};
86 Common::ParamPackage{InputCommon::GenerateKeyboardParam( 101 button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
87 Config::default_mouse_buttons[button_id])}; 102 });
88 button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); 103 context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
89 }); 104 });
90 context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
91 });
92 } 105 }
93 106
94 connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); 107 connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
@@ -138,8 +151,10 @@ void ConfigureMouseAdvanced::restoreDefaults() {
138 151
139void ConfigureMouseAdvanced::ClearAll() { 152void ConfigureMouseAdvanced::ClearAll() {
140 for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { 153 for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
141 if (button_map[i] && button_map[i]->isEnabled()) 154 const auto* const button = button_map[i];
155 if (button != nullptr && button->isEnabled()) {
142 buttons_param[i].Clear(); 156 buttons_param[i].Clear();
157 }
143 } 158 }
144 159
145 updateButtonLabels(); 160 updateButtonLabels();
diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp
index 022b94609..c3e68fdf5 100644
--- a/src/yuzu/configuration/configure_per_general.cpp
+++ b/src/yuzu/configuration/configure_per_general.cpp
@@ -13,6 +13,8 @@
13#include <QTimer> 13#include <QTimer>
14#include <QTreeView> 14#include <QTreeView>
15 15
16#include "common/common_paths.h"
17#include "common/file_util.h"
16#include "core/file_sys/control_metadata.h" 18#include "core/file_sys/control_metadata.h"
17#include "core/file_sys/patch_manager.h" 19#include "core/file_sys/patch_manager.h"
18#include "core/file_sys/xts_archive.h" 20#include "core/file_sys/xts_archive.h"
@@ -79,6 +81,14 @@ void ConfigurePerGameGeneral::applyConfiguration() {
79 disabled_addons.push_back(item.front()->text().toStdString()); 81 disabled_addons.push_back(item.front()->text().toStdString());
80 } 82 }
81 83
84 auto current = Settings::values.disabled_addons[title_id];
85 std::sort(disabled_addons.begin(), disabled_addons.end());
86 std::sort(current.begin(), current.end());
87 if (disabled_addons != current) {
88 FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
89 "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id));
90 }
91
82 Settings::values.disabled_addons[title_id] = disabled_addons; 92 Settings::values.disabled_addons[title_id] = disabled_addons;
83} 93}
84 94
@@ -88,15 +98,15 @@ void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) {
88} 98}
89 99
90void ConfigurePerGameGeneral::loadConfiguration() { 100void ConfigurePerGameGeneral::loadConfiguration() {
91 if (file == nullptr) 101 if (file == nullptr) {
92 return; 102 return;
103 }
93 104
94 const auto loader = Loader::GetLoader(file); 105 ui->display_title_id->setText(QString::fromStdString(fmt::format("{:016X}", title_id)));
95
96 ui->display_title_id->setText(fmt::format("{:016X}", title_id).c_str());
97 106
98 FileSys::PatchManager pm{title_id}; 107 FileSys::PatchManager pm{title_id};
99 const auto control = pm.GetControlMetadata(); 108 const auto control = pm.GetControlMetadata();
109 const auto loader = Loader::GetLoader(file);
100 110
101 if (control.first != nullptr) { 111 if (control.first != nullptr) {
102 ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); 112 ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
@@ -142,8 +152,10 @@ void ConfigurePerGameGeneral::loadConfiguration() {
142 const auto& disabled = Settings::values.disabled_addons[title_id]; 152 const auto& disabled = Settings::values.disabled_addons[title_id];
143 153
144 for (const auto& patch : pm.GetPatchVersionNames(update_raw)) { 154 for (const auto& patch : pm.GetPatchVersionNames(update_raw)) {
145 QStandardItem* first_item = new QStandardItem; 155 const auto name =
146 const auto name = QString::fromStdString(patch.first).replace("[D] ", ""); 156 QString::fromStdString(patch.first).replace(QStringLiteral("[D] "), QString{});
157
158 auto* const first_item = new QStandardItem;
147 first_item->setText(name); 159 first_item->setText(name);
148 first_item->setCheckable(true); 160 first_item->setCheckable(true);
149 161
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
index 10f8ba041..6d7d04c98 100644
--- a/src/yuzu/configuration/configure_profile_manager.cpp
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -97,7 +97,7 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)
97 tree_view->setContextMenuPolicy(Qt::NoContextMenu); 97 tree_view->setContextMenuPolicy(Qt::NoContextMenu);
98 98
99 item_model->insertColumns(0, 1); 99 item_model->insertColumns(0, 1);
100 item_model->setHeaderData(0, Qt::Horizontal, "Users"); 100 item_model->setHeaderData(0, Qt::Horizontal, tr("Users"));
101 101
102 // We must register all custom types with the Qt Automoc system so that we are able to use it 102 // We must register all custom types with the Qt Automoc system so that we are able to use it
103 // with signals/slots. In this case, QList falls under the umbrells of custom types. 103 // with signals/slots. In this case, QList falls under the umbrells of custom types.
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 10645a2b3..ff18ace40 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -66,8 +66,9 @@ void ConfigureSystem::setConfiguration() {
66 ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value()); 66 ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value());
67 ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value()); 67 ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value());
68 68
69 const auto rng_seed = 69 const auto rng_seed = QStringLiteral("%1")
70 QString("%1").arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'}).toUpper(); 70 .arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'})
71 .toUpper();
71 ui->rng_seed_edit->setText(rng_seed); 72 ui->rng_seed_edit->setText(rng_seed);
72 73
73 ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value()); 74 ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value());
diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp
index 18566d028..9dc34412d 100644
--- a/src/yuzu/configuration/configure_web.cpp
+++ b/src/yuzu/configuration/configure_web.cpp
@@ -78,12 +78,16 @@ void ConfigureWeb::RefreshTelemetryID() {
78void ConfigureWeb::OnLoginChanged() { 78void ConfigureWeb::OnLoginChanged() {
79 if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) { 79 if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) {
80 user_verified = true; 80 user_verified = true;
81 ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); 81
82 ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); 82 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
83 ui->label_username_verified->setPixmap(pixmap);
84 ui->label_token_verified->setPixmap(pixmap);
83 } else { 85 } else {
84 user_verified = false; 86 user_verified = false;
85 ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); 87
86 ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); 88 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
89 ui->label_username_verified->setPixmap(pixmap);
90 ui->label_token_verified->setPixmap(pixmap);
87 } 91 }
88} 92}
89 93
@@ -101,11 +105,15 @@ void ConfigureWeb::OnLoginVerified() {
101 ui->button_verify_login->setText(tr("Verify")); 105 ui->button_verify_login->setText(tr("Verify"));
102 if (verify_watcher.result()) { 106 if (verify_watcher.result()) {
103 user_verified = true; 107 user_verified = true;
104 ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); 108
105 ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); 109 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
110 ui->label_username_verified->setPixmap(pixmap);
111 ui->label_token_verified->setPixmap(pixmap);
106 } else { 112 } else {
107 ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); 113 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
108 ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); 114 ui->label_username_verified->setPixmap(pixmap);
115 ui->label_token_verified->setPixmap(pixmap);
116
109 QMessageBox::critical( 117 QMessageBox::critical(
110 this, tr("Verification failed"), 118 this, tr("Verification failed"),
111 tr("Verification failed. Check that you have entered your username and token " 119 tr("Verification failed. Check that you have entered your username and token "
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
index 67ed0ba6d..1c80082a4 100644
--- a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
+++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
@@ -135,7 +135,7 @@ GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(
135 std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent) 135 std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent)
136 : QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver( 136 : QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver(
137 debug_context) { 137 debug_context) {
138 setObjectName("TegraBreakPointsWidget"); 138 setObjectName(QStringLiteral("TegraBreakPointsWidget"));
139 139
140 status_text = new QLabel(tr("Emulation running")); 140 status_text = new QLabel(tr("Emulation running"));
141 resume_button = new QPushButton(tr("Resume")); 141 resume_button = new QPushButton(tr("Resume"));
diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp
index 86e03e46d..f594ef076 100644
--- a/src/yuzu/debugger/profiler.cpp
+++ b/src/yuzu/debugger/profiler.cpp
@@ -47,7 +47,7 @@ private:
47#endif 47#endif
48 48
49MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) { 49MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) {
50 setObjectName("MicroProfile"); 50 setObjectName(QStringLiteral("MicroProfile"));
51 setWindowTitle(tr("MicroProfile")); 51 setWindowTitle(tr("MicroProfile"));
52 resize(1000, 600); 52 resize(1000, 600);
53 // Remove the "?" button from the titlebar and enable the maximize button 53 // Remove the "?" button from the titlebar and enable the maximize button
@@ -191,7 +191,7 @@ void MicroProfileDrawText(int x, int y, u32 hex_color, const char* text, u32 tex
191 for (u32 i = 0; i < text_length; ++i) { 191 for (u32 i = 0; i < text_length; ++i) {
192 // Position the text baseline 1 pixel above the bottom of the text cell, this gives nice 192 // Position the text baseline 1 pixel above the bottom of the text cell, this gives nice
193 // vertical alignment of text for a wide range of tested fonts. 193 // vertical alignment of text for a wide range of tested fonts.
194 mp_painter->drawText(x, y + MICROPROFILE_TEXT_HEIGHT - 2, QChar(text[i])); 194 mp_painter->drawText(x, y + MICROPROFILE_TEXT_HEIGHT - 2, QString{QLatin1Char{text[i]}});
195 x += MICROPROFILE_TEXT_WIDTH + 1; 195 x += MICROPROFILE_TEXT_WIDTH + 1;
196 } 196 }
197} 197}
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 85b095688..cd8180f8b 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -91,19 +91,19 @@ WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address, const Kernel::HandleTa
91WaitTreeMutexInfo::~WaitTreeMutexInfo() = default; 91WaitTreeMutexInfo::~WaitTreeMutexInfo() = default;
92 92
93QString WaitTreeMutexInfo::GetText() const { 93QString WaitTreeMutexInfo::GetText() const {
94 return tr("waiting for mutex 0x%1").arg(mutex_address, 16, 16, QLatin1Char('0')); 94 return tr("waiting for mutex 0x%1").arg(mutex_address, 16, 16, QLatin1Char{'0'});
95} 95}
96 96
97std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() const { 97std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() const {
98 std::vector<std::unique_ptr<WaitTreeItem>> list; 98 const bool has_waiters = (mutex_value & Kernel::Mutex::MutexHasWaitersFlag) != 0;
99
100 bool has_waiters = (mutex_value & Kernel::Mutex::MutexHasWaitersFlag) != 0;
101 99
100 std::vector<std::unique_ptr<WaitTreeItem>> list;
102 list.push_back(std::make_unique<WaitTreeText>(tr("has waiters: %1").arg(has_waiters))); 101 list.push_back(std::make_unique<WaitTreeText>(tr("has waiters: %1").arg(has_waiters)));
103 list.push_back(std::make_unique<WaitTreeText>( 102 list.push_back(std::make_unique<WaitTreeText>(
104 tr("owner handle: 0x%1").arg(owner_handle, 8, 16, QLatin1Char('0')))); 103 tr("owner handle: 0x%1").arg(owner_handle, 8, 16, QLatin1Char{'0'})));
105 if (owner != nullptr) 104 if (owner != nullptr) {
106 list.push_back(std::make_unique<WaitTreeThread>(*owner)); 105 list.push_back(std::make_unique<WaitTreeThread>(*owner));
106 }
107 return list; 107 return list;
108} 108}
109 109
@@ -121,11 +121,14 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons
121 u64 base_pointer = thread.GetContext().cpu_registers[BaseRegister]; 121 u64 base_pointer = thread.GetContext().cpu_registers[BaseRegister];
122 122
123 while (base_pointer != 0) { 123 while (base_pointer != 0) {
124 u64 lr = Memory::Read64(base_pointer + sizeof(u64)); 124 const u64 lr = Memory::Read64(base_pointer + sizeof(u64));
125 if (lr == 0) 125 if (lr == 0) {
126 break; 126 break;
127 list.push_back( 127 }
128 std::make_unique<WaitTreeText>(tr("0x%1").arg(lr - sizeof(u32), 16, 16, QChar('0')))); 128
129 list.push_back(std::make_unique<WaitTreeText>(
130 tr("0x%1").arg(lr - sizeof(u32), 16, 16, QLatin1Char{'0'})));
131
129 base_pointer = Memory::Read64(base_pointer); 132 base_pointer = Memory::Read64(base_pointer);
130 } 133 }
131 134
@@ -174,10 +177,10 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeWaitObject::GetChildren() con
174 177
175QString WaitTreeWaitObject::GetResetTypeQString(Kernel::ResetType reset_type) { 178QString WaitTreeWaitObject::GetResetTypeQString(Kernel::ResetType reset_type) {
176 switch (reset_type) { 179 switch (reset_type) {
177 case Kernel::ResetType::OneShot: 180 case Kernel::ResetType::Automatic:
178 return tr("one shot"); 181 return tr("automatic reset");
179 case Kernel::ResetType::Sticky: 182 case Kernel::ResetType::Manual:
180 return tr("sticky"); 183 return tr("manual reset");
181 } 184 }
182 UNREACHABLE(); 185 UNREACHABLE();
183 return {}; 186 return {};
@@ -249,9 +252,9 @@ QString WaitTreeThread::GetText() const {
249 252
250 const auto& context = thread.GetContext(); 253 const auto& context = thread.GetContext();
251 const QString pc_info = tr(" PC = 0x%1 LR = 0x%2") 254 const QString pc_info = tr(" PC = 0x%1 LR = 0x%2")
252 .arg(context.pc, 8, 16, QLatin1Char('0')) 255 .arg(context.pc, 8, 16, QLatin1Char{'0'})
253 .arg(context.cpu_registers[30], 8, 16, QLatin1Char('0')); 256 .arg(context.cpu_registers[30], 8, 16, QLatin1Char{'0'});
254 return WaitTreeWaitObject::GetText() + pc_info + " (" + status + ") "; 257 return QStringLiteral("%1%2 (%3) ").arg(WaitTreeWaitObject::GetText(), pc_info, status);
255} 258}
256 259
257QColor WaitTreeThread::GetColor() const { 260QColor WaitTreeThread::GetColor() const {
@@ -424,7 +427,7 @@ void WaitTreeModel::InitItems() {
424} 427}
425 428
426WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("Wait Tree"), parent) { 429WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("Wait Tree"), parent) {
427 setObjectName("WaitTreeWidget"); 430 setObjectName(QStringLiteral("WaitTreeWidget"));
428 view = new QTreeView(this); 431 view = new QTreeView(this);
429 view->setHeaderHidden(true); 432 view->setHeaderHidden(true);
430 setWidget(view); 433 setWidget(view);
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index b0ca766ec..83d675773 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -14,7 +14,6 @@
14#include <QMenu> 14#include <QMenu>
15#include <QThreadPool> 15#include <QThreadPool>
16#include <fmt/format.h> 16#include <fmt/format.h>
17#include "common/common_paths.h"
18#include "common/common_types.h" 17#include "common/common_types.h"
19#include "common/logging/log.h" 18#include "common/logging/log.h"
20#include "core/file_sys/patch_manager.h" 19#include "core/file_sys/patch_manager.h"
@@ -48,7 +47,7 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
48 return QObject::eventFilter(obj, event); 47 return QObject::eventFilter(obj, event);
49 } else { 48 } else {
50 gamelist->search_field->edit_filter->clear(); 49 gamelist->search_field->edit_filter->clear();
51 edit_filter_text = ""; 50 edit_filter_text.clear();
52 } 51 }
53 break; 52 break;
54 } 53 }
@@ -71,9 +70,9 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
71 } 70 }
72 if (resultCount == 1) { 71 if (resultCount == 1) {
73 // To avoid loading error dialog loops while confirming them using enter 72 // To avoid loading error dialog loops while confirming them using enter
74 // Also users usually want to run a diffrent game after closing one 73 // Also users usually want to run a different game after closing one
75 gamelist->search_field->edit_filter->setText(""); 74 gamelist->search_field->edit_filter->clear();
76 edit_filter_text = ""; 75 edit_filter_text.clear();
77 emit gamelist->GameChosen(file_path); 76 emit gamelist->GameChosen(file_path);
78 } else { 77 } else {
79 return QObject::eventFilter(obj, event); 78 return QObject::eventFilter(obj, event);
@@ -93,7 +92,7 @@ void GameListSearchField::setFilterResult(int visible, int total) {
93} 92}
94 93
95void GameListSearchField::clear() { 94void GameListSearchField::clear() {
96 edit_filter->setText(""); 95 edit_filter->clear();
97} 96}
98 97
99void GameListSearchField::setFocus() { 98void GameListSearchField::setFocus() {
@@ -103,25 +102,26 @@ void GameListSearchField::setFocus() {
103} 102}
104 103
105GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { 104GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
106 KeyReleaseEater* keyReleaseEater = new KeyReleaseEater(parent); 105 auto* const key_release_eater = new KeyReleaseEater(parent);
107 layout_filter = new QHBoxLayout; 106 layout_filter = new QHBoxLayout;
108 layout_filter->setMargin(8); 107 layout_filter->setMargin(8);
109 label_filter = new QLabel; 108 label_filter = new QLabel;
110 label_filter->setText(tr("Filter:")); 109 label_filter->setText(tr("Filter:"));
111 edit_filter = new QLineEdit; 110 edit_filter = new QLineEdit;
112 edit_filter->setText(""); 111 edit_filter->clear();
113 edit_filter->setPlaceholderText(tr("Enter pattern to filter")); 112 edit_filter->setPlaceholderText(tr("Enter pattern to filter"));
114 edit_filter->installEventFilter(keyReleaseEater); 113 edit_filter->installEventFilter(key_release_eater);
115 edit_filter->setClearButtonEnabled(true); 114 edit_filter->setClearButtonEnabled(true);
116 connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::onTextChanged); 115 connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::onTextChanged);
117 label_filter_result = new QLabel; 116 label_filter_result = new QLabel;
118 button_filter_close = new QToolButton(this); 117 button_filter_close = new QToolButton(this);
119 button_filter_close->setText("X"); 118 button_filter_close->setText(QStringLiteral("X"));
120 button_filter_close->setCursor(Qt::ArrowCursor); 119 button_filter_close->setCursor(Qt::ArrowCursor);
121 button_filter_close->setStyleSheet("QToolButton{ border: none; padding: 0px; color: " 120 button_filter_close->setStyleSheet(
122 "#000000; font-weight: bold; background: #F0F0F0; }" 121 QStringLiteral("QToolButton{ border: none; padding: 0px; color: "
123 "QToolButton:hover{ border: none; padding: 0px; color: " 122 "#000000; font-weight: bold; background: #F0F0F0; }"
124 "#EEEEEE; font-weight: bold; background: #E81123}"); 123 "QToolButton:hover{ border: none; padding: 0px; color: "
124 "#EEEEEE; font-weight: bold; background: #E81123}"));
125 connect(button_filter_close, &QToolButton::clicked, parent, &GameList::onFilterCloseClicked); 125 connect(button_filter_close, &QToolButton::clicked, parent, &GameList::onFilterCloseClicked);
126 layout_filter->setSpacing(10); 126 layout_filter->setSpacing(10);
127 layout_filter->addWidget(label_filter); 127 layout_filter->addWidget(label_filter);
@@ -141,36 +141,34 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
141 */ 141 */
142static bool ContainsAllWords(const QString& haystack, const QString& userinput) { 142static bool ContainsAllWords(const QString& haystack, const QString& userinput) {
143 const QStringList userinput_split = 143 const QStringList userinput_split =
144 userinput.split(' ', QString::SplitBehavior::SkipEmptyParts); 144 userinput.split(QLatin1Char{' '}, QString::SplitBehavior::SkipEmptyParts);
145 145
146 return std::all_of(userinput_split.begin(), userinput_split.end(), 146 return std::all_of(userinput_split.begin(), userinput_split.end(),
147 [&haystack](const QString& s) { return haystack.contains(s); }); 147 [&haystack](const QString& s) { return haystack.contains(s); });
148} 148}
149 149
150// Event in order to filter the gamelist after editing the searchfield 150// Event in order to filter the gamelist after editing the searchfield
151void GameList::onTextChanged(const QString& newText) { 151void GameList::onTextChanged(const QString& new_text) {
152 int rowCount = tree_view->model()->rowCount(); 152 const int row_count = tree_view->model()->rowCount();
153 QString edit_filter_text = newText.toLower(); 153 const QString edit_filter_text = new_text.toLower();
154 154 const QModelIndex root_index = item_model->invisibleRootItem()->index();
155 QModelIndex root_index = item_model->invisibleRootItem()->index();
156 155
157 // If the searchfield is empty every item is visible 156 // If the searchfield is empty every item is visible
158 // Otherwise the filter gets applied 157 // Otherwise the filter gets applied
159 if (edit_filter_text.isEmpty()) { 158 if (edit_filter_text.isEmpty()) {
160 for (int i = 0; i < rowCount; ++i) { 159 for (int i = 0; i < row_count; ++i) {
161 tree_view->setRowHidden(i, root_index, false); 160 tree_view->setRowHidden(i, root_index, false);
162 } 161 }
163 search_field->setFilterResult(rowCount, rowCount); 162 search_field->setFilterResult(row_count, row_count);
164 } else { 163 } else {
165 int result_count = 0; 164 int result_count = 0;
166 for (int i = 0; i < rowCount; ++i) { 165 for (int i = 0; i < row_count; ++i) {
167 const QStandardItem* child_file = item_model->item(i, 0); 166 const QStandardItem* child_file = item_model->item(i, 0);
168 const QString file_path = 167 const QString file_path =
169 child_file->data(GameListItemPath::FullPathRole).toString().toLower(); 168 child_file->data(GameListItemPath::FullPathRole).toString().toLower();
170 QString file_name = file_path.mid(file_path.lastIndexOf('/') + 1);
171 const QString file_title = 169 const QString file_title =
172 child_file->data(GameListItemPath::TitleRole).toString().toLower(); 170 child_file->data(GameListItemPath::TitleRole).toString().toLower();
173 const QString file_programmid = 171 const QString file_program_id =
174 child_file->data(GameListItemPath::ProgramIdRole).toString().toLower(); 172 child_file->data(GameListItemPath::ProgramIdRole).toString().toLower();
175 173
176 // Only items which filename in combination with its title contains all words 174 // Only items which filename in combination with its title contains all words
@@ -178,14 +176,16 @@ void GameList::onTextChanged(const QString& newText) {
178 // The search is case insensitive because of toLower() 176 // The search is case insensitive because of toLower()
179 // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent 177 // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent
180 // multiple conversions of edit_filter_text for each game in the gamelist 178 // multiple conversions of edit_filter_text for each game in the gamelist
181 if (ContainsAllWords(file_name.append(' ').append(file_title), edit_filter_text) || 179 const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) +
182 (file_programmid.count() == 16 && edit_filter_text.contains(file_programmid))) { 180 QLatin1Char{' '} + file_title;
181 if (ContainsAllWords(file_name, edit_filter_text) ||
182 (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) {
183 tree_view->setRowHidden(i, root_index, false); 183 tree_view->setRowHidden(i, root_index, false);
184 ++result_count; 184 ++result_count;
185 } else { 185 } else {
186 tree_view->setRowHidden(i, root_index, true); 186 tree_view->setRowHidden(i, root_index, true);
187 } 187 }
188 search_field->setFilterResult(result_count, rowCount); 188 search_field->setFilterResult(result_count, row_count);
189 } 189 }
190 } 190 }
191} 191}
@@ -216,7 +216,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
216 tree_view->setEditTriggers(QHeaderView::NoEditTriggers); 216 tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
217 tree_view->setUniformRowHeights(true); 217 tree_view->setUniformRowHeights(true);
218 tree_view->setContextMenuPolicy(Qt::CustomContextMenu); 218 tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
219 tree_view->setStyleSheet("QTreeView{ border: none; }"); 219 tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }"));
220 220
221 item_model->insertColumns(0, UISettings::values.show_add_ons ? COLUMN_COUNT : COLUMN_COUNT - 1); 221 item_model->insertColumns(0, UISettings::values.show_add_ons ? COLUMN_COUNT : COLUMN_COUNT - 1);
222 item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); 222 item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));
@@ -282,9 +282,9 @@ void GameList::ValidateEntry(const QModelIndex& item) {
282 const QFileInfo file_info{file_path}; 282 const QFileInfo file_info{file_path};
283 if (file_info.isDir()) { 283 if (file_info.isDir()) {
284 const QDir dir{file_path}; 284 const QDir dir{file_path};
285 const QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files); 285 const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files);
286 if (matching_main.size() == 1) { 286 if (matching_main.size() == 1) {
287 emit GameChosen(dir.path() + DIR_SEP + matching_main[0]); 287 emit GameChosen(dir.path() + QDir::separator() + matching_main[0]);
288 } 288 }
289 return; 289 return;
290 } 290 }
@@ -360,7 +360,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
360} 360}
361 361
362void GameList::LoadCompatibilityList() { 362void GameList::LoadCompatibilityList() {
363 QFile compat_list{":compatibility_list/compatibility_list.json"}; 363 QFile compat_list{QStringLiteral(":compatibility_list/compatibility_list.json")};
364 364
365 if (!compat_list.open(QFile::ReadOnly | QFile::Text)) { 365 if (!compat_list.open(QFile::ReadOnly | QFile::Text)) {
366 LOG_ERROR(Frontend, "Unable to open game compatibility list"); 366 LOG_ERROR(Frontend, "Unable to open game compatibility list");
@@ -378,25 +378,27 @@ void GameList::LoadCompatibilityList() {
378 return; 378 return;
379 } 379 }
380 380
381 const QString string_content = content; 381 const QJsonDocument json = QJsonDocument::fromJson(content);
382 QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8()); 382 const QJsonArray arr = json.array();
383 QJsonArray arr = json.array();
384 383
385 for (const QJsonValueRef value : arr) { 384 for (const QJsonValue value : arr) {
386 QJsonObject game = value.toObject(); 385 const QJsonObject game = value.toObject();
386 const QString compatibility_key = QStringLiteral("compatibility");
387 387
388 if (game.contains("compatibility") && game["compatibility"].isDouble()) { 388 if (!game.contains(compatibility_key) || !game[compatibility_key].isDouble()) {
389 int compatibility = game["compatibility"].toInt(); 389 continue;
390 QString directory = game["directory"].toString(); 390 }
391 QJsonArray ids = game["releases"].toArray();
392 391
393 for (const QJsonValueRef id_ref : ids) { 392 const int compatibility = game[compatibility_key].toInt();
394 QJsonObject id_object = id_ref.toObject(); 393 const QString directory = game[QStringLiteral("directory")].toString();
395 QString id = id_object["id"].toString(); 394 const QJsonArray ids = game[QStringLiteral("releases")].toArray();
396 compatibility_list.emplace( 395
397 id.toUpper().toStdString(), 396 for (const QJsonValue id_ref : ids) {
398 std::make_pair(QString::number(compatibility), directory)); 397 const QJsonObject id_object = id_ref.toObject();
399 } 398 const QString id = id_object[QStringLiteral("id")].toString();
399
400 compatibility_list.emplace(id.toUpper().toStdString(),
401 std::make_pair(QString::number(compatibility), directory));
400 } 402 }
401 } 403 }
402} 404}
@@ -464,7 +466,10 @@ void GameList::LoadInterfaceLayout() {
464 item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); 466 item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());
465} 467}
466 468
467const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"}; 469const QStringList GameList::supported_file_extensions = {
470 QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"),
471 QStringLiteral("xci"), QStringLiteral("nsp"),
472};
468 473
469void GameList::RefreshGameDirectory() { 474void GameList::RefreshGameDirectory() {
470 if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) { 475 if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) {
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 56007eef8..f8f8bd6c5 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -76,7 +76,7 @@ signals:
76 void OpenPerGameGeneralRequested(const std::string& file); 76 void OpenPerGameGeneralRequested(const std::string& file);
77 77
78private slots: 78private slots:
79 void onTextChanged(const QString& newText); 79 void onTextChanged(const QString& new_text);
80 void onFilterCloseClicked(); 80 void onFilterCloseClicked();
81 81
82private: 82private:
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 2cf5c58a0..0b458ef48 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -4,11 +4,9 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <algorithm>
8#include <array> 7#include <array>
9#include <map> 8#include <map>
10#include <string> 9#include <string>
11#include <unordered_map>
12#include <utility> 10#include <utility>
13 11
14#include <QCoreApplication> 12#include <QCoreApplication>
@@ -25,8 +23,8 @@
25#include "yuzu/util/util.h" 23#include "yuzu/util/util.h"
26 24
27/** 25/**
28 * Gets the default icon (for games without valid SMDH) 26 * Gets the default icon (for games without valid title metadata)
29 * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) 27 * @param size The desired width and height of the default icon.
30 * @return QPixmap default icon 28 * @return QPixmap default icon
31 */ 29 */
32static QPixmap GetDefaultIcon(u32 size) { 30static QPixmap GetDefaultIcon(u32 size) {
@@ -46,7 +44,7 @@ public:
46 * A specialization of GameListItem for path values. 44 * A specialization of GameListItem for path values.
47 * This class ensures that for every full path value it holds, a correct string representation 45 * This class ensures that for every full path value it holds, a correct string representation
48 * of just the filename (with no extension) will be displayed to the user. 46 * of just the filename (with no extension) will be displayed to the user.
49 * If this class receives valid SMDH data, it will also display game icons and titles. 47 * If this class receives valid title metadata, it will also display game icons and titles.
50 */ 48 */
51class GameListItemPath : public GameListItem { 49class GameListItemPath : public GameListItem {
52public: 50public:
@@ -95,7 +93,7 @@ public:
95 if (row2.isEmpty()) 93 if (row2.isEmpty())
96 return row1; 94 return row1;
97 95
98 return QString(row1 + "\n " + row2); 96 return QString(row1 + QStringLiteral("\n ") + row2);
99 } 97 }
100 98
101 return GameListItem::data(role); 99 return GameListItem::data(role);
@@ -115,13 +113,14 @@ public:
115 }; 113 };
116 // clang-format off 114 // clang-format off
117 static const std::map<QString, CompatStatus> status_data = { 115 static const std::map<QString, CompatStatus> status_data = {
118 {"0", {"#5c93ed", QT_TR_NOOP("Perfect"), QT_TR_NOOP("Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.")}}, 116 {QStringLiteral("0"), {QStringLiteral("#5c93ed"), QT_TR_NOOP("Perfect"), QT_TR_NOOP("Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.")}},
119 {"1", {"#47d35c", QT_TR_NOOP("Great"), QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.")}}, 117 {QStringLiteral("1"), {QStringLiteral("#47d35c"), QT_TR_NOOP("Great"), QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.")}},
120 {"2", {"#94b242", QT_TR_NOOP("Okay"), QT_TR_NOOP("Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.")}}, 118 {QStringLiteral("2"), {QStringLiteral("#94b242"), QT_TR_NOOP("Okay"), QT_TR_NOOP("Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.")}},
121 {"3", {"#f2d624", QT_TR_NOOP("Bad"), QT_TR_NOOP("Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.")}}, 119 {QStringLiteral("3"), {QStringLiteral("#f2d624"), QT_TR_NOOP("Bad"), QT_TR_NOOP("Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.")}},
122 {"4", {"#FF0000", QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.")}}, 120 {QStringLiteral("4"), {QStringLiteral("#FF0000"), QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.")}},
123 {"5", {"#828282", QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}}, 121 {QStringLiteral("5"), {QStringLiteral("#828282"), QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}},
124 {"99", {"#000000", QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}}; 122 {QStringLiteral("99"), {QStringLiteral("#000000"), QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}},
123 };
125 // clang-format on 124 // clang-format on
126 125
127 auto iterator = status_data.find(compatibility); 126 auto iterator = status_data.find(compatibility);
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 8687e7c5a..4d951a4e7 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -9,6 +9,7 @@
9 9
10#include <QDir> 10#include <QDir>
11#include <QFileInfo> 11#include <QFileInfo>
12#include <QSettings>
12 13
13#include "common/common_paths.h" 14#include "common/common_paths.h"
14#include "common/file_util.h" 15#include "common/file_util.h"
@@ -30,13 +31,119 @@
30#include "yuzu/ui_settings.h" 31#include "yuzu/ui_settings.h"
31 32
32namespace { 33namespace {
34
35template <typename T>
36T GetGameListCachedObject(const std::string& filename, const std::string& ext,
37 const std::function<T()>& generator);
38
39template <>
40QString GetGameListCachedObject(const std::string& filename, const std::string& ext,
41 const std::function<QString()>& generator) {
42 if (!UISettings::values.cache_game_list || filename == "0000000000000000") {
43 return generator();
44 }
45
46 const auto path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
47 DIR_SEP + filename + '.' + ext;
48
49 FileUtil::CreateFullPath(path);
50
51 if (!FileUtil::Exists(path)) {
52 const auto str = generator();
53
54 std::ofstream stream(path);
55 if (stream) {
56 stream << str.toStdString();
57 }
58
59 return str;
60 }
61
62 std::ifstream stream(path);
63
64 if (stream) {
65 const std::string out(std::istreambuf_iterator<char>{stream},
66 std::istreambuf_iterator<char>{});
67 return QString::fromStdString(out);
68 }
69
70 return generator();
71}
72
73template <>
74std::pair<std::vector<u8>, std::string> GetGameListCachedObject(
75 const std::string& filename, const std::string& ext,
76 const std::function<std::pair<std::vector<u8>, std::string>()>& generator) {
77 if (!UISettings::values.cache_game_list || filename == "0000000000000000") {
78 return generator();
79 }
80
81 const auto path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
82 DIR_SEP + filename + ".jpeg";
83 const auto path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
84 DIR_SEP + filename + ".appname.txt";
85
86 FileUtil::CreateFullPath(path1);
87
88 if (!FileUtil::Exists(path1) || !FileUtil::Exists(path2)) {
89 const auto [icon, nacp] = generator();
90
91 FileUtil::IOFile file1(path1, "wb");
92 if (!file1.IsOpen()) {
93 LOG_ERROR(Frontend, "Failed to open cache file.");
94 return generator();
95 }
96
97 if (!file1.Resize(icon.size())) {
98 LOG_ERROR(Frontend, "Failed to resize cache file to necessary size.");
99 return generator();
100 }
101
102 if (file1.WriteBytes(icon.data(), icon.size()) != icon.size()) {
103 LOG_ERROR(Frontend, "Failed to write data to cache file.");
104 return generator();
105 }
106
107 std::ofstream stream2(path2, std::ios::out);
108 if (stream2) {
109 stream2 << nacp;
110 }
111
112 return std::make_pair(icon, nacp);
113 }
114
115 FileUtil::IOFile file1(path1, "rb");
116 std::ifstream stream2(path2);
117
118 if (!file1.IsOpen()) {
119 LOG_ERROR(Frontend, "Failed to open cache file for reading.");
120 return generator();
121 }
122
123 if (!stream2) {
124 LOG_ERROR(Frontend, "Failed to open cache file for reading.");
125 return generator();
126 }
127
128 std::vector<u8> vec(file1.GetSize());
129 file1.ReadBytes(vec.data(), vec.size());
130
131 if (stream2 && !vec.empty()) {
132 const std::string out(std::istreambuf_iterator<char>{stream2},
133 std::istreambuf_iterator<char>{});
134 return std::make_pair(vec, out);
135 }
136
137 return generator();
138}
139
33void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca, 140void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca,
34 std::vector<u8>& icon, std::string& name) { 141 std::vector<u8>& icon, std::string& name) {
35 auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca); 142 std::tie(icon, name) = GetGameListCachedObject<std::pair<std::vector<u8>, std::string>>(
36 if (icon_file != nullptr) 143 fmt::format("{:016X}", patch_manager.GetTitleID()), {}, [&patch_manager, &nca] {
37 icon = icon_file->ReadAllBytes(); 144 const auto [nacp, icon_f] = patch_manager.ParseControlNCA(nca);
38 if (nacp != nullptr) 145 return std::make_pair(icon_f->ReadAllBytes(), nacp->GetApplicationName());
39 name = nacp->GetApplicationName(); 146 });
40} 147}
41 148
42bool HasSupportedFileExtension(const std::string& file_name) { 149bool HasSupportedFileExtension(const std::string& file_name) {
@@ -45,7 +152,7 @@ bool HasSupportedFileExtension(const std::string& file_name) {
45} 152}
46 153
47bool IsExtractedNCAMain(const std::string& file_name) { 154bool IsExtractedNCAMain(const std::string& file_name) {
48 return QFileInfo(QString::fromStdString(file_name)).fileName() == "main"; 155 return QFileInfo(QString::fromStdString(file_name)).fileName() == QStringLiteral("main");
49} 156}
50 157
51QString FormatGameName(const std::string& physical_name) { 158QString FormatGameName(const std::string& physical_name) {
@@ -97,7 +204,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
97 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); 204 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
98 205
99 // The game list uses this as compatibility number for untested games 206 // The game list uses this as compatibility number for untested games
100 QString compatibility{"99"}; 207 QString compatibility{QStringLiteral("99")};
101 if (it != compatibility_list.end()) { 208 if (it != compatibility_list.end()) {
102 compatibility = it->second.first; 209 compatibility = it->second.first;
103 } 210 }
@@ -114,8 +221,11 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
114 }; 221 };
115 222
116 if (UISettings::values.show_add_ons) { 223 if (UISettings::values.show_add_ons) {
117 list.insert( 224 const auto patch_versions = GetGameListCachedObject<QString>(
118 2, new GameListItem(FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()))); 225 fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] {
226 return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable());
227 });
228 list.insert(2, new GameListItem(patch_versions));
119 } 229 }
120 230
121 return list; 231 return list;
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index 4f526dc7e..248fadaf3 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -67,8 +67,6 @@ public:
67 67
68private: 68private:
69 struct Hotkey { 69 struct Hotkey {
70 Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {}
71
72 QKeySequence keyseq; 70 QKeySequence keyseq;
73 QShortcut* shortcut = nullptr; 71 QShortcut* shortcut = nullptr;
74 Qt::ShortcutContext context = Qt::WindowShortcut; 72 Qt::ShortcutContext context = Qt::WindowShortcut;
diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp
index 4e2d988cd..4f2bfab48 100644
--- a/src/yuzu/loading_screen.cpp
+++ b/src/yuzu/loading_screen.cpp
@@ -30,11 +30,11 @@
30#include <QMovie> 30#include <QMovie>
31#endif 31#endif
32 32
33constexpr const char PROGRESSBAR_STYLE_PREPARE[] = R"( 33constexpr char PROGRESSBAR_STYLE_PREPARE[] = R"(
34QProgressBar {} 34QProgressBar {}
35QProgressBar::chunk {})"; 35QProgressBar::chunk {})";
36 36
37constexpr const char PROGRESSBAR_STYLE_DECOMPILE[] = R"( 37constexpr char PROGRESSBAR_STYLE_DECOMPILE[] = R"(
38QProgressBar { 38QProgressBar {
39 background-color: black; 39 background-color: black;
40 border: 2px solid white; 40 border: 2px solid white;
@@ -46,7 +46,7 @@ QProgressBar::chunk {
46 width: 1px; 46 width: 1px;
47})"; 47})";
48 48
49constexpr const char PROGRESSBAR_STYLE_BUILD[] = R"( 49constexpr char PROGRESSBAR_STYLE_BUILD[] = R"(
50QProgressBar { 50QProgressBar {
51 background-color: black; 51 background-color: black;
52 border: 2px solid white; 52 border: 2px solid white;
@@ -58,7 +58,7 @@ QProgressBar::chunk {
58 width: 1px; 58 width: 1px;
59})"; 59})";
60 60
61constexpr const char PROGRESSBAR_STYLE_COMPLETE[] = R"( 61constexpr char PROGRESSBAR_STYLE_COMPLETE[] = R"(
62QProgressBar { 62QProgressBar {
63 background-color: #0ab9e6; 63 background-color: #0ab9e6;
64 border: 2px solid white; 64 border: 2px solid white;
@@ -149,10 +149,10 @@ void LoadingScreen::OnLoadComplete() {
149void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, 149void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value,
150 std::size_t total) { 150 std::size_t total) {
151 using namespace std::chrono; 151 using namespace std::chrono;
152 auto now = high_resolution_clock::now(); 152 const auto now = high_resolution_clock::now();
153 // reset the timer if the stage changes 153 // reset the timer if the stage changes
154 if (stage != previous_stage) { 154 if (stage != previous_stage) {
155 ui->progress_bar->setStyleSheet(progressbar_style[stage]); 155 ui->progress_bar->setStyleSheet(QString::fromUtf8(progressbar_style[stage]));
156 // Hide the progress bar during the prepare stage 156 // Hide the progress bar during the prepare stage
157 if (stage == VideoCore::LoadCallbackStage::Prepare) { 157 if (stage == VideoCore::LoadCallbackStage::Prepare) {
158 ui->progress_bar->hide(); 158 ui->progress_bar->hide();
@@ -178,16 +178,16 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size
178 slow_shader_first_value = value; 178 slow_shader_first_value = value;
179 } 179 }
180 // only calculate an estimate time after a second has passed since stage change 180 // only calculate an estimate time after a second has passed since stage change
181 auto diff = duration_cast<milliseconds>(now - slow_shader_start); 181 const auto diff = duration_cast<milliseconds>(now - slow_shader_start);
182 if (diff > seconds{1}) { 182 if (diff > seconds{1}) {
183 auto eta_mseconds = 183 const auto eta_mseconds =
184 static_cast<long>(static_cast<double>(total - slow_shader_first_value) / 184 static_cast<long>(static_cast<double>(total - slow_shader_first_value) /
185 (value - slow_shader_first_value) * diff.count()); 185 (value - slow_shader_first_value) * diff.count());
186 estimate = 186 estimate =
187 tr("Estimated Time %1") 187 tr("Estimated Time %1")
188 .arg(QTime(0, 0, 0, 0) 188 .arg(QTime(0, 0, 0, 0)
189 .addMSecs(std::max<long>(eta_mseconds - diff.count() + 1000, 1000)) 189 .addMSecs(std::max<long>(eta_mseconds - diff.count() + 1000, 1000))
190 .toString("mm:ss")); 190 .toString(QStringLiteral("mm:ss")));
191 } 191 }
192 } 192 }
193 193
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index e33e3aaaf..f8a0daebd 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -198,11 +198,11 @@ GMainWindow::GMainWindow()
198 198
199 ConnectMenuEvents(); 199 ConnectMenuEvents();
200 ConnectWidgetEvents(); 200 ConnectWidgetEvents();
201
201 LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, 202 LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
202 Common::g_scm_desc); 203 Common::g_scm_desc);
204 UpdateWindowTitle();
203 205
204 setWindowTitle(QString("yuzu %1| %2-%3")
205 .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
206 show(); 206 show();
207 207
208 Core::System::GetInstance().SetContentProvider( 208 Core::System::GetInstance().SetContentProvider(
@@ -281,7 +281,7 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message
281void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) { 281void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) {
282 NXInputWebEngineView web_browser_view(this); 282 NXInputWebEngineView web_browser_view(this);
283 283
284 // Scope to contain the QProgressDialog for initalization 284 // Scope to contain the QProgressDialog for initialization
285 { 285 {
286 QProgressDialog progress(this); 286 QProgressDialog progress(this);
287 progress.setMinimumDuration(200); 287 progress.setMinimumDuration(200);
@@ -301,7 +301,7 @@ void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view
301 QWebEngineScript nx_shim; 301 QWebEngineScript nx_shim;
302 nx_shim.setSourceCode(GetNXShimInjectionScript()); 302 nx_shim.setSourceCode(GetNXShimInjectionScript());
303 nx_shim.setWorldId(QWebEngineScript::MainWorld); 303 nx_shim.setWorldId(QWebEngineScript::MainWorld);
304 nx_shim.setName("nx_inject.js"); 304 nx_shim.setName(QStringLiteral("nx_inject.js"));
305 nx_shim.setInjectionPoint(QWebEngineScript::DocumentCreation); 305 nx_shim.setInjectionPoint(QWebEngineScript::DocumentCreation);
306 nx_shim.setRunsOnSubFrames(true); 306 nx_shim.setRunsOnSubFrames(true);
307 web_browser_view.page()->profile()->scripts()->insert(nx_shim); 307 web_browser_view.page()->profile()->scripts()->insert(nx_shim);
@@ -347,7 +347,7 @@ void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view
347 const auto fire_js_keypress = [&web_browser_view](u32 key_code) { 347 const auto fire_js_keypress = [&web_browser_view](u32 key_code) {
348 web_browser_view.page()->runJavaScript( 348 web_browser_view.page()->runJavaScript(
349 QStringLiteral("document.dispatchEvent(new KeyboardEvent('keydown', {'key': %1}));") 349 QStringLiteral("document.dispatchEvent(new KeyboardEvent('keydown', {'key': %1}));")
350 .arg(QString::fromStdString(std::to_string(key_code)))); 350 .arg(key_code));
351 }; 351 };
352 352
353 QMessageBox::information( 353 QMessageBox::information(
@@ -468,7 +468,7 @@ void GMainWindow::InitializeWidgets() {
468 statusBar()->addPermanentWidget(label, 0); 468 statusBar()->addPermanentWidget(label, 0);
469 } 469 }
470 statusBar()->setVisible(true); 470 statusBar()->setVisible(true);
471 setStyleSheet("QStatusBar::item{border: none;}"); 471 setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}"));
472} 472}
473 473
474void GMainWindow::InitializeDebugWidgets() { 474void GMainWindow::InitializeDebugWidgets() {
@@ -518,58 +518,67 @@ void GMainWindow::InitializeRecentFileMenuActions() {
518void GMainWindow::InitializeHotkeys() { 518void GMainWindow::InitializeHotkeys() {
519 hotkey_registry.LoadHotkeys(); 519 hotkey_registry.LoadHotkeys();
520 520
521 ui.action_Load_File->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Load File")); 521 const QString main_window = QStringLiteral("Main Window");
522 const QString load_file = QStringLiteral("Load File");
523 const QString exit_yuzu = QStringLiteral("Exit yuzu");
524 const QString stop_emulation = QStringLiteral("Stop Emulation");
525 const QString toggle_filter_bar = QStringLiteral("Toggle Filter Bar");
526 const QString toggle_status_bar = QStringLiteral("Toggle Status Bar");
527 const QString fullscreen = QStringLiteral("Fullscreen");
528
529 ui.action_Load_File->setShortcut(hotkey_registry.GetKeySequence(main_window, load_file));
522 ui.action_Load_File->setShortcutContext( 530 ui.action_Load_File->setShortcutContext(
523 hotkey_registry.GetShortcutContext("Main Window", "Load File")); 531 hotkey_registry.GetShortcutContext(main_window, load_file));
524 532
525 ui.action_Exit->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Exit yuzu")); 533 ui.action_Exit->setShortcut(hotkey_registry.GetKeySequence(main_window, exit_yuzu));
526 ui.action_Exit->setShortcutContext( 534 ui.action_Exit->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, exit_yuzu));
527 hotkey_registry.GetShortcutContext("Main Window", "Exit yuzu"));
528 535
529 ui.action_Stop->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Stop Emulation")); 536 ui.action_Stop->setShortcut(hotkey_registry.GetKeySequence(main_window, stop_emulation));
530 ui.action_Stop->setShortcutContext( 537 ui.action_Stop->setShortcutContext(
531 hotkey_registry.GetShortcutContext("Main Window", "Stop Emulation")); 538 hotkey_registry.GetShortcutContext(main_window, stop_emulation));
532 539
533 ui.action_Show_Filter_Bar->setShortcut( 540 ui.action_Show_Filter_Bar->setShortcut(
534 hotkey_registry.GetKeySequence("Main Window", "Toggle Filter Bar")); 541 hotkey_registry.GetKeySequence(main_window, toggle_filter_bar));
535 ui.action_Show_Filter_Bar->setShortcutContext( 542 ui.action_Show_Filter_Bar->setShortcutContext(
536 hotkey_registry.GetShortcutContext("Main Window", "Toggle Filter Bar")); 543 hotkey_registry.GetShortcutContext(main_window, toggle_filter_bar));
537 544
538 ui.action_Show_Status_Bar->setShortcut( 545 ui.action_Show_Status_Bar->setShortcut(
539 hotkey_registry.GetKeySequence("Main Window", "Toggle Status Bar")); 546 hotkey_registry.GetKeySequence(main_window, toggle_status_bar));
540 ui.action_Show_Status_Bar->setShortcutContext( 547 ui.action_Show_Status_Bar->setShortcutContext(
541 hotkey_registry.GetShortcutContext("Main Window", "Toggle Status Bar")); 548 hotkey_registry.GetShortcutContext(main_window, toggle_status_bar));
542 549
543 connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated, 550 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Load File"), this),
544 this, &GMainWindow::OnMenuLoadFile); 551 &QShortcut::activated, this, &GMainWindow::OnMenuLoadFile);
545 connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause Emulation", this), 552 connect(
546 &QShortcut::activated, this, [&] { 553 hotkey_registry.GetHotkey(main_window, QStringLiteral("Continue/Pause Emulation"), this),
547 if (emulation_running) { 554 &QShortcut::activated, this, [&] {
548 if (emu_thread->IsRunning()) { 555 if (emulation_running) {
549 OnPauseGame(); 556 if (emu_thread->IsRunning()) {
550 } else { 557 OnPauseGame();
551 OnStartGame(); 558 } else {
552 } 559 OnStartGame();
553 } 560 }
554 }); 561 }
555 connect(hotkey_registry.GetHotkey("Main Window", "Restart Emulation", this), 562 });
563 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Restart Emulation"), this),
556 &QShortcut::activated, this, [this] { 564 &QShortcut::activated, this, [this] {
557 if (!Core::System::GetInstance().IsPoweredOn()) 565 if (!Core::System::GetInstance().IsPoweredOn()) {
558 return; 566 return;
559 BootGame(QString(game_path)); 567 }
568 BootGame(game_path);
560 }); 569 });
561 connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window), 570 connect(hotkey_registry.GetHotkey(main_window, fullscreen, render_window),
562 &QShortcut::activated, ui.action_Fullscreen, &QAction::trigger); 571 &QShortcut::activated, ui.action_Fullscreen, &QAction::trigger);
563 connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window), 572 connect(hotkey_registry.GetHotkey(main_window, fullscreen, render_window),
564 &QShortcut::activatedAmbiguously, ui.action_Fullscreen, &QAction::trigger); 573 &QShortcut::activatedAmbiguously, ui.action_Fullscreen, &QAction::trigger);
565 connect(hotkey_registry.GetHotkey("Main Window", "Exit Fullscreen", this), 574 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Exit Fullscreen"), this),
566 &QShortcut::activated, this, [&] { 575 &QShortcut::activated, this, [&] {
567 if (emulation_running) { 576 if (emulation_running) {
568 ui.action_Fullscreen->setChecked(false); 577 ui.action_Fullscreen->setChecked(false);
569 ToggleFullscreen(); 578 ToggleFullscreen();
570 } 579 }
571 }); 580 });
572 connect(hotkey_registry.GetHotkey("Main Window", "Toggle Speed Limit", this), 581 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Speed Limit"), this),
573 &QShortcut::activated, this, [&] { 582 &QShortcut::activated, this, [&] {
574 Settings::values.use_frame_limit = !Settings::values.use_frame_limit; 583 Settings::values.use_frame_limit = !Settings::values.use_frame_limit;
575 UpdateStatusBar(); 584 UpdateStatusBar();
@@ -578,33 +587,33 @@ void GMainWindow::InitializeHotkeys() {
578 // MSVC occurs and we make it a requirement (see: 587 // MSVC occurs and we make it a requirement (see:
579 // https://developercommunity.visualstudio.com/content/problem/93922/constexprs-are-trying-to-be-captured-in-lambda-fun.html) 588 // https://developercommunity.visualstudio.com/content/problem/93922/constexprs-are-trying-to-be-captured-in-lambda-fun.html)
580 static constexpr u16 SPEED_LIMIT_STEP = 5; 589 static constexpr u16 SPEED_LIMIT_STEP = 5;
581 connect(hotkey_registry.GetHotkey("Main Window", "Increase Speed Limit", this), 590 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Increase Speed Limit"), this),
582 &QShortcut::activated, this, [&] { 591 &QShortcut::activated, this, [&] {
583 if (Settings::values.frame_limit < 9999 - SPEED_LIMIT_STEP) { 592 if (Settings::values.frame_limit < 9999 - SPEED_LIMIT_STEP) {
584 Settings::values.frame_limit += SPEED_LIMIT_STEP; 593 Settings::values.frame_limit += SPEED_LIMIT_STEP;
585 UpdateStatusBar(); 594 UpdateStatusBar();
586 } 595 }
587 }); 596 });
588 connect(hotkey_registry.GetHotkey("Main Window", "Decrease Speed Limit", this), 597 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Decrease Speed Limit"), this),
589 &QShortcut::activated, this, [&] { 598 &QShortcut::activated, this, [&] {
590 if (Settings::values.frame_limit > SPEED_LIMIT_STEP) { 599 if (Settings::values.frame_limit > SPEED_LIMIT_STEP) {
591 Settings::values.frame_limit -= SPEED_LIMIT_STEP; 600 Settings::values.frame_limit -= SPEED_LIMIT_STEP;
592 UpdateStatusBar(); 601 UpdateStatusBar();
593 } 602 }
594 }); 603 });
595 connect(hotkey_registry.GetHotkey("Main Window", "Load Amiibo", this), &QShortcut::activated, 604 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Load Amiibo"), this),
596 this, [&] { 605 &QShortcut::activated, this, [&] {
597 if (ui.action_Load_Amiibo->isEnabled()) { 606 if (ui.action_Load_Amiibo->isEnabled()) {
598 OnLoadAmiibo(); 607 OnLoadAmiibo();
599 } 608 }
600 }); 609 });
601 connect(hotkey_registry.GetHotkey("Main Window", "Capture Screenshot", this), 610 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Capture Screenshot"), this),
602 &QShortcut::activated, this, [&] { 611 &QShortcut::activated, this, [&] {
603 if (emu_thread->IsRunning()) { 612 if (emu_thread->IsRunning()) {
604 OnCaptureScreenshot(); 613 OnCaptureScreenshot();
605 } 614 }
606 }); 615 });
607 connect(hotkey_registry.GetHotkey("Main Window", "Change Docked Mode", this), 616 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Change Docked Mode"), this),
608 &QShortcut::activated, this, [&] { 617 &QShortcut::activated, this, [&] {
609 Settings::values.use_docked_mode = !Settings::values.use_docked_mode; 618 Settings::values.use_docked_mode = !Settings::values.use_docked_mode;
610 OnDockedModeChanged(!Settings::values.use_docked_mode, 619 OnDockedModeChanged(!Settings::values.use_docked_mode,
@@ -705,7 +714,9 @@ void GMainWindow::ConnectMenuEvents() {
705 714
706 // Fullscreen 715 // Fullscreen
707 ui.action_Fullscreen->setShortcut( 716 ui.action_Fullscreen->setShortcut(
708 hotkey_registry.GetHotkey("Main Window", "Fullscreen", this)->key()); 717 hotkey_registry
718 .GetHotkey(QStringLiteral("Main Window"), QStringLiteral("Fullscreen"), this)
719 ->key());
709 connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen); 720 connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
710 721
711 // Movie 722 // Movie
@@ -742,25 +753,33 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
742QStringList GMainWindow::GetUnsupportedGLExtensions() { 753QStringList GMainWindow::GetUnsupportedGLExtensions() {
743 QStringList unsupported_ext; 754 QStringList unsupported_ext;
744 755
745 if (!GLAD_GL_ARB_direct_state_access) 756 if (!GLAD_GL_ARB_direct_state_access) {
746 unsupported_ext.append("ARB_direct_state_access"); 757 unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
747 if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) 758 }
748 unsupported_ext.append("ARB_vertex_type_10f_11f_11f_rev"); 759 if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) {
749 if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) 760 unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev"));
750 unsupported_ext.append("ARB_texture_mirror_clamp_to_edge"); 761 }
751 if (!GLAD_GL_ARB_multi_bind) 762 if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) {
752 unsupported_ext.append("ARB_multi_bind"); 763 unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge"));
764 }
765 if (!GLAD_GL_ARB_multi_bind) {
766 unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
767 }
753 768
754 // Extensions required to support some texture formats. 769 // Extensions required to support some texture formats.
755 if (!GLAD_GL_EXT_texture_compression_s3tc) 770 if (!GLAD_GL_EXT_texture_compression_s3tc) {
756 unsupported_ext.append("EXT_texture_compression_s3tc"); 771 unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
757 if (!GLAD_GL_ARB_texture_compression_rgtc) 772 }
758 unsupported_ext.append("ARB_texture_compression_rgtc"); 773 if (!GLAD_GL_ARB_texture_compression_rgtc) {
759 if (!GLAD_GL_ARB_depth_buffer_float) 774 unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
760 unsupported_ext.append("ARB_depth_buffer_float"); 775 }
761 776 if (!GLAD_GL_ARB_depth_buffer_float) {
762 for (const QString& ext : unsupported_ext) 777 unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
778 }
779
780 for (const QString& ext : unsupported_ext) {
763 LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString()); 781 LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
782 }
764 783
765 return unsupported_ext; 784 return unsupported_ext;
766} 785}
@@ -782,13 +801,13 @@ bool GMainWindow::LoadROM(const QString& filename) {
782 } 801 }
783 } 802 }
784 803
785 QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions(); 804 const QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
786 if (!unsupported_gl_extensions.empty()) { 805 if (!unsupported_gl_extensions.empty()) {
787 QMessageBox::critical(this, tr("Error while initializing OpenGL Core!"), 806 QMessageBox::critical(this, tr("Error while initializing OpenGL Core!"),
788 tr("Your GPU may not support one or more required OpenGL" 807 tr("Your GPU may not support one or more required OpenGL"
789 "extensions. Please ensure you have the latest graphics " 808 "extensions. Please ensure you have the latest graphics "
790 "driver.<br><br>Unsupported extensions:<br>") + 809 "driver.<br><br>Unsupported extensions:<br>") +
791 unsupported_gl_extensions.join("<br>")); 810 unsupported_gl_extensions.join(QStringLiteral("<br>")));
792 return false; 811 return false;
793 } 812 }
794 813
@@ -936,9 +955,7 @@ void GMainWindow::BootGame(const QString& filename) {
936 title_name = FileUtil::GetFilename(filename.toStdString()); 955 title_name = FileUtil::GetFilename(filename.toStdString());
937 } 956 }
938 957
939 setWindowTitle(QString("yuzu %1| %4 | %2-%3") 958 UpdateWindowTitle(QString::fromStdString(title_name));
940 .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc,
941 QString::fromStdString(title_name)));
942 959
943 loading_screen->Prepare(Core::System::GetInstance().GetAppLoader()); 960 loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
944 loading_screen->show(); 961 loading_screen->show();
@@ -979,8 +996,8 @@ void GMainWindow::ShutdownGame() {
979 loading_screen->Clear(); 996 loading_screen->Clear();
980 game_list->show(); 997 game_list->show();
981 game_list->setFilterFocus(); 998 game_list->setFilterFocus();
982 setWindowTitle(QString("yuzu %1| %2-%3") 999
983 .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc)); 1000 UpdateWindowTitle();
984 1001
985 // Disable status bar updates 1002 // Disable status bar updates
986 status_bar_update_timer.stop(); 1003 status_bar_update_timer.stop();
@@ -1009,7 +1026,7 @@ void GMainWindow::UpdateRecentFiles() {
1009 std::min(UISettings::values.recent_files.size(), max_recent_files_item); 1026 std::min(UISettings::values.recent_files.size(), max_recent_files_item);
1010 1027
1011 for (int i = 0; i < num_recent_files; i++) { 1028 for (int i = 0; i < num_recent_files; i++) {
1012 const QString text = QString("&%1. %2").arg(i + 1).arg( 1029 const QString text = QStringLiteral("&%1. %2").arg(i + 1).arg(
1013 QFileInfo(UISettings::values.recent_files[i]).fileName()); 1030 QFileInfo(UISettings::values.recent_files[i]).fileName());
1014 actions_recent_files[i]->setText(text); 1031 actions_recent_files[i]->setText(text);
1015 actions_recent_files[i]->setData(UISettings::values.recent_files[i]); 1032 actions_recent_files[i]->setData(UISettings::values.recent_files[i]);
@@ -1031,10 +1048,10 @@ void GMainWindow::OnGameListLoadFile(QString game_path) {
1031 1048
1032void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target) { 1049void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target) {
1033 std::string path; 1050 std::string path;
1034 std::string open_target; 1051 QString open_target;
1035 switch (target) { 1052 switch (target) {
1036 case GameListOpenTarget::SaveData: { 1053 case GameListOpenTarget::SaveData: {
1037 open_target = "Save Data"; 1054 open_target = tr("Save Data");
1038 const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); 1055 const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
1039 ASSERT(program_id != 0); 1056 ASSERT(program_id != 0);
1040 1057
@@ -1071,7 +1088,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
1071 break; 1088 break;
1072 } 1089 }
1073 case GameListOpenTarget::ModData: { 1090 case GameListOpenTarget::ModData: {
1074 open_target = "Mod Data"; 1091 open_target = tr("Mod Data");
1075 const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir); 1092 const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir);
1076 path = fmt::format("{}{:016X}", load_dir, program_id); 1093 path = fmt::format("{}{:016X}", load_dir, program_id);
1077 break; 1094 break;
@@ -1081,27 +1098,26 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
1081 } 1098 }
1082 1099
1083 const QString qpath = QString::fromStdString(path); 1100 const QString qpath = QString::fromStdString(path);
1084
1085 const QDir dir(qpath); 1101 const QDir dir(qpath);
1086 if (!dir.exists()) { 1102 if (!dir.exists()) {
1087 QMessageBox::warning(this, 1103 QMessageBox::warning(this, tr("Error Opening %1 Folder").arg(open_target),
1088 tr("Error Opening %1 Folder").arg(QString::fromStdString(open_target)),
1089 tr("Folder does not exist!")); 1104 tr("Folder does not exist!"));
1090 return; 1105 return;
1091 } 1106 }
1092 LOG_INFO(Frontend, "Opening {} path for program_id={:016x}", open_target, program_id); 1107 LOG_INFO(Frontend, "Opening {} path for program_id={:016x}", open_target.toStdString(),
1108 program_id);
1093 QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); 1109 QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
1094} 1110}
1095 1111
1096void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { 1112void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) {
1097 ASSERT(program_id != 0); 1113 ASSERT(program_id != 0);
1098 1114
1115 const QString shader_dir =
1116 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir));
1099 const QString tranferable_shader_cache_folder_path = 1117 const QString tranferable_shader_cache_folder_path =
1100 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) + "opengl" + 1118 shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable");
1101 DIR_SEP + "transferable";
1102
1103 const QString transferable_shader_cache_file_path = 1119 const QString transferable_shader_cache_file_path =
1104 tranferable_shader_cache_folder_path + DIR_SEP + 1120 tranferable_shader_cache_folder_path + QDir::separator() +
1105 QString::fromStdString(fmt::format("{:016X}.bin", program_id)); 1121 QString::fromStdString(fmt::format("{:016X}.bin", program_id));
1106 1122
1107 if (!QFile::exists(transferable_shader_cache_file_path)) { 1123 if (!QFile::exists(transferable_shader_cache_file_path)) {
@@ -1218,20 +1234,21 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
1218 return; 1234 return;
1219 } 1235 }
1220 1236
1221 bool ok; 1237 bool ok = false;
1238 const QStringList selections{tr("Full"), tr("Skeleton")};
1222 const auto res = QInputDialog::getItem( 1239 const auto res = QInputDialog::getItem(
1223 this, tr("Select RomFS Dump Mode"), 1240 this, tr("Select RomFS Dump Mode"),
1224 tr("Please select the how you would like the RomFS dumped.<br>Full will copy all of the " 1241 tr("Please select the how you would like the RomFS dumped.<br>Full will copy all of the "
1225 "files into the new directory while <br>skeleton will only create the directory " 1242 "files into the new directory while <br>skeleton will only create the directory "
1226 "structure."), 1243 "structure."),
1227 {"Full", "Skeleton"}, 0, false, &ok); 1244 selections, 0, false, &ok);
1228 if (!ok) { 1245 if (!ok) {
1229 failed(); 1246 failed();
1230 vfs->DeleteDirectory(path); 1247 vfs->DeleteDirectory(path);
1231 return; 1248 return;
1232 } 1249 }
1233 1250
1234 const auto full = res == "Full"; 1251 const auto full = res == selections.constFirst();
1235 const auto entry_size = CalculateRomFSEntrySize(extracted, full); 1252 const auto entry_size = CalculateRomFSEntrySize(extracted, full);
1236 1253
1237 QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, 1254 QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0,
@@ -1261,10 +1278,11 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
1261 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); 1278 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
1262 1279
1263 QString directory; 1280 QString directory;
1264 if (it != compatibility_list.end()) 1281 if (it != compatibility_list.end()) {
1265 directory = it->second.second; 1282 directory = it->second.second;
1283 }
1266 1284
1267 QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory)); 1285 QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory));
1268} 1286}
1269 1287
1270void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { 1288void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
@@ -1295,7 +1313,9 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
1295 1313
1296void GMainWindow::OnMenuLoadFile() { 1314void GMainWindow::OnMenuLoadFile() {
1297 const QString extensions = 1315 const QString extensions =
1298 QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main"); 1316 QStringLiteral("*.")
1317 .append(GameList::supported_file_extensions.join(QStringLiteral(" *.")))
1318 .append(QStringLiteral(" main"));
1299 const QString file_filter = tr("Switch Executable (%1);;All Files (*.*)", 1319 const QString file_filter = tr("Switch Executable (%1);;All Files (*.*)",
1300 "%1 is an identifier for the Switch executable file extensions.") 1320 "%1 is an identifier for the Switch executable file extensions.")
1301 .arg(extensions); 1321 .arg(extensions);
@@ -1319,9 +1339,9 @@ void GMainWindow::OnMenuLoadFolder() {
1319 } 1339 }
1320 1340
1321 const QDir dir{dir_path}; 1341 const QDir dir{dir_path};
1322 const QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files); 1342 const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files);
1323 if (matching_main.size() == 1) { 1343 if (matching_main.size() == 1) {
1324 BootGame(dir.path() + DIR_SEP + matching_main[0]); 1344 BootGame(dir.path() + QDir::separator() + matching_main[0]);
1325 } else { 1345 } else {
1326 QMessageBox::warning(this, tr("Invalid Directory Selected"), 1346 QMessageBox::warning(this, tr("Invalid Directory Selected"),
1327 tr("The directory you have selected does not contain a 'main' file.")); 1347 tr("The directory you have selected does not contain a 'main' file."));
@@ -1376,6 +1396,8 @@ void GMainWindow::OnMenuInstallToNAND() {
1376 tr("The file was successfully installed.")); 1396 tr("The file was successfully installed."));
1377 game_list->PopulateAsync(UISettings::values.game_directory_path, 1397 game_list->PopulateAsync(UISettings::values.game_directory_path,
1378 UISettings::values.game_directory_deepscan); 1398 UISettings::values.game_directory_deepscan);
1399 FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
1400 DIR_SEP + "game_list");
1379 }; 1401 };
1380 1402
1381 const auto failed = [this]() { 1403 const auto failed = [this]() {
@@ -1393,11 +1415,10 @@ void GMainWindow::OnMenuInstallToNAND() {
1393 QMessageBox::Yes; 1415 QMessageBox::Yes;
1394 }; 1416 };
1395 1417
1396 if (filename.endsWith("xci", Qt::CaseInsensitive) || 1418 if (filename.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) ||
1397 filename.endsWith("nsp", Qt::CaseInsensitive)) { 1419 filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
1398
1399 std::shared_ptr<FileSys::NSP> nsp; 1420 std::shared_ptr<FileSys::NSP> nsp;
1400 if (filename.endsWith("nsp", Qt::CaseInsensitive)) { 1421 if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
1401 nsp = std::make_shared<FileSys::NSP>( 1422 nsp = std::make_shared<FileSys::NSP>(
1402 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); 1423 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
1403 if (nsp->IsExtractedType()) 1424 if (nsp->IsExtractedType())
@@ -1692,9 +1713,9 @@ void GMainWindow::OnConfigure() {
1692} 1713}
1693 1714
1694void GMainWindow::OnLoadAmiibo() { 1715void GMainWindow::OnLoadAmiibo() {
1695 const QString extensions{"*.bin"}; 1716 const QString extensions{QStringLiteral("*.bin")};
1696 const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions); 1717 const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
1697 const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), "", file_filter); 1718 const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), {}, file_filter);
1698 1719
1699 if (filename.isEmpty()) { 1720 if (filename.isEmpty()) {
1700 return; 1721 return;
@@ -1756,7 +1777,7 @@ void GMainWindow::OnCaptureScreenshot() {
1756 QFileDialog png_dialog(this, tr("Capture Screenshot"), UISettings::values.screenshot_path, 1777 QFileDialog png_dialog(this, tr("Capture Screenshot"), UISettings::values.screenshot_path,
1757 tr("PNG Image (*.png)")); 1778 tr("PNG Image (*.png)"));
1758 png_dialog.setAcceptMode(QFileDialog::AcceptSave); 1779 png_dialog.setAcceptMode(QFileDialog::AcceptSave);
1759 png_dialog.setDefaultSuffix("png"); 1780 png_dialog.setDefaultSuffix(QStringLiteral("png"));
1760 if (png_dialog.exec()) { 1781 if (png_dialog.exec()) {
1761 const QString path = png_dialog.selectedFiles().first(); 1782 const QString path = png_dialog.selectedFiles().first();
1762 if (!path.isEmpty()) { 1783 if (!path.isEmpty()) {
@@ -1767,6 +1788,19 @@ void GMainWindow::OnCaptureScreenshot() {
1767 OnStartGame(); 1788 OnStartGame();
1768} 1789}
1769 1790
1791void GMainWindow::UpdateWindowTitle(const QString& title_name) {
1792 const QString full_name = QString::fromUtf8(Common::g_build_fullname);
1793 const QString branch_name = QString::fromUtf8(Common::g_scm_branch);
1794 const QString description = QString::fromUtf8(Common::g_scm_desc);
1795
1796 if (title_name.isEmpty()) {
1797 setWindowTitle(QStringLiteral("yuzu %1| %2-%3").arg(full_name, branch_name, description));
1798 } else {
1799 setWindowTitle(QStringLiteral("yuzu %1| %4 | %2-%3")
1800 .arg(full_name, branch_name, description, title_name));
1801 }
1802}
1803
1770void GMainWindow::UpdateStatusBar() { 1804void GMainWindow::UpdateStatusBar() {
1771 if (emu_thread == nullptr) { 1805 if (emu_thread == nullptr) {
1772 status_bar_update_timer.stop(); 1806 status_bar_update_timer.stop();
@@ -1806,17 +1840,17 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
1806 "data, or other bugs."); 1840 "data, or other bugs.");
1807 switch (result) { 1841 switch (result) {
1808 case Core::System::ResultStatus::ErrorSystemFiles: { 1842 case Core::System::ResultStatus::ErrorSystemFiles: {
1809 QString message = "yuzu was unable to locate a Switch system archive"; 1843 QString message = tr("yuzu was unable to locate a Switch system archive");
1810 if (!details.empty()) { 1844 if (!details.empty()) {
1811 message.append(tr(": %1. ").arg(details.c_str())); 1845 message.append(tr(": %1. ").arg(QString::fromStdString(details)));
1812 } else { 1846 } else {
1813 message.append(". "); 1847 message.append(tr(". "));
1814 } 1848 }
1815 message.append(common_message); 1849 message.append(common_message);
1816 1850
1817 answer = QMessageBox::question(this, tr("System Archive Not Found"), message, 1851 answer = QMessageBox::question(this, tr("System Archive Not Found"), message,
1818 QMessageBox::Yes | QMessageBox::No, QMessageBox::No); 1852 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
1819 status_message = "System Archive Missing"; 1853 status_message = tr("System Archive Missing");
1820 break; 1854 break;
1821 } 1855 }
1822 1856
@@ -1825,7 +1859,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
1825 message.append(common_message); 1859 message.append(common_message);
1826 answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message, 1860 answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message,
1827 QMessageBox::Yes | QMessageBox::No, QMessageBox::No); 1861 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
1828 status_message = "Shared Font Missing"; 1862 status_message = tr("Shared Font Missing");
1829 break; 1863 break;
1830 } 1864 }
1831 1865
@@ -1841,7 +1875,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
1841 "Continuing emulation may result in crashes, corrupted save data, or other " 1875 "Continuing emulation may result in crashes, corrupted save data, or other "
1842 "bugs."), 1876 "bugs."),
1843 QMessageBox::Yes | QMessageBox::No, QMessageBox::No); 1877 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
1844 status_message = "Fatal Error encountered"; 1878 status_message = tr("Fatal Error encountered");
1845 break; 1879 break;
1846 } 1880 }
1847 1881
@@ -1892,18 +1926,19 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
1892 }; 1926 };
1893 1927
1894 QString errors; 1928 QString errors;
1895 1929 if (!pdm.HasFuses()) {
1896 if (!pdm.HasFuses())
1897 errors += tr("- Missing fuses - Cannot derive SBK\n"); 1930 errors += tr("- Missing fuses - Cannot derive SBK\n");
1898 if (!pdm.HasBoot0()) 1931 }
1932 if (!pdm.HasBoot0()) {
1899 errors += tr("- Missing BOOT0 - Cannot derive master keys\n"); 1933 errors += tr("- Missing BOOT0 - Cannot derive master keys\n");
1900 if (!pdm.HasPackage2()) 1934 }
1935 if (!pdm.HasPackage2()) {
1901 errors += tr("- Missing BCPKG2-1-Normal-Main - Cannot derive general keys\n"); 1936 errors += tr("- Missing BCPKG2-1-Normal-Main - Cannot derive general keys\n");
1902 if (!pdm.HasProdInfo()) 1937 }
1938 if (!pdm.HasProdInfo()) {
1903 errors += tr("- Missing PRODINFO - Cannot derive title keys\n"); 1939 errors += tr("- Missing PRODINFO - Cannot derive title keys\n");
1904 1940 }
1905 if (!errors.isEmpty()) { 1941 if (!errors.isEmpty()) {
1906
1907 QMessageBox::warning( 1942 QMessageBox::warning(
1908 this, tr("Warning Missing Derivation Components"), 1943 this, tr("Warning Missing Derivation Components"),
1909 tr("The following are missing from your configuration that may hinder key " 1944 tr("The following are missing from your configuration that may hinder key "
@@ -1953,13 +1988,15 @@ std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProv
1953 1988
1954 std::vector<u64> romfs_tids; 1989 std::vector<u64> romfs_tids;
1955 romfs_tids.push_back(program_id); 1990 romfs_tids.push_back(program_id);
1956 for (const auto& entry : dlc_match) 1991 for (const auto& entry : dlc_match) {
1957 romfs_tids.push_back(entry.title_id); 1992 romfs_tids.push_back(entry.title_id);
1993 }
1958 1994
1959 if (romfs_tids.size() > 1) { 1995 if (romfs_tids.size() > 1) {
1960 QStringList list{"Base"}; 1996 QStringList list{QStringLiteral("Base")};
1961 for (std::size_t i = 1; i < romfs_tids.size(); ++i) 1997 for (std::size_t i = 1; i < romfs_tids.size(); ++i) {
1962 list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF)); 1998 list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF));
1999 }
1963 2000
1964 bool ok; 2001 bool ok;
1965 const auto res = QInputDialog::getItem( 2002 const auto res = QInputDialog::getItem(
@@ -2071,26 +2108,32 @@ void GMainWindow::filterBarSetChecked(bool state) {
2071} 2108}
2072 2109
2073void GMainWindow::UpdateUITheme() { 2110void GMainWindow::UpdateUITheme() {
2111 const QString default_icons = QStringLiteral(":/icons/default");
2112 const QString& current_theme = UISettings::values.theme;
2113 const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second);
2074 QStringList theme_paths(default_theme_paths); 2114 QStringList theme_paths(default_theme_paths);
2075 if (UISettings::values.theme != UISettings::themes[0].second && 2115
2076 !UISettings::values.theme.isEmpty()) { 2116 if (is_default_theme || current_theme.isEmpty()) {
2077 const QString theme_uri(":" + UISettings::values.theme + "/style.qss"); 2117 qApp->setStyleSheet({});
2118 setStyleSheet({});
2119 theme_paths.append(default_icons);
2120 QIcon::setThemeName(default_icons);
2121 } else {
2122 const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss"));
2078 QFile f(theme_uri); 2123 QFile f(theme_uri);
2079 if (f.open(QFile::ReadOnly | QFile::Text)) { 2124 if (f.open(QFile::ReadOnly | QFile::Text)) {
2080 QTextStream ts(&f); 2125 QTextStream ts(&f);
2081 qApp->setStyleSheet(ts.readAll()); 2126 qApp->setStyleSheet(ts.readAll());
2082 GMainWindow::setStyleSheet(ts.readAll()); 2127 setStyleSheet(ts.readAll());
2083 } else { 2128 } else {
2084 LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found"); 2129 LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
2085 } 2130 }
2086 theme_paths.append(QStringList{":/icons/default", ":/icons/" + UISettings::values.theme}); 2131
2087 QIcon::setThemeName(":/icons/" + UISettings::values.theme); 2132 const QString theme_name = QStringLiteral(":/icons/") + current_theme;
2088 } else { 2133 theme_paths.append({default_icons, theme_name});
2089 qApp->setStyleSheet(""); 2134 QIcon::setThemeName(theme_name);
2090 GMainWindow::setStyleSheet("");
2091 theme_paths.append(QStringList{":/icons/default"});
2092 QIcon::setThemeName(":/icons/default");
2093 } 2135 }
2136
2094 QIcon::setThemeSearchPaths(theme_paths); 2137 QIcon::setThemeSearchPaths(theme_paths);
2095 emit UpdateThemedIcons(); 2138 emit UpdateThemedIcons();
2096} 2139}
@@ -2118,8 +2161,8 @@ int main(int argc, char* argv[]) {
2118 SCOPE_EXIT({ MicroProfileShutdown(); }); 2161 SCOPE_EXIT({ MicroProfileShutdown(); });
2119 2162
2120 // Init settings params 2163 // Init settings params
2121 QCoreApplication::setOrganizationName("yuzu team"); 2164 QCoreApplication::setOrganizationName(QStringLiteral("yuzu team"));
2122 QCoreApplication::setApplicationName("yuzu"); 2165 QCoreApplication::setApplicationName(QStringLiteral("yuzu"));
2123 2166
2124 // Enables the core to make the qt created contexts current on std::threads 2167 // Enables the core to make the qt created contexts current on std::threads
2125 QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); 2168 QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index c4566ed2c..1137bbc7a 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -209,6 +209,7 @@ private slots:
209 209
210private: 210private:
211 std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); 211 std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
212 void UpdateWindowTitle(const QString& title_name = {});
212 void UpdateStatusBar(); 213 void UpdateStatusBar();
213 214
214 Ui::MainWindow ui; 215 Ui::MainWindow ui;
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index dbd318e20..a62cd6911 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -79,6 +79,7 @@ struct Values {
79 uint8_t row_1_text_id; 79 uint8_t row_1_text_id;
80 uint8_t row_2_text_id; 80 uint8_t row_2_text_id;
81 std::atomic_bool is_game_list_reload_pending{false}; 81 std::atomic_bool is_game_list_reload_pending{false};
82 bool cache_game_list;
82}; 83};
83 84
84extern Values values; 85extern Values values;
diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
index d3edf6ec3..bb5f74ec4 100644
--- a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
+++ b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
@@ -9,16 +9,19 @@
9 9
10SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) { 10SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) {
11 setWindowTitle(tr("Enter a hotkey")); 11 setWindowTitle(tr("Enter a hotkey"));
12 auto* layout = new QVBoxLayout(this); 12 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
13
13 key_sequence = new QKeySequenceEdit; 14 key_sequence = new QKeySequenceEdit;
14 layout->addWidget(key_sequence); 15
15 auto* buttons = 16 auto* const buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
16 new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
17 buttons->setCenterButtons(true); 17 buttons->setCenterButtons(true);
18
19 auto* const layout = new QVBoxLayout(this);
20 layout->addWidget(key_sequence);
18 layout->addWidget(buttons); 21 layout->addWidget(buttons);
22
19 connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); 23 connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
20 connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); 24 connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
21 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
22} 25}
23 26
24SequenceDialog::~SequenceDialog() = default; 27SequenceDialog::~SequenceDialog() = default;
diff --git a/src/yuzu/util/spinbox.cpp b/src/yuzu/util/spinbox.cpp
deleted file mode 100644
index 14ef1e884..000000000
--- a/src/yuzu/util/spinbox.cpp
+++ /dev/null
@@ -1,278 +0,0 @@
1// Licensed under GPLv2 or any later version
2// Refer to the license.txt file included.
3
4// Copyright 2014 Tony Wasserka
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// * Redistributions of source code must retain the above copyright
11// notice, this list of conditions and the following disclaimer.
12// * Redistributions in binary form must reproduce the above copyright
13// notice, this list of conditions and the following disclaimer in the
14// documentation and/or other materials provided with the distribution.
15// * Neither the name of the owner nor the names of its contributors may
16// be used to endorse or promote products derived from this software
17// without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31#include <cstdlib>
32#include <QLineEdit>
33#include <QRegExpValidator>
34#include "common/assert.h"
35#include "yuzu/util/spinbox.h"
36
37CSpinBox::CSpinBox(QWidget* parent)
38 : QAbstractSpinBox(parent), min_value(-100), max_value(100), value(0), base(10), num_digits(0) {
39 // TODO: Might be nice to not immediately call the slot.
40 // Think of an address that is being replaced by a different one, in which case a lot
41 // invalid intermediate addresses would be read from during editing.
42 connect(lineEdit(), &QLineEdit::textEdited, this, &CSpinBox::OnEditingFinished);
43
44 UpdateText();
45}
46
47void CSpinBox::SetValue(qint64 val) {
48 auto old_value = value;
49 value = std::max(std::min(val, max_value), min_value);
50
51 if (old_value != value) {
52 UpdateText();
53 emit ValueChanged(value);
54 }
55}
56
57void CSpinBox::SetRange(qint64 min, qint64 max) {
58 min_value = min;
59 max_value = max;
60
61 SetValue(value);
62 UpdateText();
63}
64
65void CSpinBox::stepBy(int steps) {
66 auto new_value = value;
67 // Scale number of steps by the currently selected digit
68 // TODO: Move this code elsewhere and enable it.
69 // TODO: Support for num_digits==0, too
70 // TODO: Support base!=16, too
71 // TODO: Make the cursor not jump back to the end of the line...
72 /*if (base == 16 && num_digits > 0) {
73 int digit = num_digits - (lineEdit()->cursorPosition() - prefix.length()) - 1;
74 digit = std::max(0, std::min(digit, num_digits - 1));
75 steps <<= digit * 4;
76 }*/
77
78 // Increment "new_value" by "steps", and perform annoying overflow checks, too.
79 if (steps < 0 && new_value + steps > new_value) {
80 new_value = std::numeric_limits<qint64>::min();
81 } else if (steps > 0 && new_value + steps < new_value) {
82 new_value = std::numeric_limits<qint64>::max();
83 } else {
84 new_value += steps;
85 }
86
87 SetValue(new_value);
88 UpdateText();
89}
90
91QAbstractSpinBox::StepEnabled CSpinBox::stepEnabled() const {
92 StepEnabled ret = StepNone;
93
94 if (value > min_value)
95 ret |= StepDownEnabled;
96
97 if (value < max_value)
98 ret |= StepUpEnabled;
99
100 return ret;
101}
102
103void CSpinBox::SetBase(int base) {
104 this->base = base;
105
106 UpdateText();
107}
108
109void CSpinBox::SetNumDigits(int num_digits) {
110 this->num_digits = num_digits;
111
112 UpdateText();
113}
114
115void CSpinBox::SetPrefix(const QString& prefix) {
116 this->prefix = prefix;
117
118 UpdateText();
119}
120
121void CSpinBox::SetSuffix(const QString& suffix) {
122 this->suffix = suffix;
123
124 UpdateText();
125}
126
127static QString StringToInputMask(const QString& input) {
128 QString mask = input;
129
130 // ... replace any special characters by their escaped counterparts ...
131 mask.replace("\\", "\\\\");
132 mask.replace("A", "\\A");
133 mask.replace("a", "\\a");
134 mask.replace("N", "\\N");
135 mask.replace("n", "\\n");
136 mask.replace("X", "\\X");
137 mask.replace("x", "\\x");
138 mask.replace("9", "\\9");
139 mask.replace("0", "\\0");
140 mask.replace("D", "\\D");
141 mask.replace("d", "\\d");
142 mask.replace("#", "\\#");
143 mask.replace("H", "\\H");
144 mask.replace("h", "\\h");
145 mask.replace("B", "\\B");
146 mask.replace("b", "\\b");
147 mask.replace(">", "\\>");
148 mask.replace("<", "\\<");
149 mask.replace("!", "\\!");
150
151 return mask;
152}
153
154void CSpinBox::UpdateText() {
155 // If a fixed number of digits is used, we put the line edit in insertion mode by setting an
156 // input mask.
157 QString mask;
158 if (num_digits != 0) {
159 mask += StringToInputMask(prefix);
160
161 // For base 10 and negative range, demand a single sign character
162 if (HasSign())
163 mask += "X"; // identified as "-" or "+" in the validator
164
165 // Uppercase digits greater than 9.
166 mask += ">";
167
168 // Match num_digits digits
169 // Digits irrelevant to the chosen number base are filtered in the validator
170 mask += QString("H").repeated(std::max(num_digits, 1));
171
172 // Switch off case conversion
173 mask += "!";
174
175 mask += StringToInputMask(suffix);
176 }
177 lineEdit()->setInputMask(mask);
178
179 // Set new text without changing the cursor position. This will cause the cursor to briefly
180 // appear at the end of the line and then to jump back to its original position. That's
181 // a bit ugly, but better than having setText() move the cursor permanently all the time.
182 int cursor_position = lineEdit()->cursorPosition();
183 lineEdit()->setText(TextFromValue());
184 lineEdit()->setCursorPosition(cursor_position);
185}
186
187QString CSpinBox::TextFromValue() {
188 return prefix + QString(HasSign() ? ((value < 0) ? "-" : "+") : "") +
189 QString("%1").arg(std::abs(value), num_digits, base, QLatin1Char('0')).toUpper() +
190 suffix;
191}
192
193qint64 CSpinBox::ValueFromText() {
194 unsigned strpos = prefix.length();
195
196 QString num_string = text().mid(strpos, text().length() - strpos - suffix.length());
197 return num_string.toLongLong(nullptr, base);
198}
199
200bool CSpinBox::HasSign() const {
201 return base == 10 && min_value < 0;
202}
203
204void CSpinBox::OnEditingFinished() {
205 // Only update for valid input
206 QString input = lineEdit()->text();
207 int pos = 0;
208 if (QValidator::Acceptable == validate(input, pos))
209 SetValue(ValueFromText());
210}
211
212QValidator::State CSpinBox::validate(QString& input, int& pos) const {
213 if (!prefix.isEmpty() && input.left(prefix.length()) != prefix)
214 return QValidator::Invalid;
215
216 int strpos = prefix.length();
217
218 // Empty "numbers" allowed as intermediate values
219 if (strpos >= input.length() - HasSign() - suffix.length())
220 return QValidator::Intermediate;
221
222 DEBUG_ASSERT(base <= 10 || base == 16);
223 QString regexp;
224
225 // Demand sign character for negative ranges
226 if (HasSign())
227 regexp += "[+\\-]";
228
229 // Match digits corresponding to the chosen number base.
230 regexp += QString("[0-%1").arg(std::min(base, 9));
231 if (base == 16) {
232 regexp += "a-fA-F";
233 }
234 regexp += "]";
235
236 // Specify number of digits
237 if (num_digits > 0) {
238 regexp += QString("{%1}").arg(num_digits);
239 } else {
240 regexp += "+";
241 }
242
243 // Match string
244 QRegExp num_regexp(regexp);
245 int num_pos = strpos;
246 QString sub_input = input.mid(strpos, input.length() - strpos - suffix.length());
247
248 if (!num_regexp.exactMatch(sub_input) && num_regexp.matchedLength() == 0)
249 return QValidator::Invalid;
250
251 sub_input = sub_input.left(num_regexp.matchedLength());
252 bool ok;
253 qint64 val = sub_input.toLongLong(&ok, base);
254
255 if (!ok)
256 return QValidator::Invalid;
257
258 // Outside boundaries => don't accept
259 if (val < min_value || val > max_value)
260 return QValidator::Invalid;
261
262 // Make sure we are actually at the end of this string...
263 strpos += num_regexp.matchedLength();
264
265 if (!suffix.isEmpty() && input.mid(strpos) != suffix) {
266 return QValidator::Invalid;
267 } else {
268 strpos += suffix.length();
269 }
270
271 if (strpos != input.length())
272 return QValidator::Invalid;
273
274 // At this point we can say for sure that the input is fine. Let's fix it up a bit though
275 input.replace(num_pos, sub_input.length(), sub_input.toUpper());
276
277 return QValidator::Acceptable;
278}
diff --git a/src/yuzu/util/spinbox.h b/src/yuzu/util/spinbox.h
deleted file mode 100644
index 2fa1db3a4..000000000
--- a/src/yuzu/util/spinbox.h
+++ /dev/null
@@ -1,86 +0,0 @@
1// Licensed under GPLv2 or any later version
2// Refer to the license.txt file included.
3
4// Copyright 2014 Tony Wasserka
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// * Redistributions of source code must retain the above copyright
11// notice, this list of conditions and the following disclaimer.
12// * Redistributions in binary form must reproduce the above copyright
13// notice, this list of conditions and the following disclaimer in the
14// documentation and/or other materials provided with the distribution.
15// * Neither the name of the owner nor the names of its contributors may
16// be used to endorse or promote products derived from this software
17// without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31#pragma once
32
33#include <QAbstractSpinBox>
34#include <QtGlobal>
35
36class QVariant;
37
38/**
39 * A custom spin box widget with enhanced functionality over Qt's QSpinBox
40 */
41class CSpinBox : public QAbstractSpinBox {
42 Q_OBJECT
43
44public:
45 explicit CSpinBox(QWidget* parent = nullptr);
46
47 void stepBy(int steps) override;
48 StepEnabled stepEnabled() const override;
49
50 void SetValue(qint64 val);
51
52 void SetRange(qint64 min, qint64 max);
53
54 void SetBase(int base);
55
56 void SetPrefix(const QString& prefix);
57 void SetSuffix(const QString& suffix);
58
59 void SetNumDigits(int num_digits);
60
61 QValidator::State validate(QString& input, int& pos) const override;
62
63signals:
64 void ValueChanged(qint64 val);
65
66private slots:
67 void OnEditingFinished();
68
69private:
70 void UpdateText();
71
72 bool HasSign() const;
73
74 QString TextFromValue();
75 qint64 ValueFromText();
76
77 qint64 min_value, max_value;
78
79 qint64 value;
80
81 QString prefix, suffix;
82
83 int base;
84
85 int num_digits;
86};
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index 62c080aff..ef31bc2d2 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -8,7 +8,7 @@
8#include "yuzu/util/util.h" 8#include "yuzu/util/util.h"
9 9
10QFont GetMonospaceFont() { 10QFont GetMonospaceFont() {
11 QFont font("monospace"); 11 QFont font(QStringLiteral("monospace"));
12 // Automatic fallback to a monospace font on on platforms without a font called "monospace" 12 // Automatic fallback to a monospace font on on platforms without a font called "monospace"
13 font.setStyleHint(QFont::Monospace); 13 font.setStyleHint(QFont::Monospace);
14 font.setFixedPitch(true); 14 font.setFixedPitch(true);
@@ -16,14 +16,16 @@ QFont GetMonospaceFont() {
16} 16}
17 17
18QString ReadableByteSize(qulonglong size) { 18QString ReadableByteSize(qulonglong size) {
19 static const std::array<const char*, 6> units = {"B", "KiB", "MiB", "GiB", "TiB", "PiB"}; 19 static constexpr std::array units{"B", "KiB", "MiB", "GiB", "TiB", "PiB"};
20 if (size == 0) 20 if (size == 0) {
21 return "0"; 21 return QStringLiteral("0");
22 int digit_groups = std::min<int>(static_cast<int>(std::log10(size) / std::log10(1024)), 22 }
23 static_cast<int>(units.size())); 23
24 return QString("%L1 %2") 24 const int digit_groups = std::min(static_cast<int>(std::log10(size) / std::log10(1024)),
25 static_cast<int>(units.size()));
26 return QStringLiteral("%L1 %2")
25 .arg(size / std::pow(1024, digit_groups), 0, 'f', 1) 27 .arg(size / std::pow(1024, digit_groups), 0, 'f', 1)
26 .arg(units[digit_groups]); 28 .arg(QString::fromUtf8(units[digit_groups]));
27} 29}
28 30
29QPixmap CreateCirclePixmapFromColor(const QColor& color) { 31QPixmap CreateCirclePixmapFromColor(const QColor& color) {
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index 297dab653..b5f06ab9e 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -4,6 +4,8 @@ add_executable(yuzu-cmd
4 config.cpp 4 config.cpp
5 config.h 5 config.h
6 default_ini.h 6 default_ini.h
7 emu_window/emu_window_sdl2_gl.cpp
8 emu_window/emu_window_sdl2_gl.h
7 emu_window/emu_window_sdl2.cpp 9 emu_window/emu_window_sdl2.cpp
8 emu_window/emu_window_sdl2.h 10 emu_window/emu_window_sdl2.h
9 resource.h 11 resource.h
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index d0ae058fd..730956427 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -26,12 +26,12 @@ Config::Config() {
26Config::~Config() = default; 26Config::~Config() = default;
27 27
28bool Config::LoadINI(const std::string& default_contents, bool retry) { 28bool Config::LoadINI(const std::string& default_contents, bool retry) {
29 const char* location = this->sdl2_config_loc.c_str(); 29 const std::string& location = this->sdl2_config_loc;
30 if (sdl2_config->ParseError() < 0) { 30 if (sdl2_config->ParseError() < 0) {
31 if (retry) { 31 if (retry) {
32 LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); 32 LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
33 FileUtil::CreateFullPath(location); 33 FileUtil::CreateFullPath(location);
34 FileUtil::WriteStringToFile(true, default_contents, location); 34 FileUtil::WriteStringToFile(true, location, default_contents);
35 sdl2_config = std::make_unique<INIReader>(location); // Reopen file 35 sdl2_config = std::make_unique<INIReader>(location); // Reopen file
36 36
37 return LoadINI(default_contents, false); 37 return LoadINI(default_contents, false);
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 68a176032..a6edc089a 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -2,53 +2,27 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm>
6#include <cstdlib>
7#include <string>
8#define SDL_MAIN_HANDLED
9#include <SDL.h> 5#include <SDL.h>
10#include <fmt/format.h>
11#include <glad/glad.h>
12#include "common/logging/log.h" 6#include "common/logging/log.h"
13#include "common/scm_rev.h"
14#include "common/string_util.h"
15#include "core/settings.h"
16#include "input_common/keyboard.h" 7#include "input_common/keyboard.h"
17#include "input_common/main.h" 8#include "input_common/main.h"
18#include "input_common/motion_emu.h" 9#include "input_common/motion_emu.h"
19#include "input_common/sdl/sdl.h" 10#include "input_common/sdl/sdl.h"
20#include "yuzu_cmd/emu_window/emu_window_sdl2.h" 11#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
21 12
22class SDLGLContext : public Core::Frontend::GraphicsContext { 13EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
23public: 14 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
24 explicit SDLGLContext() { 15 LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
25 // create a hidden window to make the shared context against 16 exit(1);
26 window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, // x position
27 SDL_WINDOWPOS_UNDEFINED, // y position
28 Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
29 SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
30 context = SDL_GL_CreateContext(window);
31 }
32
33 ~SDLGLContext() {
34 SDL_GL_DeleteContext(context);
35 SDL_DestroyWindow(window);
36 }
37
38 void MakeCurrent() override {
39 SDL_GL_MakeCurrent(window, context);
40 }
41
42 void DoneCurrent() override {
43 SDL_GL_MakeCurrent(window, nullptr);
44 } 17 }
18 InputCommon::Init();
19 SDL_SetMainReady();
20}
45 21
46 void SwapBuffers() override {} 22EmuWindow_SDL2::~EmuWindow_SDL2() {
47 23 InputCommon::Shutdown();
48private: 24 SDL_Quit();
49 SDL_Window* window; 25}
50 SDL_GLContext context;
51};
52 26
53void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { 27void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
54 TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); 28 TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
@@ -139,108 +113,6 @@ void EmuWindow_SDL2::Fullscreen() {
139 SDL_MaximizeWindow(render_window); 113 SDL_MaximizeWindow(render_window);
140} 114}
141 115
142bool EmuWindow_SDL2::SupportsRequiredGLExtensions() {
143 std::vector<std::string> unsupported_ext;
144
145 if (!GLAD_GL_ARB_direct_state_access)
146 unsupported_ext.push_back("ARB_direct_state_access");
147 if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
148 unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev");
149 if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
150 unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge");
151 if (!GLAD_GL_ARB_multi_bind)
152 unsupported_ext.push_back("ARB_multi_bind");
153
154 // Extensions required to support some texture formats.
155 if (!GLAD_GL_EXT_texture_compression_s3tc)
156 unsupported_ext.push_back("EXT_texture_compression_s3tc");
157 if (!GLAD_GL_ARB_texture_compression_rgtc)
158 unsupported_ext.push_back("ARB_texture_compression_rgtc");
159 if (!GLAD_GL_ARB_depth_buffer_float)
160 unsupported_ext.push_back("ARB_depth_buffer_float");
161
162 for (const std::string& ext : unsupported_ext)
163 LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext);
164
165 return unsupported_ext.empty();
166}
167
168EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
169 // Initialize the window
170 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
171 LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
172 exit(1);
173 }
174
175 InputCommon::Init();
176
177 SDL_SetMainReady();
178
179 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
180 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
181 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
182 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
183 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
184 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
185 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
186 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
187 SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
188
189 std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname,
190 Common::g_scm_branch, Common::g_scm_desc);
191 render_window =
192 SDL_CreateWindow(window_title.c_str(),
193 SDL_WINDOWPOS_UNDEFINED, // x position
194 SDL_WINDOWPOS_UNDEFINED, // y position
195 Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
196 SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
197
198 if (render_window == nullptr) {
199 LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError());
200 exit(1);
201 }
202
203 if (fullscreen) {
204 Fullscreen();
205 }
206 gl_context = SDL_GL_CreateContext(render_window);
207
208 if (gl_context == nullptr) {
209 LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError());
210 exit(1);
211 }
212
213 if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
214 LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError());
215 exit(1);
216 }
217
218 if (!SupportsRequiredGLExtensions()) {
219 LOG_CRITICAL(Frontend, "GPU does not support all required OpenGL extensions! Exiting...");
220 exit(1);
221 }
222
223 OnResize();
224 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
225 SDL_PumpEvents();
226 SDL_GL_SetSwapInterval(false);
227 LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
228 Common::g_scm_desc);
229 Settings::LogSettings();
230
231 DoneCurrent();
232}
233
234EmuWindow_SDL2::~EmuWindow_SDL2() {
235 InputCommon::Shutdown();
236 SDL_GL_DeleteContext(gl_context);
237 SDL_Quit();
238}
239
240void EmuWindow_SDL2::SwapBuffers() {
241 SDL_GL_SwapWindow(render_window);
242}
243
244void EmuWindow_SDL2::PollEvents() { 116void EmuWindow_SDL2::PollEvents() {
245 SDL_Event event; 117 SDL_Event event;
246 118
@@ -253,7 +125,11 @@ void EmuWindow_SDL2::PollEvents() {
253 case SDL_WINDOWEVENT_RESIZED: 125 case SDL_WINDOWEVENT_RESIZED:
254 case SDL_WINDOWEVENT_MAXIMIZED: 126 case SDL_WINDOWEVENT_MAXIMIZED:
255 case SDL_WINDOWEVENT_RESTORED: 127 case SDL_WINDOWEVENT_RESTORED:
128 OnResize();
129 break;
256 case SDL_WINDOWEVENT_MINIMIZED: 130 case SDL_WINDOWEVENT_MINIMIZED:
131 case SDL_WINDOWEVENT_EXPOSED:
132 is_shown = event.window.event == SDL_WINDOWEVENT_EXPOSED;
257 OnResize(); 133 OnResize();
258 break; 134 break;
259 case SDL_WINDOWEVENT_CLOSE: 135 case SDL_WINDOWEVENT_CLOSE:
@@ -296,20 +172,6 @@ void EmuWindow_SDL2::PollEvents() {
296 } 172 }
297} 173}
298 174
299void EmuWindow_SDL2::MakeCurrent() { 175void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) {
300 SDL_GL_MakeCurrent(render_window, gl_context);
301}
302
303void EmuWindow_SDL2::DoneCurrent() {
304 SDL_GL_MakeCurrent(render_window, nullptr);
305}
306
307void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(
308 const std::pair<unsigned, unsigned>& minimal_size) {
309
310 SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); 176 SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
311} 177}
312
313std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2::CreateSharedContext() const {
314 return std::make_unique<SDLGLContext>();
315}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index 17e98227f..d8051ebdf 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -15,24 +15,13 @@ public:
15 explicit EmuWindow_SDL2(bool fullscreen); 15 explicit EmuWindow_SDL2(bool fullscreen);
16 ~EmuWindow_SDL2(); 16 ~EmuWindow_SDL2();
17 17
18 /// Swap buffers to display the next frame
19 void SwapBuffers() override;
20
21 /// Polls window events 18 /// Polls window events
22 void PollEvents() override; 19 void PollEvents() override;
23 20
24 /// Makes the graphics context current for the caller thread
25 void MakeCurrent() override;
26
27 /// Releases the GL context from the caller thread
28 void DoneCurrent() override;
29
30 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
31
32 /// Whether the window is still open, and a close request hasn't yet been sent 21 /// Whether the window is still open, and a close request hasn't yet been sent
33 bool IsOpen() const; 22 bool IsOpen() const;
34 23
35private: 24protected:
36 /// Called by PollEvents when a key is pressed or released. 25 /// Called by PollEvents when a key is pressed or released.
37 void OnKeyEvent(int key, u8 state); 26 void OnKeyEvent(int key, u8 state);
38 27
@@ -60,20 +49,15 @@ private:
60 /// Called when user passes the fullscreen parameter flag 49 /// Called when user passes the fullscreen parameter flag
61 void Fullscreen(); 50 void Fullscreen();
62 51
63 /// Whether the GPU and driver supports the OpenGL extension required
64 bool SupportsRequiredGLExtensions();
65
66 /// Called when a configuration change affects the minimal size of the window 52 /// Called when a configuration change affects the minimal size of the window
67 void OnMinimalClientAreaChangeRequest( 53 void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) override;
68 const std::pair<unsigned, unsigned>& minimal_size) override;
69 54
70 /// Is the window still open? 55 /// Is the window still open?
71 bool is_open = true; 56 bool is_open = true;
72 57
58 /// Is the window being shown?
59 bool is_shown = true;
60
73 /// Internal SDL2 render window 61 /// Internal SDL2 render window
74 SDL_Window* render_window; 62 SDL_Window* render_window;
75
76 using SDL_GLContext = void*;
77 /// The OpenGL context associated with the window
78 SDL_GLContext gl_context;
79}; 63};
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
new file mode 100644
index 000000000..904022137
--- /dev/null
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -0,0 +1,154 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <cstdlib>
7#include <string>
8#define SDL_MAIN_HANDLED
9#include <SDL.h>
10#include <fmt/format.h>
11#include <glad/glad.h>
12#include "common/logging/log.h"
13#include "common/scm_rev.h"
14#include "common/string_util.h"
15#include "core/settings.h"
16#include "input_common/keyboard.h"
17#include "input_common/main.h"
18#include "input_common/motion_emu.h"
19#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
20
21class SDLGLContext : public Core::Frontend::GraphicsContext {
22public:
23 explicit SDLGLContext() {
24 // create a hidden window to make the shared context against
25 window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, // x position
26 SDL_WINDOWPOS_UNDEFINED, // y position
27 Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
28 SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
29 context = SDL_GL_CreateContext(window);
30 }
31
32 ~SDLGLContext() {
33 SDL_GL_DeleteContext(context);
34 SDL_DestroyWindow(window);
35 }
36
37 void MakeCurrent() override {
38 SDL_GL_MakeCurrent(window, context);
39 }
40
41 void DoneCurrent() override {
42 SDL_GL_MakeCurrent(window, nullptr);
43 }
44
45 void SwapBuffers() override {}
46
47private:
48 SDL_Window* window;
49 SDL_GLContext context;
50};
51
52bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
53 std::vector<std::string> unsupported_ext;
54
55 if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
56 unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev");
57 if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
58 unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge");
59 if (!GLAD_GL_ARB_multi_bind)
60 unsupported_ext.push_back("ARB_multi_bind");
61
62 // Extensions required to support some texture formats.
63 if (!GLAD_GL_EXT_texture_compression_s3tc)
64 unsupported_ext.push_back("EXT_texture_compression_s3tc");
65 if (!GLAD_GL_ARB_texture_compression_rgtc)
66 unsupported_ext.push_back("ARB_texture_compression_rgtc");
67 if (!GLAD_GL_ARB_depth_buffer_float)
68 unsupported_ext.push_back("ARB_depth_buffer_float");
69
70 for (const std::string& ext : unsupported_ext)
71 LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext);
72
73 return unsupported_ext.empty();
74}
75
76EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscreen) {
77 const SDL_GLprofile profile = Settings::values.use_compatibility_profile
78 ? SDL_GL_CONTEXT_PROFILE_COMPATIBILITY
79 : SDL_GL_CONTEXT_PROFILE_CORE;
80
81 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
82 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
83 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile);
84 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
85 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
86 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
87 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
88 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
89 SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
90
91 std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname,
92 Common::g_scm_branch, Common::g_scm_desc);
93 render_window =
94 SDL_CreateWindow(window_title.c_str(),
95 SDL_WINDOWPOS_UNDEFINED, // x position
96 SDL_WINDOWPOS_UNDEFINED, // y position
97 Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
98 SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
99
100 if (render_window == nullptr) {
101 LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError());
102 exit(1);
103 }
104
105 if (fullscreen) {
106 Fullscreen();
107 }
108 gl_context = SDL_GL_CreateContext(render_window);
109
110 if (gl_context == nullptr) {
111 LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError());
112 exit(1);
113 }
114
115 if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
116 LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError());
117 exit(1);
118 }
119
120 if (!SupportsRequiredGLExtensions()) {
121 LOG_CRITICAL(Frontend, "GPU does not support all required OpenGL extensions! Exiting...");
122 exit(1);
123 }
124
125 OnResize();
126 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
127 SDL_PumpEvents();
128 SDL_GL_SetSwapInterval(false);
129 LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
130 Common::g_scm_desc);
131 Settings::LogSettings();
132
133 DoneCurrent();
134}
135
136EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
137 SDL_GL_DeleteContext(gl_context);
138}
139
140void EmuWindow_SDL2_GL::SwapBuffers() {
141 SDL_GL_SwapWindow(render_window);
142}
143
144void EmuWindow_SDL2_GL::MakeCurrent() {
145 SDL_GL_MakeCurrent(render_window, gl_context);
146}
147
148void EmuWindow_SDL2_GL::DoneCurrent() {
149 SDL_GL_MakeCurrent(render_window, nullptr);
150}
151
152std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
153 return std::make_unique<SDLGLContext>();
154}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
new file mode 100644
index 000000000..630deba93
--- /dev/null
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
@@ -0,0 +1,34 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/emu_window.h"
9#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
10
11class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 {
12public:
13 explicit EmuWindow_SDL2_GL(bool fullscreen);
14 ~EmuWindow_SDL2_GL();
15
16 /// Swap buffers to display the next frame
17 void SwapBuffers() override;
18
19 /// Makes the graphics context current for the caller thread
20 void MakeCurrent() override;
21
22 /// Releases the GL context from the caller thread
23 void DoneCurrent() override;
24
25 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
26
27private:
28 /// Whether the GPU and driver supports the OpenGL extension required
29 bool SupportsRequiredGLExtensions();
30
31 using SDL_GLContext = void*;
32 /// The OpenGL context associated with the window
33 SDL_GLContext gl_context;
34};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index a1d7879b1..5d9442646 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -31,6 +31,7 @@
31#include "video_core/renderer_base.h" 31#include "video_core/renderer_base.h"
32#include "yuzu_cmd/config.h" 32#include "yuzu_cmd/config.h"
33#include "yuzu_cmd/emu_window/emu_window_sdl2.h" 33#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
34#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
34 35
35#include "core/file_sys/registered_cache.h" 36#include "core/file_sys/registered_cache.h"
36 37
@@ -173,7 +174,7 @@ int main(int argc, char** argv) {
173 Settings::values.use_gdbstub = use_gdbstub; 174 Settings::values.use_gdbstub = use_gdbstub;
174 Settings::Apply(); 175 Settings::Apply();
175 176
176 std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)}; 177 std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2_GL>(fullscreen)};
177 178
178 if (!Settings::values.use_multi_core) { 179 if (!Settings::values.use_multi_core) {
179 // Single core mode must acquire OpenGL context for entire emulation session 180 // Single core mode must acquire OpenGL context for entire emulation session
@@ -222,6 +223,7 @@ int main(int argc, char** argv) {
222 223
223 system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDL"); 224 system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDL");
224 225
226 emu_window->MakeCurrent();
225 system.Renderer().Rasterizer().LoadDiskResources(); 227 system.Renderer().Rasterizer().LoadDiskResources();
226 228
227 while (emu_window->IsOpen()) { 229 while (emu_window->IsOpen()) {