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/core/hid/emulated_controller.cpp19
-rw-r--r--src/input_common/drivers/sdl_driver.cpp28
-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/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/yuzu/configuration/configure_ringcon.ui2
27 files changed, 239 insertions, 122 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/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/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index ab64a64a2..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));
@@ -505,6 +509,9 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
505 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0"); 509 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0");
506 } else { 510 } else {
507 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");
508 } 515 }
509 516
510 // 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
@@ -512,8 +519,11 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
512 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "0"); 519 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "0");
513 } else { 520 } else {
514 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");
515 } 523 }
516 524
525 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, "1");
526
517 // 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
518 // driver on Linux. 528 // driver on Linux.
519 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0"); 529 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0");
@@ -793,7 +803,9 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p
793 // 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
794 ButtonBindings switch_to_sdl_button; 804 ButtonBindings switch_to_sdl_button;
795 805
796 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) {
797 switch_to_sdl_button = GetNintendoButtonBinding(joystick); 809 switch_to_sdl_button = GetNintendoButtonBinding(joystick);
798 } else { 810 } else {
799 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/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/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>