Compare commits

..

30 Commits

Author SHA1 Message Date
Baptiste Marie
8e3d4e3396 input_common: Redesign mouse panning 2023-06-12 00:47:52 +02:00
bunnei
569f8d3b44 Merge pull request #10668 from Kelebek1/reduce_vertex_bindings
Combine vertex/transform feedback buffer binding into a single call
2023-06-11 11:33:48 -07:00
bunnei
f4c383392d Merge pull request #10713 from t895/gradle-updates
android: Gradle updates
2023-06-11 11:31:03 -07:00
bunnei
5fcc05a4b2 Merge pull request #10711 from t895/better-extension-check
android: Use ContentResolver to get file extension
2023-06-11 11:29:08 -07:00
Charles Lombardo
92d49ad652 android: Update dependencies 2023-06-11 02:17:29 -04:00
Charles Lombardo
f23a2b514b android: Differentiate build types with new names
Change the applicationIdSuffix and app launcher title based on build type
2023-06-11 02:16:45 -04:00
Charles Lombardo
37e135d74d Android: Remove unused relWithVersionCode build type 2023-06-11 02:15:28 -04:00
Charles Lombardo
16fe64ad0c android: Use ContentResolver to get file extension
Fixes an issue where we try to resolve file extension from URIs. Sometimes the URI will not contain the file name at all and instead a string of numbers. Here we query the content resolver and guarantee that we get a file name every time.
2023-06-11 01:41:58 -04:00
bunnei
b6f2490288 Merge pull request #10703 from bunnei/fix-android-layout
android: Fix screen orientation & blurriness.
2023-06-10 16:24:16 -07:00
bunnei
ea716eb5cc android: Fix screen orientation & blurriness. 2023-06-10 15:13:06 -07:00
bunnei
6b898c6d69 Merge pull request #10670 from liamwhite/fxaa2
vk_blit_screen: use higher bit depth for fxaa
2023-06-10 14:35:23 -07:00
Morph
fa5dfcb712 Merge pull request #10685 from liamwhite/serialization-is-hard
qt: persist framerate sync option
2023-06-10 12:28:00 -04:00
Morph
cb1fd1bad8 Merge pull request #10697 from 8bitDream/codespell
Add a codespell exception for kotlin OptIn
2023-06-10 12:27:19 -04:00
Abandoned Cart
d8609eef89 Add a codespell exception for kotlin OptIn 2023-06-10 05:35:57 -04:00
bunnei
f759ff3a5c Merge pull request #10691 from t895/nro-check
android: Add proper homebrew check
2023-06-09 23:59:51 -07:00
Charles Lombardo
72d9dc9a3f android: Add proper homebrew check 2023-06-09 20:17:51 -04:00
bunnei
55b543f466 Merge pull request #10686 from t895/version-check
android: Fix input overlay version check
2023-06-09 15:27:21 -07:00
Charles Lombardo
3cce51d25b android: Fix input overlay version check 2023-06-09 15:15:57 -04:00
liamwhite
4d395b3b72 Merge pull request #10614 from xcfrg/shader-backend-status-bar
yuzu: add opengl shader backend info in status bar
2023-06-09 09:46:11 -04:00
Liam
5a0d4e1d38 qt: persist framerate sync option 2023-06-09 09:40:34 -04:00
liamwhite
b3e2c9f9f1 Merge pull request #10623 from german77/backup
service: nfc: Add backup support
2023-06-08 21:54:12 -04:00
liamwhite
2a1acbfb4d Merge pull request #10666 from liamwhite/my-framerate-is-fine
nvnflinger: allow locking framerate during video playback
2023-06-08 21:53:57 -04:00
liamwhite
a57150afbd Merge pull request #10676 from bunnei/fix-mi-5-android
android: EmulationActivity: Fix orientation on Mi Pad 5.
2023-06-08 21:53:51 -04:00
liamwhite
60cc611f38 Merge pull request #10677 from tokarevart/fix-warning
Fix a potentially uninitialized local variable warning causing a build error
2023-06-08 21:53:40 -04:00
bunnei
064bad6ddf android: EmulationActivity: Fix orientation on Mi Pad 5. 2023-06-08 17:20:13 -07:00
Liam
74671186bf vk_blit_screen: use higher bit depth for fxaa 2023-06-08 11:27:57 -04:00
Kelebek1
ace6c2318b Combine vertex/transform feedback buffer binding into a single call 2023-06-08 12:13:27 +01:00
Liam
6c34adb1de nvnflinger: allow locking framerate during video playback 2023-06-08 01:15:51 -04:00
german77
107aa52cdb service: nfc: Add backup support 2023-06-06 17:06:21 -06:00
xcfrg
a64ad8315f yuzu: add opengl shader backend info in status bar 2023-06-04 17:24:30 -04:00
59 changed files with 1089 additions and 340 deletions

View File

@@ -3,4 +3,4 @@
[codespell]
skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES
ignore-words-list = aci,allright,ba,deques,froms,hda,inout,lod,masia,nam,nax,nd,pullrequests,pullrequest,te,transfered,unstall,uscaled,zink
ignore-words-list = aci,allright,ba,deques,froms,hda,inout,lod,masia,nam,nax,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,zink

View File

@@ -74,16 +74,7 @@ android {
// Signed by release key, allowing for upload to Play Store.
release {
signingConfig = signingConfigs.getByName("debug")
isMinifyEnabled = true
isDebuggable = false
proguardFiles(
getDefaultProguardFile("proguard-android.txt"),
"proguard-rules.pro"
)
}
register("relWithVersionCode") {
resValue("string", "app_name_suffixed", "yuzu")
signingConfig = signingConfigs.getByName("debug")
isMinifyEnabled = true
isDebuggable = false
@@ -96,6 +87,7 @@ android {
// builds a release build that doesn't need signing
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
register("relWithDebInfo") {
resValue("string", "app_name_suffixed", "yuzu Debug Release")
signingConfig = signingConfigs.getByName("debug")
isMinifyEnabled = true
isDebuggable = true
@@ -103,16 +95,19 @@ android {
getDefaultProguardFile("proguard-android.txt"),
"proguard-rules.pro"
)
versionNameSuffix = "-debug"
versionNameSuffix = "-relWithDebInfo"
applicationIdSuffix = ".relWithDebInfo"
isJniDebuggable = true
}
// Signed by debug key disallowing distribution on Play Store.
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
debug {
resValue("string", "app_name_suffixed", "yuzu Debug")
isDebuggable = true
isJniDebuggable = true
versionNameSuffix = "-debug"
applicationIdSuffix = ".debug"
}
}
@@ -162,19 +157,19 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.recyclerview:recyclerview:1.3.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.fragment:fragment-ktx:1.5.7")
implementation("androidx.fragment:fragment-ktx:1.6.0")
implementation("androidx.documentfile:documentfile:1.0.1")
implementation("com.google.android.material:material:1.9.0")
implementation("androidx.preference:preference:1.2.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
implementation("io.coil-kt:coil:2.2.2")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.window:window:1.0.0")
implementation("androidx.window:window:1.1.0")
implementation("org.ini4j:ini4j:0.5.4")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.5.3")
implementation("androidx.navigation:navigation-ui-ktx:2.5.3")
implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
implementation("info.debatty:java-string-similarity:2.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
}

View File

@@ -18,7 +18,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<application
android:name="org.yuzu.yuzu_emu.YuzuApplication"
android:label="@string/app_name"
android:label="@string/app_name_suffixed"
android:icon="@drawable/ic_launcher"
android:allowBackup="true"
android:hasFragileUserData="true"

View File

@@ -223,6 +223,8 @@ object NativeLibrary {
external fun getCompany(filename: String): String
external fun isHomebrew(filename: String): Boolean
external fun setAppDirectory(directory: String)
external fun initializeGpuDriver(

View File

@@ -6,15 +6,12 @@ package org.yuzu.yuzu_emu.activities
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Rect
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.hardware.display.DisplayManager
import android.os.Bundle
import android.view.Display
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
@@ -23,7 +20,6 @@ import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
@@ -39,7 +35,6 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
import org.yuzu.yuzu_emu.fragments.EmulationFragment
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
import org.yuzu.yuzu_emu.utils.ForegroundService
import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.NfcReader
@@ -148,11 +143,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
super.onResume()
nfcReader.startScanning()
startMotionSensorListener()
NativeLibrary.notifyOrientationChange(
EmulationMenuSettings.landscapeScreenLayout,
getAdjustedRotation()
)
}
override fun onPause() {
@@ -258,23 +248,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
private fun getAdjustedRotation():Int {
val rotation = getSystemService<DisplayManager>()!!.getDisplay(Display.DEFAULT_DISPLAY).rotation
val config: Configuration = resources.configuration
if ((config.screenLayout and Configuration.SCREENLAYOUT_LONG_YES) != 0 ||
(config.screenLayout and Configuration.SCREENLAYOUT_LONG_NO) == 0) {
return rotation
}
when (rotation) {
Surface.ROTATION_0 -> return Surface.ROTATION_90
Surface.ROTATION_90 -> return Surface.ROTATION_0
Surface.ROTATION_180 -> return Surface.ROTATION_270
Surface.ROTATION_270 -> return Surface.ROTATION_180
}
return rotation
}
private fun restoreState(savedInstanceState: Bundle) {
game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
}

View File

@@ -127,13 +127,7 @@ class SearchFragment : Fragment() {
}
}
R.id.chip_homebrew -> {
baseList.filter {
Log.error("Guh - ${it.path}")
FileUtil.hasExtension(it.path, "nro")
|| FileUtil.hasExtension(it.path, "nso")
}
}
R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
R.id.chip_retail -> baseList.filter {
FileUtil.hasExtension(it.path, "xci")

View File

@@ -16,7 +16,8 @@ class Game(
val regions: String,
val path: String,
val gameId: String,
val company: String
val company: String,
val isHomebrew: Boolean
) : Parcelable {
val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime"
val keyLastPlayedTime get() = "${gameId}_LastPlayed"
@@ -31,6 +32,7 @@ class Game(
&& path == other.path
&& gameId == other.gameId
&& company == other.company
&& isHomebrew == other.isHomebrew
}
companion object {

View File

@@ -13,6 +13,8 @@ import androidx.preference.PreferenceManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.MissingFieldException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary
@@ -20,6 +22,7 @@ import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper
import java.util.Locale
@OptIn(ExperimentalSerializationApi::class)
class GamesViewModel : ViewModel() {
private val _games = MutableLiveData<List<Game>>(emptyList())
val games: LiveData<List<Game>> get() = _games
@@ -49,7 +52,13 @@ class GamesViewModel : ViewModel() {
if (storedGames!!.isNotEmpty()) {
val deserializedGames = mutableSetOf<Game>()
storedGames.forEach {
val game: Game = Json.decodeFromString(it)
val game: Game
try {
game = Json.decodeFromString(it)
} catch (e: MissingFieldException) {
return@forEach
}
val gameExists =
DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(game.path))
?.exists()

View File

@@ -765,18 +765,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
// If we have API access, calculate the safe area to draw the overlay
var cutoutLeft = 0
var cutoutBottom = 0
val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
if (insets != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
insets.boundingRectTop.bottom.toFloat() else maxY
if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2)
insets.boundingRectRight.left.toFloat() else maxX
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
if (insets != null) {
if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
insets.boundingRectTop.bottom.toFloat() else maxY
if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2)
insets.boundingRectRight.left.toFloat() else maxX
minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
}
}
// This makes sure that if we have an inset on one side of the screen, we mirror it on

View File

@@ -284,10 +284,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (result == null)
return@registerForActivityResult
if (!FileUtil.hasExtension(result.toString(), "keys")) {
if (!FileUtil.hasExtension(result, "keys")) {
MessageDialogFragment.newInstance(
R.string.reading_keys_failure,
R.string.install_keys_failure_extension_description
R.string.install_prod_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
return@registerForActivityResult
}
@@ -379,10 +379,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (result == null)
return@registerForActivityResult
if (!FileUtil.hasExtension(result.toString(), "bin")) {
if (!FileUtil.hasExtension(result, "bin")) {
MessageDialogFragment.newInstance(
R.string.reading_keys_failure,
R.string.install_keys_failure_extension_description
R.string.install_amiibo_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
return@registerForActivityResult
}

View File

@@ -7,7 +7,9 @@ import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.DocumentsContract
import android.provider.OpenableColumns
import androidx.documentfile.provider.DocumentFile
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
import java.io.BufferedInputStream
import java.io.File
@@ -324,7 +326,25 @@ object FileUtil {
}
}
fun hasExtension(path: String, extension: String): Boolean {
return path.substring(path.lastIndexOf(".") + 1).contains(extension)
fun hasExtension(path: String, extension: String): Boolean =
path.substring(path.lastIndexOf(".") + 1).contains(extension)
fun hasExtension(uri: Uri, extension: String): Boolean {
val fileName: String?
val cursor = YuzuApplication.appContext.contentResolver.query(uri, null, null, null, null)
val nameIndex = cursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor?.moveToFirst()
if (nameIndex == null) {
return false
}
fileName = cursor.getString(nameIndex)
cursor.close()
if (fileName == null) {
return false
}
return fileName.substring(fileName.lastIndexOf(".") + 1).contains(extension)
}
}

View File

@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.utils
import android.content.SharedPreferences
import android.net.Uri
import androidx.preference.PreferenceManager
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary
@@ -83,7 +82,8 @@ object GameHelper {
NativeLibrary.getRegions(filePath),
filePath,
gameId,
NativeLibrary.getCompany(filePath)
NativeLibrary.getCompany(filePath),
NativeLibrary.isHomebrew(filePath)
)
val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L)

View File

@@ -13,6 +13,7 @@
#include <android/api-level.h>
#include <android/native_window_jni.h>
#include <core/loader/nro.h>
#include "common/detached_tasks.h"
#include "common/dynamic_library.h"
@@ -93,14 +94,6 @@ public:
m_native_window = native_window;
}
u32 ScreenRotation() const {
return m_screen_rotation;
}
void SetScreenRotation(u32 screen_rotation) {
m_screen_rotation = screen_rotation;
}
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
const std::string& custom_driver_name,
const std::string& file_redirect_dir) {
@@ -281,6 +274,10 @@ public:
return GetRomMetadata(path).icon;
}
bool GetIsHomebrew(const std::string& path) {
return GetRomMetadata(path).isHomebrew;
}
void ResetRomMetadata() {
m_rom_metadata_cache.clear();
}
@@ -348,6 +345,7 @@ private:
struct RomMetadata {
std::string title;
std::vector<u8> icon;
bool isHomebrew;
};
RomMetadata GetRomMetadata(const std::string& path) {
@@ -360,11 +358,17 @@ private:
RomMetadata CacheRomMetadata(const std::string& path) {
const auto file = Core::GetGameFileFromPath(m_vfs, path);
const auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
RomMetadata entry;
loader->ReadTitle(entry.title);
loader->ReadIcon(entry.icon);
if (loader->GetFileType() == Loader::FileType::NRO) {
auto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
entry.isHomebrew = loader_nro->IsHomebrew();
} else {
entry.isHomebrew = false;
}
m_rom_metadata_cache[path] = entry;
@@ -388,7 +392,6 @@ private:
// Window management
std::unique_ptr<EmuWindow_Android> m_window;
ANativeWindow* m_native_window{};
u32 m_screen_rotation{};
// Core emulation
Core::System m_system;
@@ -414,10 +417,6 @@ private:
} // Anonymous namespace
u32 GetAndroidScreenRotation() {
return EmulationSession::GetInstance().ScreenRotation();
}
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
Common::Log::Initialize();
Common::Log::SetColorConsoleBackendEnabled(true);
@@ -461,13 +460,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env,
EmulationSession::GetInstance().SurfaceChanged();
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_notifyOrientationChange(JNIEnv* env,
[[maybe_unused]] jclass clazz,
jint layout_option,
jint rotation) {
return EmulationSession::GetInstance().SetScreenRotation(static_cast<u32>(rotation));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
[[maybe_unused]] jclass clazz,
jstring j_directory) {
@@ -662,6 +654,12 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany([[maybe_unused]] JNIEnv
return env->NewStringUTF("");
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jclass clazz,
[[maybe_unused]] jstring j_filename) {
return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation
[[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) {
// Create the default config.ini.

View File

@@ -65,11 +65,8 @@
<string name="invalid_keys_file">Invalid keys file selected</string>
<string name="install_keys_success">Keys successfully installed</string>
<string name="reading_keys_failure">Error reading encryption keys</string>
<string name="install_keys_failure_extension_description">
1. Verify your keys have the .keys extension.\n\n
2. Keys must not be stored in the Downloads folder.\n\n
Resolve the issue(s) and try again.
</string>
<string name="install_prod_keys_failure_extension_description">Verify your keys file has a .keys extension and try again.</string>
<string name="install_amiibo_keys_failure_extension_description">Verify your keys file has a .bin extension and try again.</string>
<string name="invalid_keys_error">Invalid encryption keys</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>

View File

@@ -47,12 +47,4 @@ AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
return *adsp;
}
void AudioCore::SetNVDECActive(bool active) {
nvdec_active = active;
}
bool AudioCore::IsNVDECActive() const {
return nvdec_active;
}
} // namespace AudioCore

View File

@@ -57,18 +57,6 @@ public:
*/
AudioRenderer::ADSP::ADSP& GetADSP();
/**
* Toggle NVDEC state, used to avoid stall in playback.
*
* @param active - Set true if nvdec is active, otherwise false.
*/
void SetNVDECActive(bool active);
/**
* Get NVDEC state.
*/
bool IsNVDECActive() const;
private:
/**
* Create the sinks on startup.
@@ -83,8 +71,6 @@ private:
std::unique_ptr<Sink::Sink> input_sink;
/// The ADSP in the sysmodule
std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
/// Is NVDec currently active?
bool nvdec_active{false};
};
} // namespace AudioCore

View File

@@ -10,6 +10,7 @@
// Sub-directories contained within a yuzu data directory
#define AMIIBO_DIR "amiibo"
#define CACHE_DIR "cache"
#define CONFIG_DIR "config"
#define DUMP_DIR "dump"

View File

@@ -114,6 +114,7 @@ public:
#endif
GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
GenerateYuzuPath(YuzuPath::AmiiboDir, yuzu_path / AMIIBO_DIR);
GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache);
GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config);
GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);

View File

@@ -12,6 +12,7 @@ namespace Common::FS {
enum class YuzuPath {
YuzuDir, // Where yuzu stores its data.
AmiiboDir, // Where Amiibo backups are stored.
CacheDir, // Where cached filesystem data is stored.
ConfigDir, // Where config files are stored.
DumpDir, // Where dumped data is stored.

View File

@@ -235,6 +235,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.bg_green.SetGlobal(true);
values.bg_blue.SetGlobal(true);
values.enable_compute_pipelines.SetGlobal(true);
values.use_video_framerate.SetGlobal(true);
// System
values.language_index.SetGlobal(true);

View File

@@ -482,6 +482,7 @@ struct Values {
SwitchableSetting<AstcRecompression, true> astc_recompression{
AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3,
"astc_recompression"};
SwitchableSetting<bool> use_video_framerate{false, "use_video_framerate"};
SwitchableSetting<u8> bg_red{0, "bg_red"};
SwitchableSetting<u8> bg_green{0, "bg_green"};
@@ -523,9 +524,16 @@ struct Values {
Setting<bool> tas_loop{false, "tas_loop"};
Setting<bool> mouse_panning{false, "mouse_panning"};
Setting<u8, true> mouse_panning_sensitivity{50, 1, 100, "mouse_panning_sensitivity"};
Setting<bool> mouse_enabled{false, "mouse_enabled"};
Setting<u8, true> mouse_panning_x_sensitivity{50, 1, 100, "mouse_panning_x_sensitivity"};
Setting<u8, true> mouse_panning_y_sensitivity{50, 1, 100, "mouse_panning_y_sensitivity"};
Setting<u8, true> mouse_panning_deadzone_x_counterweight{
0, 0, 100, "mouse_panning_deadzone_x_counterweight"};
Setting<u8, true> mouse_panning_deadzone_y_counterweight{
0, 0, 100, "mouse_panning_deadzone_y_counterweight"};
Setting<u8, true> mouse_panning_decay_strength{22, 0, 100, "mouse_panning_decay_strength"};
Setting<u8, true> mouse_panning_min_decay{5, 0, 100, "mouse_panning_min_decay"};
Setting<bool> mouse_enabled{false, "mouse_enabled"};
Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
Setting<bool> keyboard_enabled{false, "keyboard_enabled"};

View File

@@ -216,6 +216,14 @@ struct System::Impl {
}
}
void SetNVDECActive(bool is_nvdec_active) {
nvdec_active = is_nvdec_active;
}
bool GetNVDECActive() {
return nvdec_active;
}
void InitializeDebugger(System& system, u16 port) {
debugger = std::make_unique<Debugger>(system, port);
}
@@ -485,6 +493,8 @@ struct System::Impl {
std::atomic_bool is_powered_on{};
bool exit_lock = false;
bool nvdec_active{};
Reporter reporter;
std::unique_ptr<Memory::CheatEngine> cheat_engine;
std::unique_ptr<Tools::Freezer> memory_freezer;
@@ -594,6 +604,14 @@ void System::UnstallApplication() {
impl->UnstallApplication();
}
void System::SetNVDECActive(bool is_nvdec_active) {
impl->SetNVDECActive(is_nvdec_active);
}
bool System::GetNVDECActive() {
return impl->GetNVDECActive();
}
void System::InitializeDebugger() {
impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
}

View File

@@ -189,6 +189,9 @@ public:
std::unique_lock<std::mutex> StallApplication();
void UnstallApplication();
void SetNVDECActive(bool is_nvdec_active);
[[nodiscard]] bool GetNVDECActive();
/**
* Initialize the debugger.
*/

View File

@@ -12,6 +12,11 @@
#pragma warning(pop)
#endif
#include <fmt/format.h>
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/input.h"
#include "common/logging/log.h"
#include "common/string_util.h"
@@ -136,7 +141,7 @@ bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
if (!NFP::AmiiboCrypto::IsKeyAvailable()) {
LOG_INFO(Service_NFC, "Loading amiibo without keys");
memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
BuildAmiiboWithoutKeys();
BuildAmiiboWithoutKeys(tag_data, encrypted_tag_data);
is_plain_amiibo = true;
is_write_protected = true;
return true;
@@ -366,16 +371,25 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
// The loaded amiibo is not encrypted
if (is_plain_amiibo) {
std::vector<u8> data(sizeof(NFP::NTAG215File));
memcpy(data.data(), &tag_data, sizeof(tag_data));
WriteBackupData(tag_data.uid, data);
device_state = DeviceState::TagMounted;
mount_target = mount_target_;
return ResultSuccess;
}
if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state);
return ResultCorruptedData;
bool has_backup = HasBackup(encrypted_tag_data.uuid.uid).IsSuccess();
LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup);
return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
}
std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
WriteBackupData(encrypted_tag_data.uuid.uid, data);
device_state = DeviceState::TagMounted;
mount_target = mount_target_;
return ResultSuccess;
@@ -470,6 +484,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
if (is_plain_amiibo) {
memcpy(data.data(), &tag_data, sizeof(tag_data));
WriteBackupData(tag_data.uid, data);
} else {
if (!NFP::AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
LOG_ERROR(Service_NFP, "Failed to encode data");
@@ -477,6 +492,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
}
memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
WriteBackupData(encrypted_tag_data.uuid.uid, data);
}
if (!npad_device->WriteNfc(data)) {
@@ -488,7 +504,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
}
Result NfcDevice::Restore() {
if (device_state != DeviceState::TagMounted) {
if (device_state != DeviceState::TagFound) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ResultTagRemoved;
@@ -496,13 +512,59 @@ Result NfcDevice::Restore() {
return ResultWrongDeviceState;
}
if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
return ResultWrongDeviceState;
NFC::TagInfo tag_info{};
std::array<u8, sizeof(NFP::EncryptedNTAG215File)> data{};
Result result = GetTagInfo(tag_info, false);
if (result.IsError()) {
return result;
}
// TODO: Load amiibo from backup on system
LOG_ERROR(Service_NFP, "Not Implemented");
result = ReadBackupData(tag_info.uuid, data);
if (result.IsError()) {
return result;
}
NFP::NTAG215File temporary_tag_data{};
NFP::EncryptedNTAG215File temporary_encrypted_tag_data{};
// Fallback for encrypted amiibos without keys
if (is_write_protected) {
return ResultWriteAmiiboFailed;
}
// Fallback for plain amiibos
if (is_plain_amiibo) {
LOG_INFO(Service_NFP, "Restoring backup of plain amiibo");
memcpy(&temporary_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
temporary_encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(temporary_tag_data);
}
if (!is_plain_amiibo) {
LOG_INFO(Service_NFP, "Restoring backup of encrypted amiibo");
temporary_tag_data = {};
memcpy(&temporary_encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
}
if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) {
return ResultNotAnAmiibo;
}
if (!is_plain_amiibo) {
if (!NFP::AmiiboCrypto::DecodeAmiibo(temporary_encrypted_tag_data, temporary_tag_data)) {
LOG_ERROR(Service_NFP, "Can't decode amiibo");
return ResultCorruptedData;
}
}
// Overwrite tag contents with backup and mount the tag
tag_data = temporary_tag_data;
encrypted_tag_data = temporary_encrypted_tag_data;
device_state = DeviceState::TagMounted;
mount_target = NFP::MountTarget::All;
is_data_moddified = true;
return ResultSuccess;
}
@@ -1132,13 +1194,69 @@ Result NfcDevice::BreakTag(NFP::BreakType break_type) {
return FlushWithBreak(break_type);
}
Result NfcDevice::ReadBackupData(std::span<u8> data) const {
// Not implemented
Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const {
constexpr auto backup_dir = "backup";
const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) {
return ResultUnableToAccessBackupFile;
}
return ResultSuccess;
}
Result NfcDevice::WriteBackupData(std::span<const u8> data) {
// Not implemented
Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const {
constexpr auto backup_dir = "backup";
const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
if (!keys_file.IsOpen()) {
LOG_ERROR(Service_NFP, "Failed to open amiibo backup");
return ResultUnableToAccessBackupFile;
}
if (keys_file.Read(data) != data.size()) {
LOG_ERROR(Service_NFP, "Failed to read amiibo backup");
return ResultUnableToAccessBackupFile;
}
return ResultSuccess;
}
Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data) {
constexpr auto backup_dir = "backup";
const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
if (HasBackup(uid).IsError()) {
if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) {
return ResultBackupPathAlreadyExist;
}
if (!Common::FS::NewFile(yuzu_amiibo_dir / backup_dir / file_name)) {
return ResultBackupPathAlreadyExist;
}
}
const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
Common::FS::FileAccessMode::ReadWrite,
Common::FS::FileType::BinaryFile};
if (!keys_file.IsOpen()) {
LOG_ERROR(Service_NFP, "Failed to open amiibo backup");
return ResultUnableToAccessBackupFile;
}
if (keys_file.Write(data) != data.size()) {
LOG_ERROR(Service_NFP, "Failed to write amiibo backup");
return ResultUnableToAccessBackupFile;
}
return ResultSuccess;
}
@@ -1177,7 +1295,8 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co
return amiibo_name;
}
void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) {
void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings,
const NFP::AmiiboName& amiibo_name) const {
std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{};
// Convert from utf8 to utf16
@@ -1258,22 +1377,23 @@ void NfcDevice::UpdateRegisterInfoCrc() {
tag_data.register_info_crc = crc.checksum();
}
void NfcDevice::BuildAmiiboWithoutKeys() {
void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
const NFP::EncryptedNTAG215File& encrypted_file) const {
Service::Mii::MiiManager manager;
auto& settings = tag_data.settings;
auto& settings = stubbed_tag_data.settings;
tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_tag_data);
stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file);
// Common info
tag_data.write_counter = 0;
tag_data.amiibo_version = 0;
stubbed_tag_data.write_counter = 0;
stubbed_tag_data.amiibo_version = 0;
settings.write_date = GetAmiiboDate(GetCurrentPosixTime());
// Register info
SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'});
settings.settings.font_region.Assign(0);
settings.init_date = GetAmiiboDate(GetCurrentPosixTime());
tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0));
stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0));
// Admin info
settings.settings.amiibo_initialized.Assign(1);

View File

@@ -86,8 +86,9 @@ public:
Result GetAll(NFP::NfpData& data) const;
Result SetAll(const NFP::NfpData& data);
Result BreakTag(NFP::BreakType break_type);
Result ReadBackupData(std::span<u8> data) const;
Result WriteBackupData(std::span<const u8> data);
Result HasBackup(const NFC::UniqueSerialNumber& uid) const;
Result ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const;
Result WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data);
Result WriteNtf(std::span<const u8> data);
u64 GetHandle() const;
@@ -103,14 +104,15 @@ private:
void CloseNfcTag();
NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const;
void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name);
void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) const;
NFP::AmiiboDate GetAmiiboDate(s64 posix_time) const;
u64 GetCurrentPosixTime() const;
u64 RemoveVersionByte(u64 application_id) const;
void UpdateSettingsCrc();
void UpdateRegisterInfoCrc();
void BuildAmiiboWithoutKeys();
void BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
const NFP::EncryptedNTAG215File& encrypted_file) const;
bool is_controller_set{};
int callback_key;

View File

@@ -543,9 +543,14 @@ Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) cons
std::shared_ptr<NfcDevice> device = nullptr;
auto result = GetDeviceHandle(device_handle, device);
NFC::TagInfo tag_info{};
if (result.IsSuccess()) {
result = device->ReadBackupData(data);
result = device->GetTagInfo(tag_info, false);
}
if (result.IsSuccess()) {
result = device->ReadBackupData(tag_info.uuid, data);
result = VerifyDeviceResult(device, result);
}
@@ -557,9 +562,14 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> dat
std::shared_ptr<NfcDevice> device = nullptr;
auto result = GetDeviceHandle(device_handle, device);
NFC::TagInfo tag_info{};
if (result.IsSuccess()) {
result = device->WriteBackupData(data);
result = device->GetTagInfo(tag_info, false);
}
if (result.IsSuccess()) {
result = device->WriteBackupData(tag_info.uuid, data);
result = VerifyDeviceResult(device, result);
}

View File

@@ -302,7 +302,7 @@ Result NfcInterface::TranslateResultToServiceError(Result result) const {
return TranslateResultToNfp(result);
}
default:
if (result != ResultUnknown216) {
if (result != ResultBackupPathAlreadyExist) {
return result;
}
return ResultUnknown74;
@@ -343,6 +343,9 @@ Result NfcInterface::TranslateResultToNfp(Result result) const {
if (result == ResultApplicationAreaIsNotInitialized) {
return NFP::ResultApplicationAreaIsNotInitialized;
}
if (result == ResultCorruptedDataWithBackup) {
return NFP::ResultCorruptedDataWithBackup;
}
if (result == ResultCorruptedData) {
return NFP::ResultCorruptedData;
}
@@ -355,6 +358,9 @@ Result NfcInterface::TranslateResultToNfp(Result result) const {
if (result == ResultNotAnAmiibo) {
return NFP::ResultNotAnAmiibo;
}
if (result == ResultUnableToAccessBackupFile) {
return NFP::ResultUnableToAccessBackupFile;
}
LOG_WARNING(Service_NFC, "Result conversion not handled");
return result;
}

View File

@@ -9,20 +9,22 @@ namespace Service::NFC {
constexpr Result ResultDeviceNotFound(ErrorModule::NFC, 64);
constexpr Result ResultInvalidArgument(ErrorModule::NFC, 65);
constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFP, 68);
constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFC, 68);
constexpr Result ResultWrongDeviceState(ErrorModule::NFC, 73);
constexpr Result ResultUnknown74(ErrorModule::NFC, 74);
constexpr Result ResultUnknown76(ErrorModule::NFC, 76);
constexpr Result ResultNfcNotInitialized(ErrorModule::NFC, 77);
constexpr Result ResultNfcDisabled(ErrorModule::NFC, 80);
constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88);
constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFC, 88);
constexpr Result ResultTagRemoved(ErrorModule::NFC, 97);
constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120);
constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
constexpr Result ResultCorruptedData(ErrorModule::NFP, 144);
constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152);
constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168);
constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178);
constexpr Result ResultUnknown216(ErrorModule::NFC, 216);
constexpr Result ResultUnableToAccessBackupFile(ErrorModule::NFC, 113);
constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFC, 120);
constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFC, 128);
constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFC, 136);
constexpr Result ResultCorruptedData(ErrorModule::NFC, 144);
constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFC, 152);
constexpr Result ResultApplicationAreaExist(ErrorModule::NFC, 168);
constexpr Result ResultNotAnAmiibo(ErrorModule::NFC, 178);
constexpr Result ResultBackupPathAlreadyExist(ErrorModule::NFC, 216);
} // namespace Service::NFC

View File

@@ -126,7 +126,7 @@ void Interface::Flush(HLERequestContext& ctx) {
void Interface::Restore(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
auto result = GetManager()->Restore(device_handle);
result = TranslateResultToServiceError(result);
@@ -394,7 +394,7 @@ void Interface::BreakTag(HLERequestContext& ctx) {
void Interface::ReadBackupData(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
std::vector<u8> backup_data{};
auto result = GetManager()->ReadBackupData(device_handle, backup_data);
@@ -412,7 +412,7 @@ void Interface::WriteBackupData(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto backup_data_buffer{ctx.ReadBuffer()};
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
auto result = GetManager()->WriteBackupData(device_handle, backup_data_buffer);
result = TranslateResultToServiceError(result);

View File

@@ -17,9 +17,11 @@ constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88);
constexpr Result ResultTagRemoved(ErrorModule::NFP, 97);
constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120);
constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFP, 136);
constexpr Result ResultCorruptedData(ErrorModule::NFP, 144);
constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152);
constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168);
constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178);
constexpr Result ResultUnableToAccessBackupFile(ErrorModule::NFP, 200);
} // namespace Service::NFP

View File

@@ -69,7 +69,7 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
void nvhost_nvdec::OnOpen(DeviceFD fd) {
LOG_INFO(Service_NVDRV, "NVDEC video stream started");
system.AudioCore().SetNVDECActive(true);
system.SetNVDECActive(true);
}
void nvhost_nvdec::OnClose(DeviceFD fd) {
@@ -79,7 +79,7 @@ void nvhost_nvdec::OnClose(DeviceFD fd) {
if (iter != host1x_file.fd_to_id.end()) {
system.GPU().ClearCdmaInstance(iter->second);
}
system.AudioCore().SetNVDECActive(false);
system.SetNVDECActive(false);
}
} // namespace Service::Nvidia::Devices

View File

@@ -324,6 +324,10 @@ s64 Nvnflinger::GetNextTicks() const {
speed_scale = 0.01f;
}
}
if (system.GetNVDECActive() && settings.use_video_framerate.GetValue()) {
// Run at intended presentation rate during video playback.
speed_scale = 1.f;
}
// As an extension, treat nonpositive swap interval as framerate multiplier.
const f32 effective_fps = swap_interval <= 0 ? 120.f * static_cast<f32>(1 - swap_interval)

View File

@@ -33,7 +33,8 @@ static_assert(sizeof(NroSegmentHeader) == 0x8, "NroSegmentHeader has incorrect s
struct NroHeader {
INSERT_PADDING_BYTES(0x4);
u32_le module_header_offset;
INSERT_PADDING_BYTES(0x8);
u32 magic_ext1;
u32 magic_ext2;
u32_le magic;
INSERT_PADDING_BYTES(0x4);
u32_le file_size;
@@ -124,6 +125,16 @@ FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& nro_file) {
return FileType::Error;
}
bool AppLoader_NRO::IsHomebrew() {
// Read NSO header
NroHeader nro_header{};
if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
return false;
}
return nro_header.magic_ext1 == Common::MakeMagic('H', 'O', 'M', 'E') &&
nro_header.magic_ext2 == Common::MakeMagic('B', 'R', 'E', 'W');
}
static constexpr u32 PageAlignSize(u32 size) {
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
}

View File

@@ -38,6 +38,8 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& nro_file);
bool IsHomebrew();
FileType GetFileType() const override {
return IdentifyType(file);
}

View File

@@ -76,9 +76,6 @@ void Mouse::UpdateThread(std::stop_token stop_token) {
UpdateStickInput();
UpdateMotionInput();
if (mouse_panning_timeout++ > 20) {
StopPanning();
}
std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
}
}
@@ -88,18 +85,45 @@ void Mouse::UpdateStickInput() {
return;
}
const float sensitivity =
Settings::values.mouse_panning_sensitivity.GetValue() * default_stick_sensitivity;
const float length = last_mouse_change.Length();
// Slow movement by 4%
last_mouse_change *= 0.96f;
SetAxis(identifier, mouse_axis_x, last_mouse_change.x * sensitivity);
SetAxis(identifier, mouse_axis_y, -last_mouse_change.y * sensitivity);
// Prevent input from exceeding the max range (1.0f) too much,
// but allow some room to make it easier to sustain
if (length > 1.2f) {
last_mouse_change /= length;
last_mouse_change *= 1.2f;
}
auto mouse_change = last_mouse_change;
// Bind the mouse change to [0 <= deadzone_counterweight <= 1,1]
if (length < 1.0f) {
const float deadzone_h_counterweight =
Settings::values.mouse_panning_deadzone_x_counterweight.GetValue();
const float deadzone_v_counterweight =
Settings::values.mouse_panning_deadzone_y_counterweight.GetValue();
mouse_change /= length;
mouse_change.x *= length + (1 - length) * deadzone_h_counterweight * 0.01f;
mouse_change.y *= length + (1 - length) * deadzone_v_counterweight * 0.01f;
}
SetAxis(identifier, mouse_axis_x, mouse_change.x);
SetAxis(identifier, mouse_axis_y, -mouse_change.y);
// Decay input over time
const float clamped_length = std::min(1.0f, length);
const float decay_strength = Settings::values.mouse_panning_decay_strength.GetValue();
const float decay = 1 - clamped_length * clamped_length * decay_strength * 0.01f;
const float min_decay = Settings::values.mouse_panning_min_decay.GetValue();
const float clamped_decay = std::min(1 - min_decay / 100.0f, decay);
last_mouse_change *= clamped_decay;
}
void Mouse::UpdateMotionInput() {
const float sensitivity =
Settings::values.mouse_panning_sensitivity.GetValue() * default_motion_sensitivity;
// This may need its own sensitivity instead of using the average
const float sensitivity = (Settings::values.mouse_panning_x_sensitivity.GetValue() +
Settings::values.mouse_panning_y_sensitivity.GetValue()) /
2.0f * default_motion_sensitivity;
const float rotation_velocity = std::sqrt(last_motion_change.x * last_motion_change.x +
last_motion_change.y * last_motion_change.y);
@@ -131,49 +155,28 @@ void Mouse::UpdateMotionInput() {
void Mouse::Move(int x, int y, int center_x, int center_y) {
if (Settings::values.mouse_panning) {
mouse_panning_timeout = 0;
auto mouse_change =
const auto mouse_change =
(Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>();
const float x_sensitivity =
Settings::values.mouse_panning_x_sensitivity.GetValue() * default_stick_sensitivity;
const float y_sensitivity =
Settings::values.mouse_panning_y_sensitivity.GetValue() * default_stick_sensitivity;
last_motion_change += {-mouse_change.y, -mouse_change.x, 0};
const auto move_distance = mouse_change.Length();
if (move_distance == 0) {
return;
}
// Make slow movements at least 3 units on length
if (move_distance < 3.0f) {
// Normalize value
mouse_change /= move_distance;
mouse_change *= 3.0f;
}
// Average mouse movements
last_mouse_change = (last_mouse_change * 0.91f) + (mouse_change * 0.09f);
const auto last_move_distance = last_mouse_change.Length();
// Make fast movements clamp to 8 units on length
if (last_move_distance > 8.0f) {
// Normalize value
last_mouse_change /= last_move_distance;
last_mouse_change *= 8.0f;
}
// Ignore average if it's less than 1 unit and use current movement value
if (last_move_distance < 1.0f) {
last_mouse_change = mouse_change / mouse_change.Length();
}
last_mouse_change.x += mouse_change.x * x_sensitivity * 0.09f;
last_mouse_change.y += mouse_change.y * y_sensitivity * 0.09f;
return;
}
if (button_pressed) {
const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin;
const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f;
SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * sensitivity);
SetAxis(identifier, mouse_axis_y, static_cast<float>(-mouse_move.y) * sensitivity);
const float x_sensitivity = Settings::values.mouse_panning_x_sensitivity.GetValue();
const float y_sensitivity = Settings::values.mouse_panning_y_sensitivity.GetValue();
SetAxis(identifier, mouse_axis_x,
static_cast<float>(mouse_move.x) * x_sensitivity * 0.0012f);
SetAxis(identifier, mouse_axis_y,
static_cast<float>(-mouse_move.y) * y_sensitivity * 0.0012f);
last_motion_change = {
static_cast<float>(-mouse_move.y) / 50.0f,
@@ -241,10 +244,6 @@ void Mouse::ReleaseAllButtons() {
button_pressed = false;
}
void Mouse::StopPanning() {
last_mouse_change = {};
}
std::vector<Common::ParamPackage> Mouse::GetInputDevices() const {
std::vector<Common::ParamPackage> devices;
devices.emplace_back(Common::ParamPackage{

View File

@@ -98,7 +98,6 @@ private:
void UpdateThread(std::stop_token stop_token);
void UpdateStickInput();
void UpdateMotionInput();
void StopPanning();
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
@@ -108,7 +107,6 @@ private:
Common::Vec3<float> last_motion_change;
Common::Vec2<int> wheel_position;
bool button_pressed;
int mouse_panning_timeout{};
std::jthread update_thread;
};

View File

@@ -715,20 +715,38 @@ void BufferCache<P>::BindHostIndexBuffer() {
template <class P>
void BufferCache<P>::BindHostVertexBuffers() {
HostBindings host_bindings;
bool any_valid{false};
auto& flags = maxwell3d->dirty.flags;
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
const Binding& binding = channel_state->vertex_buffers[index];
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
SynchronizeBuffer(buffer, binding.cpu_addr, binding.size);
if (!flags[Dirty::VertexBuffer0 + index]) {
continue;
}
flags[Dirty::VertexBuffer0 + index] = false;
host_bindings.min_index = std::min(host_bindings.min_index, index);
host_bindings.max_index = std::max(host_bindings.max_index, index);
any_valid = true;
}
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
const u32 offset = buffer.Offset(binding.cpu_addr);
runtime.BindVertexBuffer(index, buffer, offset, binding.size, stride);
if (any_valid) {
host_bindings.max_index++;
for (u32 index = host_bindings.min_index; index < host_bindings.max_index; index++) {
flags[Dirty::VertexBuffer0 + index] = false;
const Binding& binding = channel_state->vertex_buffers[index];
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
SynchronizeBuffer(buffer, binding.cpu_addr, binding.size);
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
const u32 offset = buffer.Offset(binding.cpu_addr);
host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer));
host_bindings.offsets.push_back(offset);
host_bindings.sizes.push_back(binding.size);
host_bindings.strides.push_back(stride);
}
runtime.BindVertexBuffers(host_bindings);
}
}
@@ -882,15 +900,25 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
if (maxwell3d->regs.transform_feedback_enabled == 0) {
return;
}
HostBindings host_bindings;
for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
const Binding& binding = channel_state->transform_feedback_buffers[index];
if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 &&
maxwell3d->regs.transform_feedback.controls[index].stride == 0) {
break;
}
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
const u32 size = binding.size;
SynchronizeBuffer(buffer, binding.cpu_addr, size);
const u32 offset = buffer.Offset(binding.cpu_addr);
runtime.BindTransformFeedbackBuffer(index, buffer, offset, size);
host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer));
host_bindings.offsets.push_back(offset);
host_bindings.sizes.push_back(binding.size);
}
if (host_bindings.buffers.size() > 0) {
runtime.BindTransformFeedbackBuffers(host_bindings);
}
}
@@ -1616,6 +1644,8 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 si
template <class P>
void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
bool dirty_index{false};
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> dirty_vertex_buffers;
const auto scalar_replace = [buffer_id](Binding& binding) {
if (binding.buffer_id == buffer_id) {
binding.buffer_id = BufferId{};
@@ -1624,8 +1654,19 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
const auto replace = [scalar_replace](std::span<Binding> bindings) {
std::ranges::for_each(bindings, scalar_replace);
};
scalar_replace(channel_state->index_buffer);
replace(channel_state->vertex_buffers);
if (channel_state->index_buffer.buffer_id == buffer_id) {
channel_state->index_buffer.buffer_id = BufferId{};
dirty_index = true;
}
for (u32 index = 0; index < channel_state->vertex_buffers.size(); index++) {
auto& binding = channel_state->vertex_buffers[index];
if (binding.buffer_id == buffer_id) {
binding.buffer_id = BufferId{};
dirty_vertex_buffers.push_back(index);
}
}
std::ranges::for_each(channel_state->uniform_buffers, replace);
std::ranges::for_each(channel_state->storage_buffers, replace);
replace(channel_state->transform_feedback_buffers);
@@ -1642,20 +1683,21 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id]));
slot_buffers.erase(buffer_id);
NotifyBufferDeletion();
}
template <class P>
void BufferCache<P>::NotifyBufferDeletion() {
if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
channel_state->dirty_uniform_buffers.fill(~u32{0});
channel_state->uniform_buffer_binding_sizes.fill({});
}
auto& flags = maxwell3d->dirty.flags;
flags[Dirty::IndexBuffer] = true;
flags[Dirty::VertexBuffers] = true;
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
flags[Dirty::VertexBuffer0 + index] = true;
if (dirty_index) {
flags[Dirty::IndexBuffer] = true;
}
if (dirty_vertex_buffers.size() > 0) {
flags[Dirty::VertexBuffers] = true;
for (auto index : dirty_vertex_buffers) {
flags[Dirty::VertexBuffer0 + index] = true;
}
}
channel_state->has_deleted_buffers = true;
}

View File

@@ -105,6 +105,15 @@ static constexpr Binding NULL_BINDING{
.buffer_id = NULL_BUFFER_ID,
};
struct HostBindings {
boost::container::small_vector<void*, NUM_VERTEX_BUFFERS> buffers;
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets;
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes;
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides;
u32 min_index{NUM_VERTEX_BUFFERS};
u32 max_index{0};
};
class BufferCacheChannelInfo : public ChannelInfo {
public:
BufferCacheChannelInfo() = delete;
@@ -519,8 +528,6 @@ private:
void DeleteBuffer(BufferId buffer_id, bool do_not_mark = false);
void NotifyBufferDeletion();
[[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
bool is_written) const;

View File

@@ -232,6 +232,15 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset,
}
}
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) {
for (u32 index = 0; index < bindings.buffers.size(); index++) {
BindVertexBuffer(
bindings.min_index + index, *reinterpret_cast<Buffer*>(bindings.buffers[index]),
static_cast<u32>(bindings.offsets[index]), static_cast<u32>(bindings.sizes[index]),
static_cast<u32>(bindings.strides[index]));
}
}
void BufferCacheRuntime::BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer,
u32 offset, u32 size) {
if (use_assembly_shaders) {
@@ -320,6 +329,15 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, Buffer& buffer,
static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
}
void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) {
for (u32 index = 0; index < bindings.buffers.size(); index++) {
glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index,
reinterpret_cast<Buffer*>(bindings.buffers[index])->Handle(),
static_cast<GLintptr>(bindings.offsets[index]),
static_cast<GLsizeiptr>(bindings.sizes[index]));
}
}
void BufferCacheRuntime::BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
PixelFormat format) {
*texture_handles++ = buffer.View(offset, size, format);

View File

@@ -7,7 +7,7 @@
#include <span>
#include "common/common_types.h"
#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/buffer_cache/memory_tracker_base.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_opengl/gl_device.h"
@@ -87,6 +87,7 @@ public:
void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size);
void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride);
void BindVertexBuffers(VideoCommon::HostBindings& bindings);
void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size);
@@ -99,6 +100,7 @@ public:
bool is_written);
void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size);
void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings);
void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
VideoCore::Surface::PixelFormat format);

View File

@@ -37,10 +37,6 @@
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#ifdef ANDROID
extern u32 GetAndroidScreenRotation();
#endif
namespace Vulkan {
namespace {
@@ -78,47 +74,6 @@ struct ScreenRectVertex {
}
};
#ifdef ANDROID
std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
constexpr u32 ROTATION_0 = 0;
constexpr u32 ROTATION_90 = 1;
constexpr u32 ROTATION_180 = 2;
constexpr u32 ROTATION_270 = 3;
// clang-format off
switch (GetAndroidScreenRotation()) {
case ROTATION_0:
// Desktop
return { 2.f / width, 0.f, 0.f, 0.f,
0.f, 2.f / height, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
-1.f, -1.f, 0.f, 1.f};
case ROTATION_180:
// Reverse desktop
return {-2.f / width, 0.f, 0.f, 0.f,
0.f, -2.f / height, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
1.f, 1.f, 0.f, 1.f};
case ROTATION_270:
// Reverse landscape
return { 0.f, -2.f / width, 0.f, 0.f,
2.f / height, 0.f, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
-1.f, 1.f, 0.f, 1.f};
case ROTATION_90:
default:
// Landscape
return { 0.f, 2.f / width, 0.f, 0.f,
-2.f / height, 0.f, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
1.f, -1.f, 0.f, 1.f};
}
// clang-format on
}
#else
std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
// clang-format off
return { 2.f / width, 0.f, 0.f, 0.f,
@@ -128,8 +83,6 @@ std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
// clang-format on
}
#endif
u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) {
using namespace VideoCore::Surface;
return BytesPerBlock(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format));
@@ -1159,7 +1112,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
.pNext = nullptr,
.flags = 0,
.imageType = VK_IMAGE_TYPE_2D,
.format = GetFormat(framebuffer),
.format = used_on_framebuffer ? VK_FORMAT_R16G16B16A16_SFLOAT : GetFormat(framebuffer),
.extent =
{
.width = (up_scale * framebuffer.width) >> down_shift,
@@ -1180,14 +1133,14 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
const auto create_commit = [&](vk::Image& image) {
return memory_allocator.Commit(image, MemoryUsage::DeviceLocal);
};
const auto create_image_view = [&](vk::Image& image) {
const auto create_image_view = [&](vk::Image& image, bool used_on_framebuffer = false) {
return device.GetLogical().CreateImageView(VkImageViewCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.image = *image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = GetFormat(framebuffer),
.format = used_on_framebuffer ? VK_FORMAT_R16G16B16A16_SFLOAT : GetFormat(framebuffer),
.components =
{
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
@@ -1217,7 +1170,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
const u32 down_shift = Settings::values.resolution_info.down_shift;
aa_image = create_image(true, up_scale, down_shift);
aa_commit = create_commit(aa_image);
aa_image_view = create_image_view(aa_image);
aa_image_view = create_image_view(aa_image, true);
VkExtent2D size{
.width = (up_scale * framebuffer.width) >> down_shift,
.height = (up_scale * framebuffer.height) >> down_shift,

View File

@@ -7,7 +7,6 @@
#include <span>
#include <vector>
#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -502,6 +501,40 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
}
}
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) {
boost::container::small_vector<VkBuffer, 32> buffer_handles;
for (u32 index = 0; index < bindings.buffers.size(); index++) {
auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]);
auto handle = buffer.Handle();
if (handle == VK_NULL_HANDLE) {
bindings.offsets[index] = 0;
bindings.sizes[index] = VK_WHOLE_SIZE;
if (!device.HasNullDescriptor()) {
ReserveNullBuffer();
handle = *null_buffer;
}
}
buffer_handles.push_back(handle);
}
if (device.IsExtExtendedDynamicStateSupported()) {
scheduler.Record([bindings = bindings,
buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) {
cmdbuf.BindVertexBuffers2EXT(
bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(),
reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()),
reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data()),
reinterpret_cast<const VkDeviceSize*>(bindings.strides.data()));
});
} else {
scheduler.Record([bindings = bindings,
buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) {
cmdbuf.BindVertexBuffers(
bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(),
reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()));
});
}
}
void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset,
u32 size) {
if (!device.IsExtTransformFeedbackSupported()) {
@@ -523,6 +556,25 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer,
});
}
void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) {
if (!device.IsExtTransformFeedbackSupported()) {
// Already logged in the rasterizer
return;
}
boost::container::small_vector<VkBuffer, 4> buffer_handles;
for (u32 index = 0; index < bindings.buffers.size(); index++) {
auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]);
buffer_handles.push_back(buffer.Handle());
}
scheduler.Record(
[bindings = bindings, buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) {
cmdbuf.BindTransformFeedbackBuffersEXT(
0, static_cast<u32>(buffer_handles.size()), buffer_handles.data(),
reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()),
reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data()));
});
}
void BufferCacheRuntime::ReserveNullBuffer() {
if (null_buffer) {
return;

View File

@@ -18,6 +18,7 @@ namespace Vulkan {
class Device;
class DescriptorPool;
class Scheduler;
struct HostVertexBinding;
class BufferCacheRuntime;
@@ -96,8 +97,10 @@ public:
void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count);
void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride);
void BindVertexBuffers(VideoCommon::HostBindings& bindings);
void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings);
std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage,
[[maybe_unused]] u32 binding_index, u32 size) {

View File

@@ -231,7 +231,12 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
#ifdef ANDROID
// On Android, do not allow surface rotation to deviate from the frontend.
.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
#else
.preTransform = capabilities.currentTransform,
#endif
.compositeAlpha = alpha_flags,
.presentMode = present_mode,
.clipped = VK_FALSE,

View File

@@ -98,6 +98,9 @@ add_executable(yuzu
configuration/configure_input_profile_dialog.cpp
configuration/configure_input_profile_dialog.h
configuration/configure_input_profile_dialog.ui
configuration/configure_mouse_panning.cpp
configuration/configure_mouse_panning.h
configuration/configure_mouse_panning.ui
configuration/configure_motion_touch.cpp
configuration/configure_motion_touch.h
configuration/configure_motion_touch.ui

View File

@@ -101,6 +101,12 @@ const std::map<Settings::RendererBackend, QString> Config::renderer_backend_text
{Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))},
};
const std::map<Settings::ShaderBackend, QString> Config::shader_backend_texts_map = {
{Settings::ShaderBackend::GLSL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
{Settings::ShaderBackend::GLASM, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
{Settings::ShaderBackend::SPIRV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
};
// This shouldn't have anything except static initializers (no functions). So
// QKeySequence(...).toString() is NOT ALLOWED HERE.
// This must be in alphabetical order according to action name as it must have the same order as
@@ -345,6 +351,10 @@ void Config::ReadPlayerValue(std::size_t player_index) {
player_motions = default_param;
}
}
if (player_index == 0) {
ReadMousePanningValues();
}
}
void Config::ReadDebugValues() {
@@ -465,6 +475,7 @@ void Config::ReadControlValues() {
ReadKeyboardValues();
ReadMouseValues();
ReadTouchscreenValues();
ReadMousePanningValues();
ReadMotionTouchValues();
ReadHidbusValues();
ReadIrCameraValues();
@@ -475,8 +486,6 @@ void Config::ReadControlValues() {
Settings::values.enable_raw_input = false;
#endif
ReadBasicSetting(Settings::values.emulate_analog_keyboard);
Settings::values.mouse_panning = false;
ReadBasicSetting(Settings::values.mouse_panning_sensitivity);
ReadBasicSetting(Settings::values.enable_joycon_driver);
ReadBasicSetting(Settings::values.enable_procon_driver);
ReadBasicSetting(Settings::values.random_amiibo_id);
@@ -490,6 +499,16 @@ void Config::ReadControlValues() {
qt_config->endGroup();
}
void Config::ReadMousePanningValues() {
ReadBasicSetting(Settings::values.mouse_panning);
ReadBasicSetting(Settings::values.mouse_panning_x_sensitivity);
ReadBasicSetting(Settings::values.mouse_panning_y_sensitivity);
ReadBasicSetting(Settings::values.mouse_panning_deadzone_x_counterweight);
ReadBasicSetting(Settings::values.mouse_panning_deadzone_y_counterweight);
ReadBasicSetting(Settings::values.mouse_panning_decay_strength);
ReadBasicSetting(Settings::values.mouse_panning_min_decay);
}
void Config::ReadMotionTouchValues() {
int num_touch_from_button_maps =
qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
@@ -754,6 +773,7 @@ void Config::ReadRendererValues() {
ReadGlobalSetting(Settings::values.use_fast_gpu_time);
ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
ReadGlobalSetting(Settings::values.enable_compute_pipelines);
ReadGlobalSetting(Settings::values.use_video_framerate);
ReadGlobalSetting(Settings::values.bg_red);
ReadGlobalSetting(Settings::values.bg_green);
ReadGlobalSetting(Settings::values.bg_blue);
@@ -1056,6 +1076,10 @@ void Config::SavePlayerValue(std::size_t player_index) {
QString::fromStdString(player.motions[i]),
QString::fromStdString(default_param));
}
if (player_index == 0) {
SaveMousePanningValues();
}
}
void Config::SaveDebugValues() {
@@ -1092,6 +1116,16 @@ void Config::SaveTouchscreenValues() {
WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
}
void Config::SaveMousePanningValues() {
// Don't overwrite values.mouse_panning
WriteBasicSetting(Settings::values.mouse_panning_x_sensitivity);
WriteBasicSetting(Settings::values.mouse_panning_y_sensitivity);
WriteBasicSetting(Settings::values.mouse_panning_deadzone_x_counterweight);
WriteBasicSetting(Settings::values.mouse_panning_deadzone_y_counterweight);
WriteBasicSetting(Settings::values.mouse_panning_decay_strength);
WriteBasicSetting(Settings::values.mouse_panning_min_decay);
}
void Config::SaveMotionTouchValues() {
WriteBasicSetting(Settings::values.touch_device);
WriteBasicSetting(Settings::values.touch_from_button_map_index);
@@ -1178,6 +1212,7 @@ void Config::SaveControlValues() {
SaveDebugValues();
SaveMouseValues();
SaveTouchscreenValues();
SaveMousePanningValues();
SaveMotionTouchValues();
SaveHidbusValues();
SaveIrCameraValues();
@@ -1192,7 +1227,6 @@ void Config::SaveControlValues() {
WriteBasicSetting(Settings::values.random_amiibo_id);
WriteBasicSetting(Settings::values.keyboard_enabled);
WriteBasicSetting(Settings::values.emulate_analog_keyboard);
WriteBasicSetting(Settings::values.mouse_panning_sensitivity);
WriteBasicSetting(Settings::values.controller_navigation);
WriteBasicSetting(Settings::values.tas_enable);
@@ -1409,6 +1443,7 @@ void Config::SaveRendererValues() {
WriteGlobalSetting(Settings::values.use_fast_gpu_time);
WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
WriteGlobalSetting(Settings::values.enable_compute_pipelines);
WriteGlobalSetting(Settings::values.use_video_framerate);
WriteGlobalSetting(Settings::values.bg_red);
WriteGlobalSetting(Settings::values.bg_green);
WriteGlobalSetting(Settings::values.bg_blue);

View File

@@ -54,6 +54,7 @@ public:
static const std::map<bool, QString> use_docked_mode_texts_map;
static const std::map<Settings::GPUAccuracy, QString> gpu_accuracy_texts_map;
static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map;
static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map;
static constexpr UISettings::Theme default_theme{
#ifdef _WIN32
@@ -73,6 +74,7 @@ private:
void ReadKeyboardValues();
void ReadMouseValues();
void ReadTouchscreenValues();
void ReadMousePanningValues();
void ReadMotionTouchValues();
void ReadHidbusValues();
void ReadIrCameraValues();
@@ -103,6 +105,7 @@ private:
void SaveDebugValues();
void SaveMouseValues();
void SaveTouchscreenValues();
void SaveMousePanningValues();
void SaveMotionTouchValues();
void SaveHidbusValues();
void SaveIrCameraValues();

View File

@@ -42,6 +42,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {
Settings::values.use_vulkan_driver_pipeline_cache.GetValue());
ui->enable_compute_pipelines_checkbox->setChecked(
Settings::values.enable_compute_pipelines.GetValue());
ui->use_video_framerate_checkbox->setChecked(Settings::values.use_video_framerate.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->gpu_accuracy->setCurrentIndex(
@@ -91,6 +92,8 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_compute_pipelines,
ui->enable_compute_pipelines_checkbox,
enable_compute_pipelines);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_video_framerate,
ui->use_video_framerate_checkbox, use_video_framerate);
}
void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
@@ -125,6 +128,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
Settings::values.max_anisotropy.UsingGlobal());
ui->enable_compute_pipelines_checkbox->setEnabled(
Settings::values.enable_compute_pipelines.UsingGlobal());
ui->use_video_framerate_checkbox->setEnabled(
Settings::values.use_video_framerate.UsingGlobal());
return;
}
@@ -149,6 +154,9 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
ConfigurationShared::SetColoredTristate(ui->enable_compute_pipelines_checkbox,
Settings::values.enable_compute_pipelines,
enable_compute_pipelines);
ConfigurationShared::SetColoredTristate(ui->use_video_framerate_checkbox,
Settings::values.use_video_framerate,
use_video_framerate);
ConfigurationShared::SetColoredComboBox(
ui->gpu_accuracy, ui->label_gpu_accuracy,
static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));

View File

@@ -47,6 +47,7 @@ private:
ConfigurationShared::CheckState use_fast_gpu_time;
ConfigurationShared::CheckState use_vulkan_driver_pipeline_cache;
ConfigurationShared::CheckState enable_compute_pipelines;
ConfigurationShared::CheckState use_video_framerate;
const Core::System& system;
};

View File

@@ -191,6 +191,16 @@ Compute pipelines are always enabled on all other drivers.</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="use_video_framerate_checkbox">
<property name="toolTip">
<string>Run the game at normal speed during video playback, even when the framerate is unlocked.</string>
</property>
<property name="text">
<string>Sync to framerate of video playback</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="af_layout" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_1">

View File

@@ -129,9 +129,6 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
Settings::values.mouse_enabled = ui->mouse_enabled->isChecked();
Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked();
Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked();
Settings::values.mouse_panning = ui->mouse_panning->isChecked();
Settings::values.mouse_panning_sensitivity =
static_cast<float>(ui->mouse_panning_sensitivity->value());
Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
Settings::values.enable_raw_input = ui->enable_raw_input->isChecked();
Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
@@ -167,8 +164,6 @@ void ConfigureInputAdvanced::LoadConfiguration() {
ui->mouse_enabled->setChecked(Settings::values.mouse_enabled.GetValue());
ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled.GetValue());
ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard.GetValue());
ui->mouse_panning->setChecked(Settings::values.mouse_panning.GetValue());
ui->mouse_panning_sensitivity->setValue(Settings::values.mouse_panning_sensitivity.GetValue());
ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue());
ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
@@ -197,8 +192,6 @@ void ConfigureInputAdvanced::RetranslateUI() {
void ConfigureInputAdvanced::UpdateUIEnabled() {
ui->debug_configure->setEnabled(ui->debug_enabled->isChecked());
ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked());
ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked());
ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked());
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) || !defined(YUZU_USE_QT_MULTIMEDIA)
ui->enable_ir_sensor->setEnabled(false);

View File

@@ -2744,48 +2744,13 @@
</widget>
</item>
<item row="8" column="0">
<widget class="QCheckBox" name="mouse_panning">
<property name="minimumSize">
<size>
<width>0</width>
<height>23</height>
</size>
</property>
<property name="text">
<string>Enable mouse panning</string>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QSpinBox" name="mouse_panning_sensitivity">
<property name="toolTip">
<string>Mouse sensitivity</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="motion_touch">
<property name="text">
<string>Motion / Touch</string>
</property>
</widget>
</item>
<item row="9" column="2">
<item row="8" column="2">
<widget class="QPushButton" name="buttonMotionTouch">
<property name="text">
<string>Configure</string>

View File

@@ -23,6 +23,7 @@
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_input_player_widget.h"
#include "yuzu/configuration/configure_mouse_panning.h"
#include "yuzu/configuration/input_profiles.h"
#include "yuzu/util/limitable_input_dialog.h"
@@ -711,6 +712,21 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
});
}
if (player_index_ == 0) {
connect(ui->mousePanningButton, &QPushButton::clicked, [this, input_subsystem_] {
const auto right_stick_param =
emulated_controller->GetStickParam(Settings::NativeAnalog::RStick);
ConfigureMousePanning dialog(this, input_subsystem_,
right_stick_param.Get("deadzone", 0.0f),
right_stick_param.Get("range", 1.0f));
if (dialog.exec() == QDialog::Accepted) {
dialog.ApplyConfiguration();
}
});
} else {
ui->mousePanningWidget->hide();
}
// Player Connected checkbox
connect(ui->groupConnectedController, &QGroupBox::toggled,
[this](bool checked) { emit Connected(checked); });

View File

@@ -3048,6 +3048,102 @@
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="mousePanningWidget" native="true">
<layout class="QHBoxLayout" name="mousePanningHorizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<spacer name="mousePanningHorizontalSpacerLeft">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="mousePanningGroup">
<property name="title">
<string>Mouse panning</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<layout class="QVBoxLayout" name="mousePanningVerticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QPushButton" name="mousePanningButton">
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="mousePanningHorizontalSpacerRight">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>

View File

@@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QCloseEvent>
#include "common/settings.h"
#include "ui_configure_mouse_panning.h"
#include "yuzu/configuration/configure_mouse_panning.h"
ConfigureMousePanning::ConfigureMousePanning(QWidget* parent,
InputCommon::InputSubsystem* input_subsystem_,
float right_stick_deadzone, float right_stick_range)
: QDialog(parent), input_subsystem{input_subsystem_},
ui(std::make_unique<Ui::ConfigureMousePanning>()) {
ui->setupUi(this);
SetConfiguration(right_stick_deadzone, right_stick_range);
ConnectEvents();
}
ConfigureMousePanning::~ConfigureMousePanning() = default;
void ConfigureMousePanning::closeEvent(QCloseEvent* event) {
event->accept();
}
void ConfigureMousePanning::SetConfiguration(float right_stick_deadzone, float right_stick_range) {
ui->enable->setChecked(Settings::values.mouse_panning.GetValue());
ui->x_sensitivity->setValue(Settings::values.mouse_panning_x_sensitivity.GetValue());
ui->y_sensitivity->setValue(Settings::values.mouse_panning_y_sensitivity.GetValue());
ui->deadzone_x_counterweight->setValue(
Settings::values.mouse_panning_deadzone_x_counterweight.GetValue());
ui->deadzone_y_counterweight->setValue(
Settings::values.mouse_panning_deadzone_y_counterweight.GetValue());
ui->decay_strength->setValue(Settings::values.mouse_panning_decay_strength.GetValue());
ui->min_decay->setValue(Settings::values.mouse_panning_min_decay.GetValue());
if (right_stick_deadzone > 0.0f || right_stick_range != 1.0f) {
ui->warning_label->setText(QString::fromStdString(
"Mouse panning works better with a deadzone of 0% and a range of 100%.\n"
"Current values are " +
std::to_string(static_cast<int>(right_stick_deadzone * 100.0f)) + "% and " +
std::to_string(static_cast<int>(right_stick_range * 100.0f)) + "% respectively."));
} else {
ui->warning_label->hide();
}
}
void ConfigureMousePanning::SetDefaultConfiguration() {
ui->x_sensitivity->setValue(Settings::values.mouse_panning_x_sensitivity.GetDefault());
ui->y_sensitivity->setValue(Settings::values.mouse_panning_y_sensitivity.GetDefault());
ui->deadzone_x_counterweight->setValue(
Settings::values.mouse_panning_deadzone_x_counterweight.GetDefault());
ui->deadzone_y_counterweight->setValue(
Settings::values.mouse_panning_deadzone_y_counterweight.GetDefault());
ui->decay_strength->setValue(Settings::values.mouse_panning_decay_strength.GetDefault());
ui->min_decay->setValue(Settings::values.mouse_panning_min_decay.GetDefault());
}
void ConfigureMousePanning::ConnectEvents() {
connect(ui->default_button, &QPushButton::clicked, this,
&ConfigureMousePanning::SetDefaultConfiguration);
connect(ui->button_box, &QDialogButtonBox::accepted, this,
&ConfigureMousePanning::ApplyConfiguration);
connect(ui->button_box, &QDialogButtonBox::rejected, this, [this] { reject(); });
}
void ConfigureMousePanning::ApplyConfiguration() {
Settings::values.mouse_panning = ui->enable->isChecked();
Settings::values.mouse_panning_x_sensitivity = static_cast<float>(ui->x_sensitivity->value());
Settings::values.mouse_panning_y_sensitivity = static_cast<float>(ui->y_sensitivity->value());
Settings::values.mouse_panning_deadzone_x_counterweight =
static_cast<float>(ui->deadzone_x_counterweight->value());
Settings::values.mouse_panning_deadzone_y_counterweight =
static_cast<float>(ui->deadzone_y_counterweight->value());
Settings::values.mouse_panning_decay_strength = static_cast<float>(ui->decay_strength->value());
Settings::values.mouse_panning_min_decay = static_cast<float>(ui->min_decay->value());
accept();
}

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <QDialog>
namespace InputCommon {
class InputSubsystem;
}
namespace Ui {
class ConfigureMousePanning;
}
class ConfigureMousePanning : public QDialog {
Q_OBJECT
public:
explicit ConfigureMousePanning(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_,
float right_stick_deadzone, float right_stick_range);
~ConfigureMousePanning() override;
public slots:
void ApplyConfiguration();
private:
void closeEvent(QCloseEvent* event) override;
void SetConfiguration(float right_stick_deadzone, float right_stick_range);
void SetDefaultConfiguration();
void ConnectEvents();
InputCommon::InputSubsystem* input_subsystem;
std::unique_ptr<Ui::ConfigureMousePanning> ui;
};

View File

@@ -0,0 +1,238 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureMousePanning</class>
<widget class="QDialog" name="configure_mouse_panning">
<property name="windowTitle">
<string>Configure mouse panning</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QCheckBox" name="enable">
<property name="text">
<string>Enable</string>
</property>
<property name="toolTip">
<string>Can be toggled via a hotkey</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QGroupBox" name="sensitivity_box">
<property name="title">
<string>Sensitivity</string>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel" name="x_sensitivity_label">
<property name="text">
<string>Horizontal</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="x_sensitivity">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="y_sensitivity_label">
<property name="text">
<string>Vertical</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="y_sensitivity">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="deadzone_counterweight_box">
<property name="title">
<string>Deadzone counterweight</string>
</property>
<property name="toolTip">
<string>Counteracts a game's built-in deadzone</string>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel" name="deadzone_x_counterweight_label">
<property name="text">
<string>Horizontal</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="deadzone_x_counterweight">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="deadzone_y_counterweight_label">
<property name="text">
<string>Vertical</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="deadzone_y_counterweight">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="decay_box">
<property name="title">
<string>Stick decay</string>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel" name="decay_strength_label">
<property name="text">
<string>Strength</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="decay_strength">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>22</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="min_decay_label">
<property name="text">
<string>Minimum</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="min_decay">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>5</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="warning_label">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QPushButton" name="default_button">
<property name="text">
<string>Default</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="button_box">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -4116,7 +4116,13 @@ void GMainWindow::UpdateDockedButton() {
void GMainWindow::UpdateAPIText() {
const auto api = Settings::values.renderer_backend.GetValue();
const auto renderer_status_text = Config::renderer_backend_texts_map.find(api)->second;
renderer_status_button->setText(renderer_status_text.toUpper());
renderer_status_button->setText(
api == Settings::RendererBackend::OpenGL
? tr("%1 %2").arg(
renderer_status_text.toUpper(),
Config::shader_backend_texts_map.find(Settings::values.shader_backend.GetValue())
->second)
: renderer_status_text.toUpper());
}
void GMainWindow::UpdateFilterText() {

View File

@@ -140,9 +140,29 @@ udp_input_servers =
# 0 (default): Off, 1: On
mouse_panning =
# Set mouse sensitivity.
# Default: 1.0
mouse_panning_sensitivity =
# Set mouse panning horizontal sensitivity.
# Default: 50.0
mouse_panning_x_sensitivity =
# Set mouse panning vertical sensitivity.
# Default: 50.0
mouse_panning_y_sensitivity =
# Set mouse panning deadzone horizontal counterweight.
# Default: 0.0
mouse_panning_deadzone_x_counterweight =
# Set mouse panning deadzone vertical counterweight.
# Default: 0.0
mouse_panning_deadzone_y_counterweight =
# Set mouse panning stick decay strength.
# Default: 22.0
mouse_panning_decay_strength =
# Set mouse panning stick minimum decay.
# Default: 5.0
mouse_panning_minimum_decay =
# Emulate an analog control stick from keyboard inputs.
# 0 (default): Disabled, 1: Enabled