summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt34
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt23
-rw-r--r--src/android/app/src/main/jni/native.cpp91
-rw-r--r--src/common/fs/fs.cpp6
-rw-r--r--src/common/settings.h11
-rw-r--r--src/core/hid/emulated_controller.cpp19
-rw-r--r--src/input_common/drivers/mouse.cpp99
-rw-r--r--src/input_common/drivers/mouse.h2
-rw-r--r--src/input_common/drivers/sdl_driver.cpp32
-rw-r--r--src/input_common/helpers/joycon_driver.cpp42
-rw-r--r--src/input_common/helpers/joycon_driver.h1
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.cpp8
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm.cpp2
-rw-r--r--src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp4
-rw-r--r--src/shader_recompiler/shader_info.h1
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt1
-rw-r--r--src/video_core/host_shaders/opengl_lmem_warmup.comp47
-rw-r--r--src/video_core/renderer_opengl/gl_compute_pipeline.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_compute_pipeline.h5
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_device.h5
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.h5
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp6
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.cpp13
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.h3
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp16
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp18
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp81
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_fsr.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_fsr.h1
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.h1
-rw-r--r--src/video_core/renderer_vulkan/vk_smaa.cpp39
-rw-r--r--src/video_core/renderer_vulkan/vk_smaa.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp105
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp23
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_turbo_mode.cpp10
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp26
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h8
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp173
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.h28
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp50
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h161
-rw-r--r--src/yuzu/CMakeLists.txt3
-rw-r--r--src/yuzu/configuration/config.cpp33
-rw-r--r--src/yuzu/configuration/config.h2
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp7
-rw-r--r--src/yuzu/configuration/configure_input_advanced.ui37
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp16
-rw-r--r--src/yuzu/configuration/configure_input_player.ui96
-rw-r--r--src/yuzu/configuration/configure_mouse_panning.cpp79
-rw-r--r--src/yuzu/configuration/configure_mouse_panning.h35
-rw-r--r--src/yuzu/configuration/configure_mouse_panning.ui238
-rw-r--r--src/yuzu/configuration/configure_ringcon.ui2
-rw-r--r--src/yuzu/main.cpp58
-rw-r--r--src/yuzu/main.h2
-rw-r--r--src/yuzu_cmd/default_ini.h26
66 files changed, 1230 insertions, 643 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
index dd6c895fd..f54dccc69 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
@@ -29,7 +29,6 @@ import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
29import org.yuzu.yuzu_emu.model.Game 29import org.yuzu.yuzu_emu.model.Game
30import org.yuzu.yuzu_emu.model.GamesViewModel 30import org.yuzu.yuzu_emu.model.GamesViewModel
31import org.yuzu.yuzu_emu.model.HomeViewModel 31import org.yuzu.yuzu_emu.model.HomeViewModel
32import org.yuzu.yuzu_emu.utils.FileUtil
33 32
34class SearchFragment : Fragment() { 33class SearchFragment : Fragment() {
35 private var _binding: FragmentSearchBinding? = null 34 private var _binding: FragmentSearchBinding? = null
@@ -128,10 +127,7 @@ class SearchFragment : Fragment() {
128 127
129 R.id.chip_homebrew -> baseList.filter { it.isHomebrew } 128 R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
130 129
131 R.id.chip_retail -> baseList.filter { 130 R.id.chip_retail -> baseList.filter { !it.isHomebrew }
132 FileUtil.hasExtension(it.path, "xci") ||
133 FileUtil.hasExtension(it.path, "nsp")
134 }
135 131
136 else -> baseList 132 else -> baseList
137 } 133 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
index 6a048e39f..6527c64ab 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
@@ -43,7 +43,7 @@ class Game(
43 43
44 companion object { 44 companion object {
45 val extensions: Set<String> = HashSet( 45 val extensions: Set<String> = HashSet(
46 listOf(".xci", ".nsp", ".nca", ".nro") 46 listOf("xci", "nsp", "nca", "nro")
47 ) 47 )
48 } 48 }
49} 49}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 3086cfad3..f7d7aed1e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -296,7 +296,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
296 return@registerForActivityResult 296 return@registerForActivityResult
297 } 297 }
298 298
299 if (!FileUtil.hasExtension(result, "keys")) { 299 if (FileUtil.getExtension(result) != "keys") {
300 MessageDialogFragment.newInstance( 300 MessageDialogFragment.newInstance(
301 R.string.reading_keys_failure, 301 R.string.reading_keys_failure,
302 R.string.install_prod_keys_failure_extension_description 302 R.string.install_prod_keys_failure_extension_description
@@ -393,7 +393,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
393 return@registerForActivityResult 393 return@registerForActivityResult
394 } 394 }
395 395
396 if (!FileUtil.hasExtension(result, "bin")) { 396 if (FileUtil.getExtension(result) != "bin") {
397 MessageDialogFragment.newInstance( 397 MessageDialogFragment.newInstance(
398 R.string.reading_keys_failure, 398 R.string.reading_keys_failure,
399 R.string.install_amiibo_keys_failure_extension_description 399 R.string.install_amiibo_keys_failure_extension_description
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
index 9f3bbe56f..142af5f26 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@@ -7,7 +7,6 @@ import android.content.Context
7import android.database.Cursor 7import android.database.Cursor
8import android.net.Uri 8import android.net.Uri
9import android.provider.DocumentsContract 9import android.provider.DocumentsContract
10import android.provider.OpenableColumns
11import androidx.documentfile.provider.DocumentFile 10import androidx.documentfile.provider.DocumentFile
12import java.io.BufferedInputStream 11import java.io.BufferedInputStream
13import java.io.File 12import java.io.File
@@ -185,19 +184,18 @@ object FileUtil {
185 184
186 /** 185 /**
187 * Get file display name from given path 186 * Get file display name from given path
188 * @param path content uri path 187 * @param uri content uri
189 * @return String display name 188 * @return String display name
190 */ 189 */
191 fun getFilename(context: Context, path: String): String { 190 fun getFilename(uri: Uri): String {
192 val resolver = context.contentResolver 191 val resolver = YuzuApplication.appContext.contentResolver
193 val columns = arrayOf( 192 val columns = arrayOf(
194 DocumentsContract.Document.COLUMN_DISPLAY_NAME 193 DocumentsContract.Document.COLUMN_DISPLAY_NAME
195 ) 194 )
196 var filename = "" 195 var filename = ""
197 var c: Cursor? = null 196 var c: Cursor? = null
198 try { 197 try {
199 val mUri = Uri.parse(path) 198 c = resolver.query(uri, columns, null, null, null)
200 c = resolver.query(mUri, columns, null, null, null)
201 c!!.moveToNext() 199 c!!.moveToNext()
202 filename = c.getString(0) 200 filename = c.getString(0)
203 } catch (e: Exception) { 201 } catch (e: Exception) {
@@ -326,25 +324,9 @@ object FileUtil {
326 } 324 }
327 } 325 }
328 326
329 fun hasExtension(path: String, extension: String): Boolean = 327 fun getExtension(uri: Uri): String {
330 path.substring(path.lastIndexOf(".") + 1).contains(extension) 328 val fileName = getFilename(uri)
331 329 return fileName.substring(fileName.lastIndexOf(".") + 1)
332 fun hasExtension(uri: Uri, extension: String): Boolean { 330 .lowercase()
333 val fileName: String?
334 val cursor = YuzuApplication.appContext.contentResolver.query(uri, null, null, null, null)
335 val nameIndex = cursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME)
336 cursor?.moveToFirst()
337
338 if (nameIndex == null) {
339 return false
340 }
341
342 fileName = cursor.getString(nameIndex)
343 cursor.close()
344
345 if (fileName == null) {
346 return false
347 }
348 return fileName.substring(fileName.lastIndexOf(".") + 1).contains(extension)
349 } 331 }
350} 332}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
index ee9f3e570..f8e7eeca7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.utils
6import android.content.SharedPreferences 6import android.content.SharedPreferences
7import android.net.Uri 7import android.net.Uri
8import androidx.preference.PreferenceManager 8import androidx.preference.PreferenceManager
9import java.util.*
10import kotlinx.serialization.encodeToString 9import kotlinx.serialization.encodeToString
11import kotlinx.serialization.json.Json 10import kotlinx.serialization.json.Json
12import org.yuzu.yuzu_emu.NativeLibrary 11import org.yuzu.yuzu_emu.NativeLibrary
@@ -33,15 +32,9 @@ object GameHelper {
33 val children = FileUtil.listFiles(context, gamesUri) 32 val children = FileUtil.listFiles(context, gamesUri)
34 for (file in children) { 33 for (file in children) {
35 if (!file.isDirectory) { 34 if (!file.isDirectory) {
36 val filename = file.uri.toString() 35 // Check that the file has an extension we care about before trying to read out of it.
37 val extensionStart = filename.lastIndexOf('.') 36 if (Game.extensions.contains(FileUtil.getExtension(file.uri))) {
38 if (extensionStart > 0) { 37 games.add(getGame(file.uri))
39 val fileExtension = filename.substring(extensionStart)
40
41 // Check that the file has an extension we care about before trying to read out of it.
42 if (Game.extensions.contains(fileExtension.lowercase(Locale.getDefault()))) {
43 games.add(getGame(filename))
44 }
45 } 38 }
46 } 39 }
47 } 40 }
@@ -59,21 +52,19 @@ object GameHelper {
59 return games.toList() 52 return games.toList()
60 } 53 }
61 54
62 private fun getGame(filePath: String): Game { 55 private fun getGame(uri: Uri): Game {
56 val filePath = uri.toString()
63 var name = NativeLibrary.getTitle(filePath) 57 var name = NativeLibrary.getTitle(filePath)
64 58
65 // If the game's title field is empty, use the filename. 59 // If the game's title field is empty, use the filename.
66 if (name.isEmpty()) { 60 if (name.isEmpty()) {
67 name = filePath.substring(filePath.lastIndexOf("/") + 1) 61 name = FileUtil.getFilename(uri)
68 } 62 }
69 var gameId = NativeLibrary.getGameId(filePath) 63 var gameId = NativeLibrary.getGameId(filePath)
70 64
71 // If the game's ID field is empty, use the filename without extension. 65 // If the game's ID field is empty, use the filename without extension.
72 if (gameId.isEmpty()) { 66 if (gameId.isEmpty()) {
73 gameId = filePath.substring( 67 gameId = name.substring(0, name.lastIndexOf("."))
74 filePath.lastIndexOf("/") + 1,
75 filePath.lastIndexOf(".")
76 )
77 } 68 }
78 69
79 val newGame = Game( 70 val newGame = Game(
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index d576aac50..8bc6a4a04 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -60,6 +60,9 @@
60#include "video_core/rasterizer_interface.h" 60#include "video_core/rasterizer_interface.h"
61#include "video_core/renderer_base.h" 61#include "video_core/renderer_base.h"
62 62
63#define jconst [[maybe_unused]] const auto
64#define jauto [[maybe_unused]] auto
65
63namespace { 66namespace {
64 67
65class EmulationSession final { 68class EmulationSession final {
@@ -99,8 +102,8 @@ public:
99 } 102 }
100 103
101 int InstallFileToNand(std::string filename) { 104 int InstallFileToNand(std::string filename) {
102 const auto copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, 105 jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
103 std::size_t block_size) { 106 std::size_t block_size) {
104 if (src == nullptr || dest == nullptr) { 107 if (src == nullptr || dest == nullptr) {
105 return false; 108 return false;
106 } 109 }
@@ -109,10 +112,10 @@ public:
109 } 112 }
110 113
111 using namespace Common::Literals; 114 using namespace Common::Literals;
112 std::vector<u8> buffer(1_MiB); 115 [[maybe_unused]] std::vector<u8> buffer(1_MiB);
113 116
114 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { 117 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
115 const auto read = src->Read(buffer.data(), buffer.size(), i); 118 jconst read = src->Read(buffer.data(), buffer.size(), i);
116 dest->Write(buffer.data(), read, i); 119 dest->Write(buffer.data(), read, i);
117 } 120 }
118 return true; 121 return true;
@@ -129,14 +132,14 @@ public:
129 m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); 132 m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
130 m_system.GetFileSystemController().CreateFactories(*m_vfs); 133 m_system.GetFileSystemController().CreateFactories(*m_vfs);
131 134
132 std::shared_ptr<FileSys::NSP> nsp; 135 [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
133 if (filename.ends_with("nsp")) { 136 if (filename.ends_with("nsp")) {
134 nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); 137 nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
135 if (nsp->IsExtractedType()) { 138 if (nsp->IsExtractedType()) {
136 return InstallError; 139 return InstallError;
137 } 140 }
138 } else if (filename.ends_with("xci")) { 141 } else if (filename.ends_with("xci")) {
139 const auto xci = 142 jconst xci =
140 std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); 143 std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
141 nsp = xci->GetSecurePartitionNSP(); 144 nsp = xci->GetSecurePartitionNSP();
142 } else { 145 } else {
@@ -151,7 +154,7 @@ public:
151 return InstallError; 154 return InstallError;
152 } 155 }
153 156
154 const auto res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry( 157 jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
155 *nsp, true, copy_func); 158 *nsp, true, copy_func);
156 159
157 switch (res) { 160 switch (res) {
@@ -234,7 +237,7 @@ public:
234 m_system.SetFilesystem(m_vfs); 237 m_system.SetFilesystem(m_vfs);
235 238
236 // Initialize system. 239 // Initialize system.
237 auto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); 240 jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
238 m_software_keyboard = android_keyboard.get(); 241 m_software_keyboard = android_keyboard.get();
239 m_system.SetShuttingDown(false); 242 m_system.SetShuttingDown(false);
240 m_system.ApplySettings(); 243 m_system.ApplySettings();
@@ -332,7 +335,7 @@ public:
332 335
333 while (true) { 336 while (true) {
334 { 337 {
335 std::unique_lock lock(m_mutex); 338 [[maybe_unused]] std::unique_lock lock(m_mutex);
336 if (m_cv.wait_for(lock, std::chrono::milliseconds(800), 339 if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
337 [&]() { return !m_is_running; })) { 340 [&]() { return !m_is_running; })) {
338 // Emulation halted. 341 // Emulation halted.
@@ -364,7 +367,7 @@ public:
364 } 367 }
365 368
366 bool IsHandheldOnly() { 369 bool IsHandheldOnly() {
367 const auto npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); 370 jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
368 371
369 if (npad_style_set.fullkey == 1) { 372 if (npad_style_set.fullkey == 1) {
370 return false; 373 return false;
@@ -377,17 +380,17 @@ public:
377 return !Settings::values.use_docked_mode.GetValue(); 380 return !Settings::values.use_docked_mode.GetValue();
378 } 381 }
379 382
380 void SetDeviceType(int index, int type) { 383 void SetDeviceType([[maybe_unused]] int index, int type) {
381 auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); 384 jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
382 controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type)); 385 controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
383 } 386 }
384 387
385 void OnGamepadConnectEvent(int index) { 388 void OnGamepadConnectEvent([[maybe_unused]] int index) {
386 auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); 389 jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
387 390
388 // Ensure that player1 is configured correctly and handheld disconnected 391 // Ensure that player1 is configured correctly and handheld disconnected
389 if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) { 392 if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
390 auto handheld = 393 jauto handheld =
391 m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); 394 m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
392 395
393 if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { 396 if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
@@ -399,7 +402,8 @@ public:
399 402
400 // Ensure that handheld is configured correctly and player 1 disconnected 403 // Ensure that handheld is configured correctly and player 1 disconnected
401 if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) { 404 if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
402 auto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); 405 jauto player1 =
406 m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
403 407
404 if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { 408 if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
405 player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); 409 player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
@@ -413,8 +417,8 @@ public:
413 } 417 }
414 } 418 }
415 419
416 void OnGamepadDisconnectEvent(int index) { 420 void OnGamepadDisconnectEvent([[maybe_unused]] int index) {
417 auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); 421 jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
418 controller->Disconnect(); 422 controller->Disconnect();
419 } 423 }
420 424
@@ -430,7 +434,7 @@ private:
430 }; 434 };
431 435
432 RomMetadata GetRomMetadata(const std::string& path) { 436 RomMetadata GetRomMetadata(const std::string& path) {
433 if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) { 437 if (jauto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
434 return search->second; 438 return search->second;
435 } 439 }
436 440
@@ -438,14 +442,14 @@ private:
438 } 442 }
439 443
440 RomMetadata CacheRomMetadata(const std::string& path) { 444 RomMetadata CacheRomMetadata(const std::string& path) {
441 const auto file = Core::GetGameFileFromPath(m_vfs, path); 445 jconst file = Core::GetGameFileFromPath(m_vfs, path);
442 auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); 446 jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
443 447
444 RomMetadata entry; 448 RomMetadata entry;
445 loader->ReadTitle(entry.title); 449 loader->ReadTitle(entry.title);
446 loader->ReadIcon(entry.icon); 450 loader->ReadIcon(entry.icon);
447 if (loader->GetFileType() == Loader::FileType::NRO) { 451 if (loader->GetFileType() == Loader::FileType::NRO) {
448 auto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get()); 452 jauto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
449 entry.isHomebrew = loader_nro->IsHomebrew(); 453 entry.isHomebrew = loader_nro->IsHomebrew();
450 } else { 454 } else {
451 entry.isHomebrew = false; 455 entry.isHomebrew = false;
@@ -516,7 +520,7 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
516 520
517 SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); }); 521 SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); });
518 522
519 const auto result = EmulationSession::GetInstance().InitializeEmulation(filepath); 523 jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath);
520 if (result != Core::SystemResultStatus::Success) { 524 if (result != Core::SystemResultStatus::Success) {
521 return result; 525 return result;
522 } 526 }
@@ -528,24 +532,25 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
528 532
529extern "C" { 533extern "C" {
530 534
531void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jclass clazz, jobject surf) { 535void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject instance,
536 [[maybe_unused]] jobject surf) {
532 EmulationSession::GetInstance().SetNativeWindow(ANativeWindow_fromSurface(env, surf)); 537 EmulationSession::GetInstance().SetNativeWindow(ANativeWindow_fromSurface(env, surf));
533 EmulationSession::GetInstance().SurfaceChanged(); 538 EmulationSession::GetInstance().SurfaceChanged();
534} 539}
535 540
536void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jclass clazz) { 541void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jobject instance) {
537 ANativeWindow_release(EmulationSession::GetInstance().NativeWindow()); 542 ANativeWindow_release(EmulationSession::GetInstance().NativeWindow());
538 EmulationSession::GetInstance().SetNativeWindow(nullptr); 543 EmulationSession::GetInstance().SetNativeWindow(nullptr);
539 EmulationSession::GetInstance().SurfaceChanged(); 544 EmulationSession::GetInstance().SurfaceChanged();
540} 545}
541 546
542void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jclass clazz, 547void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject instance,
543 jstring j_directory) { 548 [[maybe_unused]] jstring j_directory) {
544 Common::FS::SetAppDirectory(GetJString(env, j_directory)); 549 Common::FS::SetAppDirectory(GetJString(env, j_directory));
545} 550}
546 551
547int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jclass clazz, 552int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
548 jstring j_file) { 553 [[maybe_unused]] jstring j_file) {
549 return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file)); 554 return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file));
550} 555}
551 556
@@ -570,7 +575,7 @@ void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* e
570} 575}
571 576
572jboolean JNICALL Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading( 577jboolean JNICALL Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading(
573 JNIEnv* env, [[maybe_unused]] jobject instance) { 578 JNIEnv* env, jobject instance) {
574#ifdef ARCHITECTURE_arm64 579#ifdef ARCHITECTURE_arm64
575 // If the KGSL device exists custom drivers can be loaded using adrenotools 580 // If the KGSL device exists custom drivers can be loaded using adrenotools
576 return SupportsCustomDriver(); 581 return SupportsCustomDriver();
@@ -648,8 +653,8 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(JNIEnv*
648 return static_cast<jboolean>(true); 653 return static_cast<jboolean>(true);
649} 654}
650jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent(JNIEnv* env, jclass clazz, 655jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent(JNIEnv* env, jclass clazz,
651 [[maybe_unused]] jint j_device, 656 jint j_device, jint j_button,
652 jint j_button, jint action) { 657 jint action) {
653 if (EmulationSession::GetInstance().IsRunning()) { 658 if (EmulationSession::GetInstance().IsRunning()) {
654 // Ensure gamepad is connected 659 // Ensure gamepad is connected
655 EmulationSession::GetInstance().OnGamepadConnectEvent(j_device); 660 EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
@@ -718,8 +723,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass c
718} 723}
719 724
720jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz, 725jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz,
721 [[maybe_unused]] jstring j_filename) { 726 jstring j_filename) {
722 auto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename)); 727 jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
723 jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size())); 728 jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
724 env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), 729 env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
725 reinterpret_cast<jbyte*>(icon_data.data())); 730 reinterpret_cast<jbyte*>(icon_data.data()));
@@ -727,8 +732,8 @@ jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass cla
727} 732}
728 733
729jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz, 734jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz,
730 [[maybe_unused]] jstring j_filename) { 735 jstring j_filename) {
731 auto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename)); 736 jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
732 return env->NewStringUTF(title.c_str()); 737 return env->NewStringUTF(title.c_str());
733} 738}
734 739
@@ -743,22 +748,21 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass claz
743} 748}
744 749
745jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz, 750jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz,
746 [[maybe_unused]] jstring j_filename) { 751 jstring j_filename) {
747 return env->NewStringUTF(""); 752 return env->NewStringUTF("");
748} 753}
749 754
750jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz, 755jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz,
751 [[maybe_unused]] jstring j_filename) { 756 jstring j_filename) {
752 return env->NewStringUTF(""); 757 return env->NewStringUTF("");
753} 758}
754 759
755jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz, 760jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz,
756 [[maybe_unused]] jstring j_filename) { 761 jstring j_filename) {
757 return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename)); 762 return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
758} 763}
759 764
760void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation 765void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) {
761 [[maybe_unused]] (JNIEnv* env, jclass clazz) {
762 // Create the default config.ini. 766 // Create the default config.ini.
763 Config{}; 767 Config{};
764 // Initialize the emulated system. 768 // Initialize the emulated system.
@@ -770,8 +774,7 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass cl
770} 774}
771 775
772void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2Ljava_lang_String_2Z( 776void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2Ljava_lang_String_2Z(
773 JNIEnv* env, jclass clazz, [[maybe_unused]] jstring j_file, 777 JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate) {}
774 [[maybe_unused]] jstring j_savestate, [[maybe_unused]] jboolean j_delete_savestate) {}
775 778
776void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass clazz) { 779void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass clazz) {
777 Config{}; 780 Config{};
@@ -816,7 +819,7 @@ jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jcl
816 jdoubleArray j_stats = env->NewDoubleArray(4); 819 jdoubleArray j_stats = env->NewDoubleArray(4);
817 820
818 if (EmulationSession::GetInstance().IsRunning()) { 821 if (EmulationSession::GetInstance().IsRunning()) {
819 const auto results = EmulationSession::GetInstance().PerfStats(); 822 jconst results = EmulationSession::GetInstance().PerfStats();
820 823
821 // Converting the structure into an array makes it easier to pass it to the frontend 824 // Converting the structure into an array makes it easier to pass it to the frontend
822 double stats[4] = {results.system_fps, results.average_game_fps, results.frametime, 825 double stats[4] = {results.system_fps, results.average_game_fps, results.frametime,
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp
index 1baf6d746..36e67c145 100644
--- a/src/common/fs/fs.cpp
+++ b/src/common/fs/fs.cpp
@@ -605,6 +605,12 @@ fs::file_type GetEntryType(const fs::path& path) {
605} 605}
606 606
607u64 GetSize(const fs::path& path) { 607u64 GetSize(const fs::path& path) {
608#ifdef ANDROID
609 if (Android::IsContentUri(path)) {
610 return Android::GetSize(path);
611 }
612#endif
613
608 std::error_code ec; 614 std::error_code ec;
609 615
610 const auto file_size = fs::file_size(path, ec); 616 const auto file_size = fs::file_size(path, ec);
diff --git a/src/common/settings.h b/src/common/settings.h
index 3aedf3850..ae5ed93d8 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -525,9 +525,16 @@ struct Values {
525 Setting<bool> tas_loop{false, "tas_loop"}; 525 Setting<bool> tas_loop{false, "tas_loop"};
526 526
527 Setting<bool> mouse_panning{false, "mouse_panning"}; 527 Setting<bool> mouse_panning{false, "mouse_panning"};
528 Setting<u8, true> mouse_panning_sensitivity{50, 1, 100, "mouse_panning_sensitivity"}; 528 Setting<u8, true> mouse_panning_x_sensitivity{50, 1, 100, "mouse_panning_x_sensitivity"};
529 Setting<bool> mouse_enabled{false, "mouse_enabled"}; 529 Setting<u8, true> mouse_panning_y_sensitivity{50, 1, 100, "mouse_panning_y_sensitivity"};
530 Setting<u8, true> mouse_panning_deadzone_x_counterweight{
531 0, 0, 100, "mouse_panning_deadzone_x_counterweight"};
532 Setting<u8, true> mouse_panning_deadzone_y_counterweight{
533 0, 0, 100, "mouse_panning_deadzone_y_counterweight"};
534 Setting<u8, true> mouse_panning_decay_strength{22, 0, 100, "mouse_panning_decay_strength"};
535 Setting<u8, true> mouse_panning_min_decay{5, 0, 100, "mouse_panning_min_decay"};
530 536
537 Setting<bool> mouse_enabled{false, "mouse_enabled"};
531 Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"}; 538 Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
532 Setting<bool> keyboard_enabled{false, "keyboard_enabled"}; 539 Setting<bool> keyboard_enabled{false, "keyboard_enabled"};
533 540
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index c937495f9..1ebc32c1e 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -1250,6 +1250,11 @@ Common::Input::DriverResult EmulatedController::SetPollingMode(
1250 const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode); 1250 const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
1251 const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode); 1251 const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode);
1252 1252
1253 // Restore previous state
1254 if (mapped_nfc_result != Common::Input::DriverResult::Success) {
1255 right_output_device->SetPollingMode(Common::Input::PollingMode::Active);
1256 }
1257
1253 if (virtual_nfc_result == Common::Input::DriverResult::Success) { 1258 if (virtual_nfc_result == Common::Input::DriverResult::Success) {
1254 return virtual_nfc_result; 1259 return virtual_nfc_result;
1255 } 1260 }
@@ -1329,16 +1334,22 @@ bool EmulatedController::StartNfcPolling() {
1329 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1334 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1330 auto& nfc_virtual_output_device = output_devices[3]; 1335 auto& nfc_virtual_output_device = output_devices[3];
1331 1336
1332 return nfc_output_device->StartNfcPolling() == Common::Input::NfcState::Success || 1337 const auto device_result = nfc_output_device->StartNfcPolling();
1333 nfc_virtual_output_device->StartNfcPolling() == Common::Input::NfcState::Success; 1338 const auto virtual_device_result = nfc_virtual_output_device->StartNfcPolling();
1339
1340 return device_result == Common::Input::NfcState::Success ||
1341 virtual_device_result == Common::Input::NfcState::Success;
1334} 1342}
1335 1343
1336bool EmulatedController::StopNfcPolling() { 1344bool EmulatedController::StopNfcPolling() {
1337 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1345 auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1338 auto& nfc_virtual_output_device = output_devices[3]; 1346 auto& nfc_virtual_output_device = output_devices[3];
1339 1347
1340 return nfc_output_device->StopNfcPolling() == Common::Input::NfcState::Success || 1348 const auto device_result = nfc_output_device->StopNfcPolling();
1341 nfc_virtual_output_device->StopNfcPolling() == Common::Input::NfcState::Success; 1349 const auto virtual_device_result = nfc_virtual_output_device->StopNfcPolling();
1350
1351 return device_result == Common::Input::NfcState::Success ||
1352 virtual_device_result == Common::Input::NfcState::Success;
1342} 1353}
1343 1354
1344bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) { 1355bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp
index 0c9f642bb..f07cf8a0e 100644
--- a/src/input_common/drivers/mouse.cpp
+++ b/src/input_common/drivers/mouse.cpp
@@ -76,9 +76,6 @@ void Mouse::UpdateThread(std::stop_token stop_token) {
76 UpdateStickInput(); 76 UpdateStickInput();
77 UpdateMotionInput(); 77 UpdateMotionInput();
78 78
79 if (mouse_panning_timeout++ > 20) {
80 StopPanning();
81 }
82 std::this_thread::sleep_for(std::chrono::milliseconds(update_time)); 79 std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
83 } 80 }
84} 81}
@@ -88,18 +85,45 @@ void Mouse::UpdateStickInput() {
88 return; 85 return;
89 } 86 }
90 87
91 const float sensitivity = 88 const float length = last_mouse_change.Length();
92 Settings::values.mouse_panning_sensitivity.GetValue() * default_stick_sensitivity;
93 89
94 // Slow movement by 4% 90 // Prevent input from exceeding the max range (1.0f) too much,
95 last_mouse_change *= 0.96f; 91 // but allow some room to make it easier to sustain
96 SetAxis(identifier, mouse_axis_x, last_mouse_change.x * sensitivity); 92 if (length > 1.2f) {
97 SetAxis(identifier, mouse_axis_y, -last_mouse_change.y * sensitivity); 93 last_mouse_change /= length;
94 last_mouse_change *= 1.2f;
95 }
96
97 auto mouse_change = last_mouse_change;
98
99 // Bind the mouse change to [0 <= deadzone_counterweight <= 1,1]
100 if (length < 1.0f) {
101 const float deadzone_h_counterweight =
102 Settings::values.mouse_panning_deadzone_x_counterweight.GetValue();
103 const float deadzone_v_counterweight =
104 Settings::values.mouse_panning_deadzone_y_counterweight.GetValue();
105 mouse_change /= length;
106 mouse_change.x *= length + (1 - length) * deadzone_h_counterweight * 0.01f;
107 mouse_change.y *= length + (1 - length) * deadzone_v_counterweight * 0.01f;
108 }
109
110 SetAxis(identifier, mouse_axis_x, mouse_change.x);
111 SetAxis(identifier, mouse_axis_y, -mouse_change.y);
112
113 // Decay input over time
114 const float clamped_length = std::min(1.0f, length);
115 const float decay_strength = Settings::values.mouse_panning_decay_strength.GetValue();
116 const float decay = 1 - clamped_length * clamped_length * decay_strength * 0.01f;
117 const float min_decay = Settings::values.mouse_panning_min_decay.GetValue();
118 const float clamped_decay = std::min(1 - min_decay / 100.0f, decay);
119 last_mouse_change *= clamped_decay;
98} 120}
99 121
100void Mouse::UpdateMotionInput() { 122void Mouse::UpdateMotionInput() {
101 const float sensitivity = 123 // This may need its own sensitivity instead of using the average
102 Settings::values.mouse_panning_sensitivity.GetValue() * default_motion_sensitivity; 124 const float sensitivity = (Settings::values.mouse_panning_x_sensitivity.GetValue() +
125 Settings::values.mouse_panning_y_sensitivity.GetValue()) /
126 2.0f * default_motion_sensitivity;
103 127
104 const float rotation_velocity = std::sqrt(last_motion_change.x * last_motion_change.x + 128 const float rotation_velocity = std::sqrt(last_motion_change.x * last_motion_change.x +
105 last_motion_change.y * last_motion_change.y); 129 last_motion_change.y * last_motion_change.y);
@@ -131,49 +155,28 @@ void Mouse::UpdateMotionInput() {
131 155
132void Mouse::Move(int x, int y, int center_x, int center_y) { 156void Mouse::Move(int x, int y, int center_x, int center_y) {
133 if (Settings::values.mouse_panning) { 157 if (Settings::values.mouse_panning) {
134 mouse_panning_timeout = 0; 158 const auto mouse_change =
135
136 auto mouse_change =
137 (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>(); 159 (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>();
138 last_motion_change += {-mouse_change.y, -mouse_change.x, 0}; 160 const float x_sensitivity =
139 161 Settings::values.mouse_panning_x_sensitivity.GetValue() * default_stick_sensitivity;
140 const auto move_distance = mouse_change.Length(); 162 const float y_sensitivity =
141 if (move_distance == 0) { 163 Settings::values.mouse_panning_y_sensitivity.GetValue() * default_stick_sensitivity;
142 return;
143 }
144 164
145 // Make slow movements at least 3 units on length 165 last_motion_change += {-mouse_change.y, -mouse_change.x, 0};
146 if (move_distance < 3.0f) { 166 last_mouse_change.x += mouse_change.x * x_sensitivity * 0.09f;
147 // Normalize value 167 last_mouse_change.y += mouse_change.y * y_sensitivity * 0.09f;
148 mouse_change /= move_distance;
149 mouse_change *= 3.0f;
150 }
151
152 // Average mouse movements
153 last_mouse_change = (last_mouse_change * 0.91f) + (mouse_change * 0.09f);
154
155 const auto last_move_distance = last_mouse_change.Length();
156
157 // Make fast movements clamp to 8 units on length
158 if (last_move_distance > 8.0f) {
159 // Normalize value
160 last_mouse_change /= last_move_distance;
161 last_mouse_change *= 8.0f;
162 }
163
164 // Ignore average if it's less than 1 unit and use current movement value
165 if (last_move_distance < 1.0f) {
166 last_mouse_change = mouse_change / mouse_change.Length();
167 }
168 168
169 return; 169 return;
170 } 170 }
171 171
172 if (button_pressed) { 172 if (button_pressed) {
173 const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin; 173 const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin;
174 const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f; 174 const float x_sensitivity = Settings::values.mouse_panning_x_sensitivity.GetValue();
175 SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * sensitivity); 175 const float y_sensitivity = Settings::values.mouse_panning_y_sensitivity.GetValue();
176 SetAxis(identifier, mouse_axis_y, static_cast<float>(-mouse_move.y) * sensitivity); 176 SetAxis(identifier, mouse_axis_x,
177 static_cast<float>(mouse_move.x) * x_sensitivity * 0.0012f);
178 SetAxis(identifier, mouse_axis_y,
179 static_cast<float>(-mouse_move.y) * y_sensitivity * 0.0012f);
177 180
178 last_motion_change = { 181 last_motion_change = {
179 static_cast<float>(-mouse_move.y) / 50.0f, 182 static_cast<float>(-mouse_move.y) / 50.0f,
@@ -241,10 +244,6 @@ void Mouse::ReleaseAllButtons() {
241 button_pressed = false; 244 button_pressed = false;
242} 245}
243 246
244void Mouse::StopPanning() {
245 last_mouse_change = {};
246}
247
248std::vector<Common::ParamPackage> Mouse::GetInputDevices() const { 247std::vector<Common::ParamPackage> Mouse::GetInputDevices() const {
249 std::vector<Common::ParamPackage> devices; 248 std::vector<Common::ParamPackage> devices;
250 devices.emplace_back(Common::ParamPackage{ 249 devices.emplace_back(Common::ParamPackage{
diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h
index b872c7a0f..0e8edcce1 100644
--- a/src/input_common/drivers/mouse.h
+++ b/src/input_common/drivers/mouse.h
@@ -98,7 +98,6 @@ private:
98 void UpdateThread(std::stop_token stop_token); 98 void UpdateThread(std::stop_token stop_token);
99 void UpdateStickInput(); 99 void UpdateStickInput();
100 void UpdateMotionInput(); 100 void UpdateMotionInput();
101 void StopPanning();
102 101
103 Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; 102 Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
104 103
@@ -108,7 +107,6 @@ private:
108 Common::Vec3<float> last_motion_change; 107 Common::Vec3<float> last_motion_change;
109 Common::Vec2<int> wheel_position; 108 Common::Vec2<int> wheel_position;
110 bool button_pressed; 109 bool button_pressed;
111 int mouse_panning_timeout{};
112 std::jthread update_thread; 110 std::jthread update_thread;
113}; 111};
114 112
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index 9a0439bb5..9f26392b1 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -150,6 +150,8 @@ public:
150 if (sdl_controller) { 150 if (sdl_controller) {
151 const auto type = SDL_GameControllerGetType(sdl_controller.get()); 151 const auto type = SDL_GameControllerGetType(sdl_controller.get());
152 return (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) || 152 return (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) ||
153 (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT) ||
154 (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) ||
153 (type == SDL_CONTROLLER_TYPE_PS5); 155 (type == SDL_CONTROLLER_TYPE_PS5);
154 } 156 }
155 return false; 157 return false;
@@ -228,9 +230,8 @@ public:
228 return false; 230 return false;
229 } 231 }
230 232
231 Common::Input::BatteryLevel GetBatteryLevel() { 233 Common::Input::BatteryLevel GetBatteryLevel(SDL_JoystickPowerLevel battery_level) {
232 const auto level = SDL_JoystickCurrentPowerLevel(sdl_joystick.get()); 234 switch (battery_level) {
233 switch (level) {
234 case SDL_JOYSTICK_POWER_EMPTY: 235 case SDL_JOYSTICK_POWER_EMPTY:
235 return Common::Input::BatteryLevel::Empty; 236 return Common::Input::BatteryLevel::Empty;
236 case SDL_JOYSTICK_POWER_LOW: 237 case SDL_JOYSTICK_POWER_LOW:
@@ -378,7 +379,6 @@ void SDLDriver::InitJoystick(int joystick_index) {
378 if (joystick_map.find(guid) == joystick_map.end()) { 379 if (joystick_map.find(guid) == joystick_map.end()) {
379 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); 380 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
380 PreSetController(joystick->GetPadIdentifier()); 381 PreSetController(joystick->GetPadIdentifier());
381 SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
382 joystick->EnableMotion(); 382 joystick->EnableMotion();
383 joystick_map[guid].emplace_back(std::move(joystick)); 383 joystick_map[guid].emplace_back(std::move(joystick));
384 return; 384 return;
@@ -398,7 +398,6 @@ void SDLDriver::InitJoystick(int joystick_index) {
398 const int port = static_cast<int>(joystick_guid_list.size()); 398 const int port = static_cast<int>(joystick_guid_list.size());
399 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); 399 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
400 PreSetController(joystick->GetPadIdentifier()); 400 PreSetController(joystick->GetPadIdentifier());
401 SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
402 joystick->EnableMotion(); 401 joystick->EnableMotion();
403 joystick_guid_list.emplace_back(std::move(joystick)); 402 joystick_guid_list.emplace_back(std::move(joystick));
404} 403}
@@ -438,8 +437,6 @@ void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
438 if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { 437 if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
439 const PadIdentifier identifier = joystick->GetPadIdentifier(); 438 const PadIdentifier identifier = joystick->GetPadIdentifier();
440 SetButton(identifier, event.jbutton.button, true); 439 SetButton(identifier, event.jbutton.button, true);
441 // Battery doesn't trigger an event so just update every button press
442 SetBattery(identifier, joystick->GetBatteryLevel());
443 } 440 }
444 break; 441 break;
445 } 442 }
@@ -466,6 +463,13 @@ void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
466 } 463 }
467 break; 464 break;
468 } 465 }
466 case SDL_JOYBATTERYUPDATED: {
467 if (auto joystick = GetSDLJoystickBySDLID(event.jbattery.which)) {
468 const PadIdentifier identifier = joystick->GetPadIdentifier();
469 SetBattery(identifier, joystick->GetBatteryLevel(event.jbattery.level));
470 }
471 break;
472 }
469 case SDL_JOYDEVICEREMOVED: 473 case SDL_JOYDEVICEREMOVED:
470 LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); 474 LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
471 CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); 475 CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
@@ -483,6 +487,10 @@ void SDLDriver::CloseJoysticks() {
483} 487}
484 488
485SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) { 489SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
490 // Set our application name. Currently passed to DBus by SDL and visible to the user through
491 // their desktop environment.
492 SDL_SetHint(SDL_HINT_APP_NAME, "yuzu");
493
486 if (!Settings::values.enable_raw_input) { 494 if (!Settings::values.enable_raw_input) {
487 // Disable raw input. When enabled this setting causes SDL to die when a web applet opens 495 // Disable raw input. When enabled this setting causes SDL to die when a web applet opens
488 SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); 496 SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
@@ -501,6 +509,9 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
501 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0"); 509 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0");
502 } else { 510 } else {
503 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); 511 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
512 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED, "0");
513 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS, "0");
514 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, "1");
504 } 515 }
505 516
506 // Disable hidapi drivers for pro controllers when the custom joycon driver is enabled 517 // Disable hidapi drivers for pro controllers when the custom joycon driver is enabled
@@ -508,8 +519,11 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
508 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "0"); 519 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "0");
509 } else { 520 } else {
510 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1"); 521 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
522 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0");
511 } 523 }
512 524
525 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, "1");
526
513 // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native 527 // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
514 // driver on Linux. 528 // driver on Linux.
515 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0"); 529 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0");
@@ -789,7 +803,9 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p
789 // This list also excludes Screenshot since there's not really a mapping for that 803 // This list also excludes Screenshot since there's not really a mapping for that
790 ButtonBindings switch_to_sdl_button; 804 ButtonBindings switch_to_sdl_button;
791 805
792 if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) { 806 if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO ||
807 SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT ||
808 SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) {
793 switch_to_sdl_button = GetNintendoButtonBinding(joystick); 809 switch_to_sdl_button = GetNintendoButtonBinding(joystick);
794 } else { 810 } else {
795 switch_to_sdl_button = GetDefaultButtonBinding(); 811 switch_to_sdl_button = GetDefaultButtonBinding();
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index 2c8c66951..ec984a647 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -72,6 +72,7 @@ DriverResult JoyconDriver::InitializeDevice() {
72 nfc_enabled = false; 72 nfc_enabled = false;
73 passive_enabled = false; 73 passive_enabled = false;
74 irs_enabled = false; 74 irs_enabled = false;
75 input_only_device = false;
75 gyro_sensitivity = Joycon::GyroSensitivity::DPS2000; 76 gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
76 gyro_performance = Joycon::GyroPerformance::HZ833; 77 gyro_performance = Joycon::GyroPerformance::HZ833;
77 accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8; 78 accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
@@ -86,16 +87,23 @@ DriverResult JoyconDriver::InitializeDevice() {
86 rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle); 87 rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
87 88
88 // Get fixed joycon info 89 // Get fixed joycon info
89 generic_protocol->GetVersionNumber(version); 90 if (generic_protocol->GetVersionNumber(version) != DriverResult::Success) {
90 generic_protocol->SetLowPowerMode(false); 91 // If this command fails the device doesn't accept configuration commands
91 generic_protocol->GetColor(color); 92 input_only_device = true;
92 if (handle_device_type == ControllerType::Pro) {
93 // Some 3rd party controllers aren't pro controllers
94 generic_protocol->GetControllerType(device_type);
95 } else {
96 device_type = handle_device_type;
97 } 93 }
98 generic_protocol->GetSerialNumber(serial_number); 94
95 if (!input_only_device) {
96 generic_protocol->SetLowPowerMode(false);
97 generic_protocol->GetColor(color);
98 if (handle_device_type == ControllerType::Pro) {
99 // Some 3rd party controllers aren't pro controllers
100 generic_protocol->GetControllerType(device_type);
101 } else {
102 device_type = handle_device_type;
103 }
104 generic_protocol->GetSerialNumber(serial_number);
105 }
106
99 supported_features = GetSupportedFeatures(); 107 supported_features = GetSupportedFeatures();
100 108
101 // Get Calibration data 109 // Get Calibration data
@@ -261,6 +269,10 @@ DriverResult JoyconDriver::SetPollingMode() {
261 generic_protocol->EnableImu(false); 269 generic_protocol->EnableImu(false);
262 } 270 }
263 271
272 if (input_only_device) {
273 return DriverResult::NotSupported;
274 }
275
264 if (irs_protocol->IsEnabled()) { 276 if (irs_protocol->IsEnabled()) {
265 irs_protocol->DisableIrs(); 277 irs_protocol->DisableIrs();
266 } 278 }
@@ -282,6 +294,7 @@ DriverResult JoyconDriver::SetPollingMode() {
282 } 294 }
283 irs_protocol->DisableIrs(); 295 irs_protocol->DisableIrs();
284 LOG_ERROR(Input, "Error enabling IRS"); 296 LOG_ERROR(Input, "Error enabling IRS");
297 return result;
285 } 298 }
286 299
287 if (nfc_enabled && supported_features.nfc) { 300 if (nfc_enabled && supported_features.nfc) {
@@ -291,6 +304,7 @@ DriverResult JoyconDriver::SetPollingMode() {
291 } 304 }
292 nfc_protocol->DisableNfc(); 305 nfc_protocol->DisableNfc();
293 LOG_ERROR(Input, "Error enabling NFC"); 306 LOG_ERROR(Input, "Error enabling NFC");
307 return result;
294 } 308 }
295 309
296 if (hidbus_enabled && supported_features.hidbus) { 310 if (hidbus_enabled && supported_features.hidbus) {
@@ -305,6 +319,7 @@ DriverResult JoyconDriver::SetPollingMode() {
305 ring_connected = false; 319 ring_connected = false;
306 ring_protocol->DisableRingCon(); 320 ring_protocol->DisableRingCon();
307 LOG_ERROR(Input, "Error enabling Ringcon"); 321 LOG_ERROR(Input, "Error enabling Ringcon");
322 return result;
308 } 323 }
309 324
310 if (passive_enabled && supported_features.passive) { 325 if (passive_enabled && supported_features.passive) {
@@ -333,6 +348,10 @@ JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
333 .vibration = true, 348 .vibration = true,
334 }; 349 };
335 350
351 if (input_only_device) {
352 return features;
353 }
354
336 if (device_type == ControllerType::Right) { 355 if (device_type == ControllerType::Right) {
337 features.nfc = true; 356 features.nfc = true;
338 features.irs = true; 357 features.irs = true;
@@ -517,6 +536,11 @@ DriverResult JoyconDriver::StopNfcPolling() {
517 const auto result = nfc_protocol->StopNFCPollingMode(); 536 const auto result = nfc_protocol->StopNFCPollingMode();
518 disable_input_thread = false; 537 disable_input_thread = false;
519 538
539 if (amiibo_detected) {
540 amiibo_detected = false;
541 joycon_poller->UpdateAmiibo({});
542 }
543
520 return result; 544 return result;
521} 545}
522 546
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index bc7025a21..45b32d2f8 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -120,6 +120,7 @@ private:
120 // Hardware configuration 120 // Hardware configuration
121 u8 leds{}; 121 u8 leds{};
122 ReportMode mode{}; 122 ReportMode mode{};
123 bool input_only_device{};
123 bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time 124 bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time
124 bool hidbus_enabled{}; // External device support 125 bool hidbus_enabled{}; // External device support
125 bool irs_enabled{}; // Infrared camera input 126 bool irs_enabled{}; // Infrared camera input
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
index 51669261a..88f4cec1c 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.cpp
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -73,7 +73,7 @@ DriverResult JoyconCommonProtocol::SendRawData(std::span<const u8> buffer) {
73DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, 73DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc,
74 SubCommandResponse& output) { 74 SubCommandResponse& output) {
75 constexpr int timeout_mili = 66; 75 constexpr int timeout_mili = 66;
76 constexpr int MaxTries = 15; 76 constexpr int MaxTries = 3;
77 int tries = 0; 77 int tries = 0;
78 78
79 do { 79 do {
@@ -113,9 +113,7 @@ DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const
113 return result; 113 return result;
114 } 114 }
115 115
116 result = GetSubCommandResponse(sc, output); 116 return GetSubCommandResponse(sc, output);
117
118 return DriverResult::Success;
119} 117}
120 118
121DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) { 119DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) {
@@ -158,7 +156,7 @@ DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffe
158 156
159DriverResult JoyconCommonProtocol::ReadRawSPI(SpiAddress addr, std::span<u8> output) { 157DriverResult JoyconCommonProtocol::ReadRawSPI(SpiAddress addr, std::span<u8> output) {
160 constexpr std::size_t HeaderSize = 5; 158 constexpr std::size_t HeaderSize = 5;
161 constexpr std::size_t MaxTries = 10; 159 constexpr std::size_t MaxTries = 5;
162 std::size_t tries = 0; 160 std::size_t tries = 0;
163 SubCommandResponse response{}; 161 SubCommandResponse response{};
164 std::array<u8, sizeof(ReadSpiPacket)> buffer{}; 162 std::array<u8, sizeof(ReadSpiPacket)> buffer{};
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm.cpp b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
index fd4a61a4d..b795c0179 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
@@ -461,7 +461,7 @@ std::string EmitGLASM(const Profile& profile, const RuntimeInfo& runtime_info, I
461 header += fmt::format("R{},", index); 461 header += fmt::format("R{},", index);
462 } 462 }
463 if (program.local_memory_size > 0) { 463 if (program.local_memory_size > 0) {
464 header += fmt::format("lmem[{}],", program.local_memory_size); 464 header += fmt::format("lmem[{}],", Common::DivCeil(program.local_memory_size, 4U));
465 } 465 }
466 if (program.info.uses_fswzadd) { 466 if (program.info.uses_fswzadd) {
467 header += "FSWZA[4],FSWZB[4],"; 467 header += "FSWZA[4],FSWZB[4],";
diff --git a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
index 5a4195217..70292686f 100644
--- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
+++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
@@ -424,6 +424,10 @@ void VisitUsages(Info& info, IR::Inst& inst) {
424 info.used_constant_buffer_types |= IR::Type::U32 | IR::Type::U32x2; 424 info.used_constant_buffer_types |= IR::Type::U32 | IR::Type::U32x2;
425 info.used_storage_buffer_types |= IR::Type::U32 | IR::Type::U32x2 | IR::Type::U32x4; 425 info.used_storage_buffer_types |= IR::Type::U32 | IR::Type::U32x2 | IR::Type::U32x4;
426 break; 426 break;
427 case IR::Opcode::LoadLocal:
428 case IR::Opcode::WriteLocal:
429 info.uses_local_memory = true;
430 break;
427 default: 431 default:
428 break; 432 break;
429 } 433 }
diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h
index d308db942..b4b4afd37 100644
--- a/src/shader_recompiler/shader_info.h
+++ b/src/shader_recompiler/shader_info.h
@@ -172,6 +172,7 @@ struct Info {
172 bool stores_indexed_attributes{}; 172 bool stores_indexed_attributes{};
173 173
174 bool stores_global_memory{}; 174 bool stores_global_memory{};
175 bool uses_local_memory{};
175 176
176 bool uses_fp16{}; 177 bool uses_fp16{};
177 bool uses_fp64{}; 178 bool uses_fp64{};
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index bf6439530..e9e6f278d 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -291,7 +291,7 @@ target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})
291 291
292add_dependencies(video_core host_shaders) 292add_dependencies(video_core host_shaders)
293target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE}) 293target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
294target_link_libraries(video_core PRIVATE sirit Vulkan::Headers) 294target_link_libraries(video_core PRIVATE sirit Vulkan::Headers vma)
295 295
296if (ENABLE_NSIGHT_AFTERMATH) 296if (ENABLE_NSIGHT_AFTERMATH)
297 if (NOT DEFINED ENV{NSIGHT_AFTERMATH_SDK}) 297 if (NOT DEFINED ENV{NSIGHT_AFTERMATH_SDK})
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index 2442c3c29..e61d9af80 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -33,6 +33,7 @@ set(SHADER_FILES
33 opengl_fidelityfx_fsr.frag 33 opengl_fidelityfx_fsr.frag
34 opengl_fidelityfx_fsr_easu.frag 34 opengl_fidelityfx_fsr_easu.frag
35 opengl_fidelityfx_fsr_rcas.frag 35 opengl_fidelityfx_fsr_rcas.frag
36 opengl_lmem_warmup.comp
36 opengl_present.frag 37 opengl_present.frag
37 opengl_present.vert 38 opengl_present.vert
38 opengl_present_scaleforce.frag 39 opengl_present_scaleforce.frag
diff --git a/src/video_core/host_shaders/opengl_lmem_warmup.comp b/src/video_core/host_shaders/opengl_lmem_warmup.comp
new file mode 100644
index 000000000..518268477
--- /dev/null
+++ b/src/video_core/host_shaders/opengl_lmem_warmup.comp
@@ -0,0 +1,47 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// This shader is a workaround for a quirk in NVIDIA OpenGL drivers
5// Shaders using local memory see a great performance benefit if a shader that was dispatched
6// before it had more local memory allocated.
7// This shader allocates the maximum local memory allowed on NVIDIA drivers to ensure that
8// subsequent shaders see the performance boost.
9
10// NOTE: This shader does no actual meaningful work and returns immediately,
11// it is simply a means to have the driver expect a shader using lots of local memory.
12
13#version 450
14
15layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
16
17layout(location = 0) uniform uint uniform_data;
18
19layout(binding = 0, rgba8) uniform writeonly restrict image2DArray dest_image;
20
21#define MAX_LMEM_SIZE 4080 // Size chosen to avoid errors in Nvidia's GLSL compiler
22#define NUM_LMEM_CONSTANTS 1
23#define ARRAY_SIZE MAX_LMEM_SIZE - NUM_LMEM_CONSTANTS
24
25uint lmem_0[ARRAY_SIZE];
26const uvec4 constant_values[NUM_LMEM_CONSTANTS] = uvec4[](uvec4(0));
27
28void main() {
29 const uint global_id = gl_GlobalInvocationID.x;
30 if (global_id <= 128) {
31 // Since the shader is called with a dispatch of 1x1x1
32 // This should always be the case, and this shader will not actually execute
33 return;
34 }
35 for (uint t = 0; t < uniform_data; t++) {
36 const uint offset = (t * uniform_data);
37 lmem_0[offset] = t;
38 }
39 const uint offset = (gl_GlobalInvocationID.y * uniform_data + gl_GlobalInvocationID.x);
40 const uint value = lmem_0[offset];
41 const uint const_value = constant_values[offset / 4][offset % 4];
42 const uvec4 color = uvec4(value + const_value);
43
44 // A "side-effect" is needed so the variables don't get optimized out,
45 // but this should never execute so there should be no clobbering of previously bound state.
46 imageStore(dest_image, ivec3(gl_GlobalInvocationID), color);
47}
diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
index 3151c0db8..f9ca55c36 100644
--- a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
@@ -63,6 +63,7 @@ ComputePipeline::ComputePipeline(const Device& device, TextureCache& texture_cac
63 writes_global_memory = !use_storage_buffers && 63 writes_global_memory = !use_storage_buffers &&
64 std::ranges::any_of(info.storage_buffers_descriptors, 64 std::ranges::any_of(info.storage_buffers_descriptors,
65 [](const auto& desc) { return desc.is_written; }); 65 [](const auto& desc) { return desc.is_written; });
66 uses_local_memory = info.uses_local_memory;
66 if (force_context_flush) { 67 if (force_context_flush) {
67 std::scoped_lock lock{built_mutex}; 68 std::scoped_lock lock{built_mutex};
68 built_fence.Create(); 69 built_fence.Create();
diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.h b/src/video_core/renderer_opengl/gl_compute_pipeline.h
index 9bcc72b59..c26b4fa5e 100644
--- a/src/video_core/renderer_opengl/gl_compute_pipeline.h
+++ b/src/video_core/renderer_opengl/gl_compute_pipeline.h
@@ -59,6 +59,10 @@ public:
59 return writes_global_memory; 59 return writes_global_memory;
60 } 60 }
61 61
62 [[nodiscard]] bool UsesLocalMemory() const noexcept {
63 return uses_local_memory;
64 }
65
62 void SetEngine(Tegra::Engines::KeplerCompute* kepler_compute_, 66 void SetEngine(Tegra::Engines::KeplerCompute* kepler_compute_,
63 Tegra::MemoryManager* gpu_memory_) { 67 Tegra::MemoryManager* gpu_memory_) {
64 kepler_compute = kepler_compute_; 68 kepler_compute = kepler_compute_;
@@ -84,6 +88,7 @@ private:
84 88
85 bool use_storage_buffers{}; 89 bool use_storage_buffers{};
86 bool writes_global_memory{}; 90 bool writes_global_memory{};
91 bool uses_local_memory{};
87 92
88 std::mutex built_mutex; 93 std::mutex built_mutex;
89 std::condition_variable built_condvar; 94 std::condition_variable built_condvar;
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 03d234f2f..33e63c17d 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -194,6 +194,7 @@ Device::Device(Core::Frontend::EmuWindow& emu_window) {
194 has_bool_ref_bug = true; 194 has_bool_ref_bug = true;
195 } 195 }
196 } 196 }
197 has_lmem_perf_bug = is_nvidia;
197 198
198 strict_context_required = emu_window.StrictContextRequired(); 199 strict_context_required = emu_window.StrictContextRequired();
199 // Blocks AMD and Intel OpenGL drivers on Windows from using asynchronous shader compilation. 200 // Blocks AMD and Intel OpenGL drivers on Windows from using asynchronous shader compilation.
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h
index ad27264e5..a5a6bbbba 100644
--- a/src/video_core/renderer_opengl/gl_device.h
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -192,6 +192,10 @@ public:
192 return supports_conditional_barriers; 192 return supports_conditional_barriers;
193 } 193 }
194 194
195 bool HasLmemPerfBug() const {
196 return has_lmem_perf_bug;
197 }
198
195private: 199private:
196 static bool TestVariableAoffi(); 200 static bool TestVariableAoffi();
197 static bool TestPreciseBug(); 201 static bool TestPreciseBug();
@@ -238,6 +242,7 @@ private:
238 bool can_report_memory{}; 242 bool can_report_memory{};
239 bool strict_context_required{}; 243 bool strict_context_required{};
240 bool supports_conditional_barriers{}; 244 bool supports_conditional_barriers{};
245 bool has_lmem_perf_bug{};
241 246
242 std::string vendor_name; 247 std::string vendor_name;
243}; 248};
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
index c58f760b8..23a48c6fe 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
@@ -215,6 +215,7 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c
215 215
216 writes_global_memory |= std::ranges::any_of( 216 writes_global_memory |= std::ranges::any_of(
217 info.storage_buffers_descriptors, [](const auto& desc) { return desc.is_written; }); 217 info.storage_buffers_descriptors, [](const auto& desc) { return desc.is_written; });
218 uses_local_memory |= info.uses_local_memory;
218 } 219 }
219 ASSERT(num_textures <= MAX_TEXTURES); 220 ASSERT(num_textures <= MAX_TEXTURES);
220 ASSERT(num_images <= MAX_IMAGES); 221 ASSERT(num_images <= MAX_IMAGES);
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.h b/src/video_core/renderer_opengl/gl_graphics_pipeline.h
index 7bab3be0a..7b3d7eae8 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.h
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.h
@@ -98,6 +98,10 @@ public:
98 return writes_global_memory; 98 return writes_global_memory;
99 } 99 }
100 100
101 [[nodiscard]] bool UsesLocalMemory() const noexcept {
102 return uses_local_memory;
103 }
104
101 [[nodiscard]] bool IsBuilt() noexcept; 105 [[nodiscard]] bool IsBuilt() noexcept;
102 106
103 template <typename Spec> 107 template <typename Spec>
@@ -146,6 +150,7 @@ private:
146 150
147 bool use_storage_buffers{}; 151 bool use_storage_buffers{};
148 bool writes_global_memory{}; 152 bool writes_global_memory{};
153 bool uses_local_memory{};
149 154
150 static constexpr std::size_t XFB_ENTRY_STRIDE = 3; 155 static constexpr std::size_t XFB_ENTRY_STRIDE = 3;
151 GLsizei num_xfb_attribs{}; 156 GLsizei num_xfb_attribs{};
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index fc711c44a..edf527f2d 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -222,6 +222,9 @@ void RasterizerOpenGL::PrepareDraw(bool is_indexed, Func&& draw_func) {
222 gpu.TickWork(); 222 gpu.TickWork();
223 223
224 std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; 224 std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
225 if (pipeline->UsesLocalMemory()) {
226 program_manager.LocalMemoryWarmup();
227 }
225 pipeline->SetEngine(maxwell3d, gpu_memory); 228 pipeline->SetEngine(maxwell3d, gpu_memory);
226 pipeline->Configure(is_indexed); 229 pipeline->Configure(is_indexed);
227 230
@@ -371,6 +374,9 @@ void RasterizerOpenGL::DispatchCompute() {
371 if (!pipeline) { 374 if (!pipeline) {
372 return; 375 return;
373 } 376 }
377 if (pipeline->UsesLocalMemory()) {
378 program_manager.LocalMemoryWarmup();
379 }
374 pipeline->SetEngine(kepler_compute, gpu_memory); 380 pipeline->SetEngine(kepler_compute, gpu_memory);
375 pipeline->Configure(); 381 pipeline->Configure();
376 const auto& qmd{kepler_compute->launch_description}; 382 const auto& qmd{kepler_compute->launch_description};
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp
index 98841ae65..03d4b9d06 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp
@@ -3,7 +3,9 @@
3 3
4#include <glad/glad.h> 4#include <glad/glad.h>
5 5
6#include "video_core/host_shaders/opengl_lmem_warmup_comp.h"
6#include "video_core/renderer_opengl/gl_shader_manager.h" 7#include "video_core/renderer_opengl/gl_shader_manager.h"
8#include "video_core/renderer_opengl/gl_shader_util.h"
7 9
8namespace OpenGL { 10namespace OpenGL {
9 11
@@ -17,6 +19,10 @@ ProgramManager::ProgramManager(const Device& device) {
17 if (device.UseAssemblyShaders()) { 19 if (device.UseAssemblyShaders()) {
18 glEnable(GL_COMPUTE_PROGRAM_NV); 20 glEnable(GL_COMPUTE_PROGRAM_NV);
19 } 21 }
22 if (device.HasLmemPerfBug()) {
23 lmem_warmup_program =
24 CreateProgram(HostShaders::OPENGL_LMEM_WARMUP_COMP, GL_COMPUTE_SHADER);
25 }
20} 26}
21 27
22void ProgramManager::BindComputeProgram(GLuint program) { 28void ProgramManager::BindComputeProgram(GLuint program) {
@@ -98,6 +104,13 @@ void ProgramManager::BindAssemblyPrograms(std::span<const OGLAssemblyProgram, NU
98 104
99void ProgramManager::RestoreGuestCompute() {} 105void ProgramManager::RestoreGuestCompute() {}
100 106
107void ProgramManager::LocalMemoryWarmup() {
108 if (lmem_warmup_program.handle != 0) {
109 BindComputeProgram(lmem_warmup_program.handle);
110 glDispatchCompute(1, 1, 1);
111 }
112}
113
101void ProgramManager::BindPipeline() { 114void ProgramManager::BindPipeline() {
102 if (!is_pipeline_bound) { 115 if (!is_pipeline_bound) {
103 is_pipeline_bound = true; 116 is_pipeline_bound = true;
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h
index 07ffab77f..852d8c88e 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.h
+++ b/src/video_core/renderer_opengl/gl_shader_manager.h
@@ -30,6 +30,8 @@ public:
30 30
31 void RestoreGuestCompute(); 31 void RestoreGuestCompute();
32 32
33 void LocalMemoryWarmup();
34
33private: 35private:
34 void BindPipeline(); 36 void BindPipeline();
35 37
@@ -44,6 +46,7 @@ private:
44 u32 current_stage_mask = 0; 46 u32 current_stage_mask = 0;
45 std::array<GLuint, NUM_STAGES> current_programs{}; 47 std::array<GLuint, NUM_STAGES> current_programs{};
46 GLuint current_assembly_compute_program = 0; 48 GLuint current_assembly_compute_program = 0;
49 OGLProgram lmem_warmup_program;
47}; 50};
48 51
49} // namespace OpenGL 52} // namespace OpenGL
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 77128c6e2..ddf28ca28 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -89,8 +89,8 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
89 Settings::values.renderer_debug.GetValue())), 89 Settings::values.renderer_debug.GetValue())),
90 debug_callback(Settings::values.renderer_debug ? CreateDebugCallback(instance) : nullptr), 90 debug_callback(Settings::values.renderer_debug ? CreateDebugCallback(instance) : nullptr),
91 surface(CreateSurface(instance, render_window.GetWindowInfo())), 91 surface(CreateSurface(instance, render_window.GetWindowInfo())),
92 device(CreateDevice(instance, dld, *surface)), memory_allocator(device, false), 92 device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(),
93 state_tracker(), scheduler(device, state_tracker), 93 scheduler(device, state_tracker),
94 swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width, 94 swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width,
95 render_window.GetFramebufferLayout().height, false), 95 render_window.GetFramebufferLayout().height, false),
96 present_manager(instance, render_window, device, memory_allocator, scheduler, swapchain, 96 present_manager(instance, render_window, device, memory_allocator, scheduler, swapchain,
@@ -173,7 +173,7 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr
173 return; 173 return;
174 } 174 }
175 const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout}; 175 const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
176 vk::Image staging_image = device.GetLogical().CreateImage(VkImageCreateInfo{ 176 vk::Image staging_image = memory_allocator.CreateImage(VkImageCreateInfo{
177 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, 177 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
178 .pNext = nullptr, 178 .pNext = nullptr,
179 .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, 179 .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
@@ -196,7 +196,6 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr
196 .pQueueFamilyIndices = nullptr, 196 .pQueueFamilyIndices = nullptr,
197 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, 197 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
198 }); 198 });
199 const auto image_commit = memory_allocator.Commit(staging_image, MemoryUsage::DeviceLocal);
200 199
201 const vk::ImageView dst_view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{ 200 const vk::ImageView dst_view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{
202 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 201 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
@@ -234,8 +233,8 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr
234 .queueFamilyIndexCount = 0, 233 .queueFamilyIndexCount = 0,
235 .pQueueFamilyIndices = nullptr, 234 .pQueueFamilyIndices = nullptr,
236 }; 235 };
237 const vk::Buffer dst_buffer = device.GetLogical().CreateBuffer(dst_buffer_info); 236 const vk::Buffer dst_buffer =
238 MemoryCommit dst_buffer_memory = memory_allocator.Commit(dst_buffer, MemoryUsage::Download); 237 memory_allocator.CreateBuffer(dst_buffer_info, MemoryUsage::Download);
239 238
240 scheduler.RequestOutsideRenderPassOperationContext(); 239 scheduler.RequestOutsideRenderPassOperationContext();
241 scheduler.Record([&](vk::CommandBuffer cmdbuf) { 240 scheduler.Record([&](vk::CommandBuffer cmdbuf) {
@@ -309,8 +308,9 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr
309 scheduler.Finish(); 308 scheduler.Finish();
310 309
311 // Copy backing image data to the QImage screenshot buffer 310 // Copy backing image data to the QImage screenshot buffer
312 const auto dst_memory_map = dst_buffer_memory.Map(); 311 dst_buffer.Invalidate();
313 std::memcpy(renderer_settings.screenshot_bits, dst_memory_map.data(), dst_memory_map.size()); 312 std::memcpy(renderer_settings.screenshot_bits, dst_buffer.Mapped().data(),
313 dst_buffer.Mapped().size());
314 renderer_settings.screenshot_complete_callback(false); 314 renderer_settings.screenshot_complete_callback(false);
315 renderer_settings.screenshot_requested = false; 315 renderer_settings.screenshot_requested = false;
316} 316}
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index acb143fc7..ad3b29f0e 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -162,7 +162,7 @@ void BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
162 SetUniformData(data, layout); 162 SetUniformData(data, layout);
163 SetVertexData(data, framebuffer, layout); 163 SetVertexData(data, framebuffer, layout);
164 164
165 const std::span<u8> mapped_span = buffer_commit.Map(); 165 const std::span<u8> mapped_span = buffer.Mapped();
166 std::memcpy(mapped_span.data(), &data, sizeof(data)); 166 std::memcpy(mapped_span.data(), &data, sizeof(data));
167 167
168 if (!use_accelerated) { 168 if (!use_accelerated) {
@@ -1071,14 +1071,9 @@ void BlitScreen::ReleaseRawImages() {
1071 scheduler.Wait(tick); 1071 scheduler.Wait(tick);
1072 } 1072 }
1073 raw_images.clear(); 1073 raw_images.clear();
1074 raw_buffer_commits.clear();
1075
1076 aa_image_view.reset(); 1074 aa_image_view.reset();
1077 aa_image.reset(); 1075 aa_image.reset();
1078 aa_commit = MemoryCommit{};
1079
1080 buffer.reset(); 1076 buffer.reset();
1081 buffer_commit = MemoryCommit{};
1082} 1077}
1083 1078
1084void BlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) { 1079void BlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) {
@@ -1094,20 +1089,18 @@ void BlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer
1094 .pQueueFamilyIndices = nullptr, 1089 .pQueueFamilyIndices = nullptr,
1095 }; 1090 };
1096 1091
1097 buffer = device.GetLogical().CreateBuffer(ci); 1092 buffer = memory_allocator.CreateBuffer(ci, MemoryUsage::Upload);
1098 buffer_commit = memory_allocator.Commit(buffer, MemoryUsage::Upload);
1099} 1093}
1100 1094
1101void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) { 1095void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1102 raw_images.resize(image_count); 1096 raw_images.resize(image_count);
1103 raw_image_views.resize(image_count); 1097 raw_image_views.resize(image_count);
1104 raw_buffer_commits.resize(image_count);
1105 1098
1106 const auto create_image = [&](bool used_on_framebuffer = false, u32 up_scale = 1, 1099 const auto create_image = [&](bool used_on_framebuffer = false, u32 up_scale = 1,
1107 u32 down_shift = 0) { 1100 u32 down_shift = 0) {
1108 u32 extra_usages = used_on_framebuffer ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT 1101 u32 extra_usages = used_on_framebuffer ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
1109 : VK_IMAGE_USAGE_TRANSFER_DST_BIT; 1102 : VK_IMAGE_USAGE_TRANSFER_DST_BIT;
1110 return device.GetLogical().CreateImage(VkImageCreateInfo{ 1103 return memory_allocator.CreateImage(VkImageCreateInfo{
1111 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, 1104 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
1112 .pNext = nullptr, 1105 .pNext = nullptr,
1113 .flags = 0, 1106 .flags = 0,
@@ -1130,9 +1123,6 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1130 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, 1123 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
1131 }); 1124 });
1132 }; 1125 };
1133 const auto create_commit = [&](vk::Image& image) {
1134 return memory_allocator.Commit(image, MemoryUsage::DeviceLocal);
1135 };
1136 const auto create_image_view = [&](vk::Image& image, bool used_on_framebuffer = false) { 1126 const auto create_image_view = [&](vk::Image& image, bool used_on_framebuffer = false) {
1137 return device.GetLogical().CreateImageView(VkImageViewCreateInfo{ 1127 return device.GetLogical().CreateImageView(VkImageViewCreateInfo{
1138 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 1128 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
@@ -1161,7 +1151,6 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1161 1151
1162 for (size_t i = 0; i < image_count; ++i) { 1152 for (size_t i = 0; i < image_count; ++i) {
1163 raw_images[i] = create_image(); 1153 raw_images[i] = create_image();
1164 raw_buffer_commits[i] = create_commit(raw_images[i]);
1165 raw_image_views[i] = create_image_view(raw_images[i]); 1154 raw_image_views[i] = create_image_view(raw_images[i]);
1166 } 1155 }
1167 1156
@@ -1169,7 +1158,6 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1169 const u32 up_scale = Settings::values.resolution_info.up_scale; 1158 const u32 up_scale = Settings::values.resolution_info.up_scale;
1170 const u32 down_shift = Settings::values.resolution_info.down_shift; 1159 const u32 down_shift = Settings::values.resolution_info.down_shift;
1171 aa_image = create_image(true, up_scale, down_shift); 1160 aa_image = create_image(true, up_scale, down_shift);
1172 aa_commit = create_commit(aa_image);
1173 aa_image_view = create_image_view(aa_image, true); 1161 aa_image_view = create_image_view(aa_image, true);
1174 VkExtent2D size{ 1162 VkExtent2D size{
1175 .width = (up_scale * framebuffer.width) >> down_shift, 1163 .width = (up_scale * framebuffer.width) >> down_shift,
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index 68ec20253..8365b5668 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -142,13 +142,11 @@ private:
142 vk::Sampler sampler; 142 vk::Sampler sampler;
143 143
144 vk::Buffer buffer; 144 vk::Buffer buffer;
145 MemoryCommit buffer_commit;
146 145
147 std::vector<u64> resource_ticks; 146 std::vector<u64> resource_ticks;
148 147
149 std::vector<vk::Image> raw_images; 148 std::vector<vk::Image> raw_images;
150 std::vector<vk::ImageView> raw_image_views; 149 std::vector<vk::ImageView> raw_image_views;
151 std::vector<MemoryCommit> raw_buffer_commits;
152 150
153 vk::DescriptorPool aa_descriptor_pool; 151 vk::DescriptorPool aa_descriptor_pool;
154 vk::DescriptorSetLayout aa_descriptor_set_layout; 152 vk::DescriptorSetLayout aa_descriptor_set_layout;
@@ -159,7 +157,6 @@ private:
159 vk::DescriptorSets aa_descriptor_sets; 157 vk::DescriptorSets aa_descriptor_sets;
160 vk::Image aa_image; 158 vk::Image aa_image;
161 vk::ImageView aa_image_view; 159 vk::ImageView aa_image_view;
162 MemoryCommit aa_commit;
163 160
164 u32 raw_width = 0; 161 u32 raw_width = 0;
165 u32 raw_height = 0; 162 u32 raw_height = 0;
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index f47301ad5..660f7c9ff 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -50,7 +50,7 @@ size_t BytesPerIndex(VkIndexType index_type) {
50 } 50 }
51} 51}
52 52
53vk::Buffer CreateBuffer(const Device& device, u64 size) { 53vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allocator, u64 size) {
54 VkBufferUsageFlags flags = 54 VkBufferUsageFlags flags =
55 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | 55 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
56 VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | 56 VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT |
@@ -60,7 +60,7 @@ vk::Buffer CreateBuffer(const Device& device, u64 size) {
60 if (device.IsExtTransformFeedbackSupported()) { 60 if (device.IsExtTransformFeedbackSupported()) {
61 flags |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT; 61 flags |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
62 } 62 }
63 return device.GetLogical().CreateBuffer({ 63 const VkBufferCreateInfo buffer_ci = {
64 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 64 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
65 .pNext = nullptr, 65 .pNext = nullptr,
66 .flags = 0, 66 .flags = 0,
@@ -69,7 +69,8 @@ vk::Buffer CreateBuffer(const Device& device, u64 size) {
69 .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 69 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
70 .queueFamilyIndexCount = 0, 70 .queueFamilyIndexCount = 0,
71 .pQueueFamilyIndices = nullptr, 71 .pQueueFamilyIndices = nullptr,
72 }); 72 };
73 return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
73} 74}
74} // Anonymous namespace 75} // Anonymous namespace
75 76
@@ -79,8 +80,8 @@ Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params)
79Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_, 80Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_,
80 VAddr cpu_addr_, u64 size_bytes_) 81 VAddr cpu_addr_, u64 size_bytes_)
81 : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_), 82 : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_),
82 device{&runtime.device}, buffer{CreateBuffer(*device, SizeBytes())}, 83 device{&runtime.device}, buffer{
83 commit{runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal)} { 84 CreateBuffer(*device, runtime.memory_allocator, SizeBytes())} {
84 if (runtime.device.HasDebuggingToolAttached()) { 85 if (runtime.device.HasDebuggingToolAttached()) {
85 buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str()); 86 buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str());
86 } 87 }
@@ -138,7 +139,7 @@ public:
138 const u32 num_first_offset_copies = 4; 139 const u32 num_first_offset_copies = 4;
139 const size_t bytes_per_index = BytesPerIndex(index_type); 140 const size_t bytes_per_index = BytesPerIndex(index_type);
140 const size_t size_bytes = num_triangle_indices * bytes_per_index * num_first_offset_copies; 141 const size_t size_bytes = num_triangle_indices * bytes_per_index * num_first_offset_copies;
141 buffer = device.GetLogical().CreateBuffer(VkBufferCreateInfo{ 142 const VkBufferCreateInfo buffer_ci = {
142 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 143 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
143 .pNext = nullptr, 144 .pNext = nullptr,
144 .flags = 0, 145 .flags = 0,
@@ -147,14 +148,21 @@ public:
147 .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 148 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
148 .queueFamilyIndexCount = 0, 149 .queueFamilyIndexCount = 0,
149 .pQueueFamilyIndices = nullptr, 150 .pQueueFamilyIndices = nullptr,
150 }); 151 };
152 buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
151 if (device.HasDebuggingToolAttached()) { 153 if (device.HasDebuggingToolAttached()) {
152 buffer.SetObjectNameEXT("Quad LUT"); 154 buffer.SetObjectNameEXT("Quad LUT");
153 } 155 }
154 memory_commit = memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal);
155 156
156 const StagingBufferRef staging = staging_pool.Request(size_bytes, MemoryUsage::Upload); 157 const bool host_visible = buffer.IsHostVisible();
157 u8* staging_data = staging.mapped_span.data(); 158 const StagingBufferRef staging = [&] {
159 if (host_visible) {
160 return StagingBufferRef{};
161 }
162 return staging_pool.Request(size_bytes, MemoryUsage::Upload);
163 }();
164
165 u8* staging_data = host_visible ? buffer.Mapped().data() : staging.mapped_span.data();
158 const size_t quad_size = bytes_per_index * 6; 166 const size_t quad_size = bytes_per_index * 6;
159 167
160 for (u32 first = 0; first < num_first_offset_copies; ++first) { 168 for (u32 first = 0; first < num_first_offset_copies; ++first) {
@@ -164,29 +172,33 @@ public:
164 } 172 }
165 } 173 }
166 174
167 scheduler.RequestOutsideRenderPassOperationContext(); 175 if (!host_visible) {
168 scheduler.Record([src_buffer = staging.buffer, src_offset = staging.offset, 176 scheduler.RequestOutsideRenderPassOperationContext();
169 dst_buffer = *buffer, size_bytes](vk::CommandBuffer cmdbuf) { 177 scheduler.Record([src_buffer = staging.buffer, src_offset = staging.offset,
170 const VkBufferCopy copy{ 178 dst_buffer = *buffer, size_bytes](vk::CommandBuffer cmdbuf) {
171 .srcOffset = src_offset, 179 const VkBufferCopy copy{
172 .dstOffset = 0, 180 .srcOffset = src_offset,
173 .size = size_bytes, 181 .dstOffset = 0,
174 }; 182 .size = size_bytes,
175 const VkBufferMemoryBarrier write_barrier{ 183 };
176 .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, 184 const VkBufferMemoryBarrier write_barrier{
177 .pNext = nullptr, 185 .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
178 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, 186 .pNext = nullptr,
179 .dstAccessMask = VK_ACCESS_INDEX_READ_BIT, 187 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
180 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 188 .dstAccessMask = VK_ACCESS_INDEX_READ_BIT,
181 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 189 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
182 .buffer = dst_buffer, 190 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
183 .offset = 0, 191 .buffer = dst_buffer,
184 .size = size_bytes, 192 .offset = 0,
185 }; 193 .size = size_bytes,
186 cmdbuf.CopyBuffer(src_buffer, dst_buffer, copy); 194 };
187 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, 195 cmdbuf.CopyBuffer(src_buffer, dst_buffer, copy);
188 VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, write_barrier); 196 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
189 }); 197 VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, write_barrier);
198 });
199 } else {
200 buffer.Flush();
201 }
190 } 202 }
191 203
192 void BindBuffer(u32 first) { 204 void BindBuffer(u32 first) {
@@ -587,11 +599,10 @@ void BufferCacheRuntime::ReserveNullBuffer() {
587 create_info.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT; 599 create_info.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
588 } 600 }
589 create_info.usage |= VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT; 601 create_info.usage |= VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
590 null_buffer = device.GetLogical().CreateBuffer(create_info); 602 null_buffer = memory_allocator.CreateBuffer(create_info, MemoryUsage::DeviceLocal);
591 if (device.HasDebuggingToolAttached()) { 603 if (device.HasDebuggingToolAttached()) {
592 null_buffer.SetObjectNameEXT("Null buffer"); 604 null_buffer.SetObjectNameEXT("Null buffer");
593 } 605 }
594 null_buffer_commit = memory_allocator.Commit(null_buffer, MemoryUsage::DeviceLocal);
595 606
596 scheduler.RequestOutsideRenderPassOperationContext(); 607 scheduler.RequestOutsideRenderPassOperationContext();
597 scheduler.Record([buffer = *null_buffer](vk::CommandBuffer cmdbuf) { 608 scheduler.Record([buffer = *null_buffer](vk::CommandBuffer cmdbuf) {
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index cdeef8846..95446c732 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -48,7 +48,6 @@ private:
48 48
49 const Device* device{}; 49 const Device* device{};
50 vk::Buffer buffer; 50 vk::Buffer buffer;
51 MemoryCommit commit;
52 std::vector<BufferView> views; 51 std::vector<BufferView> views;
53}; 52};
54 53
@@ -142,7 +141,6 @@ private:
142 std::shared_ptr<QuadStripIndexBuffer> quad_strip_index_buffer; 141 std::shared_ptr<QuadStripIndexBuffer> quad_strip_index_buffer;
143 142
144 vk::Buffer null_buffer; 143 vk::Buffer null_buffer;
145 MemoryCommit null_buffer_commit;
146 144
147 std::unique_ptr<Uint8Pass> uint8_pass; 145 std::unique_ptr<Uint8Pass> uint8_pass;
148 QuadIndexedPass quad_index_pass; 146 QuadIndexedPass quad_index_pass;
diff --git a/src/video_core/renderer_vulkan/vk_fsr.cpp b/src/video_core/renderer_vulkan/vk_fsr.cpp
index df972cd54..9bcdca2fb 100644
--- a/src/video_core/renderer_vulkan/vk_fsr.cpp
+++ b/src/video_core/renderer_vulkan/vk_fsr.cpp
@@ -205,10 +205,9 @@ void FSR::CreateDescriptorSets() {
205void FSR::CreateImages() { 205void FSR::CreateImages() {
206 images.resize(image_count * 2); 206 images.resize(image_count * 2);
207 image_views.resize(image_count * 2); 207 image_views.resize(image_count * 2);
208 buffer_commits.resize(image_count * 2);
209 208
210 for (size_t i = 0; i < image_count * 2; ++i) { 209 for (size_t i = 0; i < image_count * 2; ++i) {
211 images[i] = device.GetLogical().CreateImage(VkImageCreateInfo{ 210 images[i] = memory_allocator.CreateImage(VkImageCreateInfo{
212 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, 211 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
213 .pNext = nullptr, 212 .pNext = nullptr,
214 .flags = 0, 213 .flags = 0,
@@ -231,7 +230,6 @@ void FSR::CreateImages() {
231 .pQueueFamilyIndices = nullptr, 230 .pQueueFamilyIndices = nullptr,
232 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, 231 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
233 }); 232 });
234 buffer_commits[i] = memory_allocator.Commit(images[i], MemoryUsage::DeviceLocal);
235 image_views[i] = device.GetLogical().CreateImageView(VkImageViewCreateInfo{ 233 image_views[i] = device.GetLogical().CreateImageView(VkImageViewCreateInfo{
236 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 234 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
237 .pNext = nullptr, 235 .pNext = nullptr,
diff --git a/src/video_core/renderer_vulkan/vk_fsr.h b/src/video_core/renderer_vulkan/vk_fsr.h
index 5d872861f..8bb9fc23a 100644
--- a/src/video_core/renderer_vulkan/vk_fsr.h
+++ b/src/video_core/renderer_vulkan/vk_fsr.h
@@ -47,7 +47,6 @@ private:
47 vk::Sampler sampler; 47 vk::Sampler sampler;
48 std::vector<vk::Image> images; 48 std::vector<vk::Image> images;
49 std::vector<vk::ImageView> image_views; 49 std::vector<vk::ImageView> image_views;
50 std::vector<MemoryCommit> buffer_commits;
51}; 50};
52 51
53} // namespace Vulkan 52} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp
index 10ace0420..d681bd22a 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp
@@ -181,7 +181,7 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, bool is_
181 frame->height = height; 181 frame->height = height;
182 frame->is_srgb = is_srgb; 182 frame->is_srgb = is_srgb;
183 183
184 frame->image = dld.CreateImage({ 184 frame->image = memory_allocator.CreateImage({
185 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, 185 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
186 .pNext = nullptr, 186 .pNext = nullptr,
187 .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, 187 .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
@@ -204,8 +204,6 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, bool is_
204 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, 204 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
205 }); 205 });
206 206
207 frame->image_commit = memory_allocator.Commit(frame->image, MemoryUsage::DeviceLocal);
208
209 frame->image_view = dld.CreateImageView({ 207 frame->image_view = dld.CreateImageView({
210 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 208 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
211 .pNext = nullptr, 209 .pNext = nullptr,
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h
index 4ac2e2395..83e859416 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.h
+++ b/src/video_core/renderer_vulkan/vk_present_manager.h
@@ -29,7 +29,6 @@ struct Frame {
29 vk::Image image; 29 vk::Image image;
30 vk::ImageView image_view; 30 vk::ImageView image_view;
31 vk::Framebuffer framebuffer; 31 vk::Framebuffer framebuffer;
32 MemoryCommit image_commit;
33 vk::CommandBuffer cmdbuf; 32 vk::CommandBuffer cmdbuf;
34 vk::Semaphore render_ready; 33 vk::Semaphore render_ready;
35 vk::Fence present_done; 34 vk::Fence present_done;
diff --git a/src/video_core/renderer_vulkan/vk_smaa.cpp b/src/video_core/renderer_vulkan/vk_smaa.cpp
index f8735189d..5efd7d66e 100644
--- a/src/video_core/renderer_vulkan/vk_smaa.cpp
+++ b/src/video_core/renderer_vulkan/vk_smaa.cpp
@@ -25,9 +25,7 @@ namespace {
25 25
26#define ARRAY_TO_SPAN(a) std::span(a, (sizeof(a) / sizeof(a[0]))) 26#define ARRAY_TO_SPAN(a) std::span(a, (sizeof(a) / sizeof(a[0])))
27 27
28std::pair<vk::Image, MemoryCommit> CreateWrappedImage(const Device& device, 28vk::Image CreateWrappedImage(MemoryAllocator& allocator, VkExtent2D dimensions, VkFormat format) {
29 MemoryAllocator& allocator,
30 VkExtent2D dimensions, VkFormat format) {
31 const VkImageCreateInfo image_ci{ 29 const VkImageCreateInfo image_ci{
32 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, 30 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
33 .pNext = nullptr, 31 .pNext = nullptr,
@@ -46,11 +44,7 @@ std::pair<vk::Image, MemoryCommit> CreateWrappedImage(const Device& device,
46 .pQueueFamilyIndices = nullptr, 44 .pQueueFamilyIndices = nullptr,
47 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, 45 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
48 }; 46 };
49 47 return allocator.CreateImage(image_ci);
50 auto image = device.GetLogical().CreateImage(image_ci);
51 auto commit = allocator.Commit(image, Vulkan::MemoryUsage::DeviceLocal);
52
53 return std::make_pair(std::move(image), std::move(commit));
54} 48}
55 49
56void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayout target_layout, 50void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayout target_layout,
@@ -82,7 +76,7 @@ void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayo
82void UploadImage(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler, 76void UploadImage(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler,
83 vk::Image& image, VkExtent2D dimensions, VkFormat format, 77 vk::Image& image, VkExtent2D dimensions, VkFormat format,
84 std::span<const u8> initial_contents = {}) { 78 std::span<const u8> initial_contents = {}) {
85 auto upload_buffer = device.GetLogical().CreateBuffer(VkBufferCreateInfo{ 79 const VkBufferCreateInfo upload_ci = {
86 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 80 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
87 .pNext = nullptr, 81 .pNext = nullptr,
88 .flags = 0, 82 .flags = 0,
@@ -91,9 +85,10 @@ void UploadImage(const Device& device, MemoryAllocator& allocator, Scheduler& sc
91 .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 85 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
92 .queueFamilyIndexCount = 0, 86 .queueFamilyIndexCount = 0,
93 .pQueueFamilyIndices = nullptr, 87 .pQueueFamilyIndices = nullptr,
94 }); 88 };
95 auto upload_commit = allocator.Commit(upload_buffer, MemoryUsage::Upload); 89 auto upload_buffer = allocator.CreateBuffer(upload_ci, MemoryUsage::Upload);
96 std::ranges::copy(initial_contents, upload_commit.Map().begin()); 90 std::ranges::copy(initial_contents, upload_buffer.Mapped().begin());
91 upload_buffer.Flush();
97 92
98 const std::array<VkBufferImageCopy, 1> regions{{{ 93 const std::array<VkBufferImageCopy, 1> regions{{{
99 .bufferOffset = 0, 94 .bufferOffset = 0,
@@ -117,9 +112,6 @@ void UploadImage(const Device& device, MemoryAllocator& allocator, Scheduler& sc
117 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); 112 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
118 }); 113 });
119 scheduler.Finish(); 114 scheduler.Finish();
120
121 // This should go out of scope before the commit
122 auto upload_buffer2 = std::move(upload_buffer);
123} 115}
124 116
125vk::ImageView CreateWrappedImageView(const Device& device, vk::Image& image, VkFormat format) { 117vk::ImageView CreateWrappedImageView(const Device& device, vk::Image& image, VkFormat format) {
@@ -531,10 +523,8 @@ void SMAA::CreateImages() {
531 static constexpr VkExtent2D area_extent{AREATEX_WIDTH, AREATEX_HEIGHT}; 523 static constexpr VkExtent2D area_extent{AREATEX_WIDTH, AREATEX_HEIGHT};
532 static constexpr VkExtent2D search_extent{SEARCHTEX_WIDTH, SEARCHTEX_HEIGHT}; 524 static constexpr VkExtent2D search_extent{SEARCHTEX_WIDTH, SEARCHTEX_HEIGHT};
533 525
534 std::tie(m_static_images[Area], m_static_buffer_commits[Area]) = 526 m_static_images[Area] = CreateWrappedImage(m_allocator, area_extent, VK_FORMAT_R8G8_UNORM);
535 CreateWrappedImage(m_device, m_allocator, area_extent, VK_FORMAT_R8G8_UNORM); 527 m_static_images[Search] = CreateWrappedImage(m_allocator, search_extent, VK_FORMAT_R8_UNORM);
536 std::tie(m_static_images[Search], m_static_buffer_commits[Search]) =
537 CreateWrappedImage(m_device, m_allocator, search_extent, VK_FORMAT_R8_UNORM);
538 528
539 m_static_image_views[Area] = 529 m_static_image_views[Area] =
540 CreateWrappedImageView(m_device, m_static_images[Area], VK_FORMAT_R8G8_UNORM); 530 CreateWrappedImageView(m_device, m_static_images[Area], VK_FORMAT_R8G8_UNORM);
@@ -544,12 +534,11 @@ void SMAA::CreateImages() {
544 for (u32 i = 0; i < m_image_count; i++) { 534 for (u32 i = 0; i < m_image_count; i++) {
545 Images& images = m_dynamic_images.emplace_back(); 535 Images& images = m_dynamic_images.emplace_back();
546 536
547 std::tie(images.images[Blend], images.buffer_commits[Blend]) = 537 images.images[Blend] =
548 CreateWrappedImage(m_device, m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); 538 CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT);
549 std::tie(images.images[Edges], images.buffer_commits[Edges]) = 539 images.images[Edges] = CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16_SFLOAT);
550 CreateWrappedImage(m_device, m_allocator, m_extent, VK_FORMAT_R16G16_SFLOAT); 540 images.images[Output] =
551 std::tie(images.images[Output], images.buffer_commits[Output]) = 541 CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT);
552 CreateWrappedImage(m_device, m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT);
553 542
554 images.image_views[Blend] = 543 images.image_views[Blend] =
555 CreateWrappedImageView(m_device, images.images[Blend], VK_FORMAT_R16G16B16A16_SFLOAT); 544 CreateWrappedImageView(m_device, images.images[Blend], VK_FORMAT_R16G16B16A16_SFLOAT);
diff --git a/src/video_core/renderer_vulkan/vk_smaa.h b/src/video_core/renderer_vulkan/vk_smaa.h
index 99a369148..0e214258a 100644
--- a/src/video_core/renderer_vulkan/vk_smaa.h
+++ b/src/video_core/renderer_vulkan/vk_smaa.h
@@ -66,13 +66,11 @@ private:
66 std::array<vk::Pipeline, MaxSMAAStage> m_pipelines{}; 66 std::array<vk::Pipeline, MaxSMAAStage> m_pipelines{};
67 std::array<vk::RenderPass, MaxSMAAStage> m_renderpasses{}; 67 std::array<vk::RenderPass, MaxSMAAStage> m_renderpasses{};
68 68
69 std::array<MemoryCommit, MaxStaticImage> m_static_buffer_commits;
70 std::array<vk::Image, MaxStaticImage> m_static_images{}; 69 std::array<vk::Image, MaxStaticImage> m_static_images{};
71 std::array<vk::ImageView, MaxStaticImage> m_static_image_views{}; 70 std::array<vk::ImageView, MaxStaticImage> m_static_image_views{};
72 71
73 struct Images { 72 struct Images {
74 vk::DescriptorSets descriptor_sets{}; 73 vk::DescriptorSets descriptor_sets{};
75 std::array<MemoryCommit, MaxDynamicImage> buffer_commits;
76 std::array<vk::Image, MaxDynamicImage> images{}; 74 std::array<vk::Image, MaxDynamicImage> images{};
77 std::array<vk::ImageView, MaxDynamicImage> image_views{}; 75 std::array<vk::ImageView, MaxDynamicImage> image_views{};
78 std::array<vk::Framebuffer, MaxSMAAStage> framebuffers{}; 76 std::array<vk::Framebuffer, MaxSMAAStage> framebuffers{};
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
index 74ca77216..62b251a9b 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
@@ -30,55 +30,6 @@ constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8_MiB;
30constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128_MiB; 30constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128_MiB;
31constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS; 31constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS;
32 32
33constexpr VkMemoryPropertyFlags HOST_FLAGS =
34 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
35constexpr VkMemoryPropertyFlags STREAM_FLAGS = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | HOST_FLAGS;
36
37bool IsStreamHeap(VkMemoryHeap heap) noexcept {
38 return STREAM_BUFFER_SIZE < (heap.size * 2) / 3;
39}
40
41std::optional<u32> FindMemoryTypeIndex(const VkPhysicalDeviceMemoryProperties& props, u32 type_mask,
42 VkMemoryPropertyFlags flags) noexcept {
43 for (u32 type_index = 0; type_index < props.memoryTypeCount; ++type_index) {
44 if (((type_mask >> type_index) & 1) == 0) {
45 // Memory type is incompatible
46 continue;
47 }
48 const VkMemoryType& memory_type = props.memoryTypes[type_index];
49 if ((memory_type.propertyFlags & flags) != flags) {
50 // Memory type doesn't have the flags we want
51 continue;
52 }
53 if (!IsStreamHeap(props.memoryHeaps[memory_type.heapIndex])) {
54 // Memory heap is not suitable for streaming
55 continue;
56 }
57 // Success!
58 return type_index;
59 }
60 return std::nullopt;
61}
62
63u32 FindMemoryTypeIndex(const VkPhysicalDeviceMemoryProperties& props, u32 type_mask,
64 bool try_device_local) {
65 std::optional<u32> type;
66 if (try_device_local) {
67 // Try to find a DEVICE_LOCAL_BIT type, Nvidia and AMD have a dedicated heap for this
68 type = FindMemoryTypeIndex(props, type_mask, STREAM_FLAGS);
69 if (type) {
70 return *type;
71 }
72 }
73 // Otherwise try without the DEVICE_LOCAL_BIT
74 type = FindMemoryTypeIndex(props, type_mask, HOST_FLAGS);
75 if (type) {
76 return *type;
77 }
78 // This should never happen, and in case it does, signal it as an out of memory situation
79 throw vk::Exception(VK_ERROR_OUT_OF_DEVICE_MEMORY);
80}
81
82size_t Region(size_t iterator) noexcept { 33size_t Region(size_t iterator) noexcept {
83 return iterator / REGION_SIZE; 34 return iterator / REGION_SIZE;
84} 35}
@@ -87,8 +38,7 @@ size_t Region(size_t iterator) noexcept {
87StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_, 38StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
88 Scheduler& scheduler_) 39 Scheduler& scheduler_)
89 : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} { 40 : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} {
90 const vk::Device& dev = device.GetLogical(); 41 const VkBufferCreateInfo stream_ci = {
91 stream_buffer = dev.CreateBuffer(VkBufferCreateInfo{
92 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 42 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
93 .pNext = nullptr, 43 .pNext = nullptr,
94 .flags = 0, 44 .flags = 0,
@@ -99,46 +49,13 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem
99 .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 49 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
100 .queueFamilyIndexCount = 0, 50 .queueFamilyIndexCount = 0,
101 .pQueueFamilyIndices = nullptr, 51 .pQueueFamilyIndices = nullptr,
102 });
103 if (device.HasDebuggingToolAttached()) {
104 stream_buffer.SetObjectNameEXT("Stream Buffer");
105 }
106 VkMemoryDedicatedRequirements dedicated_reqs{
107 .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS,
108 .pNext = nullptr,
109 .prefersDedicatedAllocation = VK_FALSE,
110 .requiresDedicatedAllocation = VK_FALSE,
111 };
112 const auto requirements = dev.GetBufferMemoryRequirements(*stream_buffer, &dedicated_reqs);
113 const bool make_dedicated = dedicated_reqs.prefersDedicatedAllocation == VK_TRUE ||
114 dedicated_reqs.requiresDedicatedAllocation == VK_TRUE;
115 const VkMemoryDedicatedAllocateInfo dedicated_info{
116 .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
117 .pNext = nullptr,
118 .image = nullptr,
119 .buffer = *stream_buffer,
120 }; 52 };
121 const auto memory_properties = device.GetPhysical().GetMemoryProperties().memoryProperties; 53 stream_buffer = memory_allocator.CreateBuffer(stream_ci, MemoryUsage::Stream);
122 VkMemoryAllocateInfo stream_memory_info{
123 .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
124 .pNext = make_dedicated ? &dedicated_info : nullptr,
125 .allocationSize = requirements.size,
126 .memoryTypeIndex =
127 FindMemoryTypeIndex(memory_properties, requirements.memoryTypeBits, true),
128 };
129 stream_memory = dev.TryAllocateMemory(stream_memory_info);
130 if (!stream_memory) {
131 LOG_INFO(Render_Vulkan, "Dynamic memory allocation failed, trying with system memory");
132 stream_memory_info.memoryTypeIndex =
133 FindMemoryTypeIndex(memory_properties, requirements.memoryTypeBits, false);
134 stream_memory = dev.AllocateMemory(stream_memory_info);
135 }
136
137 if (device.HasDebuggingToolAttached()) { 54 if (device.HasDebuggingToolAttached()) {
138 stream_memory.SetObjectNameEXT("Stream Buffer Memory"); 55 stream_buffer.SetObjectNameEXT("Stream Buffer");
139 } 56 }
140 stream_buffer.BindMemory(*stream_memory, 0); 57 stream_pointer = stream_buffer.Mapped();
141 stream_pointer = stream_memory.Map(0, STREAM_BUFFER_SIZE); 58 ASSERT_MSG(!stream_pointer.empty(), "Stream buffer must be host visible!");
142} 59}
143 60
144StagingBufferPool::~StagingBufferPool() = default; 61StagingBufferPool::~StagingBufferPool() = default;
@@ -199,7 +116,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
199 return StagingBufferRef{ 116 return StagingBufferRef{
200 .buffer = *stream_buffer, 117 .buffer = *stream_buffer,
201 .offset = static_cast<VkDeviceSize>(offset), 118 .offset = static_cast<VkDeviceSize>(offset),
202 .mapped_span = std::span<u8>(stream_pointer + offset, size), 119 .mapped_span = stream_pointer.subspan(offset, size),
203 .usage{}, 120 .usage{},
204 .log2_level{}, 121 .log2_level{},
205 .index{}, 122 .index{},
@@ -247,7 +164,7 @@ std::optional<StagingBufferRef> StagingBufferPool::TryGetReservedBuffer(size_t s
247StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage usage, 164StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage usage,
248 bool deferred) { 165 bool deferred) {
249 const u32 log2 = Common::Log2Ceil64(size); 166 const u32 log2 = Common::Log2Ceil64(size);
250 vk::Buffer buffer = device.GetLogical().CreateBuffer({ 167 const VkBufferCreateInfo buffer_ci = {
251 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 168 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
252 .pNext = nullptr, 169 .pNext = nullptr,
253 .flags = 0, 170 .flags = 0,
@@ -259,17 +176,15 @@ StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage
259 .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 176 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
260 .queueFamilyIndexCount = 0, 177 .queueFamilyIndexCount = 0,
261 .pQueueFamilyIndices = nullptr, 178 .pQueueFamilyIndices = nullptr,
262 }); 179 };
180 vk::Buffer buffer = memory_allocator.CreateBuffer(buffer_ci, usage);
263 if (device.HasDebuggingToolAttached()) { 181 if (device.HasDebuggingToolAttached()) {
264 ++buffer_index; 182 ++buffer_index;
265 buffer.SetObjectNameEXT(fmt::format("Staging Buffer {}", buffer_index).c_str()); 183 buffer.SetObjectNameEXT(fmt::format("Staging Buffer {}", buffer_index).c_str());
266 } 184 }
267 MemoryCommit commit = memory_allocator.Commit(buffer, usage); 185 const std::span<u8> mapped_span = buffer.Mapped();
268 const std::span<u8> mapped_span = IsHostVisible(usage) ? commit.Map() : std::span<u8>{};
269
270 StagingBuffer& entry = GetCache(usage)[log2].entries.emplace_back(StagingBuffer{ 186 StagingBuffer& entry = GetCache(usage)[log2].entries.emplace_back(StagingBuffer{
271 .buffer = std::move(buffer), 187 .buffer = std::move(buffer),
272 .commit = std::move(commit),
273 .mapped_span = mapped_span, 188 .mapped_span = mapped_span,
274 .usage = usage, 189 .usage = usage,
275 .log2_level = log2, 190 .log2_level = log2,
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 4fd15f11a..5f69f08b1 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@@ -46,7 +46,6 @@ private:
46 46
47 struct StagingBuffer { 47 struct StagingBuffer {
48 vk::Buffer buffer; 48 vk::Buffer buffer;
49 MemoryCommit commit;
50 std::span<u8> mapped_span; 49 std::span<u8> mapped_span;
51 MemoryUsage usage; 50 MemoryUsage usage;
52 u32 log2_level; 51 u32 log2_level;
@@ -97,8 +96,7 @@ private:
97 Scheduler& scheduler; 96 Scheduler& scheduler;
98 97
99 vk::Buffer stream_buffer; 98 vk::Buffer stream_buffer;
100 vk::DeviceMemory stream_memory; 99 std::span<u8> stream_pointer;
101 u8* stream_pointer = nullptr;
102 100
103 size_t iterator = 0; 101 size_t iterator = 0;
104 size_t used_iterator = 0; 102 size_t used_iterator = 0;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index f3cef09dd..ce6acc30c 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -15,7 +15,6 @@
15#include "video_core/renderer_vulkan/blit_image.h" 15#include "video_core/renderer_vulkan/blit_image.h"
16#include "video_core/renderer_vulkan/maxwell_to_vk.h" 16#include "video_core/renderer_vulkan/maxwell_to_vk.h"
17#include "video_core/renderer_vulkan/vk_compute_pass.h" 17#include "video_core/renderer_vulkan/vk_compute_pass.h"
18#include "video_core/renderer_vulkan/vk_rasterizer.h"
19#include "video_core/renderer_vulkan/vk_render_pass_cache.h" 18#include "video_core/renderer_vulkan/vk_render_pass_cache.h"
20#include "video_core/renderer_vulkan/vk_scheduler.h" 19#include "video_core/renderer_vulkan/vk_scheduler.h"
21#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" 20#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -163,11 +162,12 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
163 }; 162 };
164} 163}
165 164
166[[nodiscard]] vk::Image MakeImage(const Device& device, const ImageInfo& info) { 165[[nodiscard]] vk::Image MakeImage(const Device& device, const MemoryAllocator& allocator,
166 const ImageInfo& info) {
167 if (info.type == ImageType::Buffer) { 167 if (info.type == ImageType::Buffer) {
168 return vk::Image{}; 168 return vk::Image{};
169 } 169 }
170 return device.GetLogical().CreateImage(MakeImageCreateInfo(device, info)); 170 return allocator.CreateImage(MakeImageCreateInfo(device, info));
171} 171}
172 172
173[[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) { 173[[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) {
@@ -839,14 +839,14 @@ bool TextureCacheRuntime::ShouldReinterpret(Image& dst, Image& src) {
839 839
840VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) { 840VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) {
841 const auto level = (8 * sizeof(size_t)) - std::countl_zero(needed_size - 1ULL); 841 const auto level = (8 * sizeof(size_t)) - std::countl_zero(needed_size - 1ULL);
842 if (buffer_commits[level]) { 842 if (buffers[level]) {
843 return *buffers[level]; 843 return *buffers[level];
844 } 844 }
845 const auto new_size = Common::NextPow2(needed_size); 845 const auto new_size = Common::NextPow2(needed_size);
846 static constexpr VkBufferUsageFlags flags = 846 static constexpr VkBufferUsageFlags flags =
847 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | 847 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
848 VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT; 848 VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT;
849 buffers[level] = device.GetLogical().CreateBuffer({ 849 const VkBufferCreateInfo temp_ci = {
850 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 850 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
851 .pNext = nullptr, 851 .pNext = nullptr,
852 .flags = 0, 852 .flags = 0,
@@ -855,9 +855,8 @@ VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) {
855 .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 855 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
856 .queueFamilyIndexCount = 0, 856 .queueFamilyIndexCount = 0,
857 .pQueueFamilyIndices = nullptr, 857 .pQueueFamilyIndices = nullptr,
858 }); 858 };
859 buffer_commits[level] = std::make_unique<MemoryCommit>( 859 buffers[level] = memory_allocator.CreateBuffer(temp_ci, MemoryUsage::DeviceLocal);
860 memory_allocator.Commit(buffers[level], MemoryUsage::DeviceLocal));
861 return *buffers[level]; 860 return *buffers[level];
862} 861}
863 862
@@ -1266,8 +1265,8 @@ void TextureCacheRuntime::TickFrame() {}
1266Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_, 1265Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_,
1267 VAddr cpu_addr_) 1266 VAddr cpu_addr_)
1268 : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler}, 1267 : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler},
1269 runtime{&runtime_}, original_image(MakeImage(runtime_.device, info)), 1268 runtime{&runtime_},
1270 commit(runtime_.memory_allocator.Commit(original_image, MemoryUsage::DeviceLocal)), 1269 original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info)),
1271 aspect_mask(ImageAspectMask(info.format)) { 1270 aspect_mask(ImageAspectMask(info.format)) {
1272 if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) { 1271 if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) {
1273 if (Settings::values.async_astc.GetValue()) { 1272 if (Settings::values.async_astc.GetValue()) {
@@ -1468,9 +1467,7 @@ bool Image::ScaleUp(bool ignore) {
1468 auto scaled_info = info; 1467 auto scaled_info = info;
1469 scaled_info.size.width = scaled_width; 1468 scaled_info.size.width = scaled_width;
1470 scaled_info.size.height = scaled_height; 1469 scaled_info.size.height = scaled_height;
1471 scaled_image = MakeImage(runtime->device, scaled_info); 1470 scaled_image = MakeImage(runtime->device, runtime->memory_allocator, scaled_info);
1472 auto& allocator = runtime->memory_allocator;
1473 scaled_commit = MemoryCommit(allocator.Commit(scaled_image, MemoryUsage::DeviceLocal));
1474 ignore = false; 1471 ignore = false;
1475 } 1472 }
1476 current_image = *scaled_image; 1473 current_image = *scaled_image;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index f14525dcb..220943116 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -116,7 +116,6 @@ public:
116 116
117 static constexpr size_t indexing_slots = 8 * sizeof(size_t); 117 static constexpr size_t indexing_slots = 8 * sizeof(size_t);
118 std::array<vk::Buffer, indexing_slots> buffers{}; 118 std::array<vk::Buffer, indexing_slots> buffers{};
119 std::array<std::unique_ptr<MemoryCommit>, indexing_slots> buffer_commits{};
120}; 119};
121 120
122class Image : public VideoCommon::ImageBase { 121class Image : public VideoCommon::ImageBase {
@@ -180,12 +179,10 @@ private:
180 TextureCacheRuntime* runtime{}; 179 TextureCacheRuntime* runtime{};
181 180
182 vk::Image original_image; 181 vk::Image original_image;
183 MemoryCommit commit;
184 std::vector<vk::ImageView> storage_image_views; 182 std::vector<vk::ImageView> storage_image_views;
185 VkImageAspectFlags aspect_mask = 0; 183 VkImageAspectFlags aspect_mask = 0;
186 bool initialized = false; 184 bool initialized = false;
187 vk::Image scaled_image{}; 185 vk::Image scaled_image{};
188 MemoryCommit scaled_commit{};
189 VkImage current_image{}; 186 VkImage current_image{};
190 187
191 std::unique_ptr<Framebuffer> scale_framebuffer; 188 std::unique_ptr<Framebuffer> scale_framebuffer;
diff --git a/src/video_core/renderer_vulkan/vk_turbo_mode.cpp b/src/video_core/renderer_vulkan/vk_turbo_mode.cpp
index a802d3c49..460d8d59d 100644
--- a/src/video_core/renderer_vulkan/vk_turbo_mode.cpp
+++ b/src/video_core/renderer_vulkan/vk_turbo_mode.cpp
@@ -18,7 +18,7 @@ using namespace Common::Literals;
18 18
19TurboMode::TurboMode(const vk::Instance& instance, const vk::InstanceDispatch& dld) 19TurboMode::TurboMode(const vk::Instance& instance, const vk::InstanceDispatch& dld)
20#ifndef ANDROID 20#ifndef ANDROID
21 : m_device{CreateDevice(instance, dld, VK_NULL_HANDLE)}, m_allocator{m_device, false} 21 : m_device{CreateDevice(instance, dld, VK_NULL_HANDLE)}, m_allocator{m_device}
22#endif 22#endif
23{ 23{
24 { 24 {
@@ -41,7 +41,7 @@ void TurboMode::Run(std::stop_token stop_token) {
41 auto& dld = m_device.GetLogical(); 41 auto& dld = m_device.GetLogical();
42 42
43 // Allocate buffer. 2MiB should be sufficient. 43 // Allocate buffer. 2MiB should be sufficient.
44 auto buffer = dld.CreateBuffer(VkBufferCreateInfo{ 44 const VkBufferCreateInfo buffer_ci = {
45 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 45 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
46 .pNext = nullptr, 46 .pNext = nullptr,
47 .flags = 0, 47 .flags = 0,
@@ -50,10 +50,8 @@ void TurboMode::Run(std::stop_token stop_token) {
50 .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 50 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
51 .queueFamilyIndexCount = 0, 51 .queueFamilyIndexCount = 0,
52 .pQueueFamilyIndices = nullptr, 52 .pQueueFamilyIndices = nullptr,
53 }); 53 };
54 54 vk::Buffer buffer = m_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
55 // Commit some device local memory for the buffer.
56 auto commit = m_allocator.Commit(buffer, MemoryUsage::DeviceLocal);
57 55
58 // Create the descriptor pool to contain our descriptor. 56 // Create the descriptor pool to contain our descriptor.
59 static constexpr VkDescriptorPoolSize pool_size{ 57 static constexpr VkDescriptorPoolSize pool_size{
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index b11abe311..e4ca65b58 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -22,6 +22,8 @@
22#include <adrenotools/bcenabler.h> 22#include <adrenotools/bcenabler.h>
23#endif 23#endif
24 24
25#include <vk_mem_alloc.h>
26
25namespace Vulkan { 27namespace Vulkan {
26using namespace Common::Literals; 28using namespace Common::Literals;
27namespace { 29namespace {
@@ -596,9 +598,31 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
596 598
597 graphics_queue = logical.GetQueue(graphics_family); 599 graphics_queue = logical.GetQueue(graphics_family);
598 present_queue = logical.GetQueue(present_family); 600 present_queue = logical.GetQueue(present_family);
601
602 VmaVulkanFunctions functions{};
603 functions.vkGetInstanceProcAddr = dld.vkGetInstanceProcAddr;
604 functions.vkGetDeviceProcAddr = dld.vkGetDeviceProcAddr;
605
606 const VmaAllocatorCreateInfo allocator_info = {
607 .flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT,
608 .physicalDevice = physical,
609 .device = *logical,
610 .preferredLargeHeapBlockSize = 0,
611 .pAllocationCallbacks = nullptr,
612 .pDeviceMemoryCallbacks = nullptr,
613 .pHeapSizeLimit = nullptr,
614 .pVulkanFunctions = &functions,
615 .instance = instance,
616 .vulkanApiVersion = VK_API_VERSION_1_1,
617 .pTypeExternalMemoryHandleTypes = nullptr,
618 };
619
620 vk::Check(vmaCreateAllocator(&allocator_info, &allocator));
599} 621}
600 622
601Device::~Device() = default; 623Device::~Device() {
624 vmaDestroyAllocator(allocator);
625}
602 626
603VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, 627VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
604 FormatType format_type) const { 628 FormatType format_type) const {
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 0b634a876..b84af3dfb 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -14,6 +14,8 @@
14#include "common/settings.h" 14#include "common/settings.h"
15#include "video_core/vulkan_common/vulkan_wrapper.h" 15#include "video_core/vulkan_common/vulkan_wrapper.h"
16 16
17VK_DEFINE_HANDLE(VmaAllocator)
18
17// Define all features which may be used by the implementation here. 19// Define all features which may be used by the implementation here.
18// Vulkan version in the macro describes the minimum version required for feature availability. 20// Vulkan version in the macro describes the minimum version required for feature availability.
19// If the Vulkan version is lower than the required version, the named extension is required. 21// If the Vulkan version is lower than the required version, the named extension is required.
@@ -199,6 +201,11 @@ public:
199 return dld; 201 return dld;
200 } 202 }
201 203
204 /// Returns the VMA allocator.
205 VmaAllocator GetAllocator() const {
206 return allocator;
207 }
208
202 /// Returns the logical device. 209 /// Returns the logical device.
203 const vk::Device& GetLogical() const { 210 const vk::Device& GetLogical() const {
204 return logical; 211 return logical;
@@ -630,6 +637,7 @@ private:
630 637
631private: 638private:
632 VkInstance instance; ///< Vulkan instance. 639 VkInstance instance; ///< Vulkan instance.
640 VmaAllocator allocator; ///< VMA allocator.
633 vk::DeviceDispatch dld; ///< Device function pointers. 641 vk::DeviceDispatch dld; ///< Device function pointers.
634 vk::PhysicalDevice physical; ///< Physical device. 642 vk::PhysicalDevice physical; ///< Physical device.
635 vk::Device logical; ///< Logical device. 643 vk::Device logical; ///< Logical device.
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index e28a556f8..a2ef0efa4 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -6,8 +6,6 @@
6#include <optional> 6#include <optional>
7#include <vector> 7#include <vector>
8 8
9#include <glad/glad.h>
10
11#include "common/alignment.h" 9#include "common/alignment.h"
12#include "common/assert.h" 10#include "common/assert.h"
13#include "common/common_types.h" 11#include "common/common_types.h"
@@ -17,6 +15,8 @@
17#include "video_core/vulkan_common/vulkan_memory_allocator.h" 15#include "video_core/vulkan_common/vulkan_memory_allocator.h"
18#include "video_core/vulkan_common/vulkan_wrapper.h" 16#include "video_core/vulkan_common/vulkan_wrapper.h"
19 17
18#include <vk_mem_alloc.h>
19
20namespace Vulkan { 20namespace Vulkan {
21namespace { 21namespace {
22struct Range { 22struct Range {
@@ -49,22 +49,45 @@ struct Range {
49 case MemoryUsage::Download: 49 case MemoryUsage::Download:
50 return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | 50 return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
51 VK_MEMORY_PROPERTY_HOST_CACHED_BIT; 51 VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
52 case MemoryUsage::Stream:
53 return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
54 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
52 } 55 }
53 ASSERT_MSG(false, "Invalid memory usage={}", usage); 56 ASSERT_MSG(false, "Invalid memory usage={}", usage);
54 return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; 57 return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
55} 58}
56 59
57constexpr VkExportMemoryAllocateInfo EXPORT_ALLOCATE_INFO{ 60[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferedVmaFlags(MemoryUsage usage) {
58 .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, 61 return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
59 .pNext = nullptr, 62 : VkMemoryPropertyFlagBits{};
60#ifdef _WIN32 63}
61 .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT, 64
62#elif __unix__ 65[[nodiscard]] VmaAllocationCreateFlags MemoryUsageVmaFlags(MemoryUsage usage) {
63 .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT, 66 switch (usage) {
64#else 67 case MemoryUsage::Upload:
65 .handleTypes = 0, 68 case MemoryUsage::Stream:
66#endif 69 return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
67}; 70 case MemoryUsage::Download:
71 return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
72 case MemoryUsage::DeviceLocal:
73 return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
74 VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT;
75 }
76 return {};
77}
78
79[[nodiscard]] VmaMemoryUsage MemoryUsageVma(MemoryUsage usage) {
80 switch (usage) {
81 case MemoryUsage::DeviceLocal:
82 case MemoryUsage::Stream:
83 return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
84 case MemoryUsage::Upload:
85 case MemoryUsage::Download:
86 return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
87 }
88 return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
89}
90
68} // Anonymous namespace 91} // Anonymous namespace
69 92
70class MemoryAllocation { 93class MemoryAllocation {
@@ -74,14 +97,6 @@ public:
74 : allocator{allocator_}, memory{std::move(memory_)}, allocation_size{allocation_size_}, 97 : allocator{allocator_}, memory{std::move(memory_)}, allocation_size{allocation_size_},
75 property_flags{properties}, shifted_memory_type{1U << type} {} 98 property_flags{properties}, shifted_memory_type{1U << type} {}
76 99
77#if defined(_WIN32) || defined(__unix__)
78 ~MemoryAllocation() {
79 if (owning_opengl_handle != 0) {
80 glDeleteMemoryObjectsEXT(1, &owning_opengl_handle);
81 }
82 }
83#endif
84
85 MemoryAllocation& operator=(const MemoryAllocation&) = delete; 100 MemoryAllocation& operator=(const MemoryAllocation&) = delete;
86 MemoryAllocation(const MemoryAllocation&) = delete; 101 MemoryAllocation(const MemoryAllocation&) = delete;
87 102
@@ -120,31 +135,6 @@ public:
120 return memory_mapped_span; 135 return memory_mapped_span;
121 } 136 }
122 137
123#ifdef _WIN32
124 [[nodiscard]] u32 ExportOpenGLHandle() {
125 if (!owning_opengl_handle) {
126 glCreateMemoryObjectsEXT(1, &owning_opengl_handle);
127 glImportMemoryWin32HandleEXT(owning_opengl_handle, allocation_size,
128 GL_HANDLE_TYPE_OPAQUE_WIN32_EXT,
129 memory.GetMemoryWin32HandleKHR());
130 }
131 return owning_opengl_handle;
132 }
133#elif __unix__
134 [[nodiscard]] u32 ExportOpenGLHandle() {
135 if (!owning_opengl_handle) {
136 glCreateMemoryObjectsEXT(1, &owning_opengl_handle);
137 glImportMemoryFdEXT(owning_opengl_handle, allocation_size, GL_HANDLE_TYPE_OPAQUE_FD_EXT,
138 memory.GetMemoryFdKHR());
139 }
140 return owning_opengl_handle;
141 }
142#else
143 [[nodiscard]] u32 ExportOpenGLHandle() {
144 return 0;
145 }
146#endif
147
148 /// Returns whether this allocation is compatible with the arguments. 138 /// Returns whether this allocation is compatible with the arguments.
149 [[nodiscard]] bool IsCompatible(VkMemoryPropertyFlags flags, u32 type_mask) const { 139 [[nodiscard]] bool IsCompatible(VkMemoryPropertyFlags flags, u32 type_mask) const {
150 return (flags & property_flags) == flags && (type_mask & shifted_memory_type) != 0; 140 return (flags & property_flags) == flags && (type_mask & shifted_memory_type) != 0;
@@ -182,9 +172,6 @@ private:
182 const u32 shifted_memory_type; ///< Shifted Vulkan memory type. 172 const u32 shifted_memory_type; ///< Shifted Vulkan memory type.
183 std::vector<Range> commits; ///< All commit ranges done from this allocation. 173 std::vector<Range> commits; ///< All commit ranges done from this allocation.
184 std::span<u8> memory_mapped_span; ///< Memory mapped span. Empty if not queried before. 174 std::span<u8> memory_mapped_span; ///< Memory mapped span. Empty if not queried before.
185#if defined(_WIN32) || defined(__unix__)
186 u32 owning_opengl_handle{}; ///< Owning OpenGL memory object handle.
187#endif
188}; 175};
189 176
190MemoryCommit::MemoryCommit(MemoryAllocation* allocation_, VkDeviceMemory memory_, u64 begin_, 177MemoryCommit::MemoryCommit(MemoryAllocation* allocation_, VkDeviceMemory memory_, u64 begin_,
@@ -216,24 +203,70 @@ std::span<u8> MemoryCommit::Map() {
216 return span; 203 return span;
217} 204}
218 205
219u32 MemoryCommit::ExportOpenGLHandle() const {
220 return allocation->ExportOpenGLHandle();
221}
222
223void MemoryCommit::Release() { 206void MemoryCommit::Release() {
224 if (allocation) { 207 if (allocation) {
225 allocation->Free(begin); 208 allocation->Free(begin);
226 } 209 }
227} 210}
228 211
229MemoryAllocator::MemoryAllocator(const Device& device_, bool export_allocations_) 212MemoryAllocator::MemoryAllocator(const Device& device_)
230 : device{device_}, properties{device_.GetPhysical().GetMemoryProperties().memoryProperties}, 213 : device{device_}, allocator{device.GetAllocator()},
231 export_allocations{export_allocations_}, 214 properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
232 buffer_image_granularity{ 215 buffer_image_granularity{
233 device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {} 216 device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {}
234 217
235MemoryAllocator::~MemoryAllocator() = default; 218MemoryAllocator::~MemoryAllocator() = default;
236 219
220vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
221 const VmaAllocationCreateInfo alloc_ci = {
222 .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
223 .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
224 .requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
225 .preferredFlags = 0,
226 .memoryTypeBits = 0,
227 .pool = VK_NULL_HANDLE,
228 .pUserData = nullptr,
229 .priority = 0.f,
230 };
231
232 VkImage handle{};
233 VmaAllocation allocation{};
234
235 vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr));
236
237 return vk::Image(handle, *device.GetLogical(), allocator, allocation,
238 device.GetDispatchLoader());
239}
240
241vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
242 const VmaAllocationCreateInfo alloc_ci = {
243 .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT |
244 MemoryUsageVmaFlags(usage),
245 .usage = MemoryUsageVma(usage),
246 .requiredFlags = 0,
247 .preferredFlags = MemoryUsagePreferedVmaFlags(usage),
248 .memoryTypeBits = 0,
249 .pool = VK_NULL_HANDLE,
250 .pUserData = nullptr,
251 .priority = 0.f,
252 };
253
254 VkBuffer handle{};
255 VmaAllocationInfo alloc_info{};
256 VmaAllocation allocation{};
257 VkMemoryPropertyFlags property_flags{};
258
259 vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
260 vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
261
262 u8* data = reinterpret_cast<u8*>(alloc_info.pMappedData);
263 const std::span<u8> mapped_data = data ? std::span<u8>{data, ci.size} : std::span<u8>{};
264 const bool is_coherent = property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
265
266 return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data, is_coherent,
267 device.GetDispatchLoader());
268}
269
237MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, MemoryUsage usage) { 270MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, MemoryUsage usage) {
238 // Find the fastest memory flags we can afford with the current requirements 271 // Find the fastest memory flags we can afford with the current requirements
239 const u32 type_mask = requirements.memoryTypeBits; 272 const u32 type_mask = requirements.memoryTypeBits;
@@ -253,25 +286,11 @@ MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, M
253 return TryCommit(requirements, flags).value(); 286 return TryCommit(requirements, flags).value();
254} 287}
255 288
256MemoryCommit MemoryAllocator::Commit(const vk::Buffer& buffer, MemoryUsage usage) {
257 auto commit = Commit(device.GetLogical().GetBufferMemoryRequirements(*buffer), usage);
258 buffer.BindMemory(commit.Memory(), commit.Offset());
259 return commit;
260}
261
262MemoryCommit MemoryAllocator::Commit(const vk::Image& image, MemoryUsage usage) {
263 VkMemoryRequirements requirements = device.GetLogical().GetImageMemoryRequirements(*image);
264 requirements.size = Common::AlignUp(requirements.size, buffer_image_granularity);
265 auto commit = Commit(requirements, usage);
266 image.BindMemory(commit.Memory(), commit.Offset());
267 return commit;
268}
269
270bool MemoryAllocator::TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size) { 289bool MemoryAllocator::TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size) {
271 const u32 type = FindType(flags, type_mask).value(); 290 const u32 type = FindType(flags, type_mask).value();
272 vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({ 291 vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({
273 .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, 292 .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
274 .pNext = export_allocations ? &EXPORT_ALLOCATE_INFO : nullptr, 293 .pNext = nullptr,
275 .allocationSize = size, 294 .allocationSize = size,
276 .memoryTypeIndex = type, 295 .memoryTypeIndex = type,
277 }); 296 });
@@ -342,16 +361,4 @@ std::optional<u32> MemoryAllocator::FindType(VkMemoryPropertyFlags flags, u32 ty
342 return std::nullopt; 361 return std::nullopt;
343} 362}
344 363
345bool IsHostVisible(MemoryUsage usage) noexcept {
346 switch (usage) {
347 case MemoryUsage::DeviceLocal:
348 return false;
349 case MemoryUsage::Upload:
350 case MemoryUsage::Download:
351 return true;
352 }
353 ASSERT_MSG(false, "Invalid memory usage={}", usage);
354 return false;
355}
356
357} // namespace Vulkan 364} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h
index a5bff03fe..f449bc8d0 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.h
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h
@@ -9,6 +9,8 @@
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "video_core/vulkan_common/vulkan_wrapper.h" 10#include "video_core/vulkan_common/vulkan_wrapper.h"
11 11
12VK_DEFINE_HANDLE(VmaAllocator)
13
12namespace Vulkan { 14namespace Vulkan {
13 15
14class Device; 16class Device;
@@ -17,9 +19,11 @@ class MemoryAllocation;
17 19
18/// Hints and requirements for the backing memory type of a commit 20/// Hints and requirements for the backing memory type of a commit
19enum class MemoryUsage { 21enum class MemoryUsage {
20 DeviceLocal, ///< Hints device local usages, fastest memory type to read and write from the GPU 22 DeviceLocal, ///< Requests device local host visible buffer, falling back to device local
23 ///< memory.
21 Upload, ///< Requires a host visible memory type optimized for CPU to GPU uploads 24 Upload, ///< Requires a host visible memory type optimized for CPU to GPU uploads
22 Download, ///< Requires a host visible memory type optimized for GPU to CPU readbacks 25 Download, ///< Requires a host visible memory type optimized for GPU to CPU readbacks
26 Stream, ///< Requests device local host visible buffer, falling back host memory.
23}; 27};
24 28
25/// Ownership handle of a memory commitment. 29/// Ownership handle of a memory commitment.
@@ -41,9 +45,6 @@ public:
41 /// It will map the backing allocation if it hasn't been mapped before. 45 /// It will map the backing allocation if it hasn't been mapped before.
42 std::span<u8> Map(); 46 std::span<u8> Map();
43 47
44 /// Returns an non-owning OpenGL handle, creating one if it doesn't exist.
45 u32 ExportOpenGLHandle() const;
46
47 /// Returns the Vulkan memory handler. 48 /// Returns the Vulkan memory handler.
48 VkDeviceMemory Memory() const { 49 VkDeviceMemory Memory() const {
49 return memory; 50 return memory;
@@ -74,16 +75,19 @@ public:
74 * Construct memory allocator 75 * Construct memory allocator
75 * 76 *
76 * @param device_ Device to allocate from 77 * @param device_ Device to allocate from
77 * @param export_allocations_ True when allocations have to be exported
78 * 78 *
79 * @throw vk::Exception on failure 79 * @throw vk::Exception on failure
80 */ 80 */
81 explicit MemoryAllocator(const Device& device_, bool export_allocations_); 81 explicit MemoryAllocator(const Device& device_);
82 ~MemoryAllocator(); 82 ~MemoryAllocator();
83 83
84 MemoryAllocator& operator=(const MemoryAllocator&) = delete; 84 MemoryAllocator& operator=(const MemoryAllocator&) = delete;
85 MemoryAllocator(const MemoryAllocator&) = delete; 85 MemoryAllocator(const MemoryAllocator&) = delete;
86 86
87 vk::Image CreateImage(const VkImageCreateInfo& ci) const;
88
89 vk::Buffer CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const;
90
87 /** 91 /**
88 * Commits a memory with the specified requirements. 92 * Commits a memory with the specified requirements.
89 * 93 *
@@ -97,9 +101,6 @@ public:
97 /// Commits memory required by the buffer and binds it. 101 /// Commits memory required by the buffer and binds it.
98 MemoryCommit Commit(const vk::Buffer& buffer, MemoryUsage usage); 102 MemoryCommit Commit(const vk::Buffer& buffer, MemoryUsage usage);
99 103
100 /// Commits memory required by the image and binds it.
101 MemoryCommit Commit(const vk::Image& image, MemoryUsage usage);
102
103private: 104private:
104 /// Tries to allocate a chunk of memory. 105 /// Tries to allocate a chunk of memory.
105 bool TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size); 106 bool TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size);
@@ -117,15 +118,12 @@ private:
117 /// Returns index to the fastest memory type compatible with the passed requirements. 118 /// Returns index to the fastest memory type compatible with the passed requirements.
118 std::optional<u32> FindType(VkMemoryPropertyFlags flags, u32 type_mask) const; 119 std::optional<u32> FindType(VkMemoryPropertyFlags flags, u32 type_mask) const;
119 120
120 const Device& device; ///< Device handle. 121 const Device& device; ///< Device handle.
121 const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties. 122 VmaAllocator allocator; ///< Vma allocator.
122 const bool export_allocations; ///< True when memory allocations have to be exported. 123 const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties.
123 std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations. 124 std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
124 VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers 125 VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers
125 // and optimal images 126 // and optimal images
126}; 127};
127 128
128/// Returns true when a memory usage is guaranteed to be host visible.
129bool IsHostVisible(MemoryUsage usage) noexcept;
130
131} // namespace Vulkan 129} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index 336f53700..28fcb21a0 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -12,6 +12,8 @@
12 12
13#include "video_core/vulkan_common/vulkan_wrapper.h" 13#include "video_core/vulkan_common/vulkan_wrapper.h"
14 14
15#include <vk_mem_alloc.h>
16
15namespace Vulkan::vk { 17namespace Vulkan::vk {
16 18
17namespace { 19namespace {
@@ -547,24 +549,40 @@ DebugUtilsMessenger Instance::CreateDebugUtilsMessenger(
547 return DebugUtilsMessenger(object, handle, *dld); 549 return DebugUtilsMessenger(object, handle, *dld);
548} 550}
549 551
550void Buffer::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const { 552void Image::SetObjectNameEXT(const char* name) const {
551 Check(dld->vkBindBufferMemory(owner, handle, memory, offset)); 553 SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_IMAGE, name);
552} 554}
553 555
554void Buffer::SetObjectNameEXT(const char* name) const { 556void Image::Release() const noexcept {
555 SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_BUFFER, name); 557 if (handle) {
558 vmaDestroyImage(allocator, handle, allocation);
559 }
556} 560}
557 561
558void BufferView::SetObjectNameEXT(const char* name) const { 562void Buffer::Flush() const {
559 SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_BUFFER_VIEW, name); 563 if (!is_coherent) {
564 vmaFlushAllocation(allocator, allocation, 0, VK_WHOLE_SIZE);
565 }
560} 566}
561 567
562void Image::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const { 568void Buffer::Invalidate() const {
563 Check(dld->vkBindImageMemory(owner, handle, memory, offset)); 569 if (!is_coherent) {
570 vmaInvalidateAllocation(allocator, allocation, 0, VK_WHOLE_SIZE);
571 }
564} 572}
565 573
566void Image::SetObjectNameEXT(const char* name) const { 574void Buffer::SetObjectNameEXT(const char* name) const {
567 SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_IMAGE, name); 575 SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_BUFFER, name);
576}
577
578void Buffer::Release() const noexcept {
579 if (handle) {
580 vmaDestroyBuffer(allocator, handle, allocation);
581 }
582}
583
584void BufferView::SetObjectNameEXT(const char* name) const {
585 SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_BUFFER_VIEW, name);
568} 586}
569 587
570void ImageView::SetObjectNameEXT(const char* name) const { 588void ImageView::SetObjectNameEXT(const char* name) const {
@@ -701,24 +719,12 @@ Queue Device::GetQueue(u32 family_index) const noexcept {
701 return Queue(queue, *dld); 719 return Queue(queue, *dld);
702} 720}
703 721
704Buffer Device::CreateBuffer(const VkBufferCreateInfo& ci) const {
705 VkBuffer object;
706 Check(dld->vkCreateBuffer(handle, &ci, nullptr, &object));
707 return Buffer(object, handle, *dld);
708}
709
710BufferView Device::CreateBufferView(const VkBufferViewCreateInfo& ci) const { 722BufferView Device::CreateBufferView(const VkBufferViewCreateInfo& ci) const {
711 VkBufferView object; 723 VkBufferView object;
712 Check(dld->vkCreateBufferView(handle, &ci, nullptr, &object)); 724 Check(dld->vkCreateBufferView(handle, &ci, nullptr, &object));
713 return BufferView(object, handle, *dld); 725 return BufferView(object, handle, *dld);
714} 726}
715 727
716Image Device::CreateImage(const VkImageCreateInfo& ci) const {
717 VkImage object;
718 Check(dld->vkCreateImage(handle, &ci, nullptr, &object));
719 return Image(object, handle, *dld);
720}
721
722ImageView Device::CreateImageView(const VkImageViewCreateInfo& ci) const { 728ImageView Device::CreateImageView(const VkImageViewCreateInfo& ci) const {
723 VkImageView object; 729 VkImageView object;
724 Check(dld->vkCreateImageView(handle, &ci, nullptr, &object)); 730 Check(dld->vkCreateImageView(handle, &ci, nullptr, &object));
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 4ff328a21..44fce47a5 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -32,6 +32,9 @@
32#pragma warning(disable : 26812) // Disable prefer enum class over enum 32#pragma warning(disable : 26812) // Disable prefer enum class over enum
33#endif 33#endif
34 34
35VK_DEFINE_HANDLE(VmaAllocator)
36VK_DEFINE_HANDLE(VmaAllocation)
37
35namespace Vulkan::vk { 38namespace Vulkan::vk {
36 39
37/** 40/**
@@ -616,6 +619,138 @@ public:
616 } 619 }
617}; 620};
618 621
622class Image {
623public:
624 explicit Image(VkImage handle_, VkDevice owner_, VmaAllocator allocator_,
625 VmaAllocation allocation_, const DeviceDispatch& dld_) noexcept
626 : handle{handle_}, owner{owner_}, allocator{allocator_},
627 allocation{allocation_}, dld{&dld_} {}
628 Image() = default;
629
630 Image(const Image&) = delete;
631 Image& operator=(const Image&) = delete;
632
633 Image(Image&& rhs) noexcept
634 : handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, allocator{rhs.allocator},
635 allocation{rhs.allocation}, dld{rhs.dld} {}
636
637 Image& operator=(Image&& rhs) noexcept {
638 Release();
639 handle = std::exchange(rhs.handle, nullptr);
640 owner = rhs.owner;
641 allocator = rhs.allocator;
642 allocation = rhs.allocation;
643 dld = rhs.dld;
644 return *this;
645 }
646
647 ~Image() noexcept {
648 Release();
649 }
650
651 VkImage operator*() const noexcept {
652 return handle;
653 }
654
655 void reset() noexcept {
656 Release();
657 handle = nullptr;
658 }
659
660 explicit operator bool() const noexcept {
661 return handle != nullptr;
662 }
663
664 void SetObjectNameEXT(const char* name) const;
665
666private:
667 void Release() const noexcept;
668
669 VkImage handle = nullptr;
670 VkDevice owner = nullptr;
671 VmaAllocator allocator = nullptr;
672 VmaAllocation allocation = nullptr;
673 const DeviceDispatch* dld = nullptr;
674};
675
676class Buffer {
677public:
678 explicit Buffer(VkBuffer handle_, VkDevice owner_, VmaAllocator allocator_,
679 VmaAllocation allocation_, std::span<u8> mapped_, bool is_coherent_,
680 const DeviceDispatch& dld_) noexcept
681 : handle{handle_}, owner{owner_}, allocator{allocator_},
682 allocation{allocation_}, mapped{mapped_}, is_coherent{is_coherent_}, dld{&dld_} {}
683 Buffer() = default;
684
685 Buffer(const Buffer&) = delete;
686 Buffer& operator=(const Buffer&) = delete;
687
688 Buffer(Buffer&& rhs) noexcept
689 : handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, allocator{rhs.allocator},
690 allocation{rhs.allocation}, mapped{rhs.mapped},
691 is_coherent{rhs.is_coherent}, dld{rhs.dld} {}
692
693 Buffer& operator=(Buffer&& rhs) noexcept {
694 Release();
695 handle = std::exchange(rhs.handle, nullptr);
696 owner = rhs.owner;
697 allocator = rhs.allocator;
698 allocation = rhs.allocation;
699 mapped = rhs.mapped;
700 is_coherent = rhs.is_coherent;
701 dld = rhs.dld;
702 return *this;
703 }
704
705 ~Buffer() noexcept {
706 Release();
707 }
708
709 VkBuffer operator*() const noexcept {
710 return handle;
711 }
712
713 void reset() noexcept {
714 Release();
715 handle = nullptr;
716 }
717
718 explicit operator bool() const noexcept {
719 return handle != nullptr;
720 }
721
722 /// Returns the host mapped memory, an empty span otherwise.
723 std::span<u8> Mapped() noexcept {
724 return mapped;
725 }
726
727 std::span<const u8> Mapped() const noexcept {
728 return mapped;
729 }
730
731 /// Returns true if the buffer is mapped to the host.
732 bool IsHostVisible() const noexcept {
733 return !mapped.empty();
734 }
735
736 void Flush() const;
737
738 void Invalidate() const;
739
740 void SetObjectNameEXT(const char* name) const;
741
742private:
743 void Release() const noexcept;
744
745 VkBuffer handle = nullptr;
746 VkDevice owner = nullptr;
747 VmaAllocator allocator = nullptr;
748 VmaAllocation allocation = nullptr;
749 std::span<u8> mapped = {};
750 bool is_coherent = false;
751 const DeviceDispatch* dld = nullptr;
752};
753
619class Queue { 754class Queue {
620public: 755public:
621 /// Construct an empty queue handle. 756 /// Construct an empty queue handle.
@@ -639,17 +774,6 @@ private:
639 const DeviceDispatch* dld = nullptr; 774 const DeviceDispatch* dld = nullptr;
640}; 775};
641 776
642class Buffer : public Handle<VkBuffer, VkDevice, DeviceDispatch> {
643 using Handle<VkBuffer, VkDevice, DeviceDispatch>::Handle;
644
645public:
646 /// Attaches a memory allocation.
647 void BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const;
648
649 /// Set object name.
650 void SetObjectNameEXT(const char* name) const;
651};
652
653class BufferView : public Handle<VkBufferView, VkDevice, DeviceDispatch> { 777class BufferView : public Handle<VkBufferView, VkDevice, DeviceDispatch> {
654 using Handle<VkBufferView, VkDevice, DeviceDispatch>::Handle; 778 using Handle<VkBufferView, VkDevice, DeviceDispatch>::Handle;
655 779
@@ -658,17 +782,6 @@ public:
658 void SetObjectNameEXT(const char* name) const; 782 void SetObjectNameEXT(const char* name) const;
659}; 783};
660 784
661class Image : public Handle<VkImage, VkDevice, DeviceDispatch> {
662 using Handle<VkImage, VkDevice, DeviceDispatch>::Handle;
663
664public:
665 /// Attaches a memory allocation.
666 void BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const;
667
668 /// Set object name.
669 void SetObjectNameEXT(const char* name) const;
670};
671
672class ImageView : public Handle<VkImageView, VkDevice, DeviceDispatch> { 785class ImageView : public Handle<VkImageView, VkDevice, DeviceDispatch> {
673 using Handle<VkImageView, VkDevice, DeviceDispatch>::Handle; 786 using Handle<VkImageView, VkDevice, DeviceDispatch>::Handle;
674 787
@@ -840,12 +953,8 @@ public:
840 953
841 Queue GetQueue(u32 family_index) const noexcept; 954 Queue GetQueue(u32 family_index) const noexcept;
842 955
843 Buffer CreateBuffer(const VkBufferCreateInfo& ci) const;
844
845 BufferView CreateBufferView(const VkBufferViewCreateInfo& ci) const; 956 BufferView CreateBufferView(const VkBufferViewCreateInfo& ci) const;
846 957
847 Image CreateImage(const VkImageCreateInfo& ci) const;
848
849 ImageView CreateImageView(const VkImageViewCreateInfo& ci) const; 958 ImageView CreateImageView(const VkImageViewCreateInfo& ci) const;
850 959
851 Semaphore CreateSemaphore() const; 960 Semaphore CreateSemaphore() const;
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 733c296e4..fe98e3605 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -98,6 +98,9 @@ add_executable(yuzu
98 configuration/configure_input_profile_dialog.cpp 98 configuration/configure_input_profile_dialog.cpp
99 configuration/configure_input_profile_dialog.h 99 configuration/configure_input_profile_dialog.h
100 configuration/configure_input_profile_dialog.ui 100 configuration/configure_input_profile_dialog.ui
101 configuration/configure_mouse_panning.cpp
102 configuration/configure_mouse_panning.h
103 configuration/configure_mouse_panning.ui
101 configuration/configure_motion_touch.cpp 104 configuration/configure_motion_touch.cpp
102 configuration/configure_motion_touch.h 105 configuration/configure_motion_touch.h
103 configuration/configure_motion_touch.ui 106 configuration/configure_motion_touch.ui
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index edc206a25..87ab88cfa 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -351,6 +351,10 @@ void Config::ReadPlayerValue(std::size_t player_index) {
351 player_motions = default_param; 351 player_motions = default_param;
352 } 352 }
353 } 353 }
354
355 if (player_index == 0) {
356 ReadMousePanningValues();
357 }
354} 358}
355 359
356void Config::ReadDebugValues() { 360void Config::ReadDebugValues() {
@@ -471,6 +475,7 @@ void Config::ReadControlValues() {
471 ReadKeyboardValues(); 475 ReadKeyboardValues();
472 ReadMouseValues(); 476 ReadMouseValues();
473 ReadTouchscreenValues(); 477 ReadTouchscreenValues();
478 ReadMousePanningValues();
474 ReadMotionTouchValues(); 479 ReadMotionTouchValues();
475 ReadHidbusValues(); 480 ReadHidbusValues();
476 ReadIrCameraValues(); 481 ReadIrCameraValues();
@@ -481,8 +486,6 @@ void Config::ReadControlValues() {
481 Settings::values.enable_raw_input = false; 486 Settings::values.enable_raw_input = false;
482#endif 487#endif
483 ReadBasicSetting(Settings::values.emulate_analog_keyboard); 488 ReadBasicSetting(Settings::values.emulate_analog_keyboard);
484 Settings::values.mouse_panning = false;
485 ReadBasicSetting(Settings::values.mouse_panning_sensitivity);
486 ReadBasicSetting(Settings::values.enable_joycon_driver); 489 ReadBasicSetting(Settings::values.enable_joycon_driver);
487 ReadBasicSetting(Settings::values.enable_procon_driver); 490 ReadBasicSetting(Settings::values.enable_procon_driver);
488 ReadBasicSetting(Settings::values.random_amiibo_id); 491 ReadBasicSetting(Settings::values.random_amiibo_id);
@@ -496,6 +499,16 @@ void Config::ReadControlValues() {
496 qt_config->endGroup(); 499 qt_config->endGroup();
497} 500}
498 501
502void Config::ReadMousePanningValues() {
503 ReadBasicSetting(Settings::values.mouse_panning);
504 ReadBasicSetting(Settings::values.mouse_panning_x_sensitivity);
505 ReadBasicSetting(Settings::values.mouse_panning_y_sensitivity);
506 ReadBasicSetting(Settings::values.mouse_panning_deadzone_x_counterweight);
507 ReadBasicSetting(Settings::values.mouse_panning_deadzone_y_counterweight);
508 ReadBasicSetting(Settings::values.mouse_panning_decay_strength);
509 ReadBasicSetting(Settings::values.mouse_panning_min_decay);
510}
511
499void Config::ReadMotionTouchValues() { 512void Config::ReadMotionTouchValues() {
500 int num_touch_from_button_maps = 513 int num_touch_from_button_maps =
501 qt_config->beginReadArray(QStringLiteral("touch_from_button_maps")); 514 qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
@@ -1064,6 +1077,10 @@ void Config::SavePlayerValue(std::size_t player_index) {
1064 QString::fromStdString(player.motions[i]), 1077 QString::fromStdString(player.motions[i]),
1065 QString::fromStdString(default_param)); 1078 QString::fromStdString(default_param));
1066 } 1079 }
1080
1081 if (player_index == 0) {
1082 SaveMousePanningValues();
1083 }
1067} 1084}
1068 1085
1069void Config::SaveDebugValues() { 1086void Config::SaveDebugValues() {
@@ -1100,6 +1117,16 @@ void Config::SaveTouchscreenValues() {
1100 WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15); 1117 WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
1101} 1118}
1102 1119
1120void Config::SaveMousePanningValues() {
1121 // Don't overwrite values.mouse_panning
1122 WriteBasicSetting(Settings::values.mouse_panning_x_sensitivity);
1123 WriteBasicSetting(Settings::values.mouse_panning_y_sensitivity);
1124 WriteBasicSetting(Settings::values.mouse_panning_deadzone_x_counterweight);
1125 WriteBasicSetting(Settings::values.mouse_panning_deadzone_y_counterweight);
1126 WriteBasicSetting(Settings::values.mouse_panning_decay_strength);
1127 WriteBasicSetting(Settings::values.mouse_panning_min_decay);
1128}
1129
1103void Config::SaveMotionTouchValues() { 1130void Config::SaveMotionTouchValues() {
1104 WriteBasicSetting(Settings::values.touch_device); 1131 WriteBasicSetting(Settings::values.touch_device);
1105 WriteBasicSetting(Settings::values.touch_from_button_map_index); 1132 WriteBasicSetting(Settings::values.touch_from_button_map_index);
@@ -1186,6 +1213,7 @@ void Config::SaveControlValues() {
1186 SaveDebugValues(); 1213 SaveDebugValues();
1187 SaveMouseValues(); 1214 SaveMouseValues();
1188 SaveTouchscreenValues(); 1215 SaveTouchscreenValues();
1216 SaveMousePanningValues();
1189 SaveMotionTouchValues(); 1217 SaveMotionTouchValues();
1190 SaveHidbusValues(); 1218 SaveHidbusValues();
1191 SaveIrCameraValues(); 1219 SaveIrCameraValues();
@@ -1200,7 +1228,6 @@ void Config::SaveControlValues() {
1200 WriteBasicSetting(Settings::values.random_amiibo_id); 1228 WriteBasicSetting(Settings::values.random_amiibo_id);
1201 WriteBasicSetting(Settings::values.keyboard_enabled); 1229 WriteBasicSetting(Settings::values.keyboard_enabled);
1202 WriteBasicSetting(Settings::values.emulate_analog_keyboard); 1230 WriteBasicSetting(Settings::values.emulate_analog_keyboard);
1203 WriteBasicSetting(Settings::values.mouse_panning_sensitivity);
1204 WriteBasicSetting(Settings::values.controller_navigation); 1231 WriteBasicSetting(Settings::values.controller_navigation);
1205 1232
1206 WriteBasicSetting(Settings::values.tas_enable); 1233 WriteBasicSetting(Settings::values.tas_enable);
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 0fd4baf6b..1211389d2 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -74,6 +74,7 @@ private:
74 void ReadKeyboardValues(); 74 void ReadKeyboardValues();
75 void ReadMouseValues(); 75 void ReadMouseValues();
76 void ReadTouchscreenValues(); 76 void ReadTouchscreenValues();
77 void ReadMousePanningValues();
77 void ReadMotionTouchValues(); 78 void ReadMotionTouchValues();
78 void ReadHidbusValues(); 79 void ReadHidbusValues();
79 void ReadIrCameraValues(); 80 void ReadIrCameraValues();
@@ -104,6 +105,7 @@ private:
104 void SaveDebugValues(); 105 void SaveDebugValues();
105 void SaveMouseValues(); 106 void SaveMouseValues();
106 void SaveTouchscreenValues(); 107 void SaveTouchscreenValues();
108 void SaveMousePanningValues();
107 void SaveMotionTouchValues(); 109 void SaveMotionTouchValues();
108 void SaveHidbusValues(); 110 void SaveHidbusValues();
109 void SaveIrCameraValues(); 111 void SaveIrCameraValues();
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index f13156434..3cfd5d439 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -129,9 +129,6 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
129 Settings::values.mouse_enabled = ui->mouse_enabled->isChecked(); 129 Settings::values.mouse_enabled = ui->mouse_enabled->isChecked();
130 Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked(); 130 Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked();
131 Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked(); 131 Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked();
132 Settings::values.mouse_panning = ui->mouse_panning->isChecked();
133 Settings::values.mouse_panning_sensitivity =
134 static_cast<float>(ui->mouse_panning_sensitivity->value());
135 Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); 132 Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
136 Settings::values.enable_raw_input = ui->enable_raw_input->isChecked(); 133 Settings::values.enable_raw_input = ui->enable_raw_input->isChecked();
137 Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked(); 134 Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
@@ -167,8 +164,6 @@ void ConfigureInputAdvanced::LoadConfiguration() {
167 ui->mouse_enabled->setChecked(Settings::values.mouse_enabled.GetValue()); 164 ui->mouse_enabled->setChecked(Settings::values.mouse_enabled.GetValue());
168 ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled.GetValue()); 165 ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled.GetValue());
169 ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard.GetValue()); 166 ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard.GetValue());
170 ui->mouse_panning->setChecked(Settings::values.mouse_panning.GetValue());
171 ui->mouse_panning_sensitivity->setValue(Settings::values.mouse_panning_sensitivity.GetValue());
172 ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); 167 ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
173 ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue()); 168 ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue());
174 ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue()); 169 ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
@@ -197,8 +192,6 @@ void ConfigureInputAdvanced::RetranslateUI() {
197void ConfigureInputAdvanced::UpdateUIEnabled() { 192void ConfigureInputAdvanced::UpdateUIEnabled() {
198 ui->debug_configure->setEnabled(ui->debug_enabled->isChecked()); 193 ui->debug_configure->setEnabled(ui->debug_enabled->isChecked());
199 ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); 194 ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
200 ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked());
201 ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked());
202 ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked()); 195 ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked());
203#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) || !defined(YUZU_USE_QT_MULTIMEDIA) 196#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) || !defined(YUZU_USE_QT_MULTIMEDIA)
204 ui->enable_ir_sensor->setEnabled(false); 197 ui->enable_ir_sensor->setEnabled(false);
diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui
index 2e8b13660..2994d0ab4 100644
--- a/src/yuzu/configuration/configure_input_advanced.ui
+++ b/src/yuzu/configuration/configure_input_advanced.ui
@@ -2744,48 +2744,13 @@
2744 </widget> 2744 </widget>
2745 </item> 2745 </item>
2746 <item row="8" column="0"> 2746 <item row="8" column="0">
2747 <widget class="QCheckBox" name="mouse_panning">
2748 <property name="minimumSize">
2749 <size>
2750 <width>0</width>
2751 <height>23</height>
2752 </size>
2753 </property>
2754 <property name="text">
2755 <string>Enable mouse panning</string>
2756 </property>
2757 </widget>
2758 </item>
2759 <item row="8" column="2">
2760 <widget class="QSpinBox" name="mouse_panning_sensitivity">
2761 <property name="toolTip">
2762 <string>Mouse sensitivity</string>
2763 </property>
2764 <property name="alignment">
2765 <set>Qt::AlignCenter</set>
2766 </property>
2767 <property name="suffix">
2768 <string>%</string>
2769 </property>
2770 <property name="minimum">
2771 <number>1</number>
2772 </property>
2773 <property name="maximum">
2774 <number>100</number>
2775 </property>
2776 <property name="value">
2777 <number>100</number>
2778 </property>
2779 </widget>
2780 </item>
2781 <item row="9" column="0">
2782 <widget class="QLabel" name="motion_touch"> 2747 <widget class="QLabel" name="motion_touch">
2783 <property name="text"> 2748 <property name="text">
2784 <string>Motion / Touch</string> 2749 <string>Motion / Touch</string>
2785 </property> 2750 </property>
2786 </widget> 2751 </widget>
2787 </item> 2752 </item>
2788 <item row="9" column="2"> 2753 <item row="8" column="2">
2789 <widget class="QPushButton" name="buttonMotionTouch"> 2754 <widget class="QPushButton" name="buttonMotionTouch">
2790 <property name="text"> 2755 <property name="text">
2791 <string>Configure</string> 2756 <string>Configure</string>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 2c2e7e47b..576f5b571 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -23,6 +23,7 @@
23#include "yuzu/configuration/config.h" 23#include "yuzu/configuration/config.h"
24#include "yuzu/configuration/configure_input_player.h" 24#include "yuzu/configuration/configure_input_player.h"
25#include "yuzu/configuration/configure_input_player_widget.h" 25#include "yuzu/configuration/configure_input_player_widget.h"
26#include "yuzu/configuration/configure_mouse_panning.h"
26#include "yuzu/configuration/input_profiles.h" 27#include "yuzu/configuration/input_profiles.h"
27#include "yuzu/util/limitable_input_dialog.h" 28#include "yuzu/util/limitable_input_dialog.h"
28 29
@@ -711,6 +712,21 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
711 }); 712 });
712 } 713 }
713 714
715 if (player_index_ == 0) {
716 connect(ui->mousePanningButton, &QPushButton::clicked, [this, input_subsystem_] {
717 const auto right_stick_param =
718 emulated_controller->GetStickParam(Settings::NativeAnalog::RStick);
719 ConfigureMousePanning dialog(this, input_subsystem_,
720 right_stick_param.Get("deadzone", 0.0f),
721 right_stick_param.Get("range", 1.0f));
722 if (dialog.exec() == QDialog::Accepted) {
723 dialog.ApplyConfiguration();
724 }
725 });
726 } else {
727 ui->mousePanningWidget->hide();
728 }
729
714 // Player Connected checkbox 730 // Player Connected checkbox
715 connect(ui->groupConnectedController, &QGroupBox::toggled, 731 connect(ui->groupConnectedController, &QGroupBox::toggled,
716 [this](bool checked) { emit Connected(checked); }); 732 [this](bool checked) { emit Connected(checked); });
diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui
index a9567c6ee..43f6c7b50 100644
--- a/src/yuzu/configuration/configure_input_player.ui
+++ b/src/yuzu/configuration/configure_input_player.ui
@@ -3048,6 +3048,102 @@
3048 </item> 3048 </item>
3049 </layout> 3049 </layout>
3050 </item> 3050 </item>
3051 <item>
3052 <widget class="QWidget" name="mousePanningWidget" native="true">
3053 <layout class="QHBoxLayout" name="mousePanningHorizontalLayout">
3054 <property name="spacing">
3055 <number>0</number>
3056 </property>
3057 <property name="leftMargin">
3058 <number>0</number>
3059 </property>
3060 <property name="topMargin">
3061 <number>0</number>
3062 </property>
3063 <property name="rightMargin">
3064 <number>0</number>
3065 </property>
3066 <property name="bottomMargin">
3067 <number>3</number>
3068 </property>
3069 <item>
3070 <spacer name="mousePanningHorizontalSpacerLeft">
3071 <property name="orientation">
3072 <enum>Qt::Horizontal</enum>
3073 </property>
3074 <property name="sizeHint" stdset="0">
3075 <size>
3076 <width>20</width>
3077 <height>20</height>
3078 </size>
3079 </property>
3080 </spacer>
3081 </item>
3082 <item>
3083 <widget class="QGroupBox" name="mousePanningGroup">
3084 <property name="title">
3085 <string>Mouse panning</string>
3086 </property>
3087 <property name="alignment">
3088 <set>Qt::AlignCenter</set>
3089 </property>
3090 <layout class="QVBoxLayout" name="mousePanningVerticalLayout">
3091 <property name="spacing">
3092 <number>3</number>
3093 </property>
3094 <property name="leftMargin">
3095 <number>3</number>
3096 </property>
3097 <property name="topMargin">
3098 <number>3</number>
3099 </property>
3100 <property name="rightMargin">
3101 <number>3</number>
3102 </property>
3103 <property name="bottomMargin">
3104 <number>3</number>
3105 </property>
3106 <item>
3107 <widget class="QPushButton" name="mousePanningButton">
3108 <property name="minimumSize">
3109 <size>
3110 <width>68</width>
3111 <height>0</height>
3112 </size>
3113 </property>
3114 <property name="maximumSize">
3115 <size>
3116 <width>68</width>
3117 <height>16777215</height>
3118 </size>
3119 </property>
3120 <property name="styleSheet">
3121 <string notr="true">min-width: 68px;</string>
3122 </property>
3123 <property name="text">
3124 <string>Configure</string>
3125 </property>
3126 </widget>
3127 </item>
3128 </layout>
3129 </widget>
3130 </item>
3131 <item>
3132 <spacer name="mousePanningHorizontalSpacerRight">
3133 <property name="orientation">
3134 <enum>Qt::Horizontal</enum>
3135 </property>
3136 <property name="sizeHint" stdset="0">
3137 <size>
3138 <width>20</width>
3139 <height>20</height>
3140 </size>
3141 </property>
3142 </spacer>
3143 </item>
3144 </layout>
3145 </widget>
3146 </item>
3051 </layout> 3147 </layout>
3052 </widget> 3148 </widget>
3053 </item> 3149 </item>
diff --git a/src/yuzu/configuration/configure_mouse_panning.cpp b/src/yuzu/configuration/configure_mouse_panning.cpp
new file mode 100644
index 000000000..f183d2740
--- /dev/null
+++ b/src/yuzu/configuration/configure_mouse_panning.cpp
@@ -0,0 +1,79 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <QCloseEvent>
5
6#include "common/settings.h"
7#include "ui_configure_mouse_panning.h"
8#include "yuzu/configuration/configure_mouse_panning.h"
9
10ConfigureMousePanning::ConfigureMousePanning(QWidget* parent,
11 InputCommon::InputSubsystem* input_subsystem_,
12 float right_stick_deadzone, float right_stick_range)
13 : QDialog(parent), input_subsystem{input_subsystem_},
14 ui(std::make_unique<Ui::ConfigureMousePanning>()) {
15 ui->setupUi(this);
16 SetConfiguration(right_stick_deadzone, right_stick_range);
17 ConnectEvents();
18}
19
20ConfigureMousePanning::~ConfigureMousePanning() = default;
21
22void ConfigureMousePanning::closeEvent(QCloseEvent* event) {
23 event->accept();
24}
25
26void ConfigureMousePanning::SetConfiguration(float right_stick_deadzone, float right_stick_range) {
27 ui->enable->setChecked(Settings::values.mouse_panning.GetValue());
28 ui->x_sensitivity->setValue(Settings::values.mouse_panning_x_sensitivity.GetValue());
29 ui->y_sensitivity->setValue(Settings::values.mouse_panning_y_sensitivity.GetValue());
30 ui->deadzone_x_counterweight->setValue(
31 Settings::values.mouse_panning_deadzone_x_counterweight.GetValue());
32 ui->deadzone_y_counterweight->setValue(
33 Settings::values.mouse_panning_deadzone_y_counterweight.GetValue());
34 ui->decay_strength->setValue(Settings::values.mouse_panning_decay_strength.GetValue());
35 ui->min_decay->setValue(Settings::values.mouse_panning_min_decay.GetValue());
36
37 if (right_stick_deadzone > 0.0f || right_stick_range != 1.0f) {
38 ui->warning_label->setText(QString::fromStdString(
39 "Mouse panning works better with a deadzone of 0% and a range of 100%.\n"
40 "Current values are " +
41 std::to_string(static_cast<int>(right_stick_deadzone * 100.0f)) + "% and " +
42 std::to_string(static_cast<int>(right_stick_range * 100.0f)) + "% respectively."));
43 } else {
44 ui->warning_label->hide();
45 }
46}
47
48void ConfigureMousePanning::SetDefaultConfiguration() {
49 ui->x_sensitivity->setValue(Settings::values.mouse_panning_x_sensitivity.GetDefault());
50 ui->y_sensitivity->setValue(Settings::values.mouse_panning_y_sensitivity.GetDefault());
51 ui->deadzone_x_counterweight->setValue(
52 Settings::values.mouse_panning_deadzone_x_counterweight.GetDefault());
53 ui->deadzone_y_counterweight->setValue(
54 Settings::values.mouse_panning_deadzone_y_counterweight.GetDefault());
55 ui->decay_strength->setValue(Settings::values.mouse_panning_decay_strength.GetDefault());
56 ui->min_decay->setValue(Settings::values.mouse_panning_min_decay.GetDefault());
57}
58
59void ConfigureMousePanning::ConnectEvents() {
60 connect(ui->default_button, &QPushButton::clicked, this,
61 &ConfigureMousePanning::SetDefaultConfiguration);
62 connect(ui->button_box, &QDialogButtonBox::accepted, this,
63 &ConfigureMousePanning::ApplyConfiguration);
64 connect(ui->button_box, &QDialogButtonBox::rejected, this, [this] { reject(); });
65}
66
67void ConfigureMousePanning::ApplyConfiguration() {
68 Settings::values.mouse_panning = ui->enable->isChecked();
69 Settings::values.mouse_panning_x_sensitivity = static_cast<float>(ui->x_sensitivity->value());
70 Settings::values.mouse_panning_y_sensitivity = static_cast<float>(ui->y_sensitivity->value());
71 Settings::values.mouse_panning_deadzone_x_counterweight =
72 static_cast<float>(ui->deadzone_x_counterweight->value());
73 Settings::values.mouse_panning_deadzone_y_counterweight =
74 static_cast<float>(ui->deadzone_y_counterweight->value());
75 Settings::values.mouse_panning_decay_strength = static_cast<float>(ui->decay_strength->value());
76 Settings::values.mouse_panning_min_decay = static_cast<float>(ui->min_decay->value());
77
78 accept();
79}
diff --git a/src/yuzu/configuration/configure_mouse_panning.h b/src/yuzu/configuration/configure_mouse_panning.h
new file mode 100644
index 000000000..08c6e1f62
--- /dev/null
+++ b/src/yuzu/configuration/configure_mouse_panning.h
@@ -0,0 +1,35 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <QDialog>
8
9namespace InputCommon {
10class InputSubsystem;
11}
12
13namespace Ui {
14class ConfigureMousePanning;
15}
16
17class ConfigureMousePanning : public QDialog {
18 Q_OBJECT
19public:
20 explicit ConfigureMousePanning(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_,
21 float right_stick_deadzone, float right_stick_range);
22 ~ConfigureMousePanning() override;
23
24public slots:
25 void ApplyConfiguration();
26
27private:
28 void closeEvent(QCloseEvent* event) override;
29 void SetConfiguration(float right_stick_deadzone, float right_stick_range);
30 void SetDefaultConfiguration();
31 void ConnectEvents();
32
33 InputCommon::InputSubsystem* input_subsystem;
34 std::unique_ptr<Ui::ConfigureMousePanning> ui;
35};
diff --git a/src/yuzu/configuration/configure_mouse_panning.ui b/src/yuzu/configuration/configure_mouse_panning.ui
new file mode 100644
index 000000000..75795b727
--- /dev/null
+++ b/src/yuzu/configuration/configure_mouse_panning.ui
@@ -0,0 +1,238 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureMousePanning</class>
4 <widget class="QDialog" name="configure_mouse_panning">
5 <property name="windowTitle">
6 <string>Configure mouse panning</string>
7 </property>
8 <layout class="QVBoxLayout">
9 <item>
10 <widget class="QCheckBox" name="enable">
11 <property name="text">
12 <string>Enable</string>
13 </property>
14 <property name="toolTip">
15 <string>Can be toggled via a hotkey</string>
16 </property>
17 </widget>
18 </item>
19 <item>
20 <layout class="QHBoxLayout">
21 <item>
22 <widget class="QGroupBox" name="sensitivity_box">
23 <property name="title">
24 <string>Sensitivity</string>
25 </property>
26 <layout class="QGridLayout">
27 <item row="0" column="0">
28 <widget class="QLabel" name="x_sensitivity_label">
29 <property name="text">
30 <string>Horizontal</string>
31 </property>
32 </widget>
33 </item>
34 <item row="0" column="1">
35 <widget class="QSpinBox" name="x_sensitivity">
36 <property name="alignment">
37 <set>Qt::AlignCenter</set>
38 </property>
39 <property name="suffix">
40 <string>%</string>
41 </property>
42 <property name="minimum">
43 <number>1</number>
44 </property>
45 <property name="maximum">
46 <number>100</number>
47 </property>
48 <property name="value">
49 <number>50</number>
50 </property>
51 </widget>
52 </item>
53 <item row="1" column="0">
54 <widget class="QLabel" name="y_sensitivity_label">
55 <property name="text">
56 <string>Vertical</string>
57 </property>
58 </widget>
59 </item>
60 <item row="1" column="1">
61 <widget class="QSpinBox" name="y_sensitivity">
62 <property name="alignment">
63 <set>Qt::AlignCenter</set>
64 </property>
65 <property name="suffix">
66 <string>%</string>
67 </property>
68 <property name="minimum">
69 <number>1</number>
70 </property>
71 <property name="maximum">
72 <number>100</number>
73 </property>
74 <property name="value">
75 <number>50</number>
76 </property>
77 </widget>
78 </item>
79 </layout>
80 </widget>
81 </item>
82 <item>
83 <widget class="QGroupBox" name="deadzone_counterweight_box">
84 <property name="title">
85 <string>Deadzone counterweight</string>
86 </property>
87 <property name="toolTip">
88 <string>Counteracts a game's built-in deadzone</string>
89 </property>
90 <layout class="QGridLayout">
91 <item row="0" column="0">
92 <widget class="QLabel" name="deadzone_x_counterweight_label">
93 <property name="text">
94 <string>Horizontal</string>
95 </property>
96 </widget>
97 </item>
98 <item row="0" column="1">
99 <widget class="QSpinBox" name="deadzone_x_counterweight">
100 <property name="alignment">
101 <set>Qt::AlignCenter</set>
102 </property>
103 <property name="suffix">
104 <string>%</string>
105 </property>
106 <property name="minimum">
107 <number>0</number>
108 </property>
109 <property name="maximum">
110 <number>100</number>
111 </property>
112 <property name="value">
113 <number>0</number>
114 </property>
115 </widget>
116 </item>
117 <item row="1" column="0">
118 <widget class="QLabel" name="deadzone_y_counterweight_label">
119 <property name="text">
120 <string>Vertical</string>
121 </property>
122 </widget>
123 </item>
124 <item row="1" column="1">
125 <widget class="QSpinBox" name="deadzone_y_counterweight">
126 <property name="alignment">
127 <set>Qt::AlignCenter</set>
128 </property>
129 <property name="suffix">
130 <string>%</string>
131 </property>
132 <property name="minimum">
133 <number>0</number>
134 </property>
135 <property name="maximum">
136 <number>100</number>
137 </property>
138 <property name="value">
139 <number>0</number>
140 </property>
141 </widget>
142 </item>
143 </layout>
144 </widget>
145 </item>
146 <item>
147 <widget class="QGroupBox" name="decay_box">
148 <property name="title">
149 <string>Stick decay</string>
150 </property>
151 <layout class="QGridLayout">
152 <item row="0" column="0">
153 <widget class="QLabel" name="decay_strength_label">
154 <property name="text">
155 <string>Strength</string>
156 </property>
157 </widget>
158 </item>
159 <item row="0" column="1">
160 <widget class="QSpinBox" name="decay_strength">
161 <property name="alignment">
162 <set>Qt::AlignCenter</set>
163 </property>
164 <property name="suffix">
165 <string>%</string>
166 </property>
167 <property name="minimum">
168 <number>0</number>
169 </property>
170 <property name="maximum">
171 <number>100</number>
172 </property>
173 <property name="value">
174 <number>22</number>
175 </property>
176 </widget>
177 </item>
178 <item row="1" column="0">
179 <widget class="QLabel" name="min_decay_label">
180 <property name="text">
181 <string>Minimum</string>
182 </property>
183 </widget>
184 </item>
185 <item row="1" column="1">
186 <widget class="QSpinBox" name="min_decay">
187 <property name="alignment">
188 <set>Qt::AlignCenter</set>
189 </property>
190 <property name="suffix">
191 <string>%</string>
192 </property>
193 <property name="minimum">
194 <number>0</number>
195 </property>
196 <property name="maximum">
197 <number>100</number>
198 </property>
199 <property name="value">
200 <number>5</number>
201 </property>
202 </widget>
203 </item>
204 </layout>
205 </widget>
206 </item>
207 </layout>
208 </item>
209 <item>
210 <widget class="QLabel" name="warning_label">
211 <property name="text">
212 <string/>
213 </property>
214 </widget>
215 </item>
216 <item>
217 <layout class="QHBoxLayout">
218 <item>
219 <widget class="QPushButton" name="default_button">
220 <property name="text">
221 <string>Default</string>
222 </property>
223 </widget>
224 </item>
225 <item>
226 <widget class="QDialogButtonBox" name="button_box">
227 <property name="standardButtons">
228 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
229 </property>
230 </widget>
231 </item>
232 </layout>
233 </item>
234 </layout>
235 </widget>
236 <resources/>
237 <connections/>
238</ui>
diff --git a/src/yuzu/configuration/configure_ringcon.ui b/src/yuzu/configuration/configure_ringcon.ui
index 514dff372..38ecccc3d 100644
--- a/src/yuzu/configuration/configure_ringcon.ui
+++ b/src/yuzu/configuration/configure_ringcon.ui
@@ -23,7 +23,7 @@
23 </size> 23 </size>
24 </property> 24 </property>
25 <property name="text"> 25 <property name="text">
26 <string>If you want to use this controller configure player 1 as right controller and player 2 as dual joycon before starting the game to allow this controller to be detected properly.</string> 26 <string>To use Ring-Con, configure player 1 as right Joy-Con (both physical and emulated), and player 2 as left Joy-Con (left physical and dual emulated) before starting the game.</string>
27 </property> 27 </property>
28 <property name="wordWrap"> 28 <property name="wordWrap">
29 <bool>true</bool> 29 <bool>true</bool>
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 45a39451d..2133f7343 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -447,6 +447,14 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
447 447
448#if defined(HAVE_SDL2) && !defined(_WIN32) 448#if defined(HAVE_SDL2) && !defined(_WIN32)
449 SDL_InitSubSystem(SDL_INIT_VIDEO); 449 SDL_InitSubSystem(SDL_INIT_VIDEO);
450
451 // Set a screensaver inhibition reason string. Currently passed to DBus by SDL and visible to
452 // the user through their desktop environment.
453 //: TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the
454 //: computer from sleeping
455 QByteArray wakelock_reason = tr("Running a game").toLatin1();
456 SDL_SetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME, wakelock_reason.data());
457
450 // SDL disables the screen saver by default, and setting the hint 458 // SDL disables the screen saver by default, and setting the hint
451 // SDL_HINT_VIDEO_ALLOW_SCREENSAVER doesn't seem to work, so we just enable the screen saver 459 // SDL_HINT_VIDEO_ALLOW_SCREENSAVER doesn't seem to work, so we just enable the screen saver
452 // for now. 460 // for now.
@@ -1623,45 +1631,6 @@ void GMainWindow::OnPrepareForSleep(bool prepare_sleep) {
1623} 1631}
1624 1632
1625#ifdef __unix__ 1633#ifdef __unix__
1626static std::optional<QDBusObjectPath> HoldWakeLockLinux(u32 window_id = 0) {
1627 if (!QDBusConnection::sessionBus().isConnected()) {
1628 return {};
1629 }
1630 // reference: https://flatpak.github.io/xdg-desktop-portal/#gdbus-org.freedesktop.portal.Inhibit
1631 QDBusInterface xdp(QString::fromLatin1("org.freedesktop.portal.Desktop"),
1632 QString::fromLatin1("/org/freedesktop/portal/desktop"),
1633 QString::fromLatin1("org.freedesktop.portal.Inhibit"));
1634 if (!xdp.isValid()) {
1635 LOG_WARNING(Frontend, "Couldn't connect to XDP D-Bus endpoint");
1636 return {};
1637 }
1638 QVariantMap options = {};
1639 //: TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the
1640 //: computer from sleeping
1641 options.insert(QString::fromLatin1("reason"),
1642 QCoreApplication::translate("GMainWindow", "yuzu is running a game"));
1643 // 0x4: Suspend lock; 0x8: Idle lock
1644 QDBusReply<QDBusObjectPath> reply =
1645 xdp.call(QString::fromLatin1("Inhibit"),
1646 QString::fromLatin1("x11:") + QString::number(window_id, 16), 12U, options);
1647
1648 if (reply.isValid()) {
1649 return reply.value();
1650 }
1651 LOG_WARNING(Frontend, "Couldn't read Inhibit reply from XDP: {}",
1652 reply.error().message().toStdString());
1653 return {};
1654}
1655
1656static void ReleaseWakeLockLinux(QDBusObjectPath lock) {
1657 if (!QDBusConnection::sessionBus().isConnected()) {
1658 return;
1659 }
1660 QDBusInterface unlocker(QString::fromLatin1("org.freedesktop.portal.Desktop"), lock.path(),
1661 QString::fromLatin1("org.freedesktop.portal.Request"));
1662 unlocker.call(QString::fromLatin1("Close"));
1663}
1664
1665std::array<int, 3> GMainWindow::sig_interrupt_fds{0, 0, 0}; 1634std::array<int, 3> GMainWindow::sig_interrupt_fds{0, 0, 0};
1666 1635
1667void GMainWindow::SetupSigInterrupts() { 1636void GMainWindow::SetupSigInterrupts() {
@@ -1714,12 +1683,6 @@ void GMainWindow::PreventOSSleep() {
1714 SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED); 1683 SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
1715#elif defined(HAVE_SDL2) 1684#elif defined(HAVE_SDL2)
1716 SDL_DisableScreenSaver(); 1685 SDL_DisableScreenSaver();
1717#ifdef __unix__
1718 auto reply = HoldWakeLockLinux(winId());
1719 if (reply) {
1720 wake_lock = std::move(reply.value());
1721 }
1722#endif
1723#endif 1686#endif
1724} 1687}
1725 1688
@@ -1728,11 +1691,6 @@ void GMainWindow::AllowOSSleep() {
1728 SetThreadExecutionState(ES_CONTINUOUS); 1691 SetThreadExecutionState(ES_CONTINUOUS);
1729#elif defined(HAVE_SDL2) 1692#elif defined(HAVE_SDL2)
1730 SDL_EnableScreenSaver(); 1693 SDL_EnableScreenSaver();
1731#ifdef __unix__
1732 if (!wake_lock.path().isEmpty()) {
1733 ReleaseWakeLockLinux(wake_lock);
1734 }
1735#endif
1736#endif 1694#endif
1737} 1695}
1738 1696
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index e0e775d87..2cfb96257 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -504,8 +504,6 @@ private:
504#ifdef __unix__ 504#ifdef __unix__
505 QSocketNotifier* sig_interrupt_notifier; 505 QSocketNotifier* sig_interrupt_notifier;
506 static std::array<int, 3> sig_interrupt_fds; 506 static std::array<int, 3> sig_interrupt_fds;
507
508 QDBusObjectPath wake_lock{};
509#endif 507#endif
510 508
511protected: 509protected:
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 911d461e4..119e22183 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -140,9 +140,29 @@ udp_input_servers =
140# 0 (default): Off, 1: On 140# 0 (default): Off, 1: On
141mouse_panning = 141mouse_panning =
142 142
143# Set mouse sensitivity. 143# Set mouse panning horizontal sensitivity.
144# Default: 1.0 144# Default: 50.0
145mouse_panning_sensitivity = 145mouse_panning_x_sensitivity =
146
147# Set mouse panning vertical sensitivity.
148# Default: 50.0
149mouse_panning_y_sensitivity =
150
151# Set mouse panning deadzone horizontal counterweight.
152# Default: 0.0
153mouse_panning_deadzone_x_counterweight =
154
155# Set mouse panning deadzone vertical counterweight.
156# Default: 0.0
157mouse_panning_deadzone_y_counterweight =
158
159# Set mouse panning stick decay strength.
160# Default: 22.0
161mouse_panning_decay_strength =
162
163# Set mouse panning stick minimum decay.
164# Default: 5.0
165mouse_panning_minimum_decay =
146 166
147# Emulate an analog control stick from keyboard inputs. 167# Emulate an analog control stick from keyboard inputs.
148# 0 (default): Disabled, 1: Enabled 168# 0 (default): Disabled, 1: Enabled