summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--CMakeModules/FindSimpleIni.cmake19
-rw-r--r--externals/CMakeLists.txt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt38
-rw-r--r--src/common/settings.h2
-rw-r--r--src/core/frontend/emu_window.h10
-rw-r--r--src/core/hid/emulated_controller.cpp50
-rw-r--r--src/core/hid/emulated_controller.h1
-rw-r--r--src/core/hle/service/am/applets/applet_cabinet.cpp5
-rw-r--r--src/core/hle/service/mii/types/ver3_store_data.cpp2
-rw-r--r--src/core/hle/service/nfc/common/device.cpp68
-rw-r--r--src/core/hle/service/nfc/common/device.h1
-rw-r--r--src/core/hle/service/set/set_sys.cpp66
-rw-r--r--src/core/hle/service/set/set_sys.h24
-rw-r--r--src/core/hle/service/time/clock_types.h5
-rw-r--r--src/frontend_common/CMakeLists.txt2
-rw-r--r--src/frontend_common/config.h1
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp9
-rw-r--r--src/shader_recompiler/backend/glsl/glsl_emit_context.cpp5
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp4
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate_program.cpp2
-rw-r--r--src/shader_recompiler/host_translate_info.h1
-rw-r--r--src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp13
-rw-r--r--src/shader_recompiler/ir_opt/passes.h2
-rw-r--r--src/shader_recompiler/profile.h2
-rw-r--r--src/video_core/CMakeLists.txt1
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h117
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h4
-rw-r--r--src/video_core/buffer_cache/usage_tracker.h79
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp7
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h21
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp40
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h23
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.cpp29
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.h14
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp28
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h21
-rw-r--r--src/video_core/renderer_vulkan/vk_smaa.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp18
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h1
-rw-r--r--src/video_core/texture_cache/slot_vector.h4
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp3
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h4
-rw-r--r--src/yuzu/bootmanager.cpp56
-rw-r--r--src/yuzu/bootmanager.h6
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.ui86
-rw-r--r--src/yuzu/main.cpp83
-rw-r--r--src/yuzu/main.h5
52 files changed, 715 insertions, 287 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e5cac8fe9..ec7975b87 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -290,6 +290,7 @@ find_package(lz4 REQUIRED)
290find_package(nlohmann_json 3.8 REQUIRED) 290find_package(nlohmann_json 3.8 REQUIRED)
291find_package(Opus 1.3 MODULE) 291find_package(Opus 1.3 MODULE)
292find_package(RenderDoc MODULE) 292find_package(RenderDoc MODULE)
293find_package(SimpleIni MODULE)
293find_package(stb MODULE) 294find_package(stb MODULE)
294find_package(VulkanMemoryAllocator CONFIG) 295find_package(VulkanMemoryAllocator CONFIG)
295find_package(ZLIB 1.2 REQUIRED) 296find_package(ZLIB 1.2 REQUIRED)
diff --git a/CMakeModules/FindSimpleIni.cmake b/CMakeModules/FindSimpleIni.cmake
new file mode 100644
index 000000000..ce75d7690
--- /dev/null
+++ b/CMakeModules/FindSimpleIni.cmake
@@ -0,0 +1,19 @@
1# SPDX-FileCopyrightText: 2023 Alexandre Bouvier <contact@amb.tf>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
4
5find_path(SimpleIni_INCLUDE_DIR SimpleIni.h)
6
7include(FindPackageHandleStandardArgs)
8find_package_handle_standard_args(SimpleIni
9 REQUIRED_VARS SimpleIni_INCLUDE_DIR
10)
11
12if (SimpleIni_FOUND AND NOT TARGET SimpleIni::SimpleIni)
13 add_library(SimpleIni::SimpleIni INTERFACE IMPORTED)
14 set_target_properties(SimpleIni::SimpleIni PROPERTIES
15 INTERFACE_INCLUDE_DIRECTORIES "${SimpleIni_INCLUDE_DIR}"
16 )
17endif()
18
19mark_as_advanced(SimpleIni_INCLUDE_DIR)
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 406277fd7..070151bec 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -296,4 +296,6 @@ if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client)
296endif() 296endif()
297 297
298# SimpleIni 298# SimpleIni
299add_subdirectory(simpleini) 299if (NOT TARGET SimpleIni::SimpleIni)
300 add_subdirectory(simpleini)
301endif()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
index 47bde5081..e63382e1d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
@@ -27,6 +27,8 @@ object InputHandler {
27 0x054C -> getInputDS5ButtonKey(event.keyCode) 27 0x054C -> getInputDS5ButtonKey(event.keyCode)
28 0x057E -> getInputJoyconButtonKey(event.keyCode) 28 0x057E -> getInputJoyconButtonKey(event.keyCode)
29 0x1532 -> getInputRazerButtonKey(event.keyCode) 29 0x1532 -> getInputRazerButtonKey(event.keyCode)
30 0x3537 -> getInputRedmagicButtonKey(event.keyCode)
31 0x358A -> getInputBackboneLabsButtonKey(event.keyCode)
30 else -> getInputGenericButtonKey(event.keyCode) 32 else -> getInputGenericButtonKey(event.keyCode)
31 } 33 }
32 34
@@ -227,6 +229,42 @@ object InputHandler {
227 } 229 }
228 } 230 }
229 231
232 private fun getInputRedmagicButtonKey(key: Int): Int {
233 return when (key) {
234 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
235 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
236 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
237 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
238 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
239 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
240 KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
241 KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
242 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
243 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
244 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
245 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
246 else -> -1
247 }
248 }
249
250 private fun getInputBackboneLabsButtonKey(key: Int): Int {
251 return when (key) {
252 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
253 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
254 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
255 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
256 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
257 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
258 KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
259 KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
260 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
261 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
262 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
263 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
264 else -> -1
265 }
266 }
267
230 private fun getInputGenericButtonKey(key: Int): Int { 268 private fun getInputGenericButtonKey(key: Int): Int {
231 return when (key) { 269 return when (key) {
232 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A 270 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A
diff --git a/src/common/settings.h b/src/common/settings.h
index 491f0d3e0..6425cd98f 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -384,6 +384,8 @@ struct Values {
384 Category::RendererDebug}; 384 Category::RendererDebug};
385 // TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control 385 // TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control
386 bool renderer_amdvlk_depth_bias_workaround{}; 386 bool renderer_amdvlk_depth_bias_workaround{};
387 Setting<bool> disable_buffer_reorder{linkage, false, "disable_buffer_reorder",
388 Category::RendererDebug};
387 389
388 // System 390 // System
389 SwitchableSetting<Language, true> language_index{linkage, 391 SwitchableSetting<Language, true> language_index{linkage,
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index a72df034e..c7b48a58d 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -167,6 +167,11 @@ protected:
167 */ 167 */
168 std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const; 168 std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const;
169 169
170 /**
171 * Clip the provided coordinates to be inside the touchscreen area.
172 */
173 std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
174
170 WindowSystemInfo window_info; 175 WindowSystemInfo window_info;
171 176
172 bool strict_context_required = false; 177 bool strict_context_required = false;
@@ -181,11 +186,6 @@ private:
181 // By default, ignore this request and do nothing. 186 // By default, ignore this request and do nothing.
182 } 187 }
183 188
184 /**
185 * Clip the provided coordinates to be inside the touchscreen area.
186 */
187 std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
188
189 Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout 189 Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout
190 190
191 u32 client_area_width; ///< Current client width, should be set by window impl. 191 u32 client_area_width; ///< Current client width, should be set by window impl.
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index a22015ada..a6e681e15 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -509,9 +509,11 @@ void EmulatedController::ReloadInput() {
509 }); 509 });
510 } 510 }
511 turbo_button_state = 0; 511 turbo_button_state = 0;
512 is_initalized = true;
512} 513}
513 514
514void EmulatedController::UnloadInput() { 515void EmulatedController::UnloadInput() {
516 is_initalized = false;
515 for (auto& button : button_devices) { 517 for (auto& button : button_devices) {
516 button.reset(); 518 button.reset();
517 } 519 }
@@ -1207,6 +1209,9 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
1207} 1209}
1208 1210
1209bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { 1211bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
1212 if (!is_initalized) {
1213 return false;
1214 }
1210 if (device_index >= output_devices.size()) { 1215 if (device_index >= output_devices.size()) {
1211 return false; 1216 return false;
1212 } 1217 }
@@ -1242,6 +1247,10 @@ bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
1242 const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type); 1247 const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
1243 const auto& player = Settings::values.players.GetValue()[player_index]; 1248 const auto& player = Settings::values.players.GetValue()[player_index];
1244 1249
1250 if (!is_initalized) {
1251 return false;
1252 }
1253
1245 if (!player.vibration_enabled) { 1254 if (!player.vibration_enabled) {
1246 return false; 1255 return false;
1247 } 1256 }
@@ -1261,6 +1270,10 @@ Common::Input::DriverResult EmulatedController::SetPollingMode(
1261 EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) { 1270 EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) {
1262 LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index); 1271 LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index);
1263 1272
1273 if (!is_initalized) {
1274 return Common::Input::DriverResult::InvalidHandle;
1275 }
1276
1264 auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)]; 1277 auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)];
1265 auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1278 auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1266 auto& nfc_output_device = output_devices[3]; 1279 auto& nfc_output_device = output_devices[3];
@@ -1306,6 +1319,10 @@ bool EmulatedController::SetCameraFormat(
1306 Core::IrSensor::ImageTransferProcessorFormat camera_format) { 1319 Core::IrSensor::ImageTransferProcessorFormat camera_format) {
1307 LOG_INFO(Service_HID, "Set camera format {}", camera_format); 1320 LOG_INFO(Service_HID, "Set camera format {}", camera_format);
1308 1321
1322 if (!is_initalized) {
1323 return false;
1324 }
1325
1309 auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1326 auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1310 auto& camera_output_device = output_devices[2]; 1327 auto& camera_output_device = output_devices[2];
1311 1328
@@ -1329,6 +1346,11 @@ void EmulatedController::SetRingParam(Common::ParamPackage param) {
1329} 1346}
1330 1347
1331bool EmulatedController::HasNfc() const { 1348bool EmulatedController::HasNfc() const {
1349
1350 if (!is_initalized) {
1351 return false;
1352 }
1353
1332 const auto& nfc_output_device = output_devices[3]; 1354 const auto& nfc_output_device = output_devices[3];
1333 1355
1334 switch (npad_type) { 1356 switch (npad_type) {
@@ -1366,6 +1388,10 @@ bool EmulatedController::RemoveNfcHandle() {
1366} 1388}
1367 1389
1368bool EmulatedController::StartNfcPolling() { 1390bool EmulatedController::StartNfcPolling() {
1391 if (!is_initalized) {
1392 return false;
1393 }
1394
1369 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1395 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1370 auto& nfc_virtual_output_device = output_devices[3]; 1396 auto& nfc_virtual_output_device = output_devices[3];
1371 1397
@@ -1377,6 +1403,10 @@ bool EmulatedController::StartNfcPolling() {
1377} 1403}
1378 1404
1379bool EmulatedController::StopNfcPolling() { 1405bool EmulatedController::StopNfcPolling() {
1406 if (!is_initalized) {
1407 return false;
1408 }
1409
1380 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1410 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1381 auto& nfc_virtual_output_device = output_devices[3]; 1411 auto& nfc_virtual_output_device = output_devices[3];
1382 1412
@@ -1388,6 +1418,10 @@ bool EmulatedController::StopNfcPolling() {
1388} 1418}
1389 1419
1390bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) { 1420bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
1421 if (!is_initalized) {
1422 return false;
1423 }
1424
1391 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1425 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1392 auto& nfc_virtual_output_device = output_devices[3]; 1426 auto& nfc_virtual_output_device = output_devices[3];
1393 1427
@@ -1400,6 +1434,10 @@ bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
1400 1434
1401bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request, 1435bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request,
1402 Common::Input::MifareRequest& out_data) { 1436 Common::Input::MifareRequest& out_data) {
1437 if (!is_initalized) {
1438 return false;
1439 }
1440
1403 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1441 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1404 auto& nfc_virtual_output_device = output_devices[3]; 1442 auto& nfc_virtual_output_device = output_devices[3];
1405 1443
@@ -1412,6 +1450,10 @@ bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& requ
1412} 1450}
1413 1451
1414bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) { 1452bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) {
1453 if (!is_initalized) {
1454 return false;
1455 }
1456
1415 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1457 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1416 auto& nfc_virtual_output_device = output_devices[3]; 1458 auto& nfc_virtual_output_device = output_devices[3];
1417 1459
@@ -1423,6 +1465,10 @@ bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& req
1423} 1465}
1424 1466
1425bool EmulatedController::WriteNfc(const std::vector<u8>& data) { 1467bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
1468 if (!is_initalized) {
1469 return false;
1470 }
1471
1426 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1472 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1427 auto& nfc_virtual_output_device = output_devices[3]; 1473 auto& nfc_virtual_output_device = output_devices[3];
1428 1474
@@ -1434,6 +1480,10 @@ bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
1434} 1480}
1435 1481
1436void EmulatedController::SetLedPattern() { 1482void EmulatedController::SetLedPattern() {
1483 if (!is_initalized) {
1484 return;
1485 }
1486
1437 for (auto& device : output_devices) { 1487 for (auto& device : output_devices) {
1438 if (!device) { 1488 if (!device) {
1439 continue; 1489 continue;
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index ea18c2343..d6e20ab66 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -559,6 +559,7 @@ private:
559 NpadStyleTag supported_style_tag{NpadStyleSet::All}; 559 NpadStyleTag supported_style_tag{NpadStyleSet::All};
560 bool is_connected{false}; 560 bool is_connected{false};
561 bool is_configuring{false}; 561 bool is_configuring{false};
562 bool is_initalized{false};
562 bool system_buttons_enabled{true}; 563 bool system_buttons_enabled{true};
563 f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard}; 564 f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
564 u32 turbo_button_state{0}; 565 u32 turbo_button_state{0};
diff --git a/src/core/hle/service/am/applets/applet_cabinet.cpp b/src/core/hle/service/am/applets/applet_cabinet.cpp
index b379dadeb..3906c0fa4 100644
--- a/src/core/hle/service/am/applets/applet_cabinet.cpp
+++ b/src/core/hle/service/am/applets/applet_cabinet.cpp
@@ -122,7 +122,8 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
122 Service::NFP::RegisterInfoPrivate register_info{}; 122 Service::NFP::RegisterInfoPrivate register_info{};
123 std::memcpy(register_info.amiibo_name.data(), amiibo_name.data(), 123 std::memcpy(register_info.amiibo_name.data(), amiibo_name.data(),
124 std::min(amiibo_name.size(), register_info.amiibo_name.size() - 1)); 124 std::min(amiibo_name.size(), register_info.amiibo_name.size() - 1));
125 125 register_info.mii_store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
126 register_info.mii_store_data.SetNickname({u'y', u'u', u'z', u'u'});
126 nfp_device->SetRegisterInfoPrivate(register_info); 127 nfp_device->SetRegisterInfoPrivate(register_info);
127 break; 128 break;
128 } 129 }
@@ -130,7 +131,7 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
130 nfp_device->DeleteApplicationArea(); 131 nfp_device->DeleteApplicationArea();
131 break; 132 break;
132 case Service::NFP::CabinetMode::StartRestorer: 133 case Service::NFP::CabinetMode::StartRestorer:
133 nfp_device->RestoreAmiibo(); 134 nfp_device->Restore();
134 break; 135 break;
135 case Service::NFP::CabinetMode::StartFormatter: 136 case Service::NFP::CabinetMode::StartFormatter:
136 nfp_device->Format(); 137 nfp_device->Format();
diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp
index a019cc9f7..c27646fcf 100644
--- a/src/core/hle/service/mii/types/ver3_store_data.cpp
+++ b/src/core/hle/service/mii/types/ver3_store_data.cpp
@@ -98,7 +98,7 @@ void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
98} 98}
99 99
100void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) { 100void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
101 version = 1; 101 version = 3;
102 mii_information.gender.Assign(static_cast<u8>(store_data.GetGender())); 102 mii_information.gender.Assign(static_cast<u8>(store_data.GetGender()));
103 mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor())); 103 mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor()));
104 height = store_data.GetHeight(); 104 height = store_data.GetHeight();
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index e7a00deb3..47516f883 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -401,6 +401,12 @@ Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& time
401} 401}
402 402
403Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target_) { 403Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target_) {
404 bool is_corrupted = false;
405
406 if (model_type != NFP::ModelType::Amiibo) {
407 return ResultInvalidArgument;
408 }
409
404 if (device_state != DeviceState::TagFound) { 410 if (device_state != DeviceState::TagFound) {
405 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); 411 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
406 return ResultWrongDeviceState; 412 return ResultWrongDeviceState;
@@ -420,26 +426,32 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
420 if (is_plain_amiibo) { 426 if (is_plain_amiibo) {
421 std::vector<u8> data(sizeof(NFP::NTAG215File)); 427 std::vector<u8> data(sizeof(NFP::NTAG215File));
422 memcpy(data.data(), &tag_data, sizeof(tag_data)); 428 memcpy(data.data(), &tag_data, sizeof(tag_data));
423 WriteBackupData(tag_data.uid, data); 429 }
424 430
425 device_state = DeviceState::TagMounted; 431 if (!is_plain_amiibo && !NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
426 mount_target = mount_target_; 432 LOG_ERROR(Service_NFP, "Can't decode amiibo");
427 return ResultSuccess; 433 is_corrupted = true;
428 } 434 }
429 435
430 if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { 436 if (tag_data.settings.settings.amiibo_initialized && !tag_data.owner_mii.IsValid()) {
431 bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess(); 437 LOG_ERROR(Service_NFP, "Invalid mii data");
432 LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup); 438 is_corrupted = true;
433 return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
434 } 439 }
435 440
436 std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); 441 if (!is_corrupted) {
437 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); 442 std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
438 WriteBackupData(encrypted_tag_data.uuid, data); 443 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
444 WriteBackupData(encrypted_tag_data.uuid, data);
445 }
439 446
440 device_state = DeviceState::TagMounted; 447 device_state = DeviceState::TagMounted;
441 mount_target = mount_target_; 448 mount_target = mount_target_;
442 449
450 if (is_corrupted) {
451 bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess();
452 return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
453 }
454
443 return ResultSuccess; 455 return ResultSuccess;
444} 456}
445 457
@@ -606,6 +618,17 @@ Result NfcDevice::Restore() {
606 } 618 }
607 } 619 }
608 620
621 // Restore mii data in case is corrupted by previous instances of yuzu
622 if (tag_data.settings.settings.amiibo_initialized && !tag_data.owner_mii.IsValid()) {
623 LOG_ERROR(Service_NFP, "Regenerating mii data");
624 Mii::StoreData new_mii{};
625 new_mii.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
626 new_mii.SetNickname({u'y', u'u', u'z', u'u', u'\0'});
627
628 tag_data.owner_mii.BuildFromStoreData(new_mii);
629 tag_data.mii_extension.SetFromStoreData(new_mii);
630 }
631
609 // Overwrite tag contents with backup and mount the tag 632 // Overwrite tag contents with backup and mount the tag
610 tag_data = temporary_tag_data; 633 tag_data = temporary_tag_data;
611 encrypted_tag_data = temporary_encrypted_tag_data; 634 encrypted_tag_data = temporary_encrypted_tag_data;
@@ -851,25 +874,6 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
851 return Flush(); 874 return Flush();
852} 875}
853 876
854Result NfcDevice::RestoreAmiibo() {
855 if (device_state != DeviceState::TagMounted) {
856 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
857 if (device_state == DeviceState::TagRemoved) {
858 return ResultTagRemoved;
859 }
860 return ResultWrongDeviceState;
861 }
862
863 if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
864 LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
865 return ResultWrongDeviceState;
866 }
867
868 // TODO: Load amiibo from backup on system
869 LOG_ERROR(Service_NFP, "Not Implemented");
870 return ResultSuccess;
871}
872
873Result NfcDevice::Format() { 877Result NfcDevice::Format() {
874 Result result = ResultSuccess; 878 Result result = ResultSuccess;
875 879
@@ -877,7 +881,9 @@ Result NfcDevice::Format() {
877 result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All); 881 result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All);
878 } 882 }
879 883
880 if (result.IsError()) { 884 // We are formatting all data. Corruption is not an issue.
885 if (result.IsError() &&
886 (result != ResultCorruptedData && result != ResultCorruptedDataWithBackup)) {
881 return result; 887 return result;
882 } 888 }
883 889
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h
index 0ed1ff34c..d8efe25ec 100644
--- a/src/core/hle/service/nfc/common/device.h
+++ b/src/core/hle/service/nfc/common/device.h
@@ -68,7 +68,6 @@ public:
68 68
69 Result DeleteRegisterInfo(); 69 Result DeleteRegisterInfo();
70 Result SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info); 70 Result SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info);
71 Result RestoreAmiibo();
72 Result Format(); 71 Result Format();
73 72
74 Result OpenApplicationArea(u32 access_id); 73 Result OpenApplicationArea(u32 access_id);
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index 19c667b42..f5edfdc8b 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -19,19 +19,8 @@
19 19
20namespace Service::Set { 20namespace Service::Set {
21 21
22namespace { 22Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
23constexpr u64 SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET = 0x05; 23 GetFirmwareVersionType type) {
24
25enum class GetFirmwareVersionType {
26 Version1,
27 Version2,
28};
29
30void GetFirmwareVersionImpl(Core::System& system, HLERequestContext& ctx,
31 GetFirmwareVersionType type) {
32 ASSERT_MSG(ctx.GetWriteBufferSize() == 0x100,
33 "FirmwareVersion output buffer must be 0x100 bytes in size!");
34
35 constexpr u64 FirmwareVersionSystemDataId = 0x0100000000000809; 24 constexpr u64 FirmwareVersionSystemDataId = 0x0100000000000809;
36 auto& fsc = system.GetFileSystemController(); 25 auto& fsc = system.GetFileSystemController();
37 26
@@ -52,39 +41,34 @@ void GetFirmwareVersionImpl(Core::System& system, HLERequestContext& ctx,
52 FileSys::SystemArchive::SynthesizeSystemArchive(FirmwareVersionSystemDataId)); 41 FileSys::SystemArchive::SynthesizeSystemArchive(FirmwareVersionSystemDataId));
53 } 42 }
54 43
55 const auto early_exit_failure = [&ctx](std::string_view desc, Result code) { 44 const auto early_exit_failure = [](std::string_view desc, Result code) {
56 LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).", 45 LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).",
57 desc); 46 desc);
58 IPC::ResponseBuilder rb{ctx, 2}; 47 return code;
59 rb.Push(code);
60 }; 48 };
61 49
62 const auto ver_file = romfs->GetFile("file"); 50 const auto ver_file = romfs->GetFile("file");
63 if (ver_file == nullptr) { 51 if (ver_file == nullptr) {
64 early_exit_failure("The system version archive didn't contain the file 'file'.", 52 return early_exit_failure("The system version archive didn't contain the file 'file'.",
65 FileSys::ERROR_INVALID_ARGUMENT); 53 FileSys::ERROR_INVALID_ARGUMENT);
66 return;
67 } 54 }
68 55
69 auto data = ver_file->ReadAllBytes(); 56 auto data = ver_file->ReadAllBytes();
70 if (data.size() != 0x100) { 57 if (data.size() != sizeof(FirmwareVersionFormat)) {
71 early_exit_failure("The system version file 'file' was not the correct size.", 58 return early_exit_failure("The system version file 'file' was not the correct size.",
72 FileSys::ERROR_OUT_OF_BOUNDS); 59 FileSys::ERROR_OUT_OF_BOUNDS);
73 return;
74 } 60 }
75 61
62 std::memcpy(&out_firmware, data.data(), sizeof(FirmwareVersionFormat));
63
76 // If the command is GetFirmwareVersion (as opposed to GetFirmwareVersion2), hardware will 64 // If the command is GetFirmwareVersion (as opposed to GetFirmwareVersion2), hardware will
77 // zero out the REVISION_MINOR field. 65 // zero out the REVISION_MINOR field.
78 if (type == GetFirmwareVersionType::Version1) { 66 if (type == GetFirmwareVersionType::Version1) {
79 data[SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET] = 0; 67 out_firmware.revision_minor = 0;
80 } 68 }
81 69
82 ctx.WriteBuffer(data); 70 return ResultSuccess;
83
84 IPC::ResponseBuilder rb{ctx, 2};
85 rb.Push(ResultSuccess);
86} 71}
87} // Anonymous namespace
88 72
89void SET_SYS::SetLanguageCode(HLERequestContext& ctx) { 73void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
90 IPC::RequestParser rp{ctx}; 74 IPC::RequestParser rp{ctx};
@@ -98,12 +82,32 @@ void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
98 82
99void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) { 83void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) {
100 LOG_DEBUG(Service_SET, "called"); 84 LOG_DEBUG(Service_SET, "called");
101 GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version1); 85
86 FirmwareVersionFormat firmware_data{};
87 const auto result =
88 GetFirmwareVersionImpl(firmware_data, system, GetFirmwareVersionType::Version1);
89
90 if (result.IsSuccess()) {
91 ctx.WriteBuffer(firmware_data);
92 }
93
94 IPC::ResponseBuilder rb{ctx, 2};
95 rb.Push(result);
102} 96}
103 97
104void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) { 98void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) {
105 LOG_DEBUG(Service_SET, "called"); 99 LOG_DEBUG(Service_SET, "called");
106 GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version2); 100
101 FirmwareVersionFormat firmware_data{};
102 const auto result =
103 GetFirmwareVersionImpl(firmware_data, system, GetFirmwareVersionType::Version2);
104
105 if (result.IsSuccess()) {
106 ctx.WriteBuffer(firmware_data);
107 }
108
109 IPC::ResponseBuilder rb{ctx, 2};
110 rb.Push(result);
107} 111}
108 112
109void SET_SYS::GetAccountSettings(HLERequestContext& ctx) { 113void SET_SYS::GetAccountSettings(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index 93023c6dd..5f770fd32 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -4,6 +4,7 @@
4#pragma once 4#pragma once
5 5
6#include "common/uuid.h" 6#include "common/uuid.h"
7#include "core/hle/result.h"
7#include "core/hle/service/service.h" 8#include "core/hle/service/service.h"
8#include "core/hle/service/time/clock_types.h" 9#include "core/hle/service/time/clock_types.h"
9 10
@@ -12,6 +13,29 @@ class System;
12} 13}
13 14
14namespace Service::Set { 15namespace Service::Set {
16enum class LanguageCode : u64;
17enum class GetFirmwareVersionType {
18 Version1,
19 Version2,
20};
21
22struct FirmwareVersionFormat {
23 u8 major;
24 u8 minor;
25 u8 micro;
26 INSERT_PADDING_BYTES(1);
27 u8 revision_major;
28 u8 revision_minor;
29 INSERT_PADDING_BYTES(2);
30 std::array<char, 0x20> platform;
31 std::array<u8, 0x40> version_hash;
32 std::array<char, 0x18> display_version;
33 std::array<char, 0x80> display_title;
34};
35static_assert(sizeof(FirmwareVersionFormat) == 0x100, "FirmwareVersionFormat is an invalid size");
36
37Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
38 GetFirmwareVersionType type);
15 39
16class SET_SYS final : public ServiceFramework<SET_SYS> { 40class SET_SYS final : public ServiceFramework<SET_SYS> {
17public: 41public:
diff --git a/src/core/hle/service/time/clock_types.h b/src/core/hle/service/time/clock_types.h
index 9fc01ea90..7149fffeb 100644
--- a/src/core/hle/service/time/clock_types.h
+++ b/src/core/hle/service/time/clock_types.h
@@ -11,6 +11,11 @@
11#include "core/hle/service/time/errors.h" 11#include "core/hle/service/time/errors.h"
12#include "core/hle/service/time/time_zone_types.h" 12#include "core/hle/service/time/time_zone_types.h"
13 13
14// Defined by WinBase.h on Windows
15#ifdef GetCurrentTime
16#undef GetCurrentTime
17#endif
18
14namespace Service::Time::Clock { 19namespace Service::Time::Clock {
15 20
16enum class TimeType : u8 { 21enum class TimeType : u8 {
diff --git a/src/frontend_common/CMakeLists.txt b/src/frontend_common/CMakeLists.txt
index 1537271fc..22e9337c4 100644
--- a/src/frontend_common/CMakeLists.txt
+++ b/src/frontend_common/CMakeLists.txt
@@ -7,4 +7,4 @@ add_library(frontend_common STATIC
7) 7)
8 8
9create_target_directory_groups(frontend_common) 9create_target_directory_groups(frontend_common)
10target_link_libraries(frontend_common PUBLIC core SimpleIni PRIVATE common Boost::headers) 10target_link_libraries(frontend_common PUBLIC core SimpleIni::SimpleIni PRIVATE common Boost::headers)
diff --git a/src/frontend_common/config.h b/src/frontend_common/config.h
index 20a1a8056..b3812af17 100644
--- a/src/frontend_common/config.h
+++ b/src/frontend_common/config.h
@@ -7,6 +7,7 @@
7#include <string> 7#include <string>
8#include "common/settings.h" 8#include "common/settings.h"
9 9
10#define SI_NO_CONVERSION
10#include <SimpleIni.h> 11#include <SimpleIni.h>
11#include <boost/algorithm/string/replace.hpp> 12#include <boost/algorithm/string/replace.hpp>
12 13
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
index 2705ab140..9319ea007 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
@@ -5,6 +5,7 @@
5#include "shader_recompiler/backend/glasm/glasm_emit_context.h" 5#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
6#include "shader_recompiler/frontend/ir/program.h" 6#include "shader_recompiler/frontend/ir/program.h"
7#include "shader_recompiler/frontend/ir/value.h" 7#include "shader_recompiler/frontend/ir/value.h"
8#include "shader_recompiler/profile.h"
8#include "shader_recompiler/runtime_info.h" 9#include "shader_recompiler/runtime_info.h"
9 10
10namespace Shader::Backend::GLASM { 11namespace Shader::Backend::GLASM {
@@ -35,7 +36,9 @@ void GlobalStorageOp(EmitContext& ctx, Register address, bool pointer_based, std
35 continue; 36 continue;
36 } 37 }
37 const auto& ssbo{ctx.info.storage_buffers_descriptors[index]}; 38 const auto& ssbo{ctx.info.storage_buffers_descriptors[index]};
38 ctx.Add("LDC.U64 DC.x,c{}[{}];" // ssbo_addr 39 const u64 ssbo_align_mask{~(ctx.profile.min_ssbo_alignment - 1U)};
40 ctx.Add("LDC.U64 DC.x,c{}[{}];" // unaligned_ssbo_addr
41 "AND.U64 DC.x,DC.x,{};" // ssbo_addr = unaligned_ssbo_addr & ssbo_align_mask
39 "LDC.U32 RC.x,c{}[{}];" // ssbo_size_u32 42 "LDC.U32 RC.x,c{}[{}];" // ssbo_size_u32
40 "CVT.U64.U32 DC.y,RC.x;" // ssbo_size = ssbo_size_u32 43 "CVT.U64.U32 DC.y,RC.x;" // ssbo_size = ssbo_size_u32
41 "ADD.U64 DC.y,DC.y,DC.x;" // ssbo_end = ssbo_addr + ssbo_size 44 "ADD.U64 DC.y,DC.y,DC.x;" // ssbo_end = ssbo_addr + ssbo_size
@@ -44,8 +47,8 @@ void GlobalStorageOp(EmitContext& ctx, Register address, bool pointer_based, std
44 "AND.U.CC RC.x,RC.x,RC.y;" // cond = a && b 47 "AND.U.CC RC.x,RC.x,RC.y;" // cond = a && b
45 "IF NE.x;" // if cond 48 "IF NE.x;" // if cond
46 "SUB.U64 DC.x,{}.x,DC.x;", // offset = input_addr - ssbo_addr 49 "SUB.U64 DC.x,{}.x,DC.x;", // offset = input_addr - ssbo_addr
47 ssbo.cbuf_index, ssbo.cbuf_offset, ssbo.cbuf_index, ssbo.cbuf_offset + 8, address, 50 ssbo.cbuf_index, ssbo.cbuf_offset, ssbo_align_mask, ssbo.cbuf_index,
48 address, address); 51 ssbo.cbuf_offset + 8, address, address, address);
49 if (pointer_based) { 52 if (pointer_based) {
50 ctx.Add("PK64.U DC.y,c[{}];" // host_ssbo = cbuf 53 ctx.Add("PK64.U DC.y,c[{}];" // host_ssbo = cbuf
51 "ADD.U64 DC.x,DC.x,DC.y;" // host_addr = host_ssbo + offset 54 "ADD.U64 DC.x,DC.x,DC.y;" // host_addr = host_ssbo + offset
diff --git a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
index 9ff4028c2..fd9a99449 100644
--- a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
+++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
@@ -601,7 +601,10 @@ std::string EmitContext::DefineGlobalMemoryFunctions() {
601 addr_xy[i] = fmt::format("ftou({}[{}].{})", cbuf, addr_loc / 16, Swizzle(addr_loc)); 601 addr_xy[i] = fmt::format("ftou({}[{}].{})", cbuf, addr_loc / 16, Swizzle(addr_loc));
602 size_xy[i] = fmt::format("ftou({}[{}].{})", cbuf, size_loc / 16, Swizzle(size_loc)); 602 size_xy[i] = fmt::format("ftou({}[{}].{})", cbuf, size_loc / 16, Swizzle(size_loc));
603 } 603 }
604 const auto addr_pack{fmt::format("packUint2x32(uvec2({},{}))", addr_xy[0], addr_xy[1])}; 604 const u32 ssbo_align_mask{~(static_cast<u32>(profile.min_ssbo_alignment) - 1U)};
605 const auto aligned_low_addr{fmt::format("{}&{}", addr_xy[0], ssbo_align_mask)};
606 const auto aligned_addr{fmt::format("uvec2({},{})", aligned_low_addr, addr_xy[1])};
607 const auto addr_pack{fmt::format("packUint2x32({})", aligned_addr)};
605 const auto addr_statment{fmt::format("uint64_t {}={};", ssbo_addr, addr_pack)}; 608 const auto addr_statment{fmt::format("uint64_t {}={};", ssbo_addr, addr_pack)};
606 func += addr_statment; 609 func += addr_statment;
607 610
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 57df6fc34..3350f1f85 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -891,7 +891,9 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
891 const Id ssbo_size_pointer{OpAccessChain(uniform_types.U32, cbufs[ssbo.cbuf_index].U32, 891 const Id ssbo_size_pointer{OpAccessChain(uniform_types.U32, cbufs[ssbo.cbuf_index].U32,
892 zero, ssbo_size_cbuf_offset)}; 892 zero, ssbo_size_cbuf_offset)};
893 893
894 const Id ssbo_addr{OpBitcast(U64, OpLoad(U32[2], ssbo_addr_pointer))}; 894 const u64 ssbo_align_mask{~(profile.min_ssbo_alignment - 1U)};
895 const Id unaligned_addr{OpBitcast(U64, OpLoad(U32[2], ssbo_addr_pointer))};
896 const Id ssbo_addr{OpBitwiseAnd(U64, unaligned_addr, Constant(U64, ssbo_align_mask))};
895 const Id ssbo_size{OpUConvert(U64, OpLoad(U32[1], ssbo_size_pointer))}; 897 const Id ssbo_size{OpUConvert(U64, OpLoad(U32[1], ssbo_size_pointer))};
896 const Id ssbo_end{OpIAdd(U64, ssbo_addr, ssbo_size)}; 898 const Id ssbo_end{OpIAdd(U64, ssbo_addr, ssbo_size)};
897 const Id cond{OpLogicalAnd(U1, OpUGreaterThanEqual(U1, addr, ssbo_addr), 899 const Id cond{OpLogicalAnd(U1, OpUGreaterThanEqual(U1, addr, ssbo_addr),
diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
index 8fac6bad3..321ea625b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
@@ -298,7 +298,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
298 298
299 Optimization::PositionPass(env, program); 299 Optimization::PositionPass(env, program);
300 300
301 Optimization::GlobalMemoryToStorageBufferPass(program); 301 Optimization::GlobalMemoryToStorageBufferPass(program, host_info);
302 Optimization::TexturePass(env, program, host_info); 302 Optimization::TexturePass(env, program, host_info);
303 303
304 if (Settings::values.resolution_info.active) { 304 if (Settings::values.resolution_info.active) {
diff --git a/src/shader_recompiler/host_translate_info.h b/src/shader_recompiler/host_translate_info.h
index 7d2ded907..1b53404fc 100644
--- a/src/shader_recompiler/host_translate_info.h
+++ b/src/shader_recompiler/host_translate_info.h
@@ -16,6 +16,7 @@ struct HostTranslateInfo {
16 bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered 16 bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered
17 bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers 17 bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers
18 bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS 18 bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS
19 u32 min_ssbo_alignment{}; ///< Minimum alignment supported by the device for SSBOs
19 bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry 20 bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry
20 ///< passthrough shaders 21 ///< passthrough shaders
21 bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional 22 bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional
diff --git a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
index d1e59f22e..0cea79945 100644
--- a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
+++ b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
@@ -11,6 +11,7 @@
11#include "shader_recompiler/frontend/ir/breadth_first_search.h" 11#include "shader_recompiler/frontend/ir/breadth_first_search.h"
12#include "shader_recompiler/frontend/ir/ir_emitter.h" 12#include "shader_recompiler/frontend/ir/ir_emitter.h"
13#include "shader_recompiler/frontend/ir/value.h" 13#include "shader_recompiler/frontend/ir/value.h"
14#include "shader_recompiler/host_translate_info.h"
14#include "shader_recompiler/ir_opt/passes.h" 15#include "shader_recompiler/ir_opt/passes.h"
15 16
16namespace Shader::Optimization { 17namespace Shader::Optimization {
@@ -408,7 +409,7 @@ void CollectStorageBuffers(IR::Block& block, IR::Inst& inst, StorageInfo& info)
408} 409}
409 410
410/// Returns the offset in indices (not bytes) for an equivalent storage instruction 411/// Returns the offset in indices (not bytes) for an equivalent storage instruction
411IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer) { 412IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer, u32 alignment) {
412 IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; 413 IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
413 IR::U32 offset; 414 IR::U32 offset;
414 if (const std::optional<LowAddrInfo> low_addr{TrackLowAddress(&inst)}) { 415 if (const std::optional<LowAddrInfo> low_addr{TrackLowAddress(&inst)}) {
@@ -421,7 +422,10 @@ IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer
421 } 422 }
422 // Subtract the least significant 32 bits from the guest offset. The result is the storage 423 // Subtract the least significant 32 bits from the guest offset. The result is the storage
423 // buffer offset in bytes. 424 // buffer offset in bytes.
424 const IR::U32 low_cbuf{ir.GetCbuf(ir.Imm32(buffer.index), ir.Imm32(buffer.offset))}; 425 IR::U32 low_cbuf{ir.GetCbuf(ir.Imm32(buffer.index), ir.Imm32(buffer.offset))};
426
427 // Align the offset base to match the host alignment requirements
428 low_cbuf = ir.BitwiseAnd(low_cbuf, ir.Imm32(~(alignment - 1U)));
425 return ir.ISub(offset, low_cbuf); 429 return ir.ISub(offset, low_cbuf);
426} 430}
427 431
@@ -516,7 +520,7 @@ void Replace(IR::Block& block, IR::Inst& inst, const IR::U32& storage_index,
516} 520}
517} // Anonymous namespace 521} // Anonymous namespace
518 522
519void GlobalMemoryToStorageBufferPass(IR::Program& program) { 523void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateInfo& host_info) {
520 StorageInfo info; 524 StorageInfo info;
521 for (IR::Block* const block : program.post_order_blocks) { 525 for (IR::Block* const block : program.post_order_blocks) {
522 for (IR::Inst& inst : block->Instructions()) { 526 for (IR::Inst& inst : block->Instructions()) {
@@ -540,7 +544,8 @@ void GlobalMemoryToStorageBufferPass(IR::Program& program) {
540 const IR::U32 index{IR::Value{static_cast<u32>(info.set.index_of(it))}}; 544 const IR::U32 index{IR::Value{static_cast<u32>(info.set.index_of(it))}};
541 IR::Block* const block{storage_inst.block}; 545 IR::Block* const block{storage_inst.block};
542 IR::Inst* const inst{storage_inst.inst}; 546 IR::Inst* const inst{storage_inst.inst};
543 const IR::U32 offset{StorageOffset(*block, *inst, storage_buffer)}; 547 const IR::U32 offset{
548 StorageOffset(*block, *inst, storage_buffer, host_info.min_ssbo_alignment)};
544 Replace(*block, *inst, index, offset); 549 Replace(*block, *inst, index, offset);
545 } 550 }
546} 551}
diff --git a/src/shader_recompiler/ir_opt/passes.h b/src/shader_recompiler/ir_opt/passes.h
index d4d5285e5..1e637cb23 100644
--- a/src/shader_recompiler/ir_opt/passes.h
+++ b/src/shader_recompiler/ir_opt/passes.h
@@ -16,7 +16,7 @@ void CollectShaderInfoPass(Environment& env, IR::Program& program);
16void ConditionalBarrierPass(IR::Program& program); 16void ConditionalBarrierPass(IR::Program& program);
17void ConstantPropagationPass(Environment& env, IR::Program& program); 17void ConstantPropagationPass(Environment& env, IR::Program& program);
18void DeadCodeEliminationPass(IR::Program& program); 18void DeadCodeEliminationPass(IR::Program& program);
19void GlobalMemoryToStorageBufferPass(IR::Program& program); 19void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateInfo& host_info);
20void IdentityRemovalPass(IR::Program& program); 20void IdentityRemovalPass(IR::Program& program);
21void LowerFp64ToFp32(IR::Program& program); 21void LowerFp64ToFp32(IR::Program& program);
22void LowerFp16ToFp32(IR::Program& program); 22void LowerFp16ToFp32(IR::Program& program);
diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h
index a9de9f4a9..66901a965 100644
--- a/src/shader_recompiler/profile.h
+++ b/src/shader_recompiler/profile.h
@@ -85,6 +85,8 @@ struct Profile {
85 85
86 /// Maxwell and earlier nVidia architectures have broken robust support 86 /// Maxwell and earlier nVidia architectures have broken robust support
87 bool has_broken_robust{}; 87 bool has_broken_robust{};
88
89 u64 min_ssbo_alignment{};
88}; 90};
89 91
90} // namespace Shader 92} // namespace Shader
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index b65b9f2a2..c22c7631c 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -15,6 +15,7 @@ add_library(video_core STATIC
15 buffer_cache/buffer_cache.cpp 15 buffer_cache/buffer_cache.cpp
16 buffer_cache/buffer_cache.h 16 buffer_cache/buffer_cache.h
17 buffer_cache/memory_tracker_base.h 17 buffer_cache/memory_tracker_base.h
18 buffer_cache/usage_tracker.h
18 buffer_cache/word_manager.h 19 buffer_cache/word_manager.h
19 cache_types.h 20 cache_types.h
20 cdma_pusher.cpp 21 cdma_pusher.cpp
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index f5b10411b..6d1fc3887 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -67,6 +67,7 @@ void BufferCache<P>::TickFrame() {
67 if (!channel_state) { 67 if (!channel_state) {
68 return; 68 return;
69 } 69 }
70 runtime.TickFrame(slot_buffers);
70 71
71 // Calculate hits and shots and move hit bits to the right 72 // Calculate hits and shots and move hit bits to the right
72 const u32 hits = std::reduce(channel_state->uniform_cache_hits.begin(), 73 const u32 hits = std::reduce(channel_state->uniform_cache_hits.begin(),
@@ -230,7 +231,10 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
230 for (const IntervalType& add_interval : tmp_intervals) { 231 for (const IntervalType& add_interval : tmp_intervals) {
231 common_ranges.add(add_interval); 232 common_ranges.add(add_interval);
232 } 233 }
233 runtime.CopyBuffer(dest_buffer, src_buffer, copies); 234 const auto& copy = copies[0];
235 src_buffer.MarkUsage(copy.src_offset, copy.size);
236 dest_buffer.MarkUsage(copy.dst_offset, copy.size);
237 runtime.CopyBuffer(dest_buffer, src_buffer, copies, true);
234 if (has_new_downloads) { 238 if (has_new_downloads) {
235 memory_tracker.MarkRegionAsGpuModified(*cpu_dest_address, amount); 239 memory_tracker.MarkRegionAsGpuModified(*cpu_dest_address, amount);
236 } 240 }
@@ -258,9 +262,10 @@ bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
258 common_ranges.subtract(subtract_interval); 262 common_ranges.subtract(subtract_interval);
259 263
260 const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast<u32>(size)); 264 const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast<u32>(size));
261 auto& dest_buffer = slot_buffers[buffer]; 265 Buffer& dest_buffer = slot_buffers[buffer];
262 const u32 offset = dest_buffer.Offset(*cpu_dst_address); 266 const u32 offset = dest_buffer.Offset(*cpu_dst_address);
263 runtime.ClearBuffer(dest_buffer, offset, size, value); 267 runtime.ClearBuffer(dest_buffer, offset, size, value);
268 dest_buffer.MarkUsage(offset, size);
264 return true; 269 return true;
265} 270}
266 271
@@ -603,6 +608,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
603 VAddr orig_cpu_addr = static_cast<VAddr>(second_copy.src_offset); 608 VAddr orig_cpu_addr = static_cast<VAddr>(second_copy.src_offset);
604 const IntervalType base_interval{orig_cpu_addr, orig_cpu_addr + copy.size}; 609 const IntervalType base_interval{orig_cpu_addr, orig_cpu_addr + copy.size};
605 async_downloads += std::make_pair(base_interval, 1); 610 async_downloads += std::make_pair(base_interval, 1);
611 buffer.MarkUsage(copy.src_offset, copy.size);
606 runtime.CopyBuffer(download_staging.buffer, buffer, copies, false); 612 runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
607 normalized_copies.push_back(second_copy); 613 normalized_copies.push_back(second_copy);
608 } 614 }
@@ -621,8 +627,9 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
621 // Have in mind the staging buffer offset for the copy 627 // Have in mind the staging buffer offset for the copy
622 copy.dst_offset += download_staging.offset; 628 copy.dst_offset += download_staging.offset;
623 const std::array copies{copy}; 629 const std::array copies{copy};
624 runtime.CopyBuffer(download_staging.buffer, slot_buffers[buffer_id], copies, 630 Buffer& buffer = slot_buffers[buffer_id];
625 false); 631 buffer.MarkUsage(copy.src_offset, copy.size);
632 runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
626 } 633 }
627 runtime.PostCopyBarrier(); 634 runtime.PostCopyBarrier();
628 runtime.Finish(); 635 runtime.Finish();
@@ -742,7 +749,7 @@ void BufferCache<P>::BindHostIndexBuffer() {
742 {BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}}; 749 {BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}};
743 std::memcpy(upload_staging.mapped_span.data(), 750 std::memcpy(upload_staging.mapped_span.data(),
744 draw_state.inline_index_draw_indexes.data(), size); 751 draw_state.inline_index_draw_indexes.data(), size);
745 runtime.CopyBuffer(buffer, upload_staging.buffer, copies); 752 runtime.CopyBuffer(buffer, upload_staging.buffer, copies, true);
746 } else { 753 } else {
747 buffer.ImmediateUpload(0, draw_state.inline_index_draw_indexes); 754 buffer.ImmediateUpload(0, draw_state.inline_index_draw_indexes);
748 } 755 }
@@ -754,6 +761,7 @@ void BufferCache<P>::BindHostIndexBuffer() {
754 offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes(); 761 offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes();
755 runtime.BindIndexBuffer(buffer, new_offset, size); 762 runtime.BindIndexBuffer(buffer, new_offset, size);
756 } else { 763 } else {
764 buffer.MarkUsage(offset, size);
757 runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format, 765 runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format,
758 draw_state.index_buffer.first, draw_state.index_buffer.count, 766 draw_state.index_buffer.first, draw_state.index_buffer.count,
759 buffer, offset, size); 767 buffer, offset, size);
@@ -790,6 +798,7 @@ void BufferCache<P>::BindHostVertexBuffers() {
790 798
791 const u32 stride = maxwell3d->regs.vertex_streams[index].stride; 799 const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
792 const u32 offset = buffer.Offset(binding.cpu_addr); 800 const u32 offset = buffer.Offset(binding.cpu_addr);
801 buffer.MarkUsage(offset, binding.size);
793 802
794 host_bindings.buffers.push_back(&buffer); 803 host_bindings.buffers.push_back(&buffer);
795 host_bindings.offsets.push_back(offset); 804 host_bindings.offsets.push_back(offset);
@@ -895,6 +904,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
895 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { 904 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
896 channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; 905 channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size;
897 } 906 }
907 buffer.MarkUsage(offset, size);
898 if constexpr (NEEDS_BIND_UNIFORM_INDEX) { 908 if constexpr (NEEDS_BIND_UNIFORM_INDEX) {
899 runtime.BindUniformBuffer(stage, binding_index, buffer, offset, size); 909 runtime.BindUniformBuffer(stage, binding_index, buffer, offset, size);
900 } else { 910 } else {
@@ -913,6 +923,7 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
913 SynchronizeBuffer(buffer, binding.cpu_addr, size); 923 SynchronizeBuffer(buffer, binding.cpu_addr, size);
914 924
915 const u32 offset = buffer.Offset(binding.cpu_addr); 925 const u32 offset = buffer.Offset(binding.cpu_addr);
926 buffer.MarkUsage(offset, size);
916 const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0; 927 const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
917 928
918 if (is_written) { 929 if (is_written) {
@@ -943,6 +954,7 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
943 954
944 const u32 offset = buffer.Offset(binding.cpu_addr); 955 const u32 offset = buffer.Offset(binding.cpu_addr);
945 const PixelFormat format = binding.format; 956 const PixelFormat format = binding.format;
957 buffer.MarkUsage(offset, size);
946 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 958 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
947 if (((channel_state->image_texture_buffers[stage] >> index) & 1) != 0) { 959 if (((channel_state->image_texture_buffers[stage] >> index) & 1) != 0) {
948 runtime.BindImageBuffer(buffer, offset, size, format); 960 runtime.BindImageBuffer(buffer, offset, size, format);
@@ -975,9 +987,10 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
975 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size); 987 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
976 988
977 const u32 offset = buffer.Offset(binding.cpu_addr); 989 const u32 offset = buffer.Offset(binding.cpu_addr);
990 buffer.MarkUsage(offset, size);
978 host_bindings.buffers.push_back(&buffer); 991 host_bindings.buffers.push_back(&buffer);
979 host_bindings.offsets.push_back(offset); 992 host_bindings.offsets.push_back(offset);
980 host_bindings.sizes.push_back(binding.size); 993 host_bindings.sizes.push_back(size);
981 } 994 }
982 if (host_bindings.buffers.size() > 0) { 995 if (host_bindings.buffers.size() > 0) {
983 runtime.BindTransformFeedbackBuffers(host_bindings); 996 runtime.BindTransformFeedbackBuffers(host_bindings);
@@ -1001,6 +1014,7 @@ void BufferCache<P>::BindHostComputeUniformBuffers() {
1001 SynchronizeBuffer(buffer, binding.cpu_addr, size); 1014 SynchronizeBuffer(buffer, binding.cpu_addr, size);
1002 1015
1003 const u32 offset = buffer.Offset(binding.cpu_addr); 1016 const u32 offset = buffer.Offset(binding.cpu_addr);
1017 buffer.MarkUsage(offset, size);
1004 if constexpr (NEEDS_BIND_UNIFORM_INDEX) { 1018 if constexpr (NEEDS_BIND_UNIFORM_INDEX) {
1005 runtime.BindComputeUniformBuffer(binding_index, buffer, offset, size); 1019 runtime.BindComputeUniformBuffer(binding_index, buffer, offset, size);
1006 ++binding_index; 1020 ++binding_index;
@@ -1021,6 +1035,7 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
1021 SynchronizeBuffer(buffer, binding.cpu_addr, size); 1035 SynchronizeBuffer(buffer, binding.cpu_addr, size);
1022 1036
1023 const u32 offset = buffer.Offset(binding.cpu_addr); 1037 const u32 offset = buffer.Offset(binding.cpu_addr);
1038 buffer.MarkUsage(offset, size);
1024 const bool is_written = 1039 const bool is_written =
1025 ((channel_state->written_compute_storage_buffers >> index) & 1) != 0; 1040 ((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
1026 1041
@@ -1053,6 +1068,7 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
1053 1068
1054 const u32 offset = buffer.Offset(binding.cpu_addr); 1069 const u32 offset = buffer.Offset(binding.cpu_addr);
1055 const PixelFormat format = binding.format; 1070 const PixelFormat format = binding.format;
1071 buffer.MarkUsage(offset, size);
1056 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 1072 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
1057 if (((channel_state->image_compute_texture_buffers >> index) & 1) != 0) { 1073 if (((channel_state->image_compute_texture_buffers >> index) & 1) != 0) {
1058 runtime.BindImageBuffer(buffer, offset, size, format); 1074 runtime.BindImageBuffer(buffer, offset, size, format);
@@ -1172,10 +1188,11 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) {
1172 if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end)) { 1188 if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end)) {
1173 size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size)); 1189 size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
1174 } 1190 }
1191 const BufferId buffer_id = FindBuffer(*cpu_addr, size);
1175 channel_state->vertex_buffers[index] = Binding{ 1192 channel_state->vertex_buffers[index] = Binding{
1176 .cpu_addr = *cpu_addr, 1193 .cpu_addr = *cpu_addr,
1177 .size = size, 1194 .size = size,
1178 .buffer_id = FindBuffer(*cpu_addr, size), 1195 .buffer_id = buffer_id,
1179 }; 1196 };
1180} 1197}
1181 1198
@@ -1401,7 +1418,8 @@ void BufferCache<P>::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id,
1401 .dst_offset = dst_base_offset, 1418 .dst_offset = dst_base_offset,
1402 .size = overlap.SizeBytes(), 1419 .size = overlap.SizeBytes(),
1403 }); 1420 });
1404 runtime.CopyBuffer(new_buffer, overlap, copies); 1421 new_buffer.MarkUsage(copies[0].dst_offset, copies[0].size);
1422 runtime.CopyBuffer(new_buffer, overlap, copies, true);
1405 DeleteBuffer(overlap_id, true); 1423 DeleteBuffer(overlap_id, true);
1406} 1424}
1407 1425
@@ -1414,7 +1432,9 @@ BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) {
1414 const u32 size = static_cast<u32>(overlap.end - overlap.begin); 1432 const u32 size = static_cast<u32>(overlap.end - overlap.begin);
1415 const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size); 1433 const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size);
1416 auto& new_buffer = slot_buffers[new_buffer_id]; 1434 auto& new_buffer = slot_buffers[new_buffer_id];
1417 runtime.ClearBuffer(new_buffer, 0, new_buffer.SizeBytes(), 0); 1435 const size_t size_bytes = new_buffer.SizeBytes();
1436 runtime.ClearBuffer(new_buffer, 0, size_bytes, 0);
1437 new_buffer.MarkUsage(0, size_bytes);
1418 for (const BufferId overlap_id : overlap.ids) { 1438 for (const BufferId overlap_id : overlap.ids) {
1419 JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap); 1439 JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap);
1420 } 1440 }
@@ -1467,11 +1487,6 @@ void BufferCache<P>::TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept {
1467 1487
1468template <class P> 1488template <class P>
1469bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) { 1489bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) {
1470 return SynchronizeBufferImpl(buffer, cpu_addr, size);
1471}
1472
1473template <class P>
1474bool BufferCache<P>::SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size) {
1475 boost::container::small_vector<BufferCopy, 4> copies; 1490 boost::container::small_vector<BufferCopy, 4> copies;
1476 u64 total_size_bytes = 0; 1491 u64 total_size_bytes = 0;
1477 u64 largest_copy = 0; 1492 u64 largest_copy = 0;
@@ -1494,51 +1509,6 @@ bool BufferCache<P>::SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 s
1494} 1509}
1495 1510
1496template <class P> 1511template <class P>
1497bool BufferCache<P>::SynchronizeBufferNoModified(Buffer& buffer, VAddr cpu_addr, u32 size) {
1498 boost::container::small_vector<BufferCopy, 4> copies;
1499 u64 total_size_bytes = 0;
1500 u64 largest_copy = 0;
1501 IntervalSet found_sets{};
1502 auto make_copies = [&] {
1503 for (auto& interval : found_sets) {
1504 const std::size_t sub_size = interval.upper() - interval.lower();
1505 const VAddr cpu_addr_ = interval.lower();
1506 copies.push_back(BufferCopy{
1507 .src_offset = total_size_bytes,
1508 .dst_offset = cpu_addr_ - buffer.CpuAddr(),
1509 .size = sub_size,
1510 });
1511 total_size_bytes += sub_size;
1512 largest_copy = std::max<u64>(largest_copy, sub_size);
1513 }
1514 const std::span<BufferCopy> copies_span(copies.data(), copies.size());
1515 UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
1516 };
1517 memory_tracker.ForEachUploadRange(cpu_addr, size, [&](u64 cpu_addr_out, u64 range_size) {
1518 const VAddr base_adr = cpu_addr_out;
1519 const VAddr end_adr = base_adr + range_size;
1520 const IntervalType add_interval{base_adr, end_adr};
1521 found_sets.add(add_interval);
1522 });
1523 if (found_sets.empty()) {
1524 return true;
1525 }
1526 const IntervalType search_interval{cpu_addr, cpu_addr + size};
1527 auto it = common_ranges.lower_bound(search_interval);
1528 auto it_end = common_ranges.upper_bound(search_interval);
1529 if (it == common_ranges.end()) {
1530 make_copies();
1531 return false;
1532 }
1533 while (it != it_end) {
1534 found_sets.subtract(*it);
1535 it++;
1536 }
1537 make_copies();
1538 return false;
1539}
1540
1541template <class P>
1542void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy, 1512void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy,
1543 std::span<BufferCopy> copies) { 1513 std::span<BufferCopy> copies) {
1544 if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) { 1514 if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
@@ -1586,7 +1556,8 @@ void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
1586 // Apply the staging offset 1556 // Apply the staging offset
1587 copy.src_offset += upload_staging.offset; 1557 copy.src_offset += upload_staging.offset;
1588 } 1558 }
1589 runtime.CopyBuffer(buffer, upload_staging.buffer, copies); 1559 const bool can_reorder = runtime.CanReorderUpload(buffer, copies);
1560 runtime.CopyBuffer(buffer, upload_staging.buffer, copies, true, can_reorder);
1590 } 1561 }
1591} 1562}
1592 1563
@@ -1628,7 +1599,8 @@ void BufferCache<P>::InlineMemoryImplementation(VAddr dest_address, size_t copy_
1628 }}; 1599 }};
1629 u8* const src_pointer = upload_staging.mapped_span.data(); 1600 u8* const src_pointer = upload_staging.mapped_span.data();
1630 std::memcpy(src_pointer, inlined_buffer.data(), copy_size); 1601 std::memcpy(src_pointer, inlined_buffer.data(), copy_size);
1631 runtime.CopyBuffer(buffer, upload_staging.buffer, copies); 1602 const bool can_reorder = runtime.CanReorderUpload(buffer, copies);
1603 runtime.CopyBuffer(buffer, upload_staging.buffer, copies, true, can_reorder);
1632 } else { 1604 } else {
1633 buffer.ImmediateUpload(buffer.Offset(dest_address), inlined_buffer.first(copy_size)); 1605 buffer.ImmediateUpload(buffer.Offset(dest_address), inlined_buffer.first(copy_size));
1634 } 1606 }
@@ -1681,8 +1653,9 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 si
1681 for (BufferCopy& copy : copies) { 1653 for (BufferCopy& copy : copies) {
1682 // Modify copies to have the staging offset in mind 1654 // Modify copies to have the staging offset in mind
1683 copy.dst_offset += download_staging.offset; 1655 copy.dst_offset += download_staging.offset;
1656 buffer.MarkUsage(copy.src_offset, copy.size);
1684 } 1657 }
1685 runtime.CopyBuffer(download_staging.buffer, buffer, copies_span); 1658 runtime.CopyBuffer(download_staging.buffer, buffer, copies_span, true);
1686 runtime.Finish(); 1659 runtime.Finish();
1687 for (const BufferCopy& copy : copies) { 1660 for (const BufferCopy& copy : copies) {
1688 const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset; 1661 const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset;
@@ -1780,15 +1753,25 @@ Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
1780 const u32 memory_layout_size = static_cast<u32>(gpu_memory->GetMemoryLayoutSize(gpu_addr)); 1753 const u32 memory_layout_size = static_cast<u32>(gpu_memory->GetMemoryLayoutSize(gpu_addr));
1781 return std::min(memory_layout_size, static_cast<u32>(8_MiB)); 1754 return std::min(memory_layout_size, static_cast<u32>(8_MiB));
1782 }(); 1755 }();
1783 const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr); 1756 // Alignment only applies to the offset of the buffer
1784 if (!cpu_addr || size == 0) { 1757 const u32 alignment = runtime.GetStorageBufferAlignment();
1758 const GPUVAddr aligned_gpu_addr = Common::AlignDown(gpu_addr, alignment);
1759 const u32 aligned_size = static_cast<u32>(gpu_addr - aligned_gpu_addr) + size;
1760
1761 const std::optional<VAddr> aligned_cpu_addr = gpu_memory->GpuToCpuAddress(aligned_gpu_addr);
1762 if (!aligned_cpu_addr || size == 0) {
1785 LOG_WARNING(HW_GPU, "Failed to find storage buffer for cbuf index {}", cbuf_index); 1763 LOG_WARNING(HW_GPU, "Failed to find storage buffer for cbuf index {}", cbuf_index);
1786 return NULL_BINDING; 1764 return NULL_BINDING;
1787 } 1765 }
1788 const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, YUZU_PAGESIZE); 1766 const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
1767 ASSERT_MSG(cpu_addr, "Unaligned storage buffer address not found for cbuf index {}",
1768 cbuf_index);
1769 // The end address used for size calculation does not need to be aligned
1770 const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, Core::Memory::YUZU_PAGESIZE);
1771
1789 const Binding binding{ 1772 const Binding binding{
1790 .cpu_addr = *cpu_addr, 1773 .cpu_addr = *aligned_cpu_addr,
1791 .size = is_written ? size : static_cast<u32>(cpu_end - *cpu_addr), 1774 .size = is_written ? aligned_size : static_cast<u32>(cpu_end - *aligned_cpu_addr),
1792 .buffer_id = BufferId{}, 1775 .buffer_id = BufferId{},
1793 }; 1776 };
1794 return binding; 1777 return binding;
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index eed267361..d6d696d8c 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -529,10 +529,6 @@ private:
529 529
530 bool SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size); 530 bool SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size);
531 531
532 bool SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size);
533
534 bool SynchronizeBufferNoModified(Buffer& buffer, VAddr cpu_addr, u32 size);
535
536 void UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy, 532 void UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy,
537 std::span<BufferCopy> copies); 533 std::span<BufferCopy> copies);
538 534
diff --git a/src/video_core/buffer_cache/usage_tracker.h b/src/video_core/buffer_cache/usage_tracker.h
new file mode 100644
index 000000000..ab05fe415
--- /dev/null
+++ b/src/video_core/buffer_cache/usage_tracker.h
@@ -0,0 +1,79 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/alignment.h"
7#include "common/common_types.h"
8
9namespace VideoCommon {
10
11class UsageTracker {
12 static constexpr size_t BYTES_PER_BIT_SHIFT = 6;
13 static constexpr size_t PAGE_SHIFT = 6 + BYTES_PER_BIT_SHIFT;
14 static constexpr size_t PAGE_BYTES = 1 << PAGE_SHIFT;
15
16public:
17 explicit UsageTracker(size_t size) {
18 const size_t num_pages = (size >> PAGE_SHIFT) + 1;
19 pages.resize(num_pages, 0ULL);
20 }
21
22 void Reset() noexcept {
23 std::ranges::fill(pages, 0ULL);
24 }
25
26 void Track(u64 offset, u64 size) noexcept {
27 const size_t page = offset >> PAGE_SHIFT;
28 const size_t page_end = (offset + size) >> PAGE_SHIFT;
29 TrackPage(page, offset, size);
30 if (page == page_end) {
31 return;
32 }
33 for (size_t i = page + 1; i < page_end; i++) {
34 pages[i] = ~u64{0};
35 }
36 const size_t offset_end = offset + size;
37 const size_t offset_end_page_aligned = Common::AlignDown(offset_end, PAGE_BYTES);
38 TrackPage(page_end, offset_end_page_aligned, offset_end - offset_end_page_aligned);
39 }
40
41 [[nodiscard]] bool IsUsed(u64 offset, u64 size) const noexcept {
42 const size_t page = offset >> PAGE_SHIFT;
43 const size_t page_end = (offset + size) >> PAGE_SHIFT;
44 if (IsPageUsed(page, offset, size)) {
45 return true;
46 }
47 for (size_t i = page + 1; i < page_end; i++) {
48 if (pages[i] != 0) {
49 return true;
50 }
51 }
52 const size_t offset_end = offset + size;
53 const size_t offset_end_page_aligned = Common::AlignDown(offset_end, PAGE_BYTES);
54 return IsPageUsed(page_end, offset_end_page_aligned, offset_end - offset_end_page_aligned);
55 }
56
57private:
58 void TrackPage(u64 page, u64 offset, u64 size) noexcept {
59 const size_t offset_in_page = offset % PAGE_BYTES;
60 const size_t first_bit = offset_in_page >> BYTES_PER_BIT_SHIFT;
61 const size_t num_bits = std::min(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT;
62 const size_t mask = ~u64{0} >> (64 - num_bits);
63 pages[page] |= (~u64{0} & mask) << first_bit;
64 }
65
66 bool IsPageUsed(u64 page, u64 offset, u64 size) const noexcept {
67 const size_t offset_in_page = offset % PAGE_BYTES;
68 const size_t first_bit = offset_in_page >> BYTES_PER_BIT_SHIFT;
69 const size_t num_bits = std::min(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT;
70 const size_t mask = ~u64{0} >> (64 - num_bits);
71 const size_t mask2 = (~u64{0} & mask) << first_bit;
72 return (pages[page] & mask2) != 0;
73 }
74
75private:
76 std::vector<u64> pages;
77};
78
79} // namespace VideoCommon
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 38d553d3c..dfd696de6 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -178,13 +178,14 @@ void BufferCacheRuntime::CopyBuffer(GLuint dst_buffer, Buffer& src_buffer,
178} 178}
179 179
180void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, GLuint src_buffer, 180void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, GLuint src_buffer,
181 std::span<const VideoCommon::BufferCopy> copies, bool barrier) { 181 std::span<const VideoCommon::BufferCopy> copies, bool barrier,
182 bool) {
182 CopyBuffer(dst_buffer.Handle(), src_buffer, copies, barrier); 183 CopyBuffer(dst_buffer.Handle(), src_buffer, copies, barrier);
183} 184}
184 185
185void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer, 186void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer,
186 std::span<const VideoCommon::BufferCopy> copies) { 187 std::span<const VideoCommon::BufferCopy> copies, bool) {
187 CopyBuffer(dst_buffer.Handle(), src_buffer.Handle(), copies); 188 CopyBuffer(dst_buffer.Handle(), src_buffer.Handle(), copies, true);
188} 189}
189 190
190void BufferCacheRuntime::PreCopyBarrier() { 191void BufferCacheRuntime::PreCopyBarrier() {
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index 41b746f3b..000f29a82 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -30,6 +30,8 @@ public:
30 30
31 void MakeResident(GLenum access) noexcept; 31 void MakeResident(GLenum access) noexcept;
32 32
33 void MarkUsage(u64 offset, u64 size) {}
34
33 [[nodiscard]] GLuint View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format); 35 [[nodiscard]] GLuint View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format);
34 36
35 [[nodiscard]] GLuint64EXT HostGpuAddr() const noexcept { 37 [[nodiscard]] GLuint64EXT HostGpuAddr() const noexcept {
@@ -66,22 +68,29 @@ public:
66 68
67 [[nodiscard]] StagingBufferMap DownloadStagingBuffer(size_t size); 69 [[nodiscard]] StagingBufferMap DownloadStagingBuffer(size_t size);
68 70
71 bool CanReorderUpload(const Buffer&, std::span<const VideoCommon::BufferCopy>) {
72 return false;
73 }
74
69 void CopyBuffer(GLuint dst_buffer, GLuint src_buffer, 75 void CopyBuffer(GLuint dst_buffer, GLuint src_buffer,
70 std::span<const VideoCommon::BufferCopy> copies, bool barrier = true); 76 std::span<const VideoCommon::BufferCopy> copies, bool barrier);
71 77
72 void CopyBuffer(GLuint dst_buffer, Buffer& src_buffer, 78 void CopyBuffer(GLuint dst_buffer, Buffer& src_buffer,
73 std::span<const VideoCommon::BufferCopy> copies, bool barrier = true); 79 std::span<const VideoCommon::BufferCopy> copies, bool barrier);
74 80
75 void CopyBuffer(Buffer& dst_buffer, GLuint src_buffer, 81 void CopyBuffer(Buffer& dst_buffer, GLuint src_buffer,
76 std::span<const VideoCommon::BufferCopy> copies, bool barrier = true); 82 std::span<const VideoCommon::BufferCopy> copies, bool barrier,
83 bool can_reorder_upload = false);
77 84
78 void CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer, 85 void CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer,
79 std::span<const VideoCommon::BufferCopy> copies); 86 std::span<const VideoCommon::BufferCopy> copies, bool);
80 87
81 void PreCopyBarrier(); 88 void PreCopyBarrier();
82 void PostCopyBarrier(); 89 void PostCopyBarrier();
83 void Finish(); 90 void Finish();
84 91
92 void TickFrame(VideoCommon::SlotVector<Buffer>&) noexcept {}
93
85 void ClearBuffer(Buffer& dest_buffer, u32 offset, size_t size, u32 value); 94 void ClearBuffer(Buffer& dest_buffer, u32 offset, size_t size, u32 value);
86 95
87 void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size); 96 void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size);
@@ -182,6 +191,10 @@ public:
182 return device.CanReportMemoryUsage(); 191 return device.CanReportMemoryUsage();
183 } 192 }
184 193
194 u32 GetStorageBufferAlignment() const {
195 return static_cast<u32>(device.GetShaderStorageBufferAlignment());
196 }
197
185private: 198private:
186 static constexpr std::array PABO_LUT{ 199 static constexpr std::array PABO_LUT{
187 GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV, GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV, 200 GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV, GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV,
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 2888e0238..26f2d0ea7 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -232,6 +232,7 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo
232 .has_gl_bool_ref_bug = device.HasBoolRefBug(), 232 .has_gl_bool_ref_bug = device.HasBoolRefBug(),
233 .ignore_nan_fp_comparisons = true, 233 .ignore_nan_fp_comparisons = true,
234 .gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(), 234 .gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(),
235 .min_ssbo_alignment = device.GetShaderStorageBufferAlignment(),
235 }, 236 },
236 host_info{ 237 host_info{
237 .support_float64 = true, 238 .support_float64 = true,
@@ -240,6 +241,7 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo
240 .needs_demote_reorder = device.IsAmd(), 241 .needs_demote_reorder = device.IsAmd(),
241 .support_snorm_render_buffer = false, 242 .support_snorm_render_buffer = false,
242 .support_viewport_index_layer = device.HasVertexViewportLayer(), 243 .support_viewport_index_layer = device.HasVertexViewportLayer(),
244 .min_ssbo_alignment = static_cast<u32>(device.GetShaderStorageBufferAlignment()),
243 .support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(), 245 .support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(),
244 .support_conditional_barrier = device.SupportsConditionalBarriers(), 246 .support_conditional_barrier = device.SupportsConditionalBarriers(),
245 } { 247 } {
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index d8148e89a..5958f52f7 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -79,13 +79,13 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
79} // Anonymous namespace 79} // Anonymous namespace
80 80
81Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params) 81Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params)
82 : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params) {} 82 : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params), tracker{4096} {}
83 83
84Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_, 84Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_,
85 VAddr cpu_addr_, u64 size_bytes_) 85 VAddr cpu_addr_, u64 size_bytes_)
86 : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_), 86 : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_),
87 device{&runtime.device}, buffer{ 87 device{&runtime.device}, buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())},
88 CreateBuffer(*device, runtime.memory_allocator, SizeBytes())} { 88 tracker{SizeBytes()} {
89 if (runtime.device.HasDebuggingToolAttached()) { 89 if (runtime.device.HasDebuggingToolAttached()) {
90 buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str()); 90 buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str());
91 } 91 }
@@ -355,12 +355,35 @@ bool BufferCacheRuntime::CanReportMemoryUsage() const {
355 return device.CanReportMemoryUsage(); 355 return device.CanReportMemoryUsage();
356} 356}
357 357
358u32 BufferCacheRuntime::GetStorageBufferAlignment() const {
359 return static_cast<u32>(device.GetStorageBufferAlignment());
360}
361
362void BufferCacheRuntime::TickFrame(VideoCommon::SlotVector<Buffer>& slot_buffers) noexcept {
363 for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) {
364 it->ResetUsageTracking();
365 }
366}
367
358void BufferCacheRuntime::Finish() { 368void BufferCacheRuntime::Finish() {
359 scheduler.Finish(); 369 scheduler.Finish();
360} 370}
361 371
372bool BufferCacheRuntime::CanReorderUpload(const Buffer& buffer,
373 std::span<const VideoCommon::BufferCopy> copies) {
374 if (Settings::values.disable_buffer_reorder) {
375 return false;
376 }
377 const bool can_use_upload_cmdbuf =
378 std::ranges::all_of(copies, [&](const VideoCommon::BufferCopy& copy) {
379 return !buffer.IsRegionUsed(copy.dst_offset, copy.size);
380 });
381 return can_use_upload_cmdbuf;
382}
383
362void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer, 384void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer,
363 std::span<const VideoCommon::BufferCopy> copies, bool barrier) { 385 std::span<const VideoCommon::BufferCopy> copies, bool barrier,
386 bool can_reorder_upload) {
364 if (dst_buffer == VK_NULL_HANDLE || src_buffer == VK_NULL_HANDLE) { 387 if (dst_buffer == VK_NULL_HANDLE || src_buffer == VK_NULL_HANDLE) {
365 return; 388 return;
366 } 389 }
@@ -376,9 +399,18 @@ void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer,
376 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, 399 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
377 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, 400 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
378 }; 401 };
402
379 // Measuring a popular game, this number never exceeds the specified size once data is warmed up 403 // Measuring a popular game, this number never exceeds the specified size once data is warmed up
380 boost::container::small_vector<VkBufferCopy, 8> vk_copies(copies.size()); 404 boost::container::small_vector<VkBufferCopy, 8> vk_copies(copies.size());
381 std::ranges::transform(copies, vk_copies.begin(), MakeBufferCopy); 405 std::ranges::transform(copies, vk_copies.begin(), MakeBufferCopy);
406 if (src_buffer == staging_pool.StreamBuf() && can_reorder_upload) {
407 scheduler.RecordWithUploadBuffer([src_buffer, dst_buffer, vk_copies](
408 vk::CommandBuffer, vk::CommandBuffer upload_cmdbuf) {
409 upload_cmdbuf.CopyBuffer(src_buffer, dst_buffer, vk_copies);
410 });
411 return;
412 }
413
382 scheduler.RequestOutsideRenderPassOperationContext(); 414 scheduler.RequestOutsideRenderPassOperationContext();
383 scheduler.Record([src_buffer, dst_buffer, vk_copies, barrier](vk::CommandBuffer cmdbuf) { 415 scheduler.Record([src_buffer, dst_buffer, vk_copies, barrier](vk::CommandBuffer cmdbuf) {
384 if (barrier) { 416 if (barrier) {
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 95446c732..0b3fbd6d0 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -5,6 +5,7 @@
5 5
6#include "video_core/buffer_cache/buffer_cache_base.h" 6#include "video_core/buffer_cache/buffer_cache_base.h"
7#include "video_core/buffer_cache/memory_tracker_base.h" 7#include "video_core/buffer_cache/memory_tracker_base.h"
8#include "video_core/buffer_cache/usage_tracker.h"
8#include "video_core/engines/maxwell_3d.h" 9#include "video_core/engines/maxwell_3d.h"
9#include "video_core/renderer_vulkan/vk_compute_pass.h" 10#include "video_core/renderer_vulkan/vk_compute_pass.h"
10#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" 11#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -34,6 +35,18 @@ public:
34 return *buffer; 35 return *buffer;
35 } 36 }
36 37
38 [[nodiscard]] bool IsRegionUsed(u64 offset, u64 size) const noexcept {
39 return tracker.IsUsed(offset, size);
40 }
41
42 void MarkUsage(u64 offset, u64 size) noexcept {
43 tracker.Track(offset, size);
44 }
45
46 void ResetUsageTracking() noexcept {
47 tracker.Reset();
48 }
49
37 operator VkBuffer() const noexcept { 50 operator VkBuffer() const noexcept {
38 return *buffer; 51 return *buffer;
39 } 52 }
@@ -49,6 +62,7 @@ private:
49 const Device* device{}; 62 const Device* device{};
50 vk::Buffer buffer; 63 vk::Buffer buffer;
51 std::vector<BufferView> views; 64 std::vector<BufferView> views;
65 VideoCommon::UsageTracker tracker;
52}; 66};
53 67
54class QuadArrayIndexBuffer; 68class QuadArrayIndexBuffer;
@@ -67,6 +81,8 @@ public:
67 ComputePassDescriptorQueue& compute_pass_descriptor_queue, 81 ComputePassDescriptorQueue& compute_pass_descriptor_queue,
68 DescriptorPool& descriptor_pool); 82 DescriptorPool& descriptor_pool);
69 83
84 void TickFrame(VideoCommon::SlotVector<Buffer>& slot_buffers) noexcept;
85
70 void Finish(); 86 void Finish();
71 87
72 u64 GetDeviceLocalMemory() const; 88 u64 GetDeviceLocalMemory() const;
@@ -75,16 +91,21 @@ public:
75 91
76 bool CanReportMemoryUsage() const; 92 bool CanReportMemoryUsage() const;
77 93
94 u32 GetStorageBufferAlignment() const;
95
78 [[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size); 96 [[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size);
79 97
80 [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size, bool deferred = false); 98 [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size, bool deferred = false);
81 99
100 bool CanReorderUpload(const Buffer& buffer, std::span<const VideoCommon::BufferCopy> copies);
101
82 void FreeDeferredStagingBuffer(StagingBufferRef& ref); 102 void FreeDeferredStagingBuffer(StagingBufferRef& ref);
83 103
84 void PreCopyBarrier(); 104 void PreCopyBarrier();
85 105
86 void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer, 106 void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer,
87 std::span<const VideoCommon::BufferCopy> copies, bool barrier = true); 107 std::span<const VideoCommon::BufferCopy> copies, bool barrier,
108 bool can_reorder_upload = false);
88 109
89 void PostCopyBarrier(); 110 void PostCopyBarrier();
90 111
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
index 6b288b994..ac8b6e838 100644
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
@@ -100,12 +100,14 @@ void MasterSemaphore::Wait(u64 tick) {
100 Refresh(); 100 Refresh();
101} 101}
102 102
103VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, 103VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf,
104 VkSemaphore wait_semaphore, u64 host_tick) { 104 VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
105 u64 host_tick) {
105 if (semaphore) { 106 if (semaphore) {
106 return SubmitQueueTimeline(cmdbuf, signal_semaphore, wait_semaphore, host_tick); 107 return SubmitQueueTimeline(cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore,
108 host_tick);
107 } else { 109 } else {
108 return SubmitQueueFence(cmdbuf, signal_semaphore, wait_semaphore, host_tick); 110 return SubmitQueueFence(cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore, host_tick);
109 } 111 }
110} 112}
111 113
@@ -115,6 +117,7 @@ static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{
115}; 117};
116 118
117VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, 119VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
120 vk::CommandBuffer& upload_cmdbuf,
118 VkSemaphore signal_semaphore, 121 VkSemaphore signal_semaphore,
119 VkSemaphore wait_semaphore, u64 host_tick) { 122 VkSemaphore wait_semaphore, u64 host_tick) {
120 const VkSemaphore timeline_semaphore = *semaphore; 123 const VkSemaphore timeline_semaphore = *semaphore;
@@ -123,6 +126,8 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
123 const std::array signal_values{host_tick, u64(0)}; 126 const std::array signal_values{host_tick, u64(0)};
124 const std::array signal_semaphores{timeline_semaphore, signal_semaphore}; 127 const std::array signal_semaphores{timeline_semaphore, signal_semaphore};
125 128
129 const std::array cmdbuffers{*upload_cmdbuf, *cmdbuf};
130
126 const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; 131 const u32 num_wait_semaphores = wait_semaphore ? 1 : 0;
127 const VkTimelineSemaphoreSubmitInfo timeline_si{ 132 const VkTimelineSemaphoreSubmitInfo timeline_si{
128 .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, 133 .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
@@ -138,8 +143,8 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
138 .waitSemaphoreCount = num_wait_semaphores, 143 .waitSemaphoreCount = num_wait_semaphores,
139 .pWaitSemaphores = &wait_semaphore, 144 .pWaitSemaphores = &wait_semaphore,
140 .pWaitDstStageMask = wait_stage_masks.data(), 145 .pWaitDstStageMask = wait_stage_masks.data(),
141 .commandBufferCount = 1, 146 .commandBufferCount = static_cast<u32>(cmdbuffers.size()),
142 .pCommandBuffers = cmdbuf.address(), 147 .pCommandBuffers = cmdbuffers.data(),
143 .signalSemaphoreCount = num_signal_semaphores, 148 .signalSemaphoreCount = num_signal_semaphores,
144 .pSignalSemaphores = signal_semaphores.data(), 149 .pSignalSemaphores = signal_semaphores.data(),
145 }; 150 };
@@ -147,19 +152,23 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
147 return device.GetGraphicsQueue().Submit(submit_info); 152 return device.GetGraphicsQueue().Submit(submit_info);
148} 153}
149 154
150VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, 155VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf,
151 VkSemaphore wait_semaphore, u64 host_tick) { 156 vk::CommandBuffer& upload_cmdbuf,
157 VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
158 u64 host_tick) {
152 const u32 num_signal_semaphores = signal_semaphore ? 1 : 0; 159 const u32 num_signal_semaphores = signal_semaphore ? 1 : 0;
153 const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; 160 const u32 num_wait_semaphores = wait_semaphore ? 1 : 0;
154 161
162 const std::array cmdbuffers{*upload_cmdbuf, *cmdbuf};
163
155 const VkSubmitInfo submit_info{ 164 const VkSubmitInfo submit_info{
156 .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, 165 .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
157 .pNext = nullptr, 166 .pNext = nullptr,
158 .waitSemaphoreCount = num_wait_semaphores, 167 .waitSemaphoreCount = num_wait_semaphores,
159 .pWaitSemaphores = &wait_semaphore, 168 .pWaitSemaphores = &wait_semaphore,
160 .pWaitDstStageMask = wait_stage_masks.data(), 169 .pWaitDstStageMask = wait_stage_masks.data(),
161 .commandBufferCount = 1, 170 .commandBufferCount = static_cast<u32>(cmdbuffers.size()),
162 .pCommandBuffers = cmdbuf.address(), 171 .pCommandBuffers = cmdbuffers.data(),
163 .signalSemaphoreCount = num_signal_semaphores, 172 .signalSemaphoreCount = num_signal_semaphores,
164 .pSignalSemaphores = &signal_semaphore, 173 .pSignalSemaphores = &signal_semaphore,
165 }; 174 };
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h
index 3f599d7bd..7dfb93ffb 100644
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.h
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h
@@ -52,14 +52,16 @@ public:
52 void Wait(u64 tick); 52 void Wait(u64 tick);
53 53
54 /// Submits the device graphics queue, updating the tick as necessary 54 /// Submits the device graphics queue, updating the tick as necessary
55 VkResult SubmitQueue(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, 55 VkResult SubmitQueue(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf,
56 VkSemaphore wait_semaphore, u64 host_tick); 56 VkSemaphore signal_semaphore, VkSemaphore wait_semaphore, u64 host_tick);
57 57
58private: 58private:
59 VkResult SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, 59 VkResult SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf,
60 VkSemaphore wait_semaphore, u64 host_tick); 60 VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
61 VkResult SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, 61 u64 host_tick);
62 VkSemaphore wait_semaphore, u64 host_tick); 62 VkResult SubmitQueueFence(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf,
63 VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
64 u64 host_tick);
63 65
64 void WaitThread(std::stop_token token); 66 void WaitThread(std::stop_token token);
65 67
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 89b455bff..2a13b2a72 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -373,6 +373,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
373 driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY, 373 driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
374 .has_broken_robust = 374 .has_broken_robust =
375 device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal, 375 device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal,
376 .min_ssbo_alignment = device.GetStorageBufferAlignment(),
376 }; 377 };
377 378
378 host_info = Shader::HostTranslateInfo{ 379 host_info = Shader::HostTranslateInfo{
@@ -383,6 +384,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
383 driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE, 384 driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE,
384 .support_snorm_render_buffer = true, 385 .support_snorm_render_buffer = true,
385 .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(), 386 .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(),
387 .min_ssbo_alignment = static_cast<u32>(device.GetStorageBufferAlignment()),
386 .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), 388 .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
387 .support_conditional_barrier = device.SupportsConditionalBarriers(), 389 .support_conditional_barrier = device.SupportsConditionalBarriers(),
388 }; 390 };
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index 3be7837f4..146923db4 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -22,11 +22,12 @@ namespace Vulkan {
22 22
23MICROPROFILE_DECLARE(Vulkan_WaitForWorker); 23MICROPROFILE_DECLARE(Vulkan_WaitForWorker);
24 24
25void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) { 25void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf,
26 vk::CommandBuffer upload_cmdbuf) {
26 auto command = first; 27 auto command = first;
27 while (command != nullptr) { 28 while (command != nullptr) {
28 auto next = command->GetNext(); 29 auto next = command->GetNext();
29 command->Execute(cmdbuf); 30 command->Execute(cmdbuf, upload_cmdbuf);
30 command->~Command(); 31 command->~Command();
31 command = next; 32 command = next;
32 } 33 }
@@ -180,7 +181,7 @@ void Scheduler::WorkerThread(std::stop_token stop_token) {
180 // Perform the work, tracking whether the chunk was a submission 181 // Perform the work, tracking whether the chunk was a submission
181 // before executing. 182 // before executing.
182 const bool has_submit = work->HasSubmit(); 183 const bool has_submit = work->HasSubmit();
183 work->ExecuteAll(current_cmdbuf); 184 work->ExecuteAll(current_cmdbuf, current_upload_cmdbuf);
184 185
185 // If the chunk was a submission, reallocate the command buffer. 186 // If the chunk was a submission, reallocate the command buffer.
186 if (has_submit) { 187 if (has_submit) {
@@ -205,6 +206,13 @@ void Scheduler::AllocateWorkerCommandBuffer() {
205 .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 206 .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
206 .pInheritanceInfo = nullptr, 207 .pInheritanceInfo = nullptr,
207 }); 208 });
209 current_upload_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader());
210 current_upload_cmdbuf.Begin({
211 .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
212 .pNext = nullptr,
213 .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
214 .pInheritanceInfo = nullptr,
215 });
208} 216}
209 217
210u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { 218u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
@@ -212,7 +220,17 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
212 InvalidateState(); 220 InvalidateState();
213 221
214 const u64 signal_value = master_semaphore->NextTick(); 222 const u64 signal_value = master_semaphore->NextTick();
215 Record([signal_semaphore, wait_semaphore, signal_value, this](vk::CommandBuffer cmdbuf) { 223 RecordWithUploadBuffer([signal_semaphore, wait_semaphore, signal_value,
224 this](vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) {
225 static constexpr VkMemoryBarrier WRITE_BARRIER{
226 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
227 .pNext = nullptr,
228 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
229 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
230 };
231 upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
232 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
233 upload_cmdbuf.End();
216 cmdbuf.End(); 234 cmdbuf.End();
217 235
218 if (on_submit) { 236 if (on_submit) {
@@ -221,7 +239,7 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
221 239
222 std::scoped_lock lock{submit_mutex}; 240 std::scoped_lock lock{submit_mutex};
223 switch (const VkResult result = master_semaphore->SubmitQueue( 241 switch (const VkResult result = master_semaphore->SubmitQueue(
224 cmdbuf, signal_semaphore, wait_semaphore, signal_value)) { 242 cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore, signal_value)) {
225 case VK_SUCCESS: 243 case VK_SUCCESS:
226 break; 244 break;
227 case VK_ERROR_DEVICE_LOST: 245 case VK_ERROR_DEVICE_LOST:
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index da03803aa..f8d8ca80a 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -80,7 +80,8 @@ public:
80 80
81 /// Send work to a separate thread. 81 /// Send work to a separate thread.
82 template <typename T> 82 template <typename T>
83 void Record(T&& command) { 83 requires std::is_invocable_v<T, vk::CommandBuffer, vk::CommandBuffer>
84 void RecordWithUploadBuffer(T&& command) {
84 if (chunk->Record(command)) { 85 if (chunk->Record(command)) {
85 return; 86 return;
86 } 87 }
@@ -88,6 +89,15 @@ public:
88 (void)chunk->Record(command); 89 (void)chunk->Record(command);
89 } 90 }
90 91
92 template <typename T>
93 requires std::is_invocable_v<T, vk::CommandBuffer>
94 void Record(T&& c) {
95 this->RecordWithUploadBuffer(
96 [command = std::move(c)](vk::CommandBuffer cmdbuf, vk::CommandBuffer) {
97 command(cmdbuf);
98 });
99 }
100
91 /// Returns the current command buffer tick. 101 /// Returns the current command buffer tick.
92 [[nodiscard]] u64 CurrentTick() const noexcept { 102 [[nodiscard]] u64 CurrentTick() const noexcept {
93 return master_semaphore->CurrentTick(); 103 return master_semaphore->CurrentTick();
@@ -119,7 +129,7 @@ private:
119 public: 129 public:
120 virtual ~Command() = default; 130 virtual ~Command() = default;
121 131
122 virtual void Execute(vk::CommandBuffer cmdbuf) const = 0; 132 virtual void Execute(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) const = 0;
123 133
124 Command* GetNext() const { 134 Command* GetNext() const {
125 return next; 135 return next;
@@ -142,8 +152,8 @@ private:
142 TypedCommand(TypedCommand&&) = delete; 152 TypedCommand(TypedCommand&&) = delete;
143 TypedCommand& operator=(TypedCommand&&) = delete; 153 TypedCommand& operator=(TypedCommand&&) = delete;
144 154
145 void Execute(vk::CommandBuffer cmdbuf) const override { 155 void Execute(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) const override {
146 command(cmdbuf); 156 command(cmdbuf, upload_cmdbuf);
147 } 157 }
148 158
149 private: 159 private:
@@ -152,7 +162,7 @@ private:
152 162
153 class CommandChunk final { 163 class CommandChunk final {
154 public: 164 public:
155 void ExecuteAll(vk::CommandBuffer cmdbuf); 165 void ExecuteAll(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf);
156 166
157 template <typename T> 167 template <typename T>
158 bool Record(T& command) { 168 bool Record(T& command) {
@@ -228,6 +238,7 @@ private:
228 VideoCommon::QueryCacheBase<QueryCacheParams>* query_cache = nullptr; 238 VideoCommon::QueryCacheBase<QueryCacheParams>* query_cache = nullptr;
229 239
230 vk::CommandBuffer current_cmdbuf; 240 vk::CommandBuffer current_cmdbuf;
241 vk::CommandBuffer current_upload_cmdbuf;
231 242
232 std::unique_ptr<CommandChunk> chunk; 243 std::unique_ptr<CommandChunk> chunk;
233 std::function<void()> on_submit; 244 std::function<void()> on_submit;
diff --git a/src/video_core/renderer_vulkan/vk_smaa.cpp b/src/video_core/renderer_vulkan/vk_smaa.cpp
index 5efd7d66e..70644ea82 100644
--- a/src/video_core/renderer_vulkan/vk_smaa.cpp
+++ b/src/video_core/renderer_vulkan/vk_smaa.cpp
@@ -672,7 +672,7 @@ void SMAA::UploadImages(Scheduler& scheduler) {
672 UploadImage(m_device, m_allocator, scheduler, m_static_images[Search], search_extent, 672 UploadImage(m_device, m_allocator, scheduler, m_static_images[Search], search_extent,
673 VK_FORMAT_R8_UNORM, ARRAY_TO_SPAN(searchTexBytes)); 673 VK_FORMAT_R8_UNORM, ARRAY_TO_SPAN(searchTexBytes));
674 674
675 scheduler.Record([&](vk::CommandBuffer& cmdbuf) { 675 scheduler.Record([&](vk::CommandBuffer cmdbuf) {
676 for (auto& images : m_dynamic_images) { 676 for (auto& images : m_dynamic_images) {
677 for (size_t i = 0; i < MaxDynamicImage; i++) { 677 for (size_t i = 0; i < MaxDynamicImage; i++) {
678 ClearColorImage(cmdbuf, *images.images[i]); 678 ClearColorImage(cmdbuf, *images.images[i]);
@@ -707,7 +707,7 @@ VkImageView SMAA::Draw(Scheduler& scheduler, size_t image_index, VkImage source_
707 UpdateDescriptorSets(source_image_view, image_index); 707 UpdateDescriptorSets(source_image_view, image_index);
708 708
709 scheduler.RequestOutsideRenderPassOperationContext(); 709 scheduler.RequestOutsideRenderPassOperationContext();
710 scheduler.Record([=, this](vk::CommandBuffer& cmdbuf) { 710 scheduler.Record([=, this](vk::CommandBuffer cmdbuf) {
711 TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL); 711 TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL);
712 TransitionImageLayout(cmdbuf, edges_image, VK_IMAGE_LAYOUT_GENERAL); 712 TransitionImageLayout(cmdbuf, edges_image, VK_IMAGE_LAYOUT_GENERAL);
713 BeginRenderPass(cmdbuf, m_renderpasses[EdgeDetection], edge_detection_framebuffer, 713 BeginRenderPass(cmdbuf, m_renderpasses[EdgeDetection], edge_detection_framebuffer,
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
index d3deb9072..f63a20327 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@@ -36,6 +36,10 @@ public:
36 StagingBufferRef Request(size_t size, MemoryUsage usage, bool deferred = false); 36 StagingBufferRef Request(size_t size, MemoryUsage usage, bool deferred = false);
37 void FreeDeferred(StagingBufferRef& ref); 37 void FreeDeferred(StagingBufferRef& ref);
38 38
39 [[nodiscard]] VkBuffer StreamBuf() const noexcept {
40 return *stream_buffer;
41 }
42
39 void TickFrame(); 43 void TickFrame();
40 44
41private: 45private:
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index de34f6d49..5dbec2e62 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -1785,8 +1785,22 @@ ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
1785 : VideoCommon::ImageViewBase{info, view_info, gpu_addr_}, 1785 : VideoCommon::ImageViewBase{info, view_info, gpu_addr_},
1786 buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {} 1786 buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {}
1787 1787
1788ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams& params) 1788ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageViewParams& params)
1789 : VideoCommon::ImageViewBase{params} {} 1789 : VideoCommon::ImageViewBase{params}, device{&runtime.device} {
1790 if (device->HasNullDescriptor()) {
1791 return;
1792 }
1793
1794 // Handle fallback for devices without nullDescriptor
1795 ImageInfo info{};
1796 info.format = PixelFormat::A8B8G8R8_UNORM;
1797
1798 null_image = MakeImage(*device, runtime.memory_allocator, info, {});
1799 image_handle = *null_image;
1800 for (u32 i = 0; i < Shader::NUM_TEXTURE_TYPES; i++) {
1801 image_views[i] = MakeView(VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_IMAGE_ASPECT_COLOR_BIT);
1802 }
1803}
1790 1804
1791ImageView::~ImageView() = default; 1805ImageView::~ImageView() = default;
1792 1806
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 7a0807709..edf5d7635 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -267,6 +267,7 @@ private:
267 vk::ImageView depth_view; 267 vk::ImageView depth_view;
268 vk::ImageView stencil_view; 268 vk::ImageView stencil_view;
269 vk::ImageView color_view; 269 vk::ImageView color_view;
270 vk::Image null_image;
270 VkImage image_handle = VK_NULL_HANDLE; 271 VkImage image_handle = VK_NULL_HANDLE;
271 VkImageView render_target = VK_NULL_HANDLE; 272 VkImageView render_target = VK_NULL_HANDLE;
272 VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; 273 VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
diff --git a/src/video_core/texture_cache/slot_vector.h b/src/video_core/texture_cache/slot_vector.h
index 9df6a2903..3ffa2a661 100644
--- a/src/video_core/texture_cache/slot_vector.h
+++ b/src/video_core/texture_cache/slot_vector.h
@@ -138,6 +138,10 @@ public:
138 return Iterator(this, SlotId{SlotId::INVALID_INDEX}); 138 return Iterator(this, SlotId{SlotId::INVALID_INDEX});
139 } 139 }
140 140
141 [[nodiscard]] size_t size() const noexcept {
142 return values_capacity - free_list.size();
143 }
144
141private: 145private:
142 struct NonTrivialDummy { 146 struct NonTrivialDummy {
143 NonTrivialDummy() noexcept {} 147 NonTrivialDummy() noexcept {}
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 6900b8ffa..fde36a49c 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -869,7 +869,8 @@ bool Device::ShouldBoostClocks() const {
869 driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA || 869 driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA ||
870 driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP; 870 driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP;
871 871
872 const bool is_steam_deck = vendor_id == 0x1002 && device_id == 0x163F; 872 const bool is_steam_deck = (vendor_id == 0x1002 && device_id == 0x163F) ||
873 (vendor_id == 0x1002 && device_id == 0x1435);
873 874
874 const bool is_debugging = this->HasDebuggingToolAttached(); 875 const bool is_debugging = this->HasDebuggingToolAttached();
875 876
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 0487cd3b6..a0c70797f 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -1101,6 +1101,10 @@ public:
1101 return &handle; 1101 return &handle;
1102 } 1102 }
1103 1103
1104 VkCommandBuffer operator*() const noexcept {
1105 return handle;
1106 }
1107
1104 void Begin(const VkCommandBufferBeginInfo& begin_info) const { 1108 void Begin(const VkCommandBufferBeginInfo& begin_info) const {
1105 Check(dld->vkBeginCommandBuffer(handle, &begin_info)); 1109 Check(dld->vkBeginCommandBuffer(handle, &begin_info));
1106 } 1110 }
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 2afa72140..ed5750155 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -30,7 +30,6 @@
30#include <QSize> 30#include <QSize>
31#include <QStringLiteral> 31#include <QStringLiteral>
32#include <QSurfaceFormat> 32#include <QSurfaceFormat>
33#include <QTimer>
34#include <QWindow> 33#include <QWindow>
35#include <QtCore/qobjectdefs.h> 34#include <QtCore/qobjectdefs.h>
36 35
@@ -66,6 +65,8 @@ class QObject;
66class QPaintEngine; 65class QPaintEngine;
67class QSurface; 66class QSurface;
68 67
68constexpr int default_mouse_constrain_timeout = 10;
69
69EmuThread::EmuThread(Core::System& system) : m_system{system} {} 70EmuThread::EmuThread(Core::System& system) : m_system{system} {}
70 71
71EmuThread::~EmuThread() = default; 72EmuThread::~EmuThread() = default;
@@ -304,6 +305,9 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
304 Qt::QueuedConnection); 305 Qt::QueuedConnection);
305 connect(this, &GRenderWindow::ExitSignal, parent, &GMainWindow::OnExit, Qt::QueuedConnection); 306 connect(this, &GRenderWindow::ExitSignal, parent, &GMainWindow::OnExit, Qt::QueuedConnection);
306 connect(this, &GRenderWindow::TasPlaybackStateChanged, parent, &GMainWindow::OnTasStateChanged); 307 connect(this, &GRenderWindow::TasPlaybackStateChanged, parent, &GMainWindow::OnTasStateChanged);
308
309 mouse_constrain_timer.setInterval(default_mouse_constrain_timeout);
310 connect(&mouse_constrain_timer, &QTimer::timeout, this, &GRenderWindow::ConstrainMouse);
307} 311}
308 312
309void GRenderWindow::ExecuteProgram(std::size_t program_index) { 313void GRenderWindow::ExecuteProgram(std::size_t program_index) {
@@ -393,6 +397,22 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
393 QWidget::closeEvent(event); 397 QWidget::closeEvent(event);
394} 398}
395 399
400void GRenderWindow::leaveEvent(QEvent* event) {
401 if (Settings::values.mouse_panning) {
402 const QRect& rect = QWidget::geometry();
403 QPoint position = QCursor::pos();
404
405 qint32 x = qBound(rect.left(), position.x(), rect.right());
406 qint32 y = qBound(rect.top(), position.y(), rect.bottom());
407 // Only start the timer if the mouse has left the window bound.
408 // The leave event is also triggered when the window looses focus.
409 if (x != position.x() || y != position.y()) {
410 mouse_constrain_timer.start();
411 }
412 event->accept();
413 }
414}
415
396int GRenderWindow::QtKeyToSwitchKey(Qt::Key qt_key) { 416int GRenderWindow::QtKeyToSwitchKey(Qt::Key qt_key) {
397 static constexpr std::array<std::pair<Qt::Key, Settings::NativeKeyboard::Keys>, 106> key_map = { 417 static constexpr std::array<std::pair<Qt::Key, Settings::NativeKeyboard::Keys>, 106> key_map = {
398 std::pair<Qt::Key, Settings::NativeKeyboard::Keys>{Qt::Key_A, Settings::NativeKeyboard::A}, 418 std::pair<Qt::Key, Settings::NativeKeyboard::Keys>{Qt::Key_A, Settings::NativeKeyboard::A},
@@ -658,10 +678,19 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
658 input_subsystem->GetMouse()->TouchMove(touch_x, touch_y); 678 input_subsystem->GetMouse()->TouchMove(touch_x, touch_y);
659 input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y); 679 input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y);
660 680
681 // Center mouse for mouse panning
661 if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { 682 if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
662 QCursor::setPos(mapToGlobal(QPoint{center_x, center_y})); 683 QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
663 } 684 }
664 685
686 // Constrain mouse for mouse emulation with mouse panning
687 if (Settings::values.mouse_panning && Settings::values.mouse_enabled) {
688 const auto [clamped_mouse_x, clamped_mouse_y] = ClipToTouchScreen(x, y);
689 QCursor::setPos(mapToGlobal(
690 QPoint{static_cast<int>(clamped_mouse_x), static_cast<int>(clamped_mouse_y)}));
691 }
692
693 mouse_constrain_timer.stop();
665 emit MouseActivity(); 694 emit MouseActivity();
666} 695}
667 696
@@ -675,6 +704,31 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
675 input_subsystem->GetMouse()->ReleaseButton(button); 704 input_subsystem->GetMouse()->ReleaseButton(button);
676} 705}
677 706
707void GRenderWindow::ConstrainMouse() {
708 if (emu_thread == nullptr || !Settings::values.mouse_panning) {
709 mouse_constrain_timer.stop();
710 return;
711 }
712 if (!this->isActiveWindow()) {
713 mouse_constrain_timer.stop();
714 return;
715 }
716
717 if (Settings::values.mouse_enabled) {
718 const auto pos = mapFromGlobal(QCursor::pos());
719 const int new_pos_x = std::clamp(pos.x(), 0, width());
720 const int new_pos_y = std::clamp(pos.y(), 0, height());
721
722 QCursor::setPos(mapToGlobal(QPoint{new_pos_x, new_pos_y}));
723 return;
724 }
725
726 const int center_x = width() / 2;
727 const int center_y = height() / 2;
728
729 QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
730}
731
678void GRenderWindow::wheelEvent(QWheelEvent* event) { 732void GRenderWindow::wheelEvent(QWheelEvent* event) {
679 const int x = event->angleDelta().x(); 733 const int x = event->angleDelta().x();
680 const int y = event->angleDelta().y(); 734 const int y = event->angleDelta().y();
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 87b23df12..60edd464c 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -17,6 +17,7 @@
17#include <QString> 17#include <QString>
18#include <QStringList> 18#include <QStringList>
19#include <QThread> 19#include <QThread>
20#include <QTimer>
20#include <QWidget> 21#include <QWidget>
21#include <qglobal.h> 22#include <qglobal.h>
22#include <qnamespace.h> 23#include <qnamespace.h>
@@ -38,7 +39,6 @@ class QMouseEvent;
38class QObject; 39class QObject;
39class QResizeEvent; 40class QResizeEvent;
40class QShowEvent; 41class QShowEvent;
41class QTimer;
42class QTouchEvent; 42class QTouchEvent;
43class QWheelEvent; 43class QWheelEvent;
44 44
@@ -166,6 +166,7 @@ public:
166 std::pair<u32, u32> ScaleTouch(const QPointF& pos) const; 166 std::pair<u32, u32> ScaleTouch(const QPointF& pos) const;
167 167
168 void closeEvent(QCloseEvent* event) override; 168 void closeEvent(QCloseEvent* event) override;
169 void leaveEvent(QEvent* event) override;
169 170
170 void resizeEvent(QResizeEvent* event) override; 171 void resizeEvent(QResizeEvent* event) override;
171 172
@@ -229,6 +230,7 @@ private:
229 void TouchBeginEvent(const QTouchEvent* event); 230 void TouchBeginEvent(const QTouchEvent* event);
230 void TouchUpdateEvent(const QTouchEvent* event); 231 void TouchUpdateEvent(const QTouchEvent* event);
231 void TouchEndEvent(); 232 void TouchEndEvent();
233 void ConstrainMouse();
232 234
233 void RequestCameraCapture(); 235 void RequestCameraCapture();
234 void OnCameraCapture(int requestId, const QImage& img); 236 void OnCameraCapture(int requestId, const QImage& img);
@@ -268,6 +270,8 @@ private:
268 std::unique_ptr<QTimer> camera_timer; 270 std::unique_ptr<QTimer> camera_timer;
269#endif 271#endif
270 272
273 QTimer mouse_constrain_timer;
274
271 Core::System& system; 275 Core::System& system;
272 276
273protected: 277protected:
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index ef421c754..1010038b7 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -51,6 +51,8 @@ void ConfigureDebug::SetConfiguration() {
51 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue()); 51 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue());
52 ui->enable_renderdoc_hotkey->setEnabled(runtime_lock); 52 ui->enable_renderdoc_hotkey->setEnabled(runtime_lock);
53 ui->enable_renderdoc_hotkey->setChecked(Settings::values.enable_renderdoc_hotkey.GetValue()); 53 ui->enable_renderdoc_hotkey->setChecked(Settings::values.enable_renderdoc_hotkey.GetValue());
54 ui->disable_buffer_reorder->setEnabled(runtime_lock);
55 ui->disable_buffer_reorder->setChecked(Settings::values.disable_buffer_reorder.GetValue());
54 ui->enable_graphics_debugging->setEnabled(runtime_lock); 56 ui->enable_graphics_debugging->setEnabled(runtime_lock);
55 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue()); 57 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue());
56 ui->enable_shader_feedback->setEnabled(runtime_lock); 58 ui->enable_shader_feedback->setEnabled(runtime_lock);
@@ -96,6 +98,7 @@ void ConfigureDebug::ApplyConfiguration() {
96 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked(); 98 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked();
97 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); 99 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
98 Settings::values.enable_renderdoc_hotkey = ui->enable_renderdoc_hotkey->isChecked(); 100 Settings::values.enable_renderdoc_hotkey = ui->enable_renderdoc_hotkey->isChecked();
101 Settings::values.disable_buffer_reorder = ui->disable_buffer_reorder->isChecked();
99 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); 102 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked();
100 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); 103 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked();
101 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); 104 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked();
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 76fe98924..22b51f39c 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -271,19 +271,6 @@
271 </widget> 271 </widget>
272 </item> 272 </item>
273 <item row="8" column="0"> 273 <item row="8" column="0">
274 <widget class="QCheckBox" name="disable_macro_hle">
275 <property name="enabled">
276 <bool>true</bool>
277 </property>
278 <property name="toolTip">
279 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
280 </property>
281 <property name="text">
282 <string>Disable Macro HLE</string>
283 </property>
284 </widget>
285 </item>
286 <item row="7" column="0">
287 <widget class="QCheckBox" name="dump_macros"> 274 <widget class="QCheckBox" name="dump_macros">
288 <property name="enabled"> 275 <property name="enabled">
289 <bool>true</bool> 276 <bool>true</bool>
@@ -306,17 +293,27 @@
306 </property> 293 </property>
307 </widget> 294 </widget>
308 </item> 295 </item>
309 <item row="2" column="0"> 296 <item row="6" column="0">
310 <widget class="QCheckBox" name="enable_shader_feedback"> 297 <widget class="QCheckBox" name="dump_shaders">
298 <property name="enabled">
299 <bool>true</bool>
300 </property>
311 <property name="toolTip"> 301 <property name="toolTip">
312 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string> 302 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string>
313 </property> 303 </property>
314 <property name="text"> 304 <property name="text">
315 <string>Enable Shader Feedback</string> 305 <string>Dump Game Shaders</string>
316 </property> 306 </property>
317 </widget> 307 </widget>
318 </item> 308 </item>
319 <item row="6" column="0"> 309 <item row="1" column="0">
310 <widget class="QCheckBox" name="enable_renderdoc_hotkey">
311 <property name="text">
312 <string>Enable Renderdoc Hotkey</string>
313 </property>
314 </widget>
315 </item>
316 <item row="7" column="0">
320 <widget class="QCheckBox" name="disable_macro_jit"> 317 <widget class="QCheckBox" name="disable_macro_jit">
321 <property name="enabled"> 318 <property name="enabled">
322 <bool>true</bool> 319 <bool>true</bool>
@@ -330,20 +327,17 @@
330 </widget> 327 </widget>
331 </item> 328 </item>
332 <item row="9" column="0"> 329 <item row="9" column="0">
333 <spacer name="verticalSpacer_5"> 330 <widget class="QCheckBox" name="disable_macro_hle">
334 <property name="orientation"> 331 <property name="enabled">
335 <enum>Qt::Vertical</enum> 332 <bool>true</bool>
336 </property> 333 </property>
337 <property name="sizeType"> 334 <property name="toolTip">
338 <enum>QSizePolicy::Preferred</enum> 335 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
339 </property> 336 </property>
340 <property name="sizeHint" stdset="0"> 337 <property name="text">
341 <size> 338 <string>Disable Macro HLE</string>
342 <width>20</width>
343 <height>0</height>
344 </size>
345 </property> 339 </property>
346 </spacer> 340 </widget>
347 </item> 341 </item>
348 <item row="0" column="0"> 342 <item row="0" column="0">
349 <widget class="QCheckBox" name="enable_graphics_debugging"> 343 <widget class="QCheckBox" name="enable_graphics_debugging">
@@ -358,23 +352,39 @@
358 </property> 352 </property>
359 </widget> 353 </widget>
360 </item> 354 </item>
361 <item row="5" column="0"> 355 <item row="10" column="0">
362 <widget class="QCheckBox" name="dump_shaders"> 356 <spacer name="verticalSpacer_5">
363 <property name="enabled"> 357 <property name="orientation">
364 <bool>true</bool> 358 <enum>Qt::Vertical</enum>
359 </property>
360 <property name="sizeType">
361 <enum>QSizePolicy::Preferred</enum>
365 </property> 362 </property>
363 <property name="sizeHint" stdset="0">
364 <size>
365 <width>20</width>
366 <height>0</height>
367 </size>
368 </property>
369 </spacer>
370 </item>
371 <item row="2" column="0">
372 <widget class="QCheckBox" name="enable_shader_feedback">
366 <property name="toolTip"> 373 <property name="toolTip">
367 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string> 374 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
368 </property> 375 </property>
369 <property name="text"> 376 <property name="text">
370 <string>Dump Game Shaders</string> 377 <string>Enable Shader Feedback</string>
371 </property> 378 </property>
372 </widget> 379 </widget>
373 </item> 380 </item>
374 <item row="1" column="0"> 381 <item row="5" column="0">
375 <widget class="QCheckBox" name="enable_renderdoc_hotkey"> 382 <widget class="QCheckBox" name="disable_buffer_reorder">
383 <property name="toolTip">
384 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When checked, disables reording of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
385 </property>
376 <property name="text"> 386 <property name="text">
377 <string>Enable Renderdoc Hotkey</string> 387 <string>Disable Buffer Reorder</string>
378 </property> 388 </property>
379 </widget> 389 </widget>
380 </item> 390 </item>
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 6ef518b6a..10c788290 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -48,6 +48,7 @@
48#include "core/hle/service/am/applet_ae.h" 48#include "core/hle/service/am/applet_ae.h"
49#include "core/hle/service/am/applet_oe.h" 49#include "core/hle/service/am/applet_oe.h"
50#include "core/hle/service/am/applets/applets.h" 50#include "core/hle/service/am/applets/applets.h"
51#include "core/hle/service/set/set_sys.h"
51#include "yuzu/multiplayer/state.h" 52#include "yuzu/multiplayer/state.h"
52#include "yuzu/util/controller_navigation.h" 53#include "yuzu/util/controller_navigation.h"
53 54
@@ -187,7 +188,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
187#endif 188#endif
188 189
189constexpr int default_mouse_hide_timeout = 2500; 190constexpr int default_mouse_hide_timeout = 2500;
190constexpr int default_mouse_center_timeout = 10;
191constexpr int default_input_update_timeout = 1; 191constexpr int default_input_update_timeout = 1;
192 192
193constexpr size_t CopyBufferSize = 1_MiB; 193constexpr size_t CopyBufferSize = 1_MiB;
@@ -438,9 +438,6 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
438 connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor); 438 connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
439 connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor); 439 connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor);
440 440
441 mouse_center_timer.setInterval(default_mouse_center_timeout);
442 connect(&mouse_center_timer, &QTimer::timeout, this, &GMainWindow::CenterMouseCursor);
443
444 update_input_timer.setInterval(default_input_update_timeout); 441 update_input_timer.setInterval(default_input_update_timeout);
445 connect(&update_input_timer, &QTimer::timeout, this, &GMainWindow::UpdateInputDrivers); 442 connect(&update_input_timer, &QTimer::timeout, this, &GMainWindow::UpdateInputDrivers);
446 update_input_timer.start(); 443 update_input_timer.start();
@@ -1050,7 +1047,12 @@ void GMainWindow::InitializeWidgets() {
1050 statusBar()->addPermanentWidget(label); 1047 statusBar()->addPermanentWidget(label);
1051 } 1048 }
1052 1049
1053 // TODO (flTobi): Add the widget when multiplayer is fully implemented 1050 firmware_label = new QLabel();
1051 firmware_label->setObjectName(QStringLiteral("FirmwareLabel"));
1052 firmware_label->setVisible(false);
1053 firmware_label->setFocusPolicy(Qt::NoFocus);
1054 statusBar()->addPermanentWidget(firmware_label);
1055
1054 statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0); 1056 statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
1055 statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0); 1057 statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
1056 1058
@@ -1372,14 +1374,6 @@ void GMainWindow::InitializeHotkeys() {
1372 } 1374 }
1373 }); 1375 });
1374 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { 1376 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
1375 if (Settings::values.mouse_enabled) {
1376 Settings::values.mouse_panning = false;
1377 QMessageBox::warning(
1378 this, tr("Emulated mouse is enabled"),
1379 tr("Real mouse input and mouse panning are incompatible. Please disable the "
1380 "emulated mouse in input advanced settings to allow mouse panning."));
1381 return;
1382 }
1383 Settings::values.mouse_panning = !Settings::values.mouse_panning; 1377 Settings::values.mouse_panning = !Settings::values.mouse_panning;
1384 if (Settings::values.mouse_panning) { 1378 if (Settings::values.mouse_panning) {
1385 render_window->installEventFilter(render_window); 1379 render_window->installEventFilter(render_window);
@@ -2171,6 +2165,10 @@ void GMainWindow::OnEmulationStopped() {
2171 emu_frametime_label->setVisible(false); 2165 emu_frametime_label->setVisible(false);
2172 renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan); 2166 renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan);
2173 2167
2168 if (!firmware_label->text().isEmpty()) {
2169 firmware_label->setVisible(true);
2170 }
2171
2174 current_game_path.clear(); 2172 current_game_path.clear();
2175 2173
2176 // When closing the game, destroy the GLWindow to clear the context after the game is closed 2174 // When closing the game, destroy the GLWindow to clear the context after the game is closed
@@ -4613,6 +4611,7 @@ void GMainWindow::UpdateStatusBar() {
4613 emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue()); 4611 emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue());
4614 game_fps_label->setVisible(true); 4612 game_fps_label->setVisible(true);
4615 emu_frametime_label->setVisible(true); 4613 emu_frametime_label->setVisible(true);
4614 firmware_label->setVisible(false);
4616} 4615}
4617 4616
4618void GMainWindow::UpdateGPUAccuracyButton() { 4617void GMainWindow::UpdateGPUAccuracyButton() {
@@ -4722,26 +4721,10 @@ void GMainWindow::ShowMouseCursor() {
4722 } 4721 }
4723} 4722}
4724 4723
4725void GMainWindow::CenterMouseCursor() {
4726 if (emu_thread == nullptr || !Settings::values.mouse_panning) {
4727 mouse_center_timer.stop();
4728 return;
4729 }
4730 if (!this->isActiveWindow()) {
4731 mouse_center_timer.stop();
4732 return;
4733 }
4734 const int center_x = render_window->width() / 2;
4735 const int center_y = render_window->height() / 2;
4736
4737 QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
4738}
4739
4740void GMainWindow::OnMouseActivity() { 4724void GMainWindow::OnMouseActivity() {
4741 if (!Settings::values.mouse_panning) { 4725 if (!Settings::values.mouse_panning) {
4742 ShowMouseCursor(); 4726 ShowMouseCursor();
4743 } 4727 }
4744 mouse_center_timer.stop();
4745} 4728}
4746 4729
4747void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { 4730void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
@@ -4832,6 +4815,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
4832 "games.")); 4815 "games."));
4833 } 4816 }
4834 4817
4818 SetFirmwareVersion();
4819
4835 if (behavior == ReinitializeKeyBehavior::Warning) { 4820 if (behavior == ReinitializeKeyBehavior::Warning) {
4836 game_list->PopulateAsync(UISettings::values.game_dirs); 4821 game_list->PopulateAsync(UISettings::values.game_dirs);
4837 } 4822 }
@@ -4859,7 +4844,7 @@ bool GMainWindow::CheckSystemArchiveDecryption() {
4859} 4844}
4860 4845
4861bool GMainWindow::CheckFirmwarePresence() { 4846bool GMainWindow::CheckFirmwarePresence() {
4862 constexpr u64 MiiEditId = 0x0100000000001009ull; 4847 constexpr u64 MiiEditId = static_cast<u64>(Service::AM::Applets::AppletProgramId::MiiEdit);
4863 4848
4864 auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); 4849 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4865 if (!bis_system) { 4850 if (!bis_system) {
@@ -4874,6 +4859,28 @@ bool GMainWindow::CheckFirmwarePresence() {
4874 return true; 4859 return true;
4875} 4860}
4876 4861
4862void GMainWindow::SetFirmwareVersion() {
4863 Service::Set::FirmwareVersionFormat firmware_data{};
4864 const auto result = Service::Set::GetFirmwareVersionImpl(
4865 firmware_data, *system, Service::Set::GetFirmwareVersionType::Version2);
4866
4867 if (result.IsError() || !CheckFirmwarePresence()) {
4868 LOG_INFO(Frontend, "Installed firmware: No firmware available");
4869 firmware_label->setVisible(false);
4870 return;
4871 }
4872
4873 firmware_label->setVisible(true);
4874
4875 const std::string display_version(firmware_data.display_version.data());
4876 const std::string display_title(firmware_data.display_title.data());
4877
4878 LOG_INFO(Frontend, "Installed firmware: {}", display_title);
4879
4880 firmware_label->setText(QString::fromStdString(display_version));
4881 firmware_label->setToolTip(QString::fromStdString(display_title));
4882}
4883
4877bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, 4884bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
4878 u64* selected_title_id, u8* selected_content_record_type) { 4885 u64* selected_title_id, u8* selected_content_record_type) {
4879 using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>; 4886 using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
@@ -5018,22 +5025,6 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
5018 AcceptDropEvent(event); 5025 AcceptDropEvent(event);
5019} 5026}
5020 5027
5021void GMainWindow::leaveEvent(QEvent* event) {
5022 if (Settings::values.mouse_panning) {
5023 const QRect& rect = geometry();
5024 QPoint position = QCursor::pos();
5025
5026 qint32 x = qBound(rect.left(), position.x(), rect.right());
5027 qint32 y = qBound(rect.top(), position.y(), rect.bottom());
5028 // Only start the timer if the mouse has left the window bound.
5029 // The leave event is also triggered when the window looses focus.
5030 if (x != position.x() || y != position.y()) {
5031 mouse_center_timer.start();
5032 }
5033 event->accept();
5034 }
5035}
5036
5037bool GMainWindow::ConfirmChangeGame() { 5028bool GMainWindow::ConfirmChangeGame() {
5038 if (emu_thread == nullptr) 5029 if (emu_thread == nullptr)
5039 return true; 5030 return true;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 2e13e1834..530e445f9 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -452,13 +452,13 @@ private:
452 void UpdateInputDrivers(); 452 void UpdateInputDrivers();
453 void HideMouseCursor(); 453 void HideMouseCursor();
454 void ShowMouseCursor(); 454 void ShowMouseCursor();
455 void CenterMouseCursor();
456 void OpenURL(const QUrl& url); 455 void OpenURL(const QUrl& url);
457 void LoadTranslation(); 456 void LoadTranslation();
458 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); 457 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
459 bool CheckDarkMode(); 458 bool CheckDarkMode();
460 bool CheckSystemArchiveDecryption(); 459 bool CheckSystemArchiveDecryption();
461 bool CheckFirmwarePresence(); 460 bool CheckFirmwarePresence();
461 void SetFirmwareVersion();
462 void ConfigureFilesystemProvider(const std::string& filepath); 462 void ConfigureFilesystemProvider(const std::string& filepath);
463 /** 463 /**
464 * Open (or not) the right confirm dialog based on current setting and game exit lock 464 * Open (or not) the right confirm dialog based on current setting and game exit lock
@@ -513,6 +513,7 @@ private:
513 QLabel* game_fps_label = nullptr; 513 QLabel* game_fps_label = nullptr;
514 QLabel* emu_frametime_label = nullptr; 514 QLabel* emu_frametime_label = nullptr;
515 QLabel* tas_label = nullptr; 515 QLabel* tas_label = nullptr;
516 QLabel* firmware_label = nullptr;
516 QPushButton* gpu_accuracy_button = nullptr; 517 QPushButton* gpu_accuracy_button = nullptr;
517 QPushButton* renderer_status_button = nullptr; 518 QPushButton* renderer_status_button = nullptr;
518 QPushButton* dock_status_button = nullptr; 519 QPushButton* dock_status_button = nullptr;
@@ -534,7 +535,6 @@ private:
534 bool auto_paused = false; 535 bool auto_paused = false;
535 bool auto_muted = false; 536 bool auto_muted = false;
536 QTimer mouse_hide_timer; 537 QTimer mouse_hide_timer;
537 QTimer mouse_center_timer;
538 QTimer update_input_timer; 538 QTimer update_input_timer;
539 539
540 QString startup_icon_theme; 540 QString startup_icon_theme;
@@ -591,5 +591,4 @@ protected:
591 void dropEvent(QDropEvent* event) override; 591 void dropEvent(QDropEvent* event) override;
592 void dragEnterEvent(QDragEnterEvent* event) override; 592 void dragEnterEvent(QDragEnterEvent* event) override;
593 void dragMoveEvent(QDragMoveEvent* event) override; 593 void dragMoveEvent(QDragMoveEvent* event) override;
594 void leaveEvent(QEvent* event) override;
595}; 594};