summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/build.gradle.kts25
-rw-r--r--src/android/app/src/main/AndroidManifest.xml2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt28
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt26
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt24
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt4
-rw-r--r--src/android/app/src/main/jni/native.cpp40
-rw-r--r--src/android/app/src/main/res/values/strings.xml7
-rw-r--r--src/core/loader/nro.cpp13
-rw-r--r--src/core/loader/nro.h2
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h82
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h11
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp18
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp55
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp54
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp5
-rw-r--r--src/yuzu/configuration/config.cpp2
-rw-r--r--src/yuzu/main.cpp2
25 files changed, 264 insertions, 176 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 13bb227ff..d4698ae1c 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -74,16 +74,7 @@ android {
74 74
75 // Signed by release key, allowing for upload to Play Store. 75 // Signed by release key, allowing for upload to Play Store.
76 release { 76 release {
77 signingConfig = signingConfigs.getByName("debug") 77 resValue("string", "app_name_suffixed", "yuzu")
78 isMinifyEnabled = true
79 isDebuggable = false
80 proguardFiles(
81 getDefaultProguardFile("proguard-android.txt"),
82 "proguard-rules.pro"
83 )
84 }
85
86 register("relWithVersionCode") {
87 signingConfig = signingConfigs.getByName("debug") 78 signingConfig = signingConfigs.getByName("debug")
88 isMinifyEnabled = true 79 isMinifyEnabled = true
89 isDebuggable = false 80 isDebuggable = false
@@ -96,6 +87,7 @@ android {
96 // builds a release build that doesn't need signing 87 // builds a release build that doesn't need signing
97 // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build. 88 // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
98 register("relWithDebInfo") { 89 register("relWithDebInfo") {
90 resValue("string", "app_name_suffixed", "yuzu Debug Release")
99 signingConfig = signingConfigs.getByName("debug") 91 signingConfig = signingConfigs.getByName("debug")
100 isMinifyEnabled = true 92 isMinifyEnabled = true
101 isDebuggable = true 93 isDebuggable = true
@@ -103,16 +95,19 @@ android {
103 getDefaultProguardFile("proguard-android.txt"), 95 getDefaultProguardFile("proguard-android.txt"),
104 "proguard-rules.pro" 96 "proguard-rules.pro"
105 ) 97 )
106 versionNameSuffix = "-debug" 98 versionNameSuffix = "-relWithDebInfo"
99 applicationIdSuffix = ".relWithDebInfo"
107 isJniDebuggable = true 100 isJniDebuggable = true
108 } 101 }
109 102
110 // Signed by debug key disallowing distribution on Play Store. 103 // Signed by debug key disallowing distribution on Play Store.
111 // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build. 104 // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
112 debug { 105 debug {
106 resValue("string", "app_name_suffixed", "yuzu Debug")
113 isDebuggable = true 107 isDebuggable = true
114 isJniDebuggable = true 108 isJniDebuggable = true
115 versionNameSuffix = "-debug" 109 versionNameSuffix = "-debug"
110 applicationIdSuffix = ".debug"
116 } 111 }
117 } 112 }
118 113
@@ -162,19 +157,19 @@ dependencies {
162 implementation("androidx.appcompat:appcompat:1.6.1") 157 implementation("androidx.appcompat:appcompat:1.6.1")
163 implementation("androidx.recyclerview:recyclerview:1.3.0") 158 implementation("androidx.recyclerview:recyclerview:1.3.0")
164 implementation("androidx.constraintlayout:constraintlayout:2.1.4") 159 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
165 implementation("androidx.fragment:fragment-ktx:1.5.7") 160 implementation("androidx.fragment:fragment-ktx:1.6.0")
166 implementation("androidx.documentfile:documentfile:1.0.1") 161 implementation("androidx.documentfile:documentfile:1.0.1")
167 implementation("com.google.android.material:material:1.9.0") 162 implementation("com.google.android.material:material:1.9.0")
168 implementation("androidx.preference:preference:1.2.0") 163 implementation("androidx.preference:preference:1.2.0")
169 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") 164 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
170 implementation("io.coil-kt:coil:2.2.2") 165 implementation("io.coil-kt:coil:2.2.2")
171 implementation("androidx.core:core-splashscreen:1.0.1") 166 implementation("androidx.core:core-splashscreen:1.0.1")
172 implementation("androidx.window:window:1.0.0") 167 implementation("androidx.window:window:1.1.0")
173 implementation("org.ini4j:ini4j:0.5.4") 168 implementation("org.ini4j:ini4j:0.5.4")
174 implementation("androidx.constraintlayout:constraintlayout:2.1.4") 169 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
175 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") 170 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
176 implementation("androidx.navigation:navigation-fragment-ktx:2.5.3") 171 implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
177 implementation("androidx.navigation:navigation-ui-ktx:2.5.3") 172 implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
178 implementation("info.debatty:java-string-similarity:2.0.0") 173 implementation("info.debatty:java-string-similarity:2.0.0")
179 implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") 174 implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
180} 175}
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index eef566042..1e92098ec 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
18 18
19 <application 19 <application
20 android:name="org.yuzu.yuzu_emu.YuzuApplication" 20 android:name="org.yuzu.yuzu_emu.YuzuApplication"
21 android:label="@string/app_name" 21 android:label="@string/app_name_suffixed"
22 android:icon="@drawable/ic_launcher" 22 android:icon="@drawable/ic_launcher"
23 android:allowBackup="true" 23 android:allowBackup="true"
24 android:hasFragileUserData="true" 24 android:hasFragileUserData="true"
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index c11b6bc16..22af9e435 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -223,6 +223,8 @@ object NativeLibrary {
223 223
224 external fun getCompany(filename: String): String 224 external fun getCompany(filename: String): String
225 225
226 external fun isHomebrew(filename: String): Boolean
227
226 external fun setAppDirectory(directory: String) 228 external fun setAppDirectory(directory: String)
227 229
228 external fun initializeGpuDriver( 230 external fun initializeGpuDriver(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index f4db61cb3..20a0394f5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -6,15 +6,12 @@ package org.yuzu.yuzu_emu.activities
6import android.app.Activity 6import android.app.Activity
7import android.content.Context 7import android.content.Context
8import android.content.Intent 8import android.content.Intent
9import android.content.res.Configuration
10import android.graphics.Rect 9import android.graphics.Rect
11import android.hardware.Sensor 10import android.hardware.Sensor
12import android.hardware.SensorEvent 11import android.hardware.SensorEvent
13import android.hardware.SensorEventListener 12import android.hardware.SensorEventListener
14import android.hardware.SensorManager 13import android.hardware.SensorManager
15import android.hardware.display.DisplayManager
16import android.os.Bundle 14import android.os.Bundle
17import android.view.Display
18import android.view.InputDevice 15import android.view.InputDevice
19import android.view.KeyEvent 16import android.view.KeyEvent
20import android.view.MotionEvent 17import android.view.MotionEvent
@@ -23,7 +20,6 @@ import android.view.View
23import android.view.inputmethod.InputMethodManager 20import android.view.inputmethod.InputMethodManager
24import androidx.activity.viewModels 21import androidx.activity.viewModels
25import androidx.appcompat.app.AppCompatActivity 22import androidx.appcompat.app.AppCompatActivity
26import androidx.core.content.getSystemService
27import androidx.core.view.WindowCompat 23import androidx.core.view.WindowCompat
28import androidx.core.view.WindowInsetsCompat 24import androidx.core.view.WindowInsetsCompat
29import androidx.core.view.WindowInsetsControllerCompat 25import androidx.core.view.WindowInsetsControllerCompat
@@ -39,7 +35,6 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
39import org.yuzu.yuzu_emu.fragments.EmulationFragment 35import org.yuzu.yuzu_emu.fragments.EmulationFragment
40import org.yuzu.yuzu_emu.model.Game 36import org.yuzu.yuzu_emu.model.Game
41import org.yuzu.yuzu_emu.utils.ControllerMappingHelper 37import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
42import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
43import org.yuzu.yuzu_emu.utils.ForegroundService 38import org.yuzu.yuzu_emu.utils.ForegroundService
44import org.yuzu.yuzu_emu.utils.InputHandler 39import org.yuzu.yuzu_emu.utils.InputHandler
45import org.yuzu.yuzu_emu.utils.NfcReader 40import org.yuzu.yuzu_emu.utils.NfcReader
@@ -148,11 +143,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
148 super.onResume() 143 super.onResume()
149 nfcReader.startScanning() 144 nfcReader.startScanning()
150 startMotionSensorListener() 145 startMotionSensorListener()
151
152 NativeLibrary.notifyOrientationChange(
153 EmulationMenuSettings.landscapeScreenLayout,
154 getAdjustedRotation()
155 )
156 } 146 }
157 147
158 override fun onPause() { 148 override fun onPause() {
@@ -258,24 +248,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
258 248
259 override fun onAccuracyChanged(sensor: Sensor, i: Int) {} 249 override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
260 250
261 private fun getAdjustedRotation():Int {
262 val rotation = getSystemService<DisplayManager>()!!.getDisplay(Display.DEFAULT_DISPLAY).rotation
263 val config: Configuration = resources.configuration
264
265 if ((config.screenLayout and Configuration.SCREENLAYOUT_LONG_YES) != 0 ||
266 (config.screenLayout and Configuration.SCREENLAYOUT_LONG_NO) == 0 ||
267 (config.screenLayout and Configuration.SCREENLAYOUT_SIZE_SMALL) != 0) {
268 return rotation
269 }
270 when (rotation) {
271 Surface.ROTATION_0 -> return Surface.ROTATION_90
272 Surface.ROTATION_90 -> return Surface.ROTATION_0
273 Surface.ROTATION_180 -> return Surface.ROTATION_270
274 Surface.ROTATION_270 -> return Surface.ROTATION_180
275 }
276 return rotation
277 }
278
279 private fun restoreState(savedInstanceState: Bundle) { 251 private fun restoreState(savedInstanceState: Bundle) {
280 game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!! 252 game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
281 } 253 }
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 ebc0f164a..adbe3696b 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
@@ -127,13 +127,7 @@ class SearchFragment : Fragment() {
127 } 127 }
128 } 128 }
129 129
130 R.id.chip_homebrew -> { 130 R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
131 baseList.filter {
132 Log.error("Guh - ${it.path}")
133 FileUtil.hasExtension(it.path, "nro")
134 || FileUtil.hasExtension(it.path, "nso")
135 }
136 }
137 131
138 R.id.chip_retail -> baseList.filter { 132 R.id.chip_retail -> baseList.filter {
139 FileUtil.hasExtension(it.path, "xci") 133 FileUtil.hasExtension(it.path, "xci")
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 2a17653b2..3d6782c49 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
@@ -16,7 +16,8 @@ class Game(
16 val regions: String, 16 val regions: String,
17 val path: String, 17 val path: String,
18 val gameId: String, 18 val gameId: String,
19 val company: String 19 val company: String,
20 val isHomebrew: Boolean
20) : Parcelable { 21) : Parcelable {
21 val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime" 22 val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime"
22 val keyLastPlayedTime get() = "${gameId}_LastPlayed" 23 val keyLastPlayedTime get() = "${gameId}_LastPlayed"
@@ -31,6 +32,7 @@ class Game(
31 && path == other.path 32 && path == other.path
32 && gameId == other.gameId 33 && gameId == other.gameId
33 && company == other.company 34 && company == other.company
35 && isHomebrew == other.isHomebrew
34 } 36 }
35 37
36 companion object { 38 companion object {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
index 7059856f1..d9b301210 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -13,6 +13,8 @@ import androidx.preference.PreferenceManager
13import kotlinx.coroutines.Dispatchers 13import kotlinx.coroutines.Dispatchers
14import kotlinx.coroutines.launch 14import kotlinx.coroutines.launch
15import kotlinx.coroutines.withContext 15import kotlinx.coroutines.withContext
16import kotlinx.serialization.ExperimentalSerializationApi
17import kotlinx.serialization.MissingFieldException
16import kotlinx.serialization.decodeFromString 18import kotlinx.serialization.decodeFromString
17import kotlinx.serialization.json.Json 19import kotlinx.serialization.json.Json
18import org.yuzu.yuzu_emu.NativeLibrary 20import org.yuzu.yuzu_emu.NativeLibrary
@@ -20,6 +22,7 @@ import org.yuzu.yuzu_emu.YuzuApplication
20import org.yuzu.yuzu_emu.utils.GameHelper 22import org.yuzu.yuzu_emu.utils.GameHelper
21import java.util.Locale 23import java.util.Locale
22 24
25@OptIn(ExperimentalSerializationApi::class)
23class GamesViewModel : ViewModel() { 26class GamesViewModel : ViewModel() {
24 private val _games = MutableLiveData<List<Game>>(emptyList()) 27 private val _games = MutableLiveData<List<Game>>(emptyList())
25 val games: LiveData<List<Game>> get() = _games 28 val games: LiveData<List<Game>> get() = _games
@@ -49,7 +52,13 @@ class GamesViewModel : ViewModel() {
49 if (storedGames!!.isNotEmpty()) { 52 if (storedGames!!.isNotEmpty()) {
50 val deserializedGames = mutableSetOf<Game>() 53 val deserializedGames = mutableSetOf<Game>()
51 storedGames.forEach { 54 storedGames.forEach {
52 val game: Game = Json.decodeFromString(it) 55 val game: Game
56 try {
57 game = Json.decodeFromString(it)
58 } catch (e: MissingFieldException) {
59 return@forEach
60 }
61
53 val gameExists = 62 val gameExists =
54 DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(game.path)) 63 DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(game.path))
55 ?.exists() 64 ?.exists()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
index c9f5797ac..aa424c768 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
@@ -765,18 +765,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
765 // If we have API access, calculate the safe area to draw the overlay 765 // If we have API access, calculate the safe area to draw the overlay
766 var cutoutLeft = 0 766 var cutoutLeft = 0
767 var cutoutBottom = 0 767 var cutoutBottom = 0
768 val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout 768 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
769 if (insets != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 769 val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
770 if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2) 770 if (insets != null) {
771 insets.boundingRectTop.bottom.toFloat() else maxY 771 if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
772 if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2) 772 insets.boundingRectTop.bottom.toFloat() else maxY
773 insets.boundingRectRight.left.toFloat() else maxX 773 if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2)
774 774 insets.boundingRectRight.left.toFloat() else maxX
775 minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left 775
776 minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom 776 minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
777 777 minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
778 cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left 778
779 cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom 779 cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
780 cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
781 }
780 } 782 }
781 783
782 // This makes sure that if we have an inset on one side of the screen, we mirror it on 784 // This makes sure that if we have an inset on one side of the screen, we mirror it on
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 124f62f08..3fca0a7e6 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
@@ -284,10 +284,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
284 if (result == null) 284 if (result == null)
285 return@registerForActivityResult 285 return@registerForActivityResult
286 286
287 if (!FileUtil.hasExtension(result.toString(), "keys")) { 287 if (!FileUtil.hasExtension(result, "keys")) {
288 MessageDialogFragment.newInstance( 288 MessageDialogFragment.newInstance(
289 R.string.reading_keys_failure, 289 R.string.reading_keys_failure,
290 R.string.install_keys_failure_extension_description 290 R.string.install_prod_keys_failure_extension_description
291 ).show(supportFragmentManager, MessageDialogFragment.TAG) 291 ).show(supportFragmentManager, MessageDialogFragment.TAG)
292 return@registerForActivityResult 292 return@registerForActivityResult
293 } 293 }
@@ -379,10 +379,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
379 if (result == null) 379 if (result == null)
380 return@registerForActivityResult 380 return@registerForActivityResult
381 381
382 if (!FileUtil.hasExtension(result.toString(), "bin")) { 382 if (!FileUtil.hasExtension(result, "bin")) {
383 MessageDialogFragment.newInstance( 383 MessageDialogFragment.newInstance(
384 R.string.reading_keys_failure, 384 R.string.reading_keys_failure,
385 R.string.install_keys_failure_extension_description 385 R.string.install_amiibo_keys_failure_extension_description
386 ).show(supportFragmentManager, MessageDialogFragment.TAG) 386 ).show(supportFragmentManager, MessageDialogFragment.TAG)
387 return@registerForActivityResult 387 return@registerForActivityResult
388 } 388 }
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 593dad8d3..492b1ad91 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,9 @@ 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
10import androidx.documentfile.provider.DocumentFile 11import androidx.documentfile.provider.DocumentFile
12import org.yuzu.yuzu_emu.YuzuApplication
11import org.yuzu.yuzu_emu.model.MinimalDocumentFile 13import org.yuzu.yuzu_emu.model.MinimalDocumentFile
12import java.io.BufferedInputStream 14import java.io.BufferedInputStream
13import java.io.File 15import java.io.File
@@ -324,7 +326,25 @@ object FileUtil {
324 } 326 }
325 } 327 }
326 328
327 fun hasExtension(path: String, extension: String): Boolean { 329 fun hasExtension(path: String, extension: String): Boolean =
328 return path.substring(path.lastIndexOf(".") + 1).contains(extension) 330 path.substring(path.lastIndexOf(".") + 1).contains(extension)
331
332 fun hasExtension(uri: Uri, extension: String): Boolean {
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)
329 } 349 }
330} 350}
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 ba6b5783e..42b207618 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 kotlinx.serialization.decodeFromString
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
@@ -83,7 +82,8 @@ object GameHelper {
83 NativeLibrary.getRegions(filePath), 82 NativeLibrary.getRegions(filePath),
84 filePath, 83 filePath,
85 gameId, 84 gameId,
86 NativeLibrary.getCompany(filePath) 85 NativeLibrary.getCompany(filePath),
86 NativeLibrary.isHomebrew(filePath)
87 ) 87 )
88 88
89 val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L) 89 val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L)
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index b87e04b3d..7ebed5e6a 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -13,6 +13,7 @@
13 13
14#include <android/api-level.h> 14#include <android/api-level.h>
15#include <android/native_window_jni.h> 15#include <android/native_window_jni.h>
16#include <core/loader/nro.h>
16 17
17#include "common/detached_tasks.h" 18#include "common/detached_tasks.h"
18#include "common/dynamic_library.h" 19#include "common/dynamic_library.h"
@@ -93,14 +94,6 @@ public:
93 m_native_window = native_window; 94 m_native_window = native_window;
94 } 95 }
95 96
96 u32 ScreenRotation() const {
97 return m_screen_rotation;
98 }
99
100 void SetScreenRotation(u32 screen_rotation) {
101 m_screen_rotation = screen_rotation;
102 }
103
104 void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, 97 void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
105 const std::string& custom_driver_name, 98 const std::string& custom_driver_name,
106 const std::string& file_redirect_dir) { 99 const std::string& file_redirect_dir) {
@@ -281,6 +274,10 @@ public:
281 return GetRomMetadata(path).icon; 274 return GetRomMetadata(path).icon;
282 } 275 }
283 276
277 bool GetIsHomebrew(const std::string& path) {
278 return GetRomMetadata(path).isHomebrew;
279 }
280
284 void ResetRomMetadata() { 281 void ResetRomMetadata() {
285 m_rom_metadata_cache.clear(); 282 m_rom_metadata_cache.clear();
286 } 283 }
@@ -348,6 +345,7 @@ private:
348 struct RomMetadata { 345 struct RomMetadata {
349 std::string title; 346 std::string title;
350 std::vector<u8> icon; 347 std::vector<u8> icon;
348 bool isHomebrew;
351 }; 349 };
352 350
353 RomMetadata GetRomMetadata(const std::string& path) { 351 RomMetadata GetRomMetadata(const std::string& path) {
@@ -360,11 +358,17 @@ private:
360 358
361 RomMetadata CacheRomMetadata(const std::string& path) { 359 RomMetadata CacheRomMetadata(const std::string& path) {
362 const auto file = Core::GetGameFileFromPath(m_vfs, path); 360 const auto file = Core::GetGameFileFromPath(m_vfs, path);
363 const auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); 361 auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
364 362
365 RomMetadata entry; 363 RomMetadata entry;
366 loader->ReadTitle(entry.title); 364 loader->ReadTitle(entry.title);
367 loader->ReadIcon(entry.icon); 365 loader->ReadIcon(entry.icon);
366 if (loader->GetFileType() == Loader::FileType::NRO) {
367 auto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
368 entry.isHomebrew = loader_nro->IsHomebrew();
369 } else {
370 entry.isHomebrew = false;
371 }
368 372
369 m_rom_metadata_cache[path] = entry; 373 m_rom_metadata_cache[path] = entry;
370 374
@@ -388,7 +392,6 @@ private:
388 // Window management 392 // Window management
389 std::unique_ptr<EmuWindow_Android> m_window; 393 std::unique_ptr<EmuWindow_Android> m_window;
390 ANativeWindow* m_native_window{}; 394 ANativeWindow* m_native_window{};
391 u32 m_screen_rotation{};
392 395
393 // Core emulation 396 // Core emulation
394 Core::System m_system; 397 Core::System m_system;
@@ -414,10 +417,6 @@ private:
414 417
415} // Anonymous namespace 418} // Anonymous namespace
416 419
417u32 GetAndroidScreenRotation() {
418 return EmulationSession::GetInstance().ScreenRotation();
419}
420
421static Core::SystemResultStatus RunEmulation(const std::string& filepath) { 420static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
422 Common::Log::Initialize(); 421 Common::Log::Initialize();
423 Common::Log::SetColorConsoleBackendEnabled(true); 422 Common::Log::SetColorConsoleBackendEnabled(true);
@@ -461,13 +460,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env,
461 EmulationSession::GetInstance().SurfaceChanged(); 460 EmulationSession::GetInstance().SurfaceChanged();
462} 461}
463 462
464void Java_org_yuzu_yuzu_1emu_NativeLibrary_notifyOrientationChange(JNIEnv* env,
465 [[maybe_unused]] jclass clazz,
466 jint layout_option,
467 jint rotation) {
468 return EmulationSession::GetInstance().SetScreenRotation(static_cast<u32>(rotation));
469}
470
471void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, 463void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
472 [[maybe_unused]] jclass clazz, 464 [[maybe_unused]] jclass clazz,
473 jstring j_directory) { 465 jstring j_directory) {
@@ -662,6 +654,12 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany([[maybe_unused]] JNIEnv
662 return env->NewStringUTF(""); 654 return env->NewStringUTF("");
663} 655}
664 656
657jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew([[maybe_unused]] JNIEnv* env,
658 [[maybe_unused]] jclass clazz,
659 [[maybe_unused]] jstring j_filename) {
660 return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
661}
662
665void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation 663void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation
666 [[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) { 664 [[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) {
667 // Create the default config.ini. 665 // Create the default config.ini.
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 0ae69afb4..6e9d47557 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -65,11 +65,8 @@
65 <string name="invalid_keys_file">Invalid keys file selected</string> 65 <string name="invalid_keys_file">Invalid keys file selected</string>
66 <string name="install_keys_success">Keys successfully installed</string> 66 <string name="install_keys_success">Keys successfully installed</string>
67 <string name="reading_keys_failure">Error reading encryption keys</string> 67 <string name="reading_keys_failure">Error reading encryption keys</string>
68 <string name="install_keys_failure_extension_description"> 68 <string name="install_prod_keys_failure_extension_description">Verify your keys file has a .keys extension and try again.</string>
69 1. Verify your keys have the .keys extension.\n\n 69 <string name="install_amiibo_keys_failure_extension_description">Verify your keys file has a .bin extension and try again.</string>
70 2. Keys must not be stored in the Downloads folder.\n\n
71 Resolve the issue(s) and try again.
72 </string>
73 <string name="invalid_keys_error">Invalid encryption keys</string> 70 <string name="invalid_keys_error">Invalid encryption keys</string>
74 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string> 71 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
75 <string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string> 72 <string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 73d04d7ee..7be6cf5f3 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -33,7 +33,8 @@ static_assert(sizeof(NroSegmentHeader) == 0x8, "NroSegmentHeader has incorrect s
33struct NroHeader { 33struct NroHeader {
34 INSERT_PADDING_BYTES(0x4); 34 INSERT_PADDING_BYTES(0x4);
35 u32_le module_header_offset; 35 u32_le module_header_offset;
36 INSERT_PADDING_BYTES(0x8); 36 u32 magic_ext1;
37 u32 magic_ext2;
37 u32_le magic; 38 u32_le magic;
38 INSERT_PADDING_BYTES(0x4); 39 INSERT_PADDING_BYTES(0x4);
39 u32_le file_size; 40 u32_le file_size;
@@ -124,6 +125,16 @@ FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& nro_file) {
124 return FileType::Error; 125 return FileType::Error;
125} 126}
126 127
128bool AppLoader_NRO::IsHomebrew() {
129 // Read NSO header
130 NroHeader nro_header{};
131 if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
132 return false;
133 }
134 return nro_header.magic_ext1 == Common::MakeMagic('H', 'O', 'M', 'E') &&
135 nro_header.magic_ext2 == Common::MakeMagic('B', 'R', 'E', 'W');
136}
137
127static constexpr u32 PageAlignSize(u32 size) { 138static constexpr u32 PageAlignSize(u32 size) {
128 return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK); 139 return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
129} 140}
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index ccb77b581..8de6eebc6 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -38,6 +38,8 @@ public:
38 */ 38 */
39 static FileType IdentifyType(const FileSys::VirtualFile& nro_file); 39 static FileType IdentifyType(const FileSys::VirtualFile& nro_file);
40 40
41 bool IsHomebrew();
42
41 FileType GetFileType() const override { 43 FileType GetFileType() const override {
42 return IdentifyType(file); 44 return IdentifyType(file);
43 } 45 }
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 2f281b370..251a4a880 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -715,20 +715,38 @@ void BufferCache<P>::BindHostIndexBuffer() {
715 715
716template <class P> 716template <class P>
717void BufferCache<P>::BindHostVertexBuffers() { 717void BufferCache<P>::BindHostVertexBuffers() {
718 HostBindings host_bindings;
719 bool any_valid{false};
718 auto& flags = maxwell3d->dirty.flags; 720 auto& flags = maxwell3d->dirty.flags;
719 for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { 721 for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
720 const Binding& binding = channel_state->vertex_buffers[index];
721 Buffer& buffer = slot_buffers[binding.buffer_id];
722 TouchBuffer(buffer, binding.buffer_id);
723 SynchronizeBuffer(buffer, binding.cpu_addr, binding.size);
724 if (!flags[Dirty::VertexBuffer0 + index]) { 722 if (!flags[Dirty::VertexBuffer0 + index]) {
725 continue; 723 continue;
726 } 724 }
727 flags[Dirty::VertexBuffer0 + index] = false; 725 host_bindings.min_index = std::min(host_bindings.min_index, index);
726 host_bindings.max_index = std::max(host_bindings.max_index, index);
727 any_valid = true;
728 }
728 729
729 const u32 stride = maxwell3d->regs.vertex_streams[index].stride; 730 if (any_valid) {
730 const u32 offset = buffer.Offset(binding.cpu_addr); 731 host_bindings.max_index++;
731 runtime.BindVertexBuffer(index, buffer, offset, binding.size, stride); 732 for (u32 index = host_bindings.min_index; index < host_bindings.max_index; index++) {
733 flags[Dirty::VertexBuffer0 + index] = false;
734
735 const Binding& binding = channel_state->vertex_buffers[index];
736 Buffer& buffer = slot_buffers[binding.buffer_id];
737
738 TouchBuffer(buffer, binding.buffer_id);
739 SynchronizeBuffer(buffer, binding.cpu_addr, binding.size);
740
741 const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
742 const u32 offset = buffer.Offset(binding.cpu_addr);
743
744 host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer));
745 host_bindings.offsets.push_back(offset);
746 host_bindings.sizes.push_back(binding.size);
747 host_bindings.strides.push_back(stride);
748 }
749 runtime.BindVertexBuffers(host_bindings);
732 } 750 }
733} 751}
734 752
@@ -882,15 +900,25 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
882 if (maxwell3d->regs.transform_feedback_enabled == 0) { 900 if (maxwell3d->regs.transform_feedback_enabled == 0) {
883 return; 901 return;
884 } 902 }
903 HostBindings host_bindings;
885 for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) { 904 for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
886 const Binding& binding = channel_state->transform_feedback_buffers[index]; 905 const Binding& binding = channel_state->transform_feedback_buffers[index];
906 if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 &&
907 maxwell3d->regs.transform_feedback.controls[index].stride == 0) {
908 break;
909 }
887 Buffer& buffer = slot_buffers[binding.buffer_id]; 910 Buffer& buffer = slot_buffers[binding.buffer_id];
888 TouchBuffer(buffer, binding.buffer_id); 911 TouchBuffer(buffer, binding.buffer_id);
889 const u32 size = binding.size; 912 const u32 size = binding.size;
890 SynchronizeBuffer(buffer, binding.cpu_addr, size); 913 SynchronizeBuffer(buffer, binding.cpu_addr, size);
891 914
892 const u32 offset = buffer.Offset(binding.cpu_addr); 915 const u32 offset = buffer.Offset(binding.cpu_addr);
893 runtime.BindTransformFeedbackBuffer(index, buffer, offset, size); 916 host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer));
917 host_bindings.offsets.push_back(offset);
918 host_bindings.sizes.push_back(binding.size);
919 }
920 if (host_bindings.buffers.size() > 0) {
921 runtime.BindTransformFeedbackBuffers(host_bindings);
894 } 922 }
895} 923}
896 924
@@ -1616,6 +1644,8 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 si
1616 1644
1617template <class P> 1645template <class P>
1618void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) { 1646void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
1647 bool dirty_index{false};
1648 boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> dirty_vertex_buffers;
1619 const auto scalar_replace = [buffer_id](Binding& binding) { 1649 const auto scalar_replace = [buffer_id](Binding& binding) {
1620 if (binding.buffer_id == buffer_id) { 1650 if (binding.buffer_id == buffer_id) {
1621 binding.buffer_id = BufferId{}; 1651 binding.buffer_id = BufferId{};
@@ -1624,8 +1654,19 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
1624 const auto replace = [scalar_replace](std::span<Binding> bindings) { 1654 const auto replace = [scalar_replace](std::span<Binding> bindings) {
1625 std::ranges::for_each(bindings, scalar_replace); 1655 std::ranges::for_each(bindings, scalar_replace);
1626 }; 1656 };
1627 scalar_replace(channel_state->index_buffer); 1657
1628 replace(channel_state->vertex_buffers); 1658 if (channel_state->index_buffer.buffer_id == buffer_id) {
1659 channel_state->index_buffer.buffer_id = BufferId{};
1660 dirty_index = true;
1661 }
1662
1663 for (u32 index = 0; index < channel_state->vertex_buffers.size(); index++) {
1664 auto& binding = channel_state->vertex_buffers[index];
1665 if (binding.buffer_id == buffer_id) {
1666 binding.buffer_id = BufferId{};
1667 dirty_vertex_buffers.push_back(index);
1668 }
1669 }
1629 std::ranges::for_each(channel_state->uniform_buffers, replace); 1670 std::ranges::for_each(channel_state->uniform_buffers, replace);
1630 std::ranges::for_each(channel_state->storage_buffers, replace); 1671 std::ranges::for_each(channel_state->storage_buffers, replace);
1631 replace(channel_state->transform_feedback_buffers); 1672 replace(channel_state->transform_feedback_buffers);
@@ -1642,20 +1683,21 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
1642 delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id])); 1683 delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id]));
1643 slot_buffers.erase(buffer_id); 1684 slot_buffers.erase(buffer_id);
1644 1685
1645 NotifyBufferDeletion();
1646}
1647
1648template <class P>
1649void BufferCache<P>::NotifyBufferDeletion() {
1650 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { 1686 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
1651 channel_state->dirty_uniform_buffers.fill(~u32{0}); 1687 channel_state->dirty_uniform_buffers.fill(~u32{0});
1652 channel_state->uniform_buffer_binding_sizes.fill({}); 1688 channel_state->uniform_buffer_binding_sizes.fill({});
1653 } 1689 }
1690
1654 auto& flags = maxwell3d->dirty.flags; 1691 auto& flags = maxwell3d->dirty.flags;
1655 flags[Dirty::IndexBuffer] = true; 1692 if (dirty_index) {
1656 flags[Dirty::VertexBuffers] = true; 1693 flags[Dirty::IndexBuffer] = true;
1657 for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { 1694 }
1658 flags[Dirty::VertexBuffer0 + index] = true; 1695
1696 if (dirty_vertex_buffers.size() > 0) {
1697 flags[Dirty::VertexBuffers] = true;
1698 for (auto index : dirty_vertex_buffers) {
1699 flags[Dirty::VertexBuffer0 + index] = true;
1700 }
1659 } 1701 }
1660 channel_state->has_deleted_buffers = true; 1702 channel_state->has_deleted_buffers = true;
1661} 1703}
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index 60a1f285e..cf359e241 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -105,6 +105,15 @@ static constexpr Binding NULL_BINDING{
105 .buffer_id = NULL_BUFFER_ID, 105 .buffer_id = NULL_BUFFER_ID,
106}; 106};
107 107
108struct HostBindings {
109 boost::container::small_vector<void*, NUM_VERTEX_BUFFERS> buffers;
110 boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets;
111 boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes;
112 boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides;
113 u32 min_index{NUM_VERTEX_BUFFERS};
114 u32 max_index{0};
115};
116
108class BufferCacheChannelInfo : public ChannelInfo { 117class BufferCacheChannelInfo : public ChannelInfo {
109public: 118public:
110 BufferCacheChannelInfo() = delete; 119 BufferCacheChannelInfo() = delete;
@@ -519,8 +528,6 @@ private:
519 528
520 void DeleteBuffer(BufferId buffer_id, bool do_not_mark = false); 529 void DeleteBuffer(BufferId buffer_id, bool do_not_mark = false);
521 530
522 void NotifyBufferDeletion();
523
524 [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index, 531 [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
525 bool is_written) const; 532 bool is_written) const;
526 533
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index c419714d4..0cc546a3a 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -232,6 +232,15 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset,
232 } 232 }
233} 233}
234 234
235void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) {
236 for (u32 index = 0; index < bindings.buffers.size(); index++) {
237 BindVertexBuffer(
238 bindings.min_index + index, *reinterpret_cast<Buffer*>(bindings.buffers[index]),
239 static_cast<u32>(bindings.offsets[index]), static_cast<u32>(bindings.sizes[index]),
240 static_cast<u32>(bindings.strides[index]));
241 }
242}
243
235void BufferCacheRuntime::BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, 244void BufferCacheRuntime::BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer,
236 u32 offset, u32 size) { 245 u32 offset, u32 size) {
237 if (use_assembly_shaders) { 246 if (use_assembly_shaders) {
@@ -320,6 +329,15 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, Buffer& buffer,
320 static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); 329 static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
321} 330}
322 331
332void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) {
333 for (u32 index = 0; index < bindings.buffers.size(); index++) {
334 glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index,
335 reinterpret_cast<Buffer*>(bindings.buffers[index])->Handle(),
336 static_cast<GLintptr>(bindings.offsets[index]),
337 static_cast<GLsizeiptr>(bindings.sizes[index]));
338 }
339}
340
323void BufferCacheRuntime::BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, 341void BufferCacheRuntime::BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
324 PixelFormat format) { 342 PixelFormat format) {
325 *texture_handles++ = buffer.View(offset, size, format); 343 *texture_handles++ = buffer.View(offset, size, format);
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index a24991585..e4e000284 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -7,7 +7,7 @@
7#include <span> 7#include <span>
8 8
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "video_core/buffer_cache/buffer_cache.h" 10#include "video_core/buffer_cache/buffer_cache_base.h"
11#include "video_core/buffer_cache/memory_tracker_base.h" 11#include "video_core/buffer_cache/memory_tracker_base.h"
12#include "video_core/rasterizer_interface.h" 12#include "video_core/rasterizer_interface.h"
13#include "video_core/renderer_opengl/gl_device.h" 13#include "video_core/renderer_opengl/gl_device.h"
@@ -87,6 +87,7 @@ public:
87 void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size); 87 void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size);
88 88
89 void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride); 89 void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride);
90 void BindVertexBuffers(VideoCommon::HostBindings& bindings);
90 91
91 void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size); 92 void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size);
92 93
@@ -99,6 +100,7 @@ public:
99 bool is_written); 100 bool is_written);
100 101
101 void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size); 102 void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size);
103 void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings);
102 104
103 void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, 105 void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
104 VideoCore::Surface::PixelFormat format); 106 VideoCore::Surface::PixelFormat format);
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 7cdde992b..acb143fc7 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -37,10 +37,6 @@
37#include "video_core/vulkan_common/vulkan_memory_allocator.h" 37#include "video_core/vulkan_common/vulkan_memory_allocator.h"
38#include "video_core/vulkan_common/vulkan_wrapper.h" 38#include "video_core/vulkan_common/vulkan_wrapper.h"
39 39
40#ifdef ANDROID
41extern u32 GetAndroidScreenRotation();
42#endif
43
44namespace Vulkan { 40namespace Vulkan {
45 41
46namespace { 42namespace {
@@ -78,47 +74,6 @@ struct ScreenRectVertex {
78 } 74 }
79}; 75};
80 76
81#ifdef ANDROID
82
83std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
84 constexpr u32 ROTATION_0 = 0;
85 constexpr u32 ROTATION_90 = 1;
86 constexpr u32 ROTATION_180 = 2;
87 constexpr u32 ROTATION_270 = 3;
88
89 // clang-format off
90 switch (GetAndroidScreenRotation()) {
91 case ROTATION_0:
92 // Desktop
93 return { 2.f / width, 0.f, 0.f, 0.f,
94 0.f, 2.f / height, 0.f, 0.f,
95 0.f, 0.f, 1.f, 0.f,
96 -1.f, -1.f, 0.f, 1.f};
97 case ROTATION_180:
98 // Reverse desktop
99 return {-2.f / width, 0.f, 0.f, 0.f,
100 0.f, -2.f / height, 0.f, 0.f,
101 0.f, 0.f, 1.f, 0.f,
102 1.f, 1.f, 0.f, 1.f};
103 case ROTATION_270:
104 // Reverse landscape
105 return { 0.f, -2.f / width, 0.f, 0.f,
106 2.f / height, 0.f, 0.f, 0.f,
107 0.f, 0.f, 1.f, 0.f,
108 -1.f, 1.f, 0.f, 1.f};
109 case ROTATION_90:
110 default:
111 // Landscape
112 return { 0.f, 2.f / width, 0.f, 0.f,
113 -2.f / height, 0.f, 0.f, 0.f,
114 0.f, 0.f, 1.f, 0.f,
115 1.f, -1.f, 0.f, 1.f};
116 }
117 // clang-format on
118}
119
120#else
121
122std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) { 77std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
123 // clang-format off 78 // clang-format off
124 return { 2.f / width, 0.f, 0.f, 0.f, 79 return { 2.f / width, 0.f, 0.f, 0.f,
@@ -128,8 +83,6 @@ std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
128 // clang-format on 83 // clang-format on
129} 84}
130 85
131#endif
132
133u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) { 86u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) {
134 using namespace VideoCore::Surface; 87 using namespace VideoCore::Surface;
135 return BytesPerBlock(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)); 88 return BytesPerBlock(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format));
@@ -1159,7 +1112,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1159 .pNext = nullptr, 1112 .pNext = nullptr,
1160 .flags = 0, 1113 .flags = 0,
1161 .imageType = VK_IMAGE_TYPE_2D, 1114 .imageType = VK_IMAGE_TYPE_2D,
1162 .format = GetFormat(framebuffer), 1115 .format = used_on_framebuffer ? VK_FORMAT_R16G16B16A16_SFLOAT : GetFormat(framebuffer),
1163 .extent = 1116 .extent =
1164 { 1117 {
1165 .width = (up_scale * framebuffer.width) >> down_shift, 1118 .width = (up_scale * framebuffer.width) >> down_shift,
@@ -1180,14 +1133,14 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1180 const auto create_commit = [&](vk::Image& image) { 1133 const auto create_commit = [&](vk::Image& image) {
1181 return memory_allocator.Commit(image, MemoryUsage::DeviceLocal); 1134 return memory_allocator.Commit(image, MemoryUsage::DeviceLocal);
1182 }; 1135 };
1183 const auto create_image_view = [&](vk::Image& image) { 1136 const auto create_image_view = [&](vk::Image& image, bool used_on_framebuffer = false) {
1184 return device.GetLogical().CreateImageView(VkImageViewCreateInfo{ 1137 return device.GetLogical().CreateImageView(VkImageViewCreateInfo{
1185 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 1138 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
1186 .pNext = nullptr, 1139 .pNext = nullptr,
1187 .flags = 0, 1140 .flags = 0,
1188 .image = *image, 1141 .image = *image,
1189 .viewType = VK_IMAGE_VIEW_TYPE_2D, 1142 .viewType = VK_IMAGE_VIEW_TYPE_2D,
1190 .format = GetFormat(framebuffer), 1143 .format = used_on_framebuffer ? VK_FORMAT_R16G16B16A16_SFLOAT : GetFormat(framebuffer),
1191 .components = 1144 .components =
1192 { 1145 {
1193 .r = VK_COMPONENT_SWIZZLE_IDENTITY, 1146 .r = VK_COMPONENT_SWIZZLE_IDENTITY,
@@ -1217,7 +1170,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1217 const u32 down_shift = Settings::values.resolution_info.down_shift; 1170 const u32 down_shift = Settings::values.resolution_info.down_shift;
1218 aa_image = create_image(true, up_scale, down_shift); 1171 aa_image = create_image(true, up_scale, down_shift);
1219 aa_commit = create_commit(aa_image); 1172 aa_commit = create_commit(aa_image);
1220 aa_image_view = create_image_view(aa_image); 1173 aa_image_view = create_image_view(aa_image, true);
1221 VkExtent2D size{ 1174 VkExtent2D size{
1222 .width = (up_scale * framebuffer.width) >> down_shift, 1175 .width = (up_scale * framebuffer.width) >> down_shift,
1223 .height = (up_scale * framebuffer.height) >> down_shift, 1176 .height = (up_scale * framebuffer.height) >> down_shift,
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index daa128399..d72d99899 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -7,7 +7,6 @@
7#include <span> 7#include <span>
8#include <vector> 8#include <vector>
9 9
10#include "video_core/buffer_cache/buffer_cache.h"
11#include "video_core/renderer_vulkan/maxwell_to_vk.h" 10#include "video_core/renderer_vulkan/maxwell_to_vk.h"
12#include "video_core/renderer_vulkan/vk_buffer_cache.h" 11#include "video_core/renderer_vulkan/vk_buffer_cache.h"
13#include "video_core/renderer_vulkan/vk_scheduler.h" 12#include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -502,6 +501,40 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
502 } 501 }
503} 502}
504 503
504void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) {
505 boost::container::small_vector<VkBuffer, 32> buffer_handles;
506 for (u32 index = 0; index < bindings.buffers.size(); index++) {
507 auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]);
508 auto handle = buffer.Handle();
509 if (handle == VK_NULL_HANDLE) {
510 bindings.offsets[index] = 0;
511 bindings.sizes[index] = VK_WHOLE_SIZE;
512 if (!device.HasNullDescriptor()) {
513 ReserveNullBuffer();
514 handle = *null_buffer;
515 }
516 }
517 buffer_handles.push_back(handle);
518 }
519 if (device.IsExtExtendedDynamicStateSupported()) {
520 scheduler.Record([bindings = bindings,
521 buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) {
522 cmdbuf.BindVertexBuffers2EXT(
523 bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(),
524 reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()),
525 reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data()),
526 reinterpret_cast<const VkDeviceSize*>(bindings.strides.data()));
527 });
528 } else {
529 scheduler.Record([bindings = bindings,
530 buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) {
531 cmdbuf.BindVertexBuffers(
532 bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(),
533 reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()));
534 });
535 }
536}
537
505void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, 538void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset,
506 u32 size) { 539 u32 size) {
507 if (!device.IsExtTransformFeedbackSupported()) { 540 if (!device.IsExtTransformFeedbackSupported()) {
@@ -523,6 +556,25 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer,
523 }); 556 });
524} 557}
525 558
559void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) {
560 if (!device.IsExtTransformFeedbackSupported()) {
561 // Already logged in the rasterizer
562 return;
563 }
564 boost::container::small_vector<VkBuffer, 4> buffer_handles;
565 for (u32 index = 0; index < bindings.buffers.size(); index++) {
566 auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]);
567 buffer_handles.push_back(buffer.Handle());
568 }
569 scheduler.Record(
570 [bindings = bindings, buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) {
571 cmdbuf.BindTransformFeedbackBuffersEXT(
572 0, static_cast<u32>(buffer_handles.size()), buffer_handles.data(),
573 reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()),
574 reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data()));
575 });
576}
577
526void BufferCacheRuntime::ReserveNullBuffer() { 578void BufferCacheRuntime::ReserveNullBuffer() {
527 if (null_buffer) { 579 if (null_buffer) {
528 return; 580 return;
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 92b4f7859..92d3e9f32 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -18,6 +18,7 @@ namespace Vulkan {
18class Device; 18class Device;
19class DescriptorPool; 19class DescriptorPool;
20class Scheduler; 20class Scheduler;
21struct HostVertexBinding;
21 22
22class BufferCacheRuntime; 23class BufferCacheRuntime;
23 24
@@ -96,8 +97,10 @@ public:
96 void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count); 97 void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count);
97 98
98 void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride); 99 void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride);
100 void BindVertexBuffers(VideoCommon::HostBindings& bindings);
99 101
100 void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size); 102 void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
103 void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings);
101 104
102 std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage, 105 std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage,
103 [[maybe_unused]] u32 binding_index, u32 size) { 106 [[maybe_unused]] u32 binding_index, u32 size) {
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index afcf34fba..d3cddac69 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -231,7 +231,12 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo
231 .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, 231 .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
232 .queueFamilyIndexCount = 0, 232 .queueFamilyIndexCount = 0,
233 .pQueueFamilyIndices = nullptr, 233 .pQueueFamilyIndices = nullptr,
234#ifdef ANDROID
235 // On Android, do not allow surface rotation to deviate from the frontend.
236 .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
237#else
234 .preTransform = capabilities.currentTransform, 238 .preTransform = capabilities.currentTransform,
239#endif
235 .compositeAlpha = alpha_flags, 240 .compositeAlpha = alpha_flags,
236 .presentMode = present_mode, 241 .presentMode = present_mode,
237 .clipped = VK_FALSE, 242 .clipped = VK_FALSE,
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index fe12fa8f3..bac9dff90 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -760,6 +760,7 @@ void Config::ReadRendererValues() {
760 ReadGlobalSetting(Settings::values.use_fast_gpu_time); 760 ReadGlobalSetting(Settings::values.use_fast_gpu_time);
761 ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache); 761 ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
762 ReadGlobalSetting(Settings::values.enable_compute_pipelines); 762 ReadGlobalSetting(Settings::values.enable_compute_pipelines);
763 ReadGlobalSetting(Settings::values.use_video_framerate);
763 ReadGlobalSetting(Settings::values.bg_red); 764 ReadGlobalSetting(Settings::values.bg_red);
764 ReadGlobalSetting(Settings::values.bg_green); 765 ReadGlobalSetting(Settings::values.bg_green);
765 ReadGlobalSetting(Settings::values.bg_blue); 766 ReadGlobalSetting(Settings::values.bg_blue);
@@ -1415,6 +1416,7 @@ void Config::SaveRendererValues() {
1415 WriteGlobalSetting(Settings::values.use_fast_gpu_time); 1416 WriteGlobalSetting(Settings::values.use_fast_gpu_time);
1416 WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache); 1417 WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
1417 WriteGlobalSetting(Settings::values.enable_compute_pipelines); 1418 WriteGlobalSetting(Settings::values.enable_compute_pipelines);
1419 WriteGlobalSetting(Settings::values.use_video_framerate);
1418 WriteGlobalSetting(Settings::values.bg_red); 1420 WriteGlobalSetting(Settings::values.bg_red);
1419 WriteGlobalSetting(Settings::values.bg_green); 1421 WriteGlobalSetting(Settings::values.bg_green);
1420 WriteGlobalSetting(Settings::values.bg_blue); 1422 WriteGlobalSetting(Settings::values.bg_blue);
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 9d06b21b6..013715b44 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -3067,7 +3067,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) {
3067 return false; 3067 return false;
3068 } 3068 }
3069 3069
3070 std::array<u8, 0x1000> buffer{}; 3070 std::vector<u8> buffer(1_MiB);
3071 3071
3072 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { 3072 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
3073 if (install_progress->wasCanceled()) { 3073 if (install_progress->wasCanceled()) {