summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/build.gradle.kts2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt49
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt42
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt48
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt213
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt71
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt138
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt15
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt266
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt67
-rw-r--r--src/android/app/src/main/jni/native.cpp36
-rw-r--r--src/android/app/src/main/res/drawable/ic_export.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_import.xml9
-rw-r--r--src/android/app/src/main/res/layout/activity_settings.xml2
-rw-r--r--src/android/app/src/main/res/layout/card_installable.xml71
-rw-r--r--src/android/app/src/main/res/layout/dialog_progress_bar.xml28
-rw-r--r--src/android/app/src/main/res/layout/fragment_emulation.xml5
-rw-r--r--src/android/app/src/main/res/layout/fragment_installables.xml31
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml7
-rw-r--r--src/android/app/src/main/res/values-de/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-es/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-fr/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-it/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-ja/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-ko/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-nb/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-pl/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-pt-rBR/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-pt-rPT/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-ru/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-uk/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-w600dp/integers.xml6
-rw-r--r--src/android/app/src/main/res/values-zh-rCN/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-zh-rTW/strings.xml1
-rw-r--r--src/android/app/src/main/res/values/integers.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml24
-rw-r--r--src/common/settings.cpp10
-rw-r--r--src/common/settings.h4
-rw-r--r--src/common/settings_setting.h2
-rw-r--r--src/core/core.cpp7
-rw-r--r--src/core/file_sys/partition_filesystem.cpp1
-rw-r--r--src/core/hle/service/am/am.cpp216
-rw-r--r--src/core/hle/service/am/am.h33
-rw-r--r--src/core/hle/service/am/applet_ae.cpp48
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.cpp52
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.h7
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp65
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp1
-rw-r--r--src/core/hle/service/mii/mii.cpp114
-rw-r--r--src/core/hle/service/mii/mii.h18
-rw-r--r--src/core/hle/service/mii/mii_database_manager.cpp2
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp6
-rw-r--r--src/core/hle/service/mii/mii_types.h2
-rw-r--r--src/core/hle/service/mii/types/core_data.cpp5
-rw-r--r--src/core/hle/service/mii/types/raw_data.cpp12
-rw-r--r--src/core/hle/service/nfc/common/device.cpp2
-rw-r--r--src/core/hle/service/ns/iplatform_service_manager.cpp17
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp15
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp7
-rw-r--r--src/video_core/CMakeLists.txt6
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h18
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h12
-rw-r--r--src/video_core/control/channel_state_cache.h2
-rw-r--r--src/video_core/engines/draw_manager.h1
-rw-r--r--src/video_core/engines/maxwell_3d.cpp74
-rw-r--r--src/video_core/engines/maxwell_3d.h3
-rw-r--r--src/video_core/engines/maxwell_dma.cpp17
-rw-r--r--src/video_core/engines/maxwell_dma.h55
-rw-r--r--src/video_core/engines/puller.cpp13
-rw-r--r--src/video_core/fence_manager.h21
-rw-r--r--src/video_core/gpu.cpp4
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt6
-rw-r--r--src/video_core/host_shaders/convert_msaa_to_non_msaa.comp7
-rw-r--r--src/video_core/host_shaders/convert_non_msaa_to_msaa.comp7
-rw-r--r--src/video_core/host_shaders/queries_prefix_scan_sum.comp173
-rw-r--r--src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp138
-rw-r--r--src/video_core/host_shaders/resolve_conditional_render.comp20
-rw-r--r--src/video_core/macro/macro_hle.cpp49
-rw-r--r--src/video_core/query_cache.h13
-rw-r--r--src/video_core/query_cache/bank_base.h104
-rw-r--r--src/video_core/query_cache/query_base.h70
-rw-r--r--src/video_core/query_cache/query_cache.h580
-rw-r--r--src/video_core/query_cache/query_cache_base.h181
-rw-r--r--src/video_core/query_cache/query_stream.h149
-rw-r--r--src/video_core/query_cache/types.h74
-rw-r--r--src/video_core/rasterizer_interface.h13
-rw-r--r--src/video_core/renderer_null/null_rasterizer.cpp18
-rw-r--r--src/video_core/renderer_null/null_rasterizer.h7
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.h2
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp40
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h7
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp3
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.cpp320
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.h50
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp1595
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.h106
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp135
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h14
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp9
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h12
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp127
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h7
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp23
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h85
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp5
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h40
-rw-r--r--src/yuzu/main.cpp53
-rw-r--r--src/yuzu/main.h4
-rw-r--r--src/yuzu/main.ui9
121 files changed, 5408 insertions, 982 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 431f899b3..84a3308b7 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -214,7 +214,7 @@ dependencies {
214 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") 214 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
215 implementation("io.coil-kt:coil:2.2.2") 215 implementation("io.coil-kt:coil:2.2.2")
216 implementation("androidx.core:core-splashscreen:1.0.1") 216 implementation("androidx.core:core-splashscreen:1.0.1")
217 implementation("androidx.window:window:1.1.0") 217 implementation("androidx.window:window:1.2.0-beta03")
218 implementation("org.ini4j:ini4j:0.5.4") 218 implementation("org.ini4j:ini4j:0.5.4")
219 implementation("androidx.constraintlayout:constraintlayout:2.1.4") 219 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
220 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") 220 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
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 21f67f32a..6e39e542b 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
@@ -247,7 +247,12 @@ object NativeLibrary {
247 247
248 external fun setAppDirectory(directory: String) 248 external fun setAppDirectory(directory: String)
249 249
250 external fun installFileToNand(filename: String): Int 250 /**
251 * Installs a nsp or xci file to nand
252 * @param filename String representation of file uri
253 * @param extension Lowercase string representation of file extension without "."
254 */
255 external fun installFileToNand(filename: String, extension: String): Int
251 256
252 external fun initializeGpuDriver( 257 external fun initializeGpuDriver(
253 hookLibDir: String?, 258 hookLibDir: String?,
@@ -512,6 +517,11 @@ object NativeLibrary {
512 external fun submitInlineKeyboardInput(key_code: Int) 517 external fun submitInlineKeyboardInput(key_code: Int)
513 518
514 /** 519 /**
520 * Creates a generic user directory if it doesn't exist already
521 */
522 external fun initializeEmptyUserDirectory()
523
524 /**
515 * Button type for use in onTouchEvent 525 * Button type for use in onTouchEvent
516 */ 526 */
517 object ButtonType { 527 object ButtonType {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
index 1675627a1..58ce343f4 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
@@ -49,6 +49,7 @@ class HomeSettingAdapter(
49 holder.option.onClick.invoke() 49 holder.option.onClick.invoke()
50 } else { 50 } else {
51 MessageDialogFragment.newInstance( 51 MessageDialogFragment.newInstance(
52 activity,
52 titleId = holder.option.disabledTitleId, 53 titleId = holder.option.disabledTitleId,
53 descriptionId = holder.option.disabledMessageId 54 descriptionId = holder.option.disabledMessageId
54 ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) 55 ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
new file mode 100644
index 000000000..e960fbaab
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
@@ -0,0 +1,49 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.adapters
5
6import android.view.LayoutInflater
7import android.view.View
8import android.view.ViewGroup
9import androidx.recyclerview.widget.RecyclerView
10import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
11import org.yuzu.yuzu_emu.model.Installable
12
13class InstallableAdapter(private val installables: List<Installable>) :
14 RecyclerView.Adapter<InstallableAdapter.InstallableViewHolder>() {
15 override fun onCreateViewHolder(
16 parent: ViewGroup,
17 viewType: Int
18 ): InstallableAdapter.InstallableViewHolder {
19 val binding =
20 CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false)
21 return InstallableViewHolder(binding)
22 }
23
24 override fun getItemCount(): Int = installables.size
25
26 override fun onBindViewHolder(holder: InstallableAdapter.InstallableViewHolder, position: Int) =
27 holder.bind(installables[position])
28
29 inner class InstallableViewHolder(val binding: CardInstallableBinding) :
30 RecyclerView.ViewHolder(binding.root) {
31 lateinit var installable: Installable
32
33 fun bind(installable: Installable) {
34 this.installable = installable
35
36 binding.title.setText(installable.titleId)
37 binding.description.setText(installable.descriptionId)
38
39 if (installable.install != null) {
40 binding.buttonInstall.visibility = View.VISIBLE
41 binding.buttonInstall.setOnClickListener { installable.install.invoke() }
42 }
43 if (installable.export != null) {
44 binding.buttonExport.visibility = View.VISIBLE
45 binding.buttonExport.setOnClickListener { installable.export.invoke() }
46 }
47 }
48 }
49}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index 4d2f2f604..c73edd50e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
@@ -21,6 +21,7 @@ import androidx.navigation.navArgs
21import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
22import kotlinx.coroutines.flow.collectLatest 22import kotlinx.coroutines.flow.collectLatest
23import kotlinx.coroutines.launch 23import kotlinx.coroutines.launch
24import org.yuzu.yuzu_emu.NativeLibrary
24import java.io.IOException 25import java.io.IOException
25import org.yuzu.yuzu_emu.R 26import org.yuzu.yuzu_emu.R
26import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding 27import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
@@ -168,7 +169,7 @@ class SettingsActivity : AppCompatActivity() {
168 if (!settingsFile.delete()) { 169 if (!settingsFile.delete()) {
169 throw IOException("Failed to delete $settingsFile") 170 throw IOException("Failed to delete $settingsFile")
170 } 171 }
171 Settings.settingsList.forEach { it.reset() } 172 NativeLibrary.reloadSettings()
172 173
173 Toast.makeText( 174 Toast.makeText(
174 applicationContext, 175 applicationContext,
@@ -181,12 +182,14 @@ class SettingsActivity : AppCompatActivity() {
181 private fun setInsets() { 182 private fun setInsets() {
182 ViewCompat.setOnApplyWindowInsetsListener( 183 ViewCompat.setOnApplyWindowInsetsListener(
183 binding.navigationBarShade 184 binding.navigationBarShade
184 ) { view: View, windowInsets: WindowInsetsCompat -> 185 ) { _: View, windowInsets: WindowInsetsCompat ->
185 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 186 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
186 187
187 val mlpShade = view.layoutParams as MarginLayoutParams 188 // The only situation where we care to have a nav bar shade is when it's at the bottom
188 mlpShade.height = barInsets.bottom 189 // of the screen where scrolling list elements can go behind it.
189 view.layoutParams = mlpShade 190 val mlpNavShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
191 mlpNavShade.height = barInsets.bottom
192 binding.navigationBarShade.layoutParams = mlpNavShade
190 193
191 windowInsets 194 windowInsets
192 } 195 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 750638bc9..e6ad2aa77 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -17,6 +17,7 @@ import android.os.Handler
17import android.os.Looper 17import android.os.Looper
18import android.view.* 18import android.view.*
19import android.widget.TextView 19import android.widget.TextView
20import android.widget.Toast
20import androidx.activity.OnBackPressedCallback 21import androidx.activity.OnBackPressedCallback
21import androidx.appcompat.widget.PopupMenu 22import androidx.appcompat.widget.PopupMenu
22import androidx.core.content.res.ResourcesCompat 23import androidx.core.content.res.ResourcesCompat
@@ -53,6 +54,7 @@ import org.yuzu.yuzu_emu.model.Game
53import org.yuzu.yuzu_emu.model.EmulationViewModel 54import org.yuzu.yuzu_emu.model.EmulationViewModel
54import org.yuzu.yuzu_emu.overlay.InputOverlay 55import org.yuzu.yuzu_emu.overlay.InputOverlay
55import org.yuzu.yuzu_emu.utils.* 56import org.yuzu.yuzu_emu.utils.*
57import java.lang.NullPointerException
56 58
57class EmulationFragment : Fragment(), SurfaceHolder.Callback { 59class EmulationFragment : Fragment(), SurfaceHolder.Callback {
58 private lateinit var preferences: SharedPreferences 60 private lateinit var preferences: SharedPreferences
@@ -104,10 +106,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
104 null 106 null
105 } 107 }
106 } 108 }
107 game = if (args.game != null) { 109
108 args.game!! 110 try {
109 } else { 111 game = if (args.game != null) {
110 intentGame ?: error("[EmulationFragment] No bootable game present!") 112 args.game!!
113 } else {
114 intentGame!!
115 }
116 } catch (e: NullPointerException) {
117 Toast.makeText(
118 requireContext(),
119 R.string.no_game_present,
120 Toast.LENGTH_SHORT
121 ).show()
122 requireActivity().finish()
123 return
111 } 124 }
112 125
113 // So this fragment doesn't restart on configuration changes; i.e. rotation. 126 // So this fragment doesn't restart on configuration changes; i.e. rotation.
@@ -131,6 +144,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
131 // This is using the correct scope, lint is just acting up 144 // This is using the correct scope, lint is just acting up
132 @SuppressLint("UnsafeRepeatOnLifecycleDetector") 145 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
133 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 146 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
147 super.onViewCreated(view, savedInstanceState)
148 if (requireActivity().isFinishing) {
149 return
150 }
151
134 binding.surfaceEmulation.holder.addCallback(this) 152 binding.surfaceEmulation.holder.addCallback(this)
135 binding.showFpsText.setTextColor(Color.YELLOW) 153 binding.showFpsText.setTextColor(Color.YELLOW)
136 binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } 154 binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
@@ -286,25 +304,23 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
286 304
287 override fun onConfigurationChanged(newConfig: Configuration) { 305 override fun onConfigurationChanged(newConfig: Configuration) {
288 super.onConfigurationChanged(newConfig) 306 super.onConfigurationChanged(newConfig)
307 if (_binding == null) {
308 return
309 }
310
289 updateScreenLayout() 311 updateScreenLayout()
290 if (emulationActivity?.isInPictureInPictureMode == true) { 312 if (emulationActivity?.isInPictureInPictureMode == true) {
291 if (binding.drawerLayout.isOpen) { 313 if (binding.drawerLayout.isOpen) {
292 binding.drawerLayout.close() 314 binding.drawerLayout.close()
293 } 315 }
294 if (EmulationMenuSettings.showOverlay) { 316 if (EmulationMenuSettings.showOverlay) {
295 binding.surfaceInputOverlay.post { 317 binding.surfaceInputOverlay.visibility = View.INVISIBLE
296 binding.surfaceInputOverlay.visibility = View.INVISIBLE
297 }
298 } 318 }
299 } else { 319 } else {
300 if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) { 320 if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
301 binding.surfaceInputOverlay.post { 321 binding.surfaceInputOverlay.visibility = View.VISIBLE
302 binding.surfaceInputOverlay.visibility = View.VISIBLE
303 }
304 } else { 322 } else {
305 binding.surfaceInputOverlay.post { 323 binding.surfaceInputOverlay.visibility = View.INVISIBLE
306 binding.surfaceInputOverlay.visibility = View.INVISIBLE
307 }
308 } 324 }
309 if (!isInFoldableLayout) { 325 if (!isInFoldableLayout) {
310 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 326 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index c119e69c9..8923c0ea2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -118,18 +118,13 @@ class HomeSettingsFragment : Fragment() {
118 ) 118 )
119 add( 119 add(
120 HomeSetting( 120 HomeSetting(
121 R.string.install_amiibo_keys, 121 R.string.manage_yuzu_data,
122 R.string.install_amiibo_keys_description, 122 R.string.manage_yuzu_data_description,
123 R.drawable.ic_nfc, 123 R.drawable.ic_install,
124 { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) } 124 {
125 ) 125 binding.root.findNavController()
126 ) 126 .navigate(R.id.action_homeSettingsFragment_to_installableFragment)
127 add( 127 }
128 HomeSetting(
129 R.string.install_game_content,
130 R.string.install_game_content_description,
131 R.drawable.ic_system_update_alt,
132 { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
133 ) 128 )
134 ) 129 )
135 add( 130 add(
@@ -150,35 +145,6 @@ class HomeSettingsFragment : Fragment() {
150 ) 145 )
151 add( 146 add(
152 HomeSetting( 147 HomeSetting(
153 R.string.manage_save_data,
154 R.string.import_export_saves_description,
155 R.drawable.ic_save,
156 {
157 ImportExportSavesFragment().show(
158 parentFragmentManager,
159 ImportExportSavesFragment.TAG
160 )
161 }
162 )
163 )
164 add(
165 HomeSetting(
166 R.string.install_prod_keys,
167 R.string.install_prod_keys_description,
168 R.drawable.ic_unlock,
169 { mainActivity.getProdKey.launch(arrayOf("*/*")) }
170 )
171 )
172 add(
173 HomeSetting(
174 R.string.install_firmware,
175 R.string.install_firmware_description,
176 R.drawable.ic_firmware,
177 { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
178 )
179 )
180 add(
181 HomeSetting(
182 R.string.share_log, 148 R.string.share_log,
183 R.string.share_log_description, 149 R.string.share_log_description,
184 R.drawable.ic_log, 150 R.drawable.ic_log,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
deleted file mode 100644
index f38aeea53..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
+++ /dev/null
@@ -1,213 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.content.Intent
8import android.net.Uri
9import android.os.Bundle
10import android.provider.DocumentsContract
11import android.widget.Toast
12import androidx.activity.result.ActivityResultLauncher
13import androidx.activity.result.contract.ActivityResultContracts
14import androidx.appcompat.app.AppCompatActivity
15import androidx.documentfile.provider.DocumentFile
16import androidx.fragment.app.DialogFragment
17import com.google.android.material.dialog.MaterialAlertDialogBuilder
18import java.io.BufferedOutputStream
19import java.io.File
20import java.io.FileOutputStream
21import java.io.FilenameFilter
22import java.time.LocalDateTime
23import java.time.format.DateTimeFormatter
24import java.util.zip.ZipEntry
25import java.util.zip.ZipOutputStream
26import kotlinx.coroutines.CoroutineScope
27import kotlinx.coroutines.Dispatchers
28import kotlinx.coroutines.launch
29import kotlinx.coroutines.withContext
30import org.yuzu.yuzu_emu.R
31import org.yuzu.yuzu_emu.YuzuApplication
32import org.yuzu.yuzu_emu.features.DocumentProvider
33import org.yuzu.yuzu_emu.getPublicFilesDir
34import org.yuzu.yuzu_emu.utils.FileUtil
35
36class ImportExportSavesFragment : DialogFragment() {
37 private val context = YuzuApplication.appContext
38 private val savesFolder =
39 "${context.getPublicFilesDir().canonicalPath}/nand/user/save/0000000000000000"
40
41 // Get first subfolder in saves folder (should be the user folder)
42 private val savesFolderRoot = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: ""
43 private var lastZipCreated: File? = null
44
45 private lateinit var startForResultExportSave: ActivityResultLauncher<Intent>
46 private lateinit var documentPicker: ActivityResultLauncher<Array<String>>
47
48 override fun onCreate(savedInstanceState: Bundle?) {
49 super.onCreate(savedInstanceState)
50 val activity = requireActivity() as AppCompatActivity
51
52 val activityResultRegistry = requireActivity().activityResultRegistry
53 startForResultExportSave = activityResultRegistry.register(
54 "startForResultExportSaveKey",
55 ActivityResultContracts.StartActivityForResult()
56 ) {
57 File(context.getPublicFilesDir().canonicalPath, "temp").deleteRecursively()
58 }
59 documentPicker = activityResultRegistry.register(
60 "documentPickerKey",
61 ActivityResultContracts.OpenDocument()
62 ) {
63 it?.let { uri -> importSave(uri, activity) }
64 }
65 }
66
67 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
68 return if (savesFolderRoot == "") {
69 MaterialAlertDialogBuilder(requireContext())
70 .setTitle(R.string.manage_save_data)
71 .setMessage(R.string.import_export_saves_no_profile)
72 .setPositiveButton(android.R.string.ok, null)
73 .show()
74 } else {
75 MaterialAlertDialogBuilder(requireContext())
76 .setTitle(R.string.manage_save_data)
77 .setMessage(R.string.manage_save_data_description)
78 .setNegativeButton(R.string.export_saves) { _, _ ->
79 exportSave()
80 }
81 .setPositiveButton(R.string.import_saves) { _, _ ->
82 documentPicker.launch(arrayOf("application/zip"))
83 }
84 .setNeutralButton(android.R.string.cancel, null)
85 .show()
86 }
87 }
88
89 /**
90 * Zips the save files located in the given folder path and creates a new zip file with the current date and time.
91 * @return true if the zip file is successfully created, false otherwise.
92 */
93 private fun zipSave(): Boolean {
94 try {
95 val tempFolder = File(requireContext().getPublicFilesDir().canonicalPath, "temp")
96 tempFolder.mkdirs()
97 val saveFolder = File(savesFolderRoot)
98 val outputZipFile = File(
99 tempFolder,
100 "yuzu saves - ${
101 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
102 }.zip"
103 )
104 outputZipFile.createNewFile()
105 ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
106 saveFolder.walkTopDown().forEach { file ->
107 val zipFileName =
108 file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/")
109 if (zipFileName == "") {
110 return@forEach
111 }
112 val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
113 zos.putNextEntry(entry)
114 if (file.isFile) {
115 file.inputStream().use { fis -> fis.copyTo(zos) }
116 }
117 }
118 }
119 lastZipCreated = outputZipFile
120 } catch (e: Exception) {
121 return false
122 }
123 return true
124 }
125
126 /**
127 * Exports the save file located in the given folder path by creating a zip file and sharing it via intent.
128 */
129 private fun exportSave() {
130 CoroutineScope(Dispatchers.IO).launch {
131 val wasZipCreated = zipSave()
132 val lastZipFile = lastZipCreated
133 if (!wasZipCreated || lastZipFile == null) {
134 withContext(Dispatchers.Main) {
135 Toast.makeText(context, "Failed to export save", Toast.LENGTH_LONG).show()
136 }
137 return@launch
138 }
139
140 withContext(Dispatchers.Main) {
141 val file = DocumentFile.fromSingleUri(
142 context,
143 DocumentsContract.buildDocumentUri(
144 DocumentProvider.AUTHORITY,
145 "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
146 )
147 )!!
148 val intent = Intent(Intent.ACTION_SEND)
149 .setDataAndType(file.uri, "application/zip")
150 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
151 .putExtra(Intent.EXTRA_STREAM, file.uri)
152 startForResultExportSave.launch(Intent.createChooser(intent, "Share save file"))
153 }
154 }
155 }
156
157 /**
158 * Imports the save files contained in the zip file, and replaces any existing ones with the new save file.
159 * @param zipUri The Uri of the zip file containing the save file(s) to import.
160 */
161 private fun importSave(zipUri: Uri, activity: AppCompatActivity) {
162 val inputZip = context.contentResolver.openInputStream(zipUri)
163 // A zip needs to have at least one subfolder named after a TitleId in order to be considered valid.
164 var validZip = false
165 val savesFolder = File(savesFolderRoot)
166 val cacheSaveDir = File("${context.cacheDir.path}/saves/")
167 cacheSaveDir.mkdir()
168
169 if (inputZip == null) {
170 Toast.makeText(context, context.getString(R.string.fatal_error), Toast.LENGTH_LONG)
171 .show()
172 return
173 }
174
175 val filterTitleId =
176 FilenameFilter { _, dirName -> dirName.matches(Regex("^0100[\\dA-Fa-f]{12}$")) }
177
178 try {
179 CoroutineScope(Dispatchers.IO).launch {
180 FileUtil.unzip(inputZip, cacheSaveDir)
181 cacheSaveDir.list(filterTitleId)?.forEach { savePath ->
182 File(savesFolder, savePath).deleteRecursively()
183 File(cacheSaveDir, savePath).copyRecursively(File(savesFolder, savePath), true)
184 validZip = true
185 }
186
187 withContext(Dispatchers.Main) {
188 if (!validZip) {
189 MessageDialogFragment.newInstance(
190 titleId = R.string.save_file_invalid_zip_structure,
191 descriptionId = R.string.save_file_invalid_zip_structure_description
192 ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
193 return@withContext
194 }
195 Toast.makeText(
196 context,
197 context.getString(R.string.save_file_imported_success),
198 Toast.LENGTH_LONG
199 ).show()
200 }
201
202 cacheSaveDir.deleteRecursively()
203 }
204 } catch (e: Exception) {
205 Toast.makeText(context, context.getString(R.string.fatal_error), Toast.LENGTH_LONG)
206 .show()
207 }
208 }
209
210 companion object {
211 const val TAG = "ImportExportSavesFragment"
212 }
213}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
index 18bc34b9f..f128deda8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
@@ -9,6 +9,7 @@ import android.view.LayoutInflater
9import android.view.View 9import android.view.View
10import android.view.ViewGroup 10import android.view.ViewGroup
11import android.widget.Toast 11import android.widget.Toast
12import androidx.appcompat.app.AlertDialog
12import androidx.appcompat.app.AppCompatActivity 13import androidx.appcompat.app.AppCompatActivity
13import androidx.fragment.app.DialogFragment 14import androidx.fragment.app.DialogFragment
14import androidx.fragment.app.activityViewModels 15import androidx.fragment.app.activityViewModels
@@ -18,6 +19,7 @@ import androidx.lifecycle.lifecycleScope
18import androidx.lifecycle.repeatOnLifecycle 19import androidx.lifecycle.repeatOnLifecycle
19import com.google.android.material.dialog.MaterialAlertDialogBuilder 20import com.google.android.material.dialog.MaterialAlertDialogBuilder
20import kotlinx.coroutines.launch 21import kotlinx.coroutines.launch
22import org.yuzu.yuzu_emu.R
21import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 23import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
22import org.yuzu.yuzu_emu.model.TaskViewModel 24import org.yuzu.yuzu_emu.model.TaskViewModel
23 25
@@ -28,19 +30,25 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
28 30
29 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 31 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
30 val titleId = requireArguments().getInt(TITLE) 32 val titleId = requireArguments().getInt(TITLE)
33 val cancellable = requireArguments().getBoolean(CANCELLABLE)
31 34
32 binding = DialogProgressBarBinding.inflate(layoutInflater) 35 binding = DialogProgressBarBinding.inflate(layoutInflater)
33 binding.progressBar.isIndeterminate = true 36 binding.progressBar.isIndeterminate = true
34 val dialog = MaterialAlertDialogBuilder(requireContext()) 37 val dialog = MaterialAlertDialogBuilder(requireContext())
35 .setTitle(titleId) 38 .setTitle(titleId)
36 .setView(binding.root) 39 .setView(binding.root)
37 .create() 40
38 dialog.setCanceledOnTouchOutside(false) 41 if (cancellable) {
42 dialog.setNegativeButton(android.R.string.cancel, null)
43 }
44
45 val alertDialog = dialog.create()
46 alertDialog.setCanceledOnTouchOutside(false)
39 47
40 if (!taskViewModel.isRunning.value) { 48 if (!taskViewModel.isRunning.value) {
41 taskViewModel.runTask() 49 taskViewModel.runTask()
42 } 50 }
43 return dialog 51 return alertDialog
44 } 52 }
45 53
46 override fun onCreateView( 54 override fun onCreateView(
@@ -53,24 +61,50 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
53 61
54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 62 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
55 super.onViewCreated(view, savedInstanceState) 63 super.onViewCreated(view, savedInstanceState)
56 viewLifecycleOwner.lifecycleScope.launch { 64 viewLifecycleOwner.lifecycleScope.apply {
57 repeatOnLifecycle(Lifecycle.State.CREATED) { 65 launch {
58 taskViewModel.isComplete.collect { 66 repeatOnLifecycle(Lifecycle.State.CREATED) {
59 if (it) { 67 taskViewModel.isComplete.collect {
60 dismiss() 68 if (it) {
61 when (val result = taskViewModel.result.value) { 69 dismiss()
62 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG) 70 when (val result = taskViewModel.result.value) {
63 .show() 71 is String -> Toast.makeText(
64 72 requireContext(),
65 is MessageDialogFragment -> result.show( 73 result,
66 requireActivity().supportFragmentManager, 74 Toast.LENGTH_LONG
67 MessageDialogFragment.TAG 75 ).show()
68 ) 76
77 is MessageDialogFragment -> result.show(
78 requireActivity().supportFragmentManager,
79 MessageDialogFragment.TAG
80 )
81 }
82 taskViewModel.clear()
69 } 83 }
70 taskViewModel.clear()
71 } 84 }
72 } 85 }
73 } 86 }
87 launch {
88 repeatOnLifecycle(Lifecycle.State.CREATED) {
89 taskViewModel.cancelled.collect {
90 if (it) {
91 dialog?.setTitle(R.string.cancelling)
92 }
93 }
94 }
95 }
96 }
97 }
98
99 // By default, the ProgressDialog will immediately dismiss itself upon a button being pressed.
100 // Setting the OnClickListener again after the dialog is shown overrides this behavior.
101 override fun onResume() {
102 super.onResume()
103 val alertDialog = dialog as AlertDialog
104 val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE)
105 negativeButton.setOnClickListener {
106 alertDialog.setTitle(getString(R.string.cancelling))
107 taskViewModel.setCancelled(true)
74 } 108 }
75 } 109 }
76 110
@@ -78,16 +112,19 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
78 const val TAG = "IndeterminateProgressDialogFragment" 112 const val TAG = "IndeterminateProgressDialogFragment"
79 113
80 private const val TITLE = "Title" 114 private const val TITLE = "Title"
115 private const val CANCELLABLE = "Cancellable"
81 116
82 fun newInstance( 117 fun newInstance(
83 activity: AppCompatActivity, 118 activity: AppCompatActivity,
84 titleId: Int, 119 titleId: Int,
120 cancellable: Boolean = false,
85 task: () -> Any 121 task: () -> Any
86 ): IndeterminateProgressDialogFragment { 122 ): IndeterminateProgressDialogFragment {
87 val dialog = IndeterminateProgressDialogFragment() 123 val dialog = IndeterminateProgressDialogFragment()
88 val args = Bundle() 124 val args = Bundle()
89 ViewModelProvider(activity)[TaskViewModel::class.java].task = task 125 ViewModelProvider(activity)[TaskViewModel::class.java].task = task
90 args.putInt(TITLE, titleId) 126 args.putInt(TITLE, titleId)
127 args.putBoolean(CANCELLABLE, cancellable)
91 dialog.arguments = args 128 dialog.arguments = args
92 return dialog 129 return dialog
93 } 130 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
new file mode 100644
index 000000000..ec116ab62
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
@@ -0,0 +1,138 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.os.Bundle
7import android.view.LayoutInflater
8import android.view.View
9import android.view.ViewGroup
10import androidx.core.view.ViewCompat
11import androidx.core.view.WindowInsetsCompat
12import androidx.core.view.updatePadding
13import androidx.fragment.app.Fragment
14import androidx.fragment.app.activityViewModels
15import androidx.navigation.findNavController
16import androidx.recyclerview.widget.GridLayoutManager
17import com.google.android.material.transition.MaterialSharedAxis
18import org.yuzu.yuzu_emu.R
19import org.yuzu.yuzu_emu.adapters.InstallableAdapter
20import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding
21import org.yuzu.yuzu_emu.model.HomeViewModel
22import org.yuzu.yuzu_emu.model.Installable
23import org.yuzu.yuzu_emu.ui.main.MainActivity
24
25class InstallableFragment : Fragment() {
26 private var _binding: FragmentInstallablesBinding? = null
27 private val binding get() = _binding!!
28
29 private val homeViewModel: HomeViewModel by activityViewModels()
30
31 override fun onCreate(savedInstanceState: Bundle?) {
32 super.onCreate(savedInstanceState)
33 enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
34 returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
35 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
36 }
37
38 override fun onCreateView(
39 inflater: LayoutInflater,
40 container: ViewGroup?,
41 savedInstanceState: Bundle?
42 ): View {
43 _binding = FragmentInstallablesBinding.inflate(layoutInflater)
44 return binding.root
45 }
46
47 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48 super.onViewCreated(view, savedInstanceState)
49
50 val mainActivity = requireActivity() as MainActivity
51
52 homeViewModel.setNavigationVisibility(visible = false, animated = true)
53 homeViewModel.setStatusBarShadeVisibility(visible = false)
54
55 binding.toolbarInstallables.setNavigationOnClickListener {
56 binding.root.findNavController().popBackStack()
57 }
58
59 val installables = listOf(
60 Installable(
61 R.string.user_data,
62 R.string.user_data_description,
63 install = { mainActivity.importUserData.launch(arrayOf("application/zip")) },
64 export = { mainActivity.exportUserData.launch("export.zip") }
65 ),
66 Installable(
67 R.string.install_game_content,
68 R.string.install_game_content_description,
69 install = { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
70 ),
71 Installable(
72 R.string.install_firmware,
73 R.string.install_firmware_description,
74 install = { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
75 ),
76 if (mainActivity.savesFolderRoot != "") {
77 Installable(
78 R.string.manage_save_data,
79 R.string.import_export_saves_description,
80 install = { mainActivity.importSaves.launch(arrayOf("application/zip")) },
81 export = { mainActivity.exportSave() }
82 )
83 } else {
84 Installable(
85 R.string.manage_save_data,
86 R.string.import_export_saves_description,
87 install = { mainActivity.importSaves.launch(arrayOf("application/zip")) }
88 )
89 },
90 Installable(
91 R.string.install_prod_keys,
92 R.string.install_prod_keys_description,
93 install = { mainActivity.getProdKey.launch(arrayOf("*/*")) }
94 ),
95 Installable(
96 R.string.install_amiibo_keys,
97 R.string.install_amiibo_keys_description,
98 install = { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
99 )
100 )
101
102 binding.listInstallables.apply {
103 layoutManager = GridLayoutManager(
104 requireContext(),
105 resources.getInteger(R.integer.grid_columns)
106 )
107 adapter = InstallableAdapter(installables)
108 }
109
110 setInsets()
111 }
112
113 private fun setInsets() =
114 ViewCompat.setOnApplyWindowInsetsListener(
115 binding.root
116 ) { _: View, windowInsets: WindowInsetsCompat ->
117 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
118 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
119
120 val leftInsets = barInsets.left + cutoutInsets.left
121 val rightInsets = barInsets.right + cutoutInsets.right
122
123 val mlpAppBar = binding.toolbarInstallables.layoutParams as ViewGroup.MarginLayoutParams
124 mlpAppBar.leftMargin = leftInsets
125 mlpAppBar.rightMargin = rightInsets
126 binding.toolbarInstallables.layoutParams = mlpAppBar
127
128 val mlpScrollAbout =
129 binding.listInstallables.layoutParams as ViewGroup.MarginLayoutParams
130 mlpScrollAbout.leftMargin = leftInsets
131 mlpScrollAbout.rightMargin = rightInsets
132 binding.listInstallables.layoutParams = mlpScrollAbout
133
134 binding.listInstallables.updatePadding(bottom = barInsets.bottom)
135
136 windowInsets
137 }
138}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
index 7d1c2c8dd..541b22f47 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
@@ -4,14 +4,21 @@
4package org.yuzu.yuzu_emu.fragments 4package org.yuzu.yuzu_emu.fragments
5 5
6import android.app.Dialog 6import android.app.Dialog
7import android.content.DialogInterface
7import android.content.Intent 8import android.content.Intent
8import android.net.Uri 9import android.net.Uri
9import android.os.Bundle 10import android.os.Bundle
10import androidx.fragment.app.DialogFragment 11import androidx.fragment.app.DialogFragment
12import androidx.fragment.app.FragmentActivity
13import androidx.fragment.app.activityViewModels
14import androidx.lifecycle.ViewModelProvider
11import com.google.android.material.dialog.MaterialAlertDialogBuilder 15import com.google.android.material.dialog.MaterialAlertDialogBuilder
12import org.yuzu.yuzu_emu.R 16import org.yuzu.yuzu_emu.R
17import org.yuzu.yuzu_emu.model.MessageDialogViewModel
13 18
14class MessageDialogFragment : DialogFragment() { 19class MessageDialogFragment : DialogFragment() {
20 private val messageDialogViewModel: MessageDialogViewModel by activityViewModels()
21
15 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 22 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
16 val titleId = requireArguments().getInt(TITLE_ID) 23 val titleId = requireArguments().getInt(TITLE_ID)
17 val titleString = requireArguments().getString(TITLE_STRING)!! 24 val titleString = requireArguments().getString(TITLE_STRING)!!
@@ -37,6 +44,12 @@ class MessageDialogFragment : DialogFragment() {
37 return dialog.show() 44 return dialog.show()
38 } 45 }
39 46
47 override fun onDismiss(dialog: DialogInterface) {
48 super.onDismiss(dialog)
49 messageDialogViewModel.dismissAction.invoke()
50 messageDialogViewModel.clear()
51 }
52
40 private fun openLink(link: String) { 53 private fun openLink(link: String) {
41 val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link)) 54 val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
42 startActivity(intent) 55 startActivity(intent)
@@ -52,11 +65,13 @@ class MessageDialogFragment : DialogFragment() {
52 private const val HELP_LINK = "Link" 65 private const val HELP_LINK = "Link"
53 66
54 fun newInstance( 67 fun newInstance(
68 activity: FragmentActivity,
55 titleId: Int = 0, 69 titleId: Int = 0,
56 titleString: String = "", 70 titleString: String = "",
57 descriptionId: Int = 0, 71 descriptionId: Int = 0,
58 descriptionString: String = "", 72 descriptionString: String = "",
59 helpLinkId: Int = 0 73 helpLinkId: Int = 0,
74 dismissAction: () -> Unit = {}
60 ): MessageDialogFragment { 75 ): MessageDialogFragment {
61 val dialog = MessageDialogFragment() 76 val dialog = MessageDialogFragment()
62 val bundle = Bundle() 77 val bundle = Bundle()
@@ -67,6 +82,8 @@ class MessageDialogFragment : DialogFragment() {
67 putString(DESCRIPTION_STRING, descriptionString) 82 putString(DESCRIPTION_STRING, descriptionString)
68 putInt(HELP_LINK, helpLinkId) 83 putInt(HELP_LINK, helpLinkId)
69 } 84 }
85 ViewModelProvider(activity)[MessageDialogViewModel::class.java].dismissAction =
86 dismissAction
70 dialog.arguments = bundle 87 dialog.arguments = bundle
71 return dialog 88 return dialog
72 } 89 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index fbb2f6e18..c66bb635a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -295,8 +295,10 @@ class SetupFragment : Fragment() {
295 295
296 override fun onSaveInstanceState(outState: Bundle) { 296 override fun onSaveInstanceState(outState: Bundle) {
297 super.onSaveInstanceState(outState) 297 super.onSaveInstanceState(outState)
298 outState.putBoolean(KEY_NEXT_VISIBILITY, binding.buttonNext.isVisible) 298 if (_binding != null) {
299 outState.putBoolean(KEY_BACK_VISIBILITY, binding.buttonBack.isVisible) 299 outState.putBoolean(KEY_NEXT_VISIBILITY, binding.buttonNext.isVisible)
300 outState.putBoolean(KEY_BACK_VISIBILITY, binding.buttonBack.isVisible)
301 }
300 outState.putBooleanArray(KEY_HAS_BEEN_WARNED, hasBeenWarned) 302 outState.putBooleanArray(KEY_HAS_BEEN_WARNED, hasBeenWarned)
301 } 303 }
302 304
@@ -353,11 +355,15 @@ class SetupFragment : Fragment() {
353 } 355 }
354 356
355 fun pageForward() { 357 fun pageForward() {
356 binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 358 if (_binding != null) {
359 binding.viewPager2.currentItem += 1
360 }
357 } 361 }
358 362
359 fun pageBackward() { 363 fun pageBackward() {
360 binding.viewPager2.currentItem = binding.viewPager2.currentItem - 1 364 if (_binding != null) {
365 binding.viewPager2.currentItem -= 1
366 }
361 } 367 }
362 368
363 fun setPageWarned(page: Int) { 369 fun setPageWarned(page: Int) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt
new file mode 100644
index 000000000..36a7c97b8
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt
@@ -0,0 +1,13 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import androidx.annotation.StringRes
7
8data class Installable(
9 @StringRes val titleId: Int,
10 @StringRes val descriptionId: Int,
11 val install: (() -> Unit)? = null,
12 val export: (() -> Unit)? = null
13)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt
new file mode 100644
index 000000000..36ffd08d2
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt
@@ -0,0 +1,14 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import androidx.lifecycle.ViewModel
7
8class MessageDialogViewModel : ViewModel() {
9 var dismissAction: () -> Unit = {}
10
11 fun clear() {
12 dismissAction = {}
13 }
14}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
index 531c2aaf0..16a794dee 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
@@ -20,12 +20,20 @@ class TaskViewModel : ViewModel() {
20 val isRunning: StateFlow<Boolean> get() = _isRunning 20 val isRunning: StateFlow<Boolean> get() = _isRunning
21 private val _isRunning = MutableStateFlow(false) 21 private val _isRunning = MutableStateFlow(false)
22 22
23 val cancelled: StateFlow<Boolean> get() = _cancelled
24 private val _cancelled = MutableStateFlow(false)
25
23 lateinit var task: () -> Any 26 lateinit var task: () -> Any
24 27
25 fun clear() { 28 fun clear() {
26 _result.value = Any() 29 _result.value = Any()
27 _isComplete.value = false 30 _isComplete.value = false
28 _isRunning.value = false 31 _isRunning.value = false
32 _cancelled.value = false
33 }
34
35 fun setCancelled(value: Boolean) {
36 _cancelled.value = value
29 } 37 }
30 38
31 fun runTask() { 39 fun runTask() {
@@ -42,3 +50,9 @@ class TaskViewModel : ViewModel() {
42 } 50 }
43 } 51 }
44} 52}
53
54enum class TaskState {
55 Completed,
56 Failed,
57 Cancelled
58}
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 c055c2e35..a13faf3c7 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
@@ -352,7 +352,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
352 } 352 }
353 353
354 private fun addOverlayControls(layout: String) { 354 private fun addOverlayControls(layout: String) {
355 val windowSize = getSafeScreenSize(context) 355 val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
356 if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) { 356 if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) {
357 overlayButtons.add( 357 overlayButtons.add(
358 initializeOverlayButton( 358 initializeOverlayButton(
@@ -593,7 +593,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
593 } 593 }
594 594
595 private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) { 595 private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) {
596 val windowSize = getSafeScreenSize(context) 596 val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
597 val min = windowSize.first 597 val min = windowSize.first
598 val max = windowSize.second 598 val max = windowSize.second
599 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() 599 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
@@ -968,14 +968,17 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
968 * @return A pair of points, the first being the top left corner of the safe area, 968 * @return A pair of points, the first being the top left corner of the safe area,
969 * the second being the bottom right corner of the safe area 969 * the second being the bottom right corner of the safe area
970 */ 970 */
971 private fun getSafeScreenSize(context: Context): Pair<Point, Point> { 971 private fun getSafeScreenSize(
972 context: Context,
973 screenSize: Pair<Int, Int>
974 ): Pair<Point, Point> {
972 // Get screen size 975 // Get screen size
973 val windowMetrics = WindowMetricsCalculator.getOrCreate() 976 val windowMetrics = WindowMetricsCalculator.getOrCreate()
974 .computeCurrentWindowMetrics(context as Activity) 977 .computeCurrentWindowMetrics(context as Activity)
975 var maxY = windowMetrics.bounds.height().toFloat() 978 var maxX = screenSize.first.toFloat()
976 var maxX = windowMetrics.bounds.width().toFloat() 979 var maxY = screenSize.second.toFloat()
977 var minY = 0
978 var minX = 0 980 var minX = 0
981 var minY = 0
979 982
980 // If we have API access, calculate the safe area to draw the overlay 983 // If we have API access, calculate the safe area to draw the overlay
981 var cutoutLeft = 0 984 var cutoutLeft = 0
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 b6b6c6c17..0fa5df5e5 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
@@ -6,6 +6,7 @@ package org.yuzu.yuzu_emu.ui.main
6import android.content.Intent 6import android.content.Intent
7import android.net.Uri 7import android.net.Uri
8import android.os.Bundle 8import android.os.Bundle
9import android.provider.DocumentsContract
9import android.view.View 10import android.view.View
10import android.view.ViewGroup.MarginLayoutParams 11import android.view.ViewGroup.MarginLayoutParams
11import android.view.WindowManager 12import android.view.WindowManager
@@ -19,6 +20,7 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
19import androidx.core.view.ViewCompat 20import androidx.core.view.ViewCompat
20import androidx.core.view.WindowCompat 21import androidx.core.view.WindowCompat
21import androidx.core.view.WindowInsetsCompat 22import androidx.core.view.WindowInsetsCompat
23import androidx.documentfile.provider.DocumentFile
22import androidx.lifecycle.Lifecycle 24import androidx.lifecycle.Lifecycle
23import androidx.lifecycle.lifecycleScope 25import androidx.lifecycle.lifecycleScope
24import androidx.lifecycle.repeatOnLifecycle 26import androidx.lifecycle.repeatOnLifecycle
@@ -29,6 +31,7 @@ import androidx.preference.PreferenceManager
29import com.google.android.material.color.MaterialColors 31import com.google.android.material.color.MaterialColors
30import com.google.android.material.dialog.MaterialAlertDialogBuilder 32import com.google.android.material.dialog.MaterialAlertDialogBuilder
31import com.google.android.material.navigation.NavigationBarView 33import com.google.android.material.navigation.NavigationBarView
34import kotlinx.coroutines.CoroutineScope
32import java.io.File 35import java.io.File
33import java.io.FilenameFilter 36import java.io.FilenameFilter
34import java.io.IOException 37import java.io.IOException
@@ -41,21 +44,40 @@ import org.yuzu.yuzu_emu.R
41import org.yuzu.yuzu_emu.activities.EmulationActivity 44import org.yuzu.yuzu_emu.activities.EmulationActivity
42import org.yuzu.yuzu_emu.databinding.ActivityMainBinding 45import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
43import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 46import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
47import org.yuzu.yuzu_emu.features.DocumentProvider
44import org.yuzu.yuzu_emu.features.settings.model.Settings 48import org.yuzu.yuzu_emu.features.settings.model.Settings
45import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment 49import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
46import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 50import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
51import org.yuzu.yuzu_emu.getPublicFilesDir
47import org.yuzu.yuzu_emu.model.GamesViewModel 52import org.yuzu.yuzu_emu.model.GamesViewModel
48import org.yuzu.yuzu_emu.model.HomeViewModel 53import org.yuzu.yuzu_emu.model.HomeViewModel
54import org.yuzu.yuzu_emu.model.TaskState
55import org.yuzu.yuzu_emu.model.TaskViewModel
49import org.yuzu.yuzu_emu.utils.* 56import org.yuzu.yuzu_emu.utils.*
57import java.io.BufferedInputStream
58import java.io.BufferedOutputStream
59import java.io.FileOutputStream
60import java.time.LocalDateTime
61import java.time.format.DateTimeFormatter
62import java.util.zip.ZipEntry
63import java.util.zip.ZipInputStream
50 64
51class MainActivity : AppCompatActivity(), ThemeProvider { 65class MainActivity : AppCompatActivity(), ThemeProvider {
52 private lateinit var binding: ActivityMainBinding 66 private lateinit var binding: ActivityMainBinding
53 67
54 private val homeViewModel: HomeViewModel by viewModels() 68 private val homeViewModel: HomeViewModel by viewModels()
55 private val gamesViewModel: GamesViewModel by viewModels() 69 private val gamesViewModel: GamesViewModel by viewModels()
70 private val taskViewModel: TaskViewModel by viewModels()
56 71
57 override var themeId: Int = 0 72 override var themeId: Int = 0
58 73
74 private val savesFolder
75 get() = "${getPublicFilesDir().canonicalPath}/nand/user/save/0000000000000000"
76
77 // Get first subfolder in saves folder (should be the user folder)
78 val savesFolderRoot get() = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: ""
79 private var lastZipCreated: File? = null
80
59 override fun onCreate(savedInstanceState: Bundle?) { 81 override fun onCreate(savedInstanceState: Bundle?) {
60 val splashScreen = installSplashScreen() 82 val splashScreen = installSplashScreen()
61 splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } 83 splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
@@ -307,6 +329,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
307 fun processKey(result: Uri): Boolean { 329 fun processKey(result: Uri): Boolean {
308 if (FileUtil.getExtension(result) != "keys") { 330 if (FileUtil.getExtension(result) != "keys") {
309 MessageDialogFragment.newInstance( 331 MessageDialogFragment.newInstance(
332 this,
310 titleId = R.string.reading_keys_failure, 333 titleId = R.string.reading_keys_failure,
311 descriptionId = R.string.install_prod_keys_failure_extension_description 334 descriptionId = R.string.install_prod_keys_failure_extension_description
312 ).show(supportFragmentManager, MessageDialogFragment.TAG) 335 ).show(supportFragmentManager, MessageDialogFragment.TAG)
@@ -336,6 +359,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
336 return true 359 return true
337 } else { 360 } else {
338 MessageDialogFragment.newInstance( 361 MessageDialogFragment.newInstance(
362 this,
339 titleId = R.string.invalid_keys_error, 363 titleId = R.string.invalid_keys_error,
340 descriptionId = R.string.install_keys_failure_description, 364 descriptionId = R.string.install_keys_failure_description,
341 helpLinkId = R.string.dumping_keys_quickstart_link 365 helpLinkId = R.string.dumping_keys_quickstart_link
@@ -371,11 +395,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
371 val task: () -> Any = { 395 val task: () -> Any = {
372 var messageToShow: Any 396 var messageToShow: Any
373 try { 397 try {
374 FileUtil.unzip(inputZip, cacheFirmwareDir) 398 FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheFirmwareDir)
375 val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 399 val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
376 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 400 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
377 messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { 401 messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
378 MessageDialogFragment.newInstance( 402 MessageDialogFragment.newInstance(
403 this,
379 titleId = R.string.firmware_installed_failure, 404 titleId = R.string.firmware_installed_failure,
380 descriptionId = R.string.firmware_installed_failure_description 405 descriptionId = R.string.firmware_installed_failure_description
381 ) 406 )
@@ -395,7 +420,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
395 IndeterminateProgressDialogFragment.newInstance( 420 IndeterminateProgressDialogFragment.newInstance(
396 this, 421 this,
397 R.string.firmware_installing, 422 R.string.firmware_installing,
398 task 423 task = task
399 ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) 424 ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
400 } 425 }
401 426
@@ -407,6 +432,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
407 432
408 if (FileUtil.getExtension(result) != "bin") { 433 if (FileUtil.getExtension(result) != "bin") {
409 MessageDialogFragment.newInstance( 434 MessageDialogFragment.newInstance(
435 this,
410 titleId = R.string.reading_keys_failure, 436 titleId = R.string.reading_keys_failure,
411 descriptionId = R.string.install_amiibo_keys_failure_extension_description 437 descriptionId = R.string.install_amiibo_keys_failure_extension_description
412 ).show(supportFragmentManager, MessageDialogFragment.TAG) 438 ).show(supportFragmentManager, MessageDialogFragment.TAG)
@@ -434,6 +460,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
434 ).show() 460 ).show()
435 } else { 461 } else {
436 MessageDialogFragment.newInstance( 462 MessageDialogFragment.newInstance(
463 this,
437 titleId = R.string.invalid_keys_error, 464 titleId = R.string.invalid_keys_error,
438 descriptionId = R.string.install_keys_failure_description, 465 descriptionId = R.string.install_keys_failure_description,
439 helpLinkId = R.string.dumping_keys_quickstart_link 466 helpLinkId = R.string.dumping_keys_quickstart_link
@@ -501,7 +528,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
501 if (documents.isNotEmpty()) { 528 if (documents.isNotEmpty()) {
502 IndeterminateProgressDialogFragment.newInstance( 529 IndeterminateProgressDialogFragment.newInstance(
503 this@MainActivity, 530 this@MainActivity,
504 R.string.install_game_content 531 R.string.installing_game_content
505 ) { 532 ) {
506 var installSuccess = 0 533 var installSuccess = 0
507 var installOverwrite = 0 534 var installOverwrite = 0
@@ -509,7 +536,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
509 var errorExtension = 0 536 var errorExtension = 0
510 var errorOther = 0 537 var errorOther = 0
511 documents.forEach { 538 documents.forEach {
512 when (NativeLibrary.installFileToNand(it.toString())) { 539 when (
540 NativeLibrary.installFileToNand(
541 it.toString(),
542 FileUtil.getExtension(it)
543 )
544 ) {
513 NativeLibrary.InstallFileToNandResult.Success -> { 545 NativeLibrary.InstallFileToNandResult.Success -> {
514 installSuccess += 1 546 installSuccess += 1
515 } 547 }
@@ -583,12 +615,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
583 installResult.append(separator) 615 installResult.append(separator)
584 } 616 }
585 return@newInstance MessageDialogFragment.newInstance( 617 return@newInstance MessageDialogFragment.newInstance(
618 this,
586 titleId = R.string.install_game_content_failure, 619 titleId = R.string.install_game_content_failure,
587 descriptionString = installResult.toString().trim(), 620 descriptionString = installResult.toString().trim(),
588 helpLinkId = R.string.install_game_content_help_link 621 helpLinkId = R.string.install_game_content_help_link
589 ) 622 )
590 } else { 623 } else {
591 return@newInstance MessageDialogFragment.newInstance( 624 return@newInstance MessageDialogFragment.newInstance(
625 this,
592 titleId = R.string.install_game_content_success, 626 titleId = R.string.install_game_content_success,
593 descriptionString = installResult.toString().trim() 627 descriptionString = installResult.toString().trim()
594 ) 628 )
@@ -596,4 +630,228 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
596 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) 630 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
597 } 631 }
598 } 632 }
633
634 val exportUserData = registerForActivityResult(
635 ActivityResultContracts.CreateDocument("application/zip")
636 ) { result ->
637 if (result == null) {
638 return@registerForActivityResult
639 }
640
641 IndeterminateProgressDialogFragment.newInstance(
642 this,
643 R.string.exporting_user_data,
644 true
645 ) {
646 val zipResult = FileUtil.zipFromInternalStorage(
647 File(DirectoryInitialization.userDirectory!!),
648 DirectoryInitialization.userDirectory!!,
649 BufferedOutputStream(contentResolver.openOutputStream(result)),
650 taskViewModel.cancelled
651 )
652 return@newInstance when (zipResult) {
653 TaskState.Completed -> getString(R.string.user_data_export_success)
654 TaskState.Failed -> R.string.export_failed
655 TaskState.Cancelled -> R.string.user_data_export_cancelled
656 }
657 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
658 }
659
660 val importUserData =
661 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
662 if (result == null) {
663 return@registerForActivityResult
664 }
665
666 IndeterminateProgressDialogFragment.newInstance(
667 this,
668 R.string.importing_user_data
669 ) {
670 val checkStream =
671 ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
672 var isYuzuBackup = false
673 checkStream.use { stream ->
674 var ze: ZipEntry? = null
675 while (stream.nextEntry?.also { ze = it } != null) {
676 val itemName = ze!!.name.trim()
677 if (itemName == "/config/config.ini" || itemName == "config/config.ini") {
678 isYuzuBackup = true
679 return@use
680 }
681 }
682 }
683 if (!isYuzuBackup) {
684 return@newInstance MessageDialogFragment.newInstance(
685 this,
686 titleId = R.string.invalid_yuzu_backup,
687 descriptionId = R.string.user_data_import_failed_description
688 )
689 }
690
691 // Clear existing user data
692 File(DirectoryInitialization.userDirectory!!).deleteRecursively()
693
694 // Copy archive to internal storage
695 try {
696 FileUtil.unzipToInternalStorage(
697 BufferedInputStream(contentResolver.openInputStream(result)),
698 File(DirectoryInitialization.userDirectory!!)
699 )
700 } catch (e: Exception) {
701 return@newInstance MessageDialogFragment.newInstance(
702 this,
703 titleId = R.string.import_failed,
704 descriptionId = R.string.user_data_import_failed_description
705 )
706 }
707
708 // Reinitialize relevant data
709 NativeLibrary.initializeEmulation()
710 gamesViewModel.reloadGames(false)
711
712 return@newInstance getString(R.string.user_data_import_success)
713 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
714 }
715
716 /**
717 * Zips the save files located in the given folder path and creates a new zip file with the current date and time.
718 * @return true if the zip file is successfully created, false otherwise.
719 */
720 private fun zipSave(): Boolean {
721 try {
722 val tempFolder = File(getPublicFilesDir().canonicalPath, "temp")
723 tempFolder.mkdirs()
724 val saveFolder = File(savesFolderRoot)
725 val outputZipFile = File(
726 tempFolder,
727 "yuzu saves - ${
728 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
729 }.zip"
730 )
731 outputZipFile.createNewFile()
732 val result = FileUtil.zipFromInternalStorage(
733 saveFolder,
734 savesFolderRoot,
735 BufferedOutputStream(FileOutputStream(outputZipFile))
736 )
737 if (result == TaskState.Failed) {
738 return false
739 }
740 lastZipCreated = outputZipFile
741 } catch (e: Exception) {
742 return false
743 }
744 return true
745 }
746
747 /**
748 * Exports the save file located in the given folder path by creating a zip file and sharing it via intent.
749 */
750 fun exportSave() {
751 CoroutineScope(Dispatchers.IO).launch {
752 val wasZipCreated = zipSave()
753 val lastZipFile = lastZipCreated
754 if (!wasZipCreated || lastZipFile == null) {
755 withContext(Dispatchers.Main) {
756 Toast.makeText(
757 this@MainActivity,
758 getString(R.string.export_save_failed),
759 Toast.LENGTH_LONG
760 ).show()
761 }
762 return@launch
763 }
764
765 withContext(Dispatchers.Main) {
766 val file = DocumentFile.fromSingleUri(
767 this@MainActivity,
768 DocumentsContract.buildDocumentUri(
769 DocumentProvider.AUTHORITY,
770 "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
771 )
772 )!!
773 val intent = Intent(Intent.ACTION_SEND)
774 .setDataAndType(file.uri, "application/zip")
775 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
776 .putExtra(Intent.EXTRA_STREAM, file.uri)
777 startForResultExportSave.launch(
778 Intent.createChooser(
779 intent,
780 getString(R.string.share_save_file)
781 )
782 )
783 }
784 }
785 }
786
787 private val startForResultExportSave =
788 registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ ->
789 File(getPublicFilesDir().canonicalPath, "temp").deleteRecursively()
790 }
791
792 val importSaves =
793 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
794 if (result == null) {
795 return@registerForActivityResult
796 }
797
798 NativeLibrary.initializeEmptyUserDirectory()
799
800 val inputZip = contentResolver.openInputStream(result)
801 // A zip needs to have at least one subfolder named after a TitleId in order to be considered valid.
802 var validZip = false
803 val savesFolder = File(savesFolderRoot)
804 val cacheSaveDir = File("${applicationContext.cacheDir.path}/saves/")
805 cacheSaveDir.mkdir()
806
807 if (inputZip == null) {
808 Toast.makeText(
809 applicationContext,
810 getString(R.string.fatal_error),
811 Toast.LENGTH_LONG
812 ).show()
813 return@registerForActivityResult
814 }
815
816 val filterTitleId =
817 FilenameFilter { _, dirName -> dirName.matches(Regex("^0100[\\dA-Fa-f]{12}$")) }
818
819 try {
820 CoroutineScope(Dispatchers.IO).launch {
821 FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
822 cacheSaveDir.list(filterTitleId)?.forEach { savePath ->
823 File(savesFolder, savePath).deleteRecursively()
824 File(cacheSaveDir, savePath).copyRecursively(
825 File(savesFolder, savePath),
826 true
827 )
828 validZip = true
829 }
830
831 withContext(Dispatchers.Main) {
832 if (!validZip) {
833 MessageDialogFragment.newInstance(
834 this@MainActivity,
835 titleId = R.string.save_file_invalid_zip_structure,
836 descriptionId = R.string.save_file_invalid_zip_structure_description
837 ).show(supportFragmentManager, MessageDialogFragment.TAG)
838 return@withContext
839 }
840 Toast.makeText(
841 applicationContext,
842 getString(R.string.save_file_imported_success),
843 Toast.LENGTH_LONG
844 ).show()
845 }
846
847 cacheSaveDir.deleteRecursively()
848 }
849 } catch (e: Exception) {
850 Toast.makeText(
851 applicationContext,
852 getString(R.string.fatal_error),
853 Toast.LENGTH_LONG
854 ).show()
855 }
856 }
599} 857}
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 142af5f26..c3f53f1c5 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
@@ -8,6 +8,7 @@ import android.database.Cursor
8import android.net.Uri 8import android.net.Uri
9import android.provider.DocumentsContract 9import android.provider.DocumentsContract
10import androidx.documentfile.provider.DocumentFile 10import androidx.documentfile.provider.DocumentFile
11import kotlinx.coroutines.flow.StateFlow
11import java.io.BufferedInputStream 12import java.io.BufferedInputStream
12import java.io.File 13import java.io.File
13import java.io.FileOutputStream 14import java.io.FileOutputStream
@@ -18,6 +19,9 @@ import java.util.zip.ZipEntry
18import java.util.zip.ZipInputStream 19import java.util.zip.ZipInputStream
19import org.yuzu.yuzu_emu.YuzuApplication 20import org.yuzu.yuzu_emu.YuzuApplication
20import org.yuzu.yuzu_emu.model.MinimalDocumentFile 21import org.yuzu.yuzu_emu.model.MinimalDocumentFile
22import org.yuzu.yuzu_emu.model.TaskState
23import java.io.BufferedOutputStream
24import java.util.zip.ZipOutputStream
21 25
22object FileUtil { 26object FileUtil {
23 const val PATH_TREE = "tree" 27 const val PATH_TREE = "tree"
@@ -282,30 +286,65 @@ object FileUtil {
282 286
283 /** 287 /**
284 * Extracts the given zip file into the given directory. 288 * Extracts the given zip file into the given directory.
285 * @exception IOException if the file was being created outside of the target directory
286 */ 289 */
287 @Throws(SecurityException::class) 290 @Throws(SecurityException::class)
288 fun unzip(zipStream: InputStream, destDir: File): Boolean { 291 fun unzipToInternalStorage(zipStream: BufferedInputStream, destDir: File) {
289 ZipInputStream(BufferedInputStream(zipStream)).use { zis -> 292 ZipInputStream(zipStream).use { zis ->
290 var entry: ZipEntry? = zis.nextEntry 293 var entry: ZipEntry? = zis.nextEntry
291 while (entry != null) { 294 while (entry != null) {
292 val entryName = entry.name 295 val newFile = File(destDir, entry.name)
293 val entryFile = File(destDir, entryName) 296 val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile
294 if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) { 297
295 throw SecurityException("Entry is outside of the target dir: " + entryFile.name) 298 if (!newFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) {
299 throw SecurityException("Zip file attempted path traversal! ${entry.name}")
300 }
301
302 if (!destinationDirectory.isDirectory && !destinationDirectory.mkdirs()) {
303 throw IOException("Failed to create directory $destinationDirectory")
296 } 304 }
297 if (entry.isDirectory) { 305
298 entryFile.mkdirs() 306 if (!entry.isDirectory) {
299 } else { 307 newFile.outputStream().use { fos -> zis.copyTo(fos) }
300 entryFile.parentFile?.mkdirs()
301 entryFile.createNewFile()
302 entryFile.outputStream().use { fos -> zis.copyTo(fos) }
303 } 308 }
304 entry = zis.nextEntry 309 entry = zis.nextEntry
305 } 310 }
306 } 311 }
312 }
307 313
308 return true 314 /**
315 * Creates a zip file from a directory within internal storage
316 * @param inputFile File representation of the item that will be zipped
317 * @param rootDir Directory containing the inputFile
318 * @param outputStream Stream where the zip file will be output
319 */
320 fun zipFromInternalStorage(
321 inputFile: File,
322 rootDir: String,
323 outputStream: BufferedOutputStream,
324 cancelled: StateFlow<Boolean>? = null
325 ): TaskState {
326 try {
327 ZipOutputStream(outputStream).use { zos ->
328 inputFile.walkTopDown().forEach { file ->
329 if (cancelled?.value == true) {
330 return TaskState.Cancelled
331 }
332
333 if (!file.isDirectory) {
334 val entryName =
335 file.absolutePath.removePrefix(rootDir).removePrefix("/")
336 val entry = ZipEntry(entryName)
337 zos.putNextEntry(entry)
338 if (file.isFile) {
339 file.inputStream().use { fis -> fis.copyTo(zos) }
340 }
341 }
342 }
343 }
344 } catch (e: Exception) {
345 return TaskState.Failed
346 }
347 return TaskState.Completed
309 } 348 }
310 349
311 fun isRootTreeUri(uri: Uri): Boolean { 350 fun isRootTreeUri(uri: Uri): Boolean {
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index f31fe054b..9cf71680c 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -13,6 +13,8 @@
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 <common/fs/fs.h>
17#include <core/file_sys/savedata_factory.h>
16#include <core/loader/nro.h> 18#include <core/loader/nro.h>
17#include <jni.h> 19#include <jni.h>
18 20
@@ -102,7 +104,7 @@ public:
102 m_native_window = native_window; 104 m_native_window = native_window;
103 } 105 }
104 106
105 int InstallFileToNand(std::string filename) { 107 int InstallFileToNand(std::string filename, std::string file_extension) {
106 jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, 108 jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
107 std::size_t block_size) { 109 std::size_t block_size) {
108 if (src == nullptr || dest == nullptr) { 110 if (src == nullptr || dest == nullptr) {
@@ -134,15 +136,11 @@ public:
134 m_system.GetFileSystemController().CreateFactories(*m_vfs); 136 m_system.GetFileSystemController().CreateFactories(*m_vfs);
135 137
136 [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp; 138 [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
137 if (filename.ends_with("nsp")) { 139 if (file_extension == "nsp") {
138 nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); 140 nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
139 if (nsp->IsExtractedType()) { 141 if (nsp->IsExtractedType()) {
140 return InstallError; 142 return InstallError;
141 } 143 }
142 } else if (filename.ends_with("xci")) {
143 jconst xci =
144 std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
145 nsp = xci->GetSecurePartitionNSP();
146 } else { 144 } else {
147 return ErrorFilenameExtension; 145 return ErrorFilenameExtension;
148 } 146 }
@@ -607,8 +605,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject
607} 605}
608 606
609int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance, 607int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
610 [[maybe_unused]] jstring j_file) { 608 jstring j_file,
611 return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file)); 609 jstring j_file_extension) {
610 return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file),
611 GetJString(env, j_file_extension));
612} 612}
613 613
614void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz, 614void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz,
@@ -879,4 +879,24 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardInput(JNIEnv* env
879 EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code); 879 EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code);
880} 880}
881 881
882void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv* env,
883 jobject instance) {
884 const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir);
885 auto vfs_nand_dir = EmulationSession::GetInstance().System().GetFilesystem()->OpenDirectory(
886 Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
887
888 Service::Account::ProfileManager manager;
889 const auto user_id = manager.GetUser(static_cast<std::size_t>(0));
890 ASSERT(user_id);
891
892 const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
893 EmulationSession::GetInstance().System(), vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
894 FileSys::SaveDataType::SaveData, 1, user_id->AsU128(), 0);
895
896 const auto full_path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
897 if (!Common::FS::CreateParentDirs(full_path)) {
898 LOG_WARNING(Frontend, "Failed to create full path of the default user's save directory");
899 }
900}
901
882} // extern "C" 902} // extern "C"
diff --git a/src/android/app/src/main/res/drawable/ic_export.xml b/src/android/app/src/main/res/drawable/ic_export.xml
new file mode 100644
index 000000000..463d2f41c
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_export.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_import.xml b/src/android/app/src/main/res/drawable/ic_import.xml
new file mode 100644
index 000000000..3a99dd5e6
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_import.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
9</vector>
diff --git a/src/android/app/src/main/res/layout/activity_settings.xml b/src/android/app/src/main/res/layout/activity_settings.xml
index 8a026a30a..a187665f2 100644
--- a/src/android/app/src/main/res/layout/activity_settings.xml
+++ b/src/android/app/src/main/res/layout/activity_settings.xml
@@ -22,7 +22,7 @@
22 22
23 <View 23 <View
24 android:id="@+id/navigation_bar_shade" 24 android:id="@+id/navigation_bar_shade"
25 android:layout_width="match_parent" 25 android:layout_width="0dp"
26 android:layout_height="1px" 26 android:layout_height="1px"
27 android:background="@android:color/transparent" 27 android:background="@android:color/transparent"
28 android:clickable="false" 28 android:clickable="false"
diff --git a/src/android/app/src/main/res/layout/card_installable.xml b/src/android/app/src/main/res/layout/card_installable.xml
new file mode 100644
index 000000000..f5b0e3741
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_installable.xml
@@ -0,0 +1,71 @@
1<?xml version="1.0" encoding="utf-8"?>
2<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 style="?attr/materialCardViewOutlinedStyle"
6 android:layout_width="match_parent"
7 android:layout_height="wrap_content"
8 android:layout_marginHorizontal="16dp"
9 android:layout_marginVertical="12dp">
10
11 <LinearLayout
12 android:layout_width="match_parent"
13 android:layout_height="wrap_content"
14 android:layout_margin="16dp"
15 android:orientation="horizontal"
16 android:layout_gravity="center">
17
18 <LinearLayout
19 android:layout_width="0dp"
20 android:layout_height="wrap_content"
21 android:layout_marginEnd="16dp"
22 android:layout_weight="1"
23 android:orientation="vertical">
24
25 <com.google.android.material.textview.MaterialTextView
26 android:id="@+id/title"
27 style="@style/TextAppearance.Material3.TitleMedium"
28 android:layout_width="match_parent"
29 android:layout_height="wrap_content"
30 android:text="@string/user_data"
31 android:textAlignment="viewStart" />
32
33 <com.google.android.material.textview.MaterialTextView
34 android:id="@+id/description"
35 style="@style/TextAppearance.Material3.BodyMedium"
36 android:layout_width="match_parent"
37 android:layout_height="wrap_content"
38 android:layout_marginTop="6dp"
39 android:text="@string/user_data_description"
40 android:textAlignment="viewStart" />
41
42 </LinearLayout>
43
44 <Button
45 android:id="@+id/button_export"
46 style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
47 android:layout_width="wrap_content"
48 android:layout_height="wrap_content"
49 android:layout_gravity="center_vertical"
50 android:contentDescription="@string/export"
51 android:tooltipText="@string/export"
52 android:visibility="gone"
53 app:icon="@drawable/ic_export"
54 tools:visibility="visible" />
55
56 <Button
57 android:id="@+id/button_install"
58 style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
59 android:layout_width="wrap_content"
60 android:layout_height="wrap_content"
61 android:layout_gravity="center_vertical"
62 android:layout_marginStart="12dp"
63 android:contentDescription="@string/string_import"
64 android:tooltipText="@string/string_import"
65 android:visibility="gone"
66 app:icon="@drawable/ic_import"
67 tools:visibility="visible" />
68
69 </LinearLayout>
70
71</com.google.android.material.card.MaterialCardView>
diff --git a/src/android/app/src/main/res/layout/dialog_progress_bar.xml b/src/android/app/src/main/res/layout/dialog_progress_bar.xml
index d17711a65..0209ea082 100644
--- a/src/android/app/src/main/res/layout/dialog_progress_bar.xml
+++ b/src/android/app/src/main/res/layout/dialog_progress_bar.xml
@@ -1,24 +1,8 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2<com.google.android.material.progressindicator.LinearProgressIndicator xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 xmlns:app="http://schemas.android.com/apk/res-auto" 3 xmlns:app="http://schemas.android.com/apk/res-auto"
6 android:orientation="vertical"> 4 android:id="@+id/progress_bar"
7 5 android:layout_width="match_parent"
8 <com.google.android.material.progressindicator.LinearProgressIndicator 6 android:layout_height="wrap_content"
9 android:id="@+id/progress_bar" 7 android:padding="24dp"
10 android:layout_width="match_parent" 8 app:trackCornerRadius="4dp" />
11 android:layout_height="wrap_content"
12 android:layout_margin="24dp"
13 app:trackCornerRadius="4dp" />
14
15 <TextView
16 android:id="@+id/progress_text"
17 android:layout_width="match_parent"
18 android:layout_height="wrap_content"
19 android:layout_marginLeft="24dp"
20 android:layout_marginRight="24dp"
21 android:layout_marginBottom="24dp"
22 android:gravity="end" />
23
24</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml
index da97d85c1..750ce094a 100644
--- a/src/android/app/src/main/res/layout/fragment_emulation.xml
+++ b/src/android/app/src/main/res/layout/fragment_emulation.xml
@@ -32,7 +32,8 @@
32 android:layout_width="wrap_content" 32 android:layout_width="wrap_content"
33 android:layout_height="wrap_content" 33 android:layout_height="wrap_content"
34 android:layout_gravity="center" 34 android:layout_gravity="center"
35 android:focusable="false"> 35 android:focusable="false"
36 android:clickable="false">
36 37
37 <androidx.constraintlayout.widget.ConstraintLayout 38 <androidx.constraintlayout.widget.ConstraintLayout
38 android:id="@+id/loading_layout" 39 android:id="@+id/loading_layout"
@@ -155,7 +156,7 @@
155 android:id="@+id/in_game_menu" 156 android:id="@+id/in_game_menu"
156 android:layout_width="wrap_content" 157 android:layout_width="wrap_content"
157 android:layout_height="match_parent" 158 android:layout_height="match_parent"
158 android:layout_gravity="start|bottom" 159 android:layout_gravity="start"
159 app:headerLayout="@layout/header_in_game" 160 app:headerLayout="@layout/header_in_game"
160 app:menu="@menu/menu_in_game" 161 app:menu="@menu/menu_in_game"
161 tools:visibility="gone" /> 162 tools:visibility="gone" />
diff --git a/src/android/app/src/main/res/layout/fragment_installables.xml b/src/android/app/src/main/res/layout/fragment_installables.xml
new file mode 100644
index 000000000..3a4df81a6
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_installables.xml
@@ -0,0 +1,31 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:id="@+id/coordinator_licenses"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="?attr/colorSurface">
8
9 <com.google.android.material.appbar.AppBarLayout
10 android:id="@+id/appbar_installables"
11 android:layout_width="match_parent"
12 android:layout_height="wrap_content"
13 android:fitsSystemWindows="true">
14
15 <com.google.android.material.appbar.MaterialToolbar
16 android:id="@+id/toolbar_installables"
17 android:layout_width="match_parent"
18 android:layout_height="?attr/actionBarSize"
19 app:title="@string/manage_yuzu_data"
20 app:navigationIcon="@drawable/ic_back" />
21
22 </com.google.android.material.appbar.AppBarLayout>
23
24 <androidx.recyclerview.widget.RecyclerView
25 android:id="@+id/list_installables"
26 android:layout_width="match_parent"
27 android:layout_height="match_parent"
28 android:clipToPadding="false"
29 app:layout_behavior="@string/appbar_scrolling_view_behavior" />
30
31</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index 2e0ce7a3d..2356b802b 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -19,6 +19,9 @@
19 <action 19 <action
20 android:id="@+id/action_homeSettingsFragment_to_earlyAccessFragment" 20 android:id="@+id/action_homeSettingsFragment_to_earlyAccessFragment"
21 app:destination="@id/earlyAccessFragment" /> 21 app:destination="@id/earlyAccessFragment" />
22 <action
23 android:id="@+id/action_homeSettingsFragment_to_installableFragment"
24 app:destination="@id/installableFragment" />
22 </fragment> 25 </fragment>
23 26
24 <fragment 27 <fragment
@@ -88,5 +91,9 @@
88 <action 91 <action
89 android:id="@+id/action_global_settingsActivity" 92 android:id="@+id/action_global_settingsActivity"
90 app:destination="@id/settingsActivity" /> 93 app:destination="@id/settingsActivity" />
94 <fragment
95 android:id="@+id/installableFragment"
96 android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"
97 android:label="InstallableFragment" />
91 98
92</navigation> 99</navigation>
diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml
index daaa7ffde..dd0f36392 100644
--- a/src/android/app/src/main/res/values-de/strings.xml
+++ b/src/android/app/src/main/res/values-de/strings.xml
@@ -79,7 +79,6 @@
79 <string name="manage_save_data">Speicherdaten verwalten</string> 79 <string name="manage_save_data">Speicherdaten verwalten</string>
80 <string name="manage_save_data_description">Speicherdaten gefunden. Bitte wähle unten eine Option aus.</string> 80 <string name="manage_save_data_description">Speicherdaten gefunden. Bitte wähle unten eine Option aus.</string>
81 <string name="import_export_saves_description">Speicherdaten importieren oder exportieren</string> 81 <string name="import_export_saves_description">Speicherdaten importieren oder exportieren</string>
82 <string name="import_export_saves_no_profile">Keine Speicherdaten gefunden. Bitte starte ein Spiel und versuche es erneut.</string>
83 <string name="save_file_imported_success">Erfolgreich importiert</string> 82 <string name="save_file_imported_success">Erfolgreich importiert</string>
84 <string name="save_file_invalid_zip_structure">Ungültige Speicherverzeichnisstruktur</string> 83 <string name="save_file_invalid_zip_structure">Ungültige Speicherverzeichnisstruktur</string>
85 <string name="save_file_invalid_zip_structure_description">Der erste Unterordnername muss die Titel-ID des Spiels sein.</string> 84 <string name="save_file_invalid_zip_structure_description">Der erste Unterordnername muss die Titel-ID des Spiels sein.</string>
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml
index e9129cb00..d398f862f 100644
--- a/src/android/app/src/main/res/values-es/strings.xml
+++ b/src/android/app/src/main/res/values-es/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Administrar datos de guardado</string> 81 <string name="manage_save_data">Administrar datos de guardado</string>
82 <string name="manage_save_data_description">Guardar los datos encontrados. Por favor, seleccione una opción de abajo.</string> 82 <string name="manage_save_data_description">Guardar los datos encontrados. Por favor, seleccione una opción de abajo.</string>
83 <string name="import_export_saves_description">Importar o exportar archivos de guardado</string> 83 <string name="import_export_saves_description">Importar o exportar archivos de guardado</string>
84 <string name="import_export_saves_no_profile">No se han encontrado datos de guardado. Por favor, ejecute un juego y vuelva a intentarlo.</string>
85 <string name="save_file_imported_success">Importado correctamente</string> 84 <string name="save_file_imported_success">Importado correctamente</string>
86 <string name="save_file_invalid_zip_structure">Estructura del directorio de guardado no válido</string> 85 <string name="save_file_invalid_zip_structure">Estructura del directorio de guardado no válido</string>
87 <string name="save_file_invalid_zip_structure_description">El nombre de la primera subcarpeta debe ser el Title ID del juego.</string> 86 <string name="save_file_invalid_zip_structure_description">El nombre de la primera subcarpeta debe ser el Title ID del juego.</string>
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml
index 2d99d618e..a7abd9077 100644
--- a/src/android/app/src/main/res/values-fr/strings.xml
+++ b/src/android/app/src/main/res/values-fr/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Gérer les données de sauvegarde</string> 81 <string name="manage_save_data">Gérer les données de sauvegarde</string>
82 <string name="manage_save_data_description">Données de sauvegarde trouvées. Veuillez sélectionner une option ci-dessous.</string> 82 <string name="manage_save_data_description">Données de sauvegarde trouvées. Veuillez sélectionner une option ci-dessous.</string>
83 <string name="import_export_saves_description">Importer ou exporter des fichiers de sauvegarde</string> 83 <string name="import_export_saves_description">Importer ou exporter des fichiers de sauvegarde</string>
84 <string name="import_export_saves_no_profile">Aucune données de sauvegarde trouvées. Veuillez lancer un jeu et réessayer.</string>
85 <string name="save_file_imported_success">Importé avec succès</string> 84 <string name="save_file_imported_success">Importé avec succès</string>
86 <string name="save_file_invalid_zip_structure">Structure de répertoire de sauvegarde non valide</string> 85 <string name="save_file_invalid_zip_structure">Structure de répertoire de sauvegarde non valide</string>
87 <string name="save_file_invalid_zip_structure_description">Le nom du premier sous-dossier doit être l\'identifiant du titre du jeu.</string> 86 <string name="save_file_invalid_zip_structure_description">Le nom du premier sous-dossier doit être l\'identifiant du titre du jeu.</string>
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml
index d9c3de385..b18161801 100644
--- a/src/android/app/src/main/res/values-it/strings.xml
+++ b/src/android/app/src/main/res/values-it/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Gestisci i salvataggi</string> 81 <string name="manage_save_data">Gestisci i salvataggi</string>
82 <string name="manage_save_data_description">Salvataggio non trovato. Seleziona un\'opzione di seguito.</string> 82 <string name="manage_save_data_description">Salvataggio non trovato. Seleziona un\'opzione di seguito.</string>
83 <string name="import_export_saves_description">Importa o esporta i salvataggi</string> 83 <string name="import_export_saves_description">Importa o esporta i salvataggi</string>
84 <string name="import_export_saves_no_profile">Nessun salvataggio trovato. Avvia un gioco e riprova.</string>
85 <string name="save_file_imported_success">Importato con successo</string> 84 <string name="save_file_imported_success">Importato con successo</string>
86 <string name="save_file_invalid_zip_structure">La struttura della cartella dei salvataggi è invalida</string> 85 <string name="save_file_invalid_zip_structure">La struttura della cartella dei salvataggi è invalida</string>
87 <string name="save_file_invalid_zip_structure_description">La prima sotto cartella <b>deve</b> chiamarsi come l\'ID del titolo del gioco.</string> 86 <string name="save_file_invalid_zip_structure_description">La prima sotto cartella <b>deve</b> chiamarsi come l\'ID del titolo del gioco.</string>
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml
index 7a226cd5c..88fa5a0bb 100644
--- a/src/android/app/src/main/res/values-ja/strings.xml
+++ b/src/android/app/src/main/res/values-ja/strings.xml
@@ -80,7 +80,6 @@
80 <string name="manage_save_data">セーブデータを管理</string> 80 <string name="manage_save_data">セーブデータを管理</string>
81 <string name="manage_save_data_description">セーブデータが見つかりました。以下のオプションから選択してください。</string> 81 <string name="manage_save_data_description">セーブデータが見つかりました。以下のオプションから選択してください。</string>
82 <string name="import_export_saves_description">セーブファイルをインポート/エクスポート</string> 82 <string name="import_export_saves_description">セーブファイルをインポート/エクスポート</string>
83 <string name="import_export_saves_no_profile">セーブデータがありません。ゲームを起動してから再度お試しください。</string>
84 <string name="save_file_imported_success">インポートが完了しました</string> 83 <string name="save_file_imported_success">インポートが完了しました</string>
85 <string name="save_file_invalid_zip_structure">セーブデータのディレクトリ構造が無効です</string> 84 <string name="save_file_invalid_zip_structure">セーブデータのディレクトリ構造が無効です</string>
86 <string name="save_file_invalid_zip_structure_description">最初のサブフォルダ名は、ゲームのタイトルIDである必要があります。</string> 85 <string name="save_file_invalid_zip_structure_description">最初のサブフォルダ名は、ゲームのタイトルIDである必要があります。</string>
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml
index 427b6e5a0..4b658255c 100644
--- a/src/android/app/src/main/res/values-ko/strings.xml
+++ b/src/android/app/src/main/res/values-ko/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">저장 데이터 관리</string> 81 <string name="manage_save_data">저장 데이터 관리</string>
82 <string name="manage_save_data_description">데이터를 저장했습니다. 아래에서 옵션을 선택하세요.</string> 82 <string name="manage_save_data_description">데이터를 저장했습니다. 아래에서 옵션을 선택하세요.</string>
83 <string name="import_export_saves_description">저장 파일 가져오기 또는 내보내기</string> 83 <string name="import_export_saves_description">저장 파일 가져오기 또는 내보내기</string>
84 <string name="import_export_saves_no_profile">저장 데이터를 찾을 수 없습니다. 게임을 실행한 후 다시 시도하세요.</string>
85 <string name="save_file_imported_success">가져오기 성공</string> 84 <string name="save_file_imported_success">가져오기 성공</string>
86 <string name="save_file_invalid_zip_structure">저장 디렉터리 구조가 잘못됨</string> 85 <string name="save_file_invalid_zip_structure">저장 디렉터리 구조가 잘못됨</string>
87 <string name="save_file_invalid_zip_structure_description">첫 번째 하위 폴더 이름은 게임의 타이틀 ID여야 합니다.</string> 86 <string name="save_file_invalid_zip_structure_description">첫 번째 하위 폴더 이름은 게임의 타이틀 ID여야 합니다.</string>
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml
index ce8d7a9e4..dd602a389 100644
--- a/src/android/app/src/main/res/values-nb/strings.xml
+++ b/src/android/app/src/main/res/values-nb/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Administrere lagringsdata</string> 81 <string name="manage_save_data">Administrere lagringsdata</string>
82 <string name="manage_save_data_description">Lagringsdata funnet. Velg et alternativ nedenfor.</string> 82 <string name="manage_save_data_description">Lagringsdata funnet. Velg et alternativ nedenfor.</string>
83 <string name="import_export_saves_description">Importer eller eksporter lagringsfiler</string> 83 <string name="import_export_saves_description">Importer eller eksporter lagringsfiler</string>
84 <string name="import_export_saves_no_profile">Ingen lagringsdata funnet. Start et nytt spill og prøv på nytt.</string>
85 <string name="save_file_imported_success">Vellykket import</string> 84 <string name="save_file_imported_success">Vellykket import</string>
86 <string name="save_file_invalid_zip_structure">Ugyldig struktur for lagringskatalog</string> 85 <string name="save_file_invalid_zip_structure">Ugyldig struktur for lagringskatalog</string>
87 <string name="save_file_invalid_zip_structure_description">Det første undermappenavnet må være spillets tittel-ID.</string> 86 <string name="save_file_invalid_zip_structure_description">Det første undermappenavnet må være spillets tittel-ID.</string>
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml
index c2c24b48f..2fdd1f952 100644
--- a/src/android/app/src/main/res/values-pl/strings.xml
+++ b/src/android/app/src/main/res/values-pl/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Zarządzaj plikami zapisów gier</string> 81 <string name="manage_save_data">Zarządzaj plikami zapisów gier</string>
82 <string name="manage_save_data_description">Znaleziono pliki zapisów gier. Wybierz opcję poniżej.</string> 82 <string name="manage_save_data_description">Znaleziono pliki zapisów gier. Wybierz opcję poniżej.</string>
83 <string name="import_export_saves_description">Importuj lub wyeksportuj pliki zapisów</string> 83 <string name="import_export_saves_description">Importuj lub wyeksportuj pliki zapisów</string>
84 <string name="import_export_saves_no_profile">Nie znaleziono plików zapisów. Uruchom grę i spróbuj ponownie.</string>
85 <string name="save_file_imported_success">Zaimportowano pomyślnie</string> 84 <string name="save_file_imported_success">Zaimportowano pomyślnie</string>
86 <string name="save_file_invalid_zip_structure">Niepoprawna struktura folderów</string> 85 <string name="save_file_invalid_zip_structure">Niepoprawna struktura folderów</string>
87 <string name="save_file_invalid_zip_structure_description">Pierwszy podkatalog musi zawierać w nazwie numer ID tytułu gry.</string> 86 <string name="save_file_invalid_zip_structure_description">Pierwszy podkatalog musi zawierać w nazwie numer ID tytułu gry.</string>
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml
index 04f276108..2f26367fe 100644
--- a/src/android/app/src/main/res/values-pt-rBR/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Gerir dados guardados</string> 81 <string name="manage_save_data">Gerir dados guardados</string>
82 <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string> 82 <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string>
83 <string name="import_export_saves_description">Importa ou exporta dados guardados</string> 83 <string name="import_export_saves_description">Importa ou exporta dados guardados</string>
84 <string name="import_export_saves_no_profile">Dados não encontrados. Por favor lança o jogo e tenta novamente.</string>
85 <string name="save_file_imported_success">Importado com sucesso</string> 84 <string name="save_file_imported_success">Importado com sucesso</string>
86 <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string> 85 <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string>
87 <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string> 86 <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string>
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml
index 66a3a1a2e..4e1eb4cd7 100644
--- a/src/android/app/src/main/res/values-pt-rPT/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Gerir dados guardados</string> 81 <string name="manage_save_data">Gerir dados guardados</string>
82 <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string> 82 <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string>
83 <string name="import_export_saves_description">Importa ou exporta dados guardados</string> 83 <string name="import_export_saves_description">Importa ou exporta dados guardados</string>
84 <string name="import_export_saves_no_profile">Dados não encontrados. Por favor lança o jogo e tenta novamente.</string>
85 <string name="save_file_imported_success">Importado com sucesso</string> 84 <string name="save_file_imported_success">Importado com sucesso</string>
86 <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string> 85 <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string>
87 <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string> 86 <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string>
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml
index f770e954f..f5695dc93 100644
--- a/src/android/app/src/main/res/values-ru/strings.xml
+++ b/src/android/app/src/main/res/values-ru/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Управление данными сохранений</string> 81 <string name="manage_save_data">Управление данными сохранений</string>
82 <string name="manage_save_data_description">Найдено данные сохранений. Пожалуйста, выберите вариант ниже.</string> 82 <string name="manage_save_data_description">Найдено данные сохранений. Пожалуйста, выберите вариант ниже.</string>
83 <string name="import_export_saves_description">Импорт или экспорт файлов сохранения</string> 83 <string name="import_export_saves_description">Импорт или экспорт файлов сохранения</string>
84 <string name="import_export_saves_no_profile">Данные сохранений не найдены. Пожалуйста, запустите игру и повторите попытку.</string>
85 <string name="save_file_imported_success">Успешно импортировано</string> 84 <string name="save_file_imported_success">Успешно импортировано</string>
86 <string name="save_file_invalid_zip_structure">Недопустимая структура папки сохранения</string> 85 <string name="save_file_invalid_zip_structure">Недопустимая структура папки сохранения</string>
87 <string name="save_file_invalid_zip_structure_description">Название первой вложенной папки должно быть идентификатором игры.</string> 86 <string name="save_file_invalid_zip_structure_description">Название первой вложенной папки должно быть идентификатором игры.</string>
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml
index ea3ab1b15..061bc6f04 100644
--- a/src/android/app/src/main/res/values-uk/strings.xml
+++ b/src/android/app/src/main/res/values-uk/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Керування даними збережень</string> 81 <string name="manage_save_data">Керування даними збережень</string>
82 <string name="manage_save_data_description">Знайдено дані збережень. Будь ласка, виберіть варіант нижче.</string> 82 <string name="manage_save_data_description">Знайдено дані збережень. Будь ласка, виберіть варіант нижче.</string>
83 <string name="import_export_saves_description">Імпорт або експорт файлів збереження</string> 83 <string name="import_export_saves_description">Імпорт або експорт файлів збереження</string>
84 <string name="import_export_saves_no_profile">Дані збережень не знайдено. Будь ласка, запустіть гру та повторіть спробу.</string>
85 <string name="save_file_imported_success">Успішно імпортовано</string> 84 <string name="save_file_imported_success">Успішно імпортовано</string>
86 <string name="save_file_invalid_zip_structure">Неприпустима структура папки збереження</string> 85 <string name="save_file_invalid_zip_structure">Неприпустима структура папки збереження</string>
87 <string name="save_file_invalid_zip_structure_description">Назва першої вкладеної папки має бути ідентифікатором гри.</string> 86 <string name="save_file_invalid_zip_structure_description">Назва першої вкладеної папки має бути ідентифікатором гри.</string>
diff --git a/src/android/app/src/main/res/values-w600dp/integers.xml b/src/android/app/src/main/res/values-w600dp/integers.xml
new file mode 100644
index 000000000..9975db801
--- /dev/null
+++ b/src/android/app/src/main/res/values-w600dp/integers.xml
@@ -0,0 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <integer name="grid_columns">2</integer>
5
6</resources>
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml
index b45a5a528..fe6dd5eaa 100644
--- a/src/android/app/src/main/res/values-zh-rCN/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">管理存档数据</string> 81 <string name="manage_save_data">管理存档数据</string>
82 <string name="manage_save_data_description">已找到存档数据,请选择下方的选项。</string> 82 <string name="manage_save_data_description">已找到存档数据,请选择下方的选项。</string>
83 <string name="import_export_saves_description">导入或导出存档</string> 83 <string name="import_export_saves_description">导入或导出存档</string>
84 <string name="import_export_saves_no_profile">找不到存档数据,请启动游戏并重试。</string>
85 <string name="save_file_imported_success">已成功导入存档</string> 84 <string name="save_file_imported_success">已成功导入存档</string>
86 <string name="save_file_invalid_zip_structure">无效的存档目录</string> 85 <string name="save_file_invalid_zip_structure">无效的存档目录</string>
87 <string name="save_file_invalid_zip_structure_description">第一个子文件夹名称必须为当前游戏的 ID。</string> 86 <string name="save_file_invalid_zip_structure_description">第一个子文件夹名称必须为当前游戏的 ID。</string>
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml
index 3aab889e4..9b3e54224 100644
--- a/src/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">管理儲存資料</string> 81 <string name="manage_save_data">管理儲存資料</string>
82 <string name="manage_save_data_description">已找到儲存資料,請選取下方的選項。</string> 82 <string name="manage_save_data_description">已找到儲存資料,請選取下方的選項。</string>
83 <string name="import_export_saves_description">匯入或匯出儲存檔案</string> 83 <string name="import_export_saves_description">匯入或匯出儲存檔案</string>
84 <string name="import_export_saves_no_profile">找不到儲存資料,請啟動遊戲並重試。</string>
85 <string name="save_file_imported_success">已成功匯入</string> 84 <string name="save_file_imported_success">已成功匯入</string>
86 <string name="save_file_invalid_zip_structure">無效的儲存目錄結構</string> 85 <string name="save_file_invalid_zip_structure">無效的儲存目錄結構</string>
87 <string name="save_file_invalid_zip_structure_description">首個子資料夾名稱必須為遊戲標題 ID。</string> 86 <string name="save_file_invalid_zip_structure_description">首個子資料夾名稱必須為遊戲標題 ID。</string>
diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml
index 5e39bc7d9..dc527965c 100644
--- a/src/android/app/src/main/res/values/integers.xml
+++ b/src/android/app/src/main/res/values/integers.xml
@@ -1,6 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<resources> 2<resources>
3 <integer name="game_title_lines">2</integer> 3 <integer name="grid_columns">1</integer>
4 4
5 <!-- Default SWITCH landscape layout --> 5 <!-- Default SWITCH landscape layout -->
6 <integer name="SWITCH_BUTTON_A_X">760</integer> 6 <integer name="SWITCH_BUTTON_A_X">760</integer>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index b163e6fc1..e51edf872 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -90,7 +90,6 @@
90 <string name="manage_save_data">Manage save data</string> 90 <string name="manage_save_data">Manage save data</string>
91 <string name="manage_save_data_description">Save data found. Please select an option below.</string> 91 <string name="manage_save_data_description">Save data found. Please select an option below.</string>
92 <string name="import_export_saves_description">Import or export save files</string> 92 <string name="import_export_saves_description">Import or export save files</string>
93 <string name="import_export_saves_no_profile">No save data found. Please launch a game and retry.</string>
94 <string name="save_file_imported_success">Imported successfully</string> 93 <string name="save_file_imported_success">Imported successfully</string>
95 <string name="save_file_invalid_zip_structure">Invalid save directory structure</string> 94 <string name="save_file_invalid_zip_structure">Invalid save directory structure</string>
96 <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string> 95 <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string>
@@ -101,12 +100,13 @@
101 <string name="firmware_installing">Installing firmware</string> 100 <string name="firmware_installing">Installing firmware</string>
102 <string name="firmware_installed_success">Firmware installed successfully</string> 101 <string name="firmware_installed_success">Firmware installed successfully</string>
103 <string name="firmware_installed_failure">Firmware installation failed</string> 102 <string name="firmware_installed_failure">Firmware installation failed</string>
104 <string name="firmware_installed_failure_description">Verify that the ZIP contains valid firmware and try again.</string> 103 <string name="firmware_installed_failure_description">Make sure the firmware nca files are at the root of the zip and try again.</string>
105 <string name="share_log">Share debug logs</string> 104 <string name="share_log">Share debug logs</string>
106 <string name="share_log_description">Share yuzu\'s log file to debug issues</string> 105 <string name="share_log_description">Share yuzu\'s log file to debug issues</string>
107 <string name="share_log_missing">No log file found</string> 106 <string name="share_log_missing">No log file found</string>
108 <string name="install_game_content">Install game content</string> 107 <string name="install_game_content">Install game content</string>
109 <string name="install_game_content_description">Install game updates or DLC</string> 108 <string name="install_game_content_description">Install game updates or DLC</string>
109 <string name="installing_game_content">Installing content…</string>
110 <string name="install_game_content_failure">Error installing file(s) to NAND</string> 110 <string name="install_game_content_failure">Error installing file(s) to NAND</string>
111 <string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string> 111 <string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string>
112 <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string> 112 <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string>
@@ -118,6 +118,10 @@
118 <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string> 118 <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
119 <string name="custom_driver_not_supported">Custom drivers not supported</string> 119 <string name="custom_driver_not_supported">Custom drivers not supported</string>
120 <string name="custom_driver_not_supported_description">Custom driver loading isn\'t currently supported for this device.\nCheck this option again in the future to see if support was added!</string> 120 <string name="custom_driver_not_supported_description">Custom driver loading isn\'t currently supported for this device.\nCheck this option again in the future to see if support was added!</string>
121 <string name="manage_yuzu_data">Manage yuzu data</string>
122 <string name="manage_yuzu_data_description">Import/export firmware, keys, user data, and more!</string>
123 <string name="share_save_file">Share save file</string>
124 <string name="export_save_failed">Failed to export save</string>
121 125
122 <!-- About screen strings --> 126 <!-- About screen strings -->
123 <string name="gaia_is_not_real">Gaia isn\'t real</string> 127 <string name="gaia_is_not_real">Gaia isn\'t real</string>
@@ -128,6 +132,16 @@
128 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string> 132 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
129 <string name="licenses_description">Projects that make yuzu for Android possible</string> 133 <string name="licenses_description">Projects that make yuzu for Android possible</string>
130 <string name="build">Build</string> 134 <string name="build">Build</string>
135 <string name="user_data">User data</string>
136 <string name="user_data_description">Import/export all app data.\n\nWhen importing user data, all existing user data will be deleted!</string>
137 <string name="exporting_user_data">Exporting user data…</string>
138 <string name="importing_user_data">Importing user data…</string>
139 <string name="import_user_data">Import user data</string>
140 <string name="invalid_yuzu_backup">Invalid yuzu backup</string>
141 <string name="user_data_export_success">User data exported successfully</string>
142 <string name="user_data_import_success">User data imported successfully</string>
143 <string name="user_data_export_cancelled">Export cancelled</string>
144 <string name="user_data_import_failed_description">Make sure the user data folders are at the root of the zip folder and contain a config file at config/config.ini and try again.</string>
131 <string name="support_link">https://discord.gg/u77vRWY</string> 145 <string name="support_link">https://discord.gg/u77vRWY</string>
132 <string name="website_link">https://yuzu-emu.org/</string> 146 <string name="website_link">https://yuzu-emu.org/</string>
133 <string name="github_link">https://github.com/yuzu-emu</string> 147 <string name="github_link">https://github.com/yuzu-emu</string>
@@ -215,6 +229,11 @@
215 <string name="auto">Auto</string> 229 <string name="auto">Auto</string>
216 <string name="submit">Submit</string> 230 <string name="submit">Submit</string>
217 <string name="string_null">Null</string> 231 <string name="string_null">Null</string>
232 <string name="string_import">Import</string>
233 <string name="export">Export</string>
234 <string name="export_failed">Export failed</string>
235 <string name="import_failed">Import failed</string>
236 <string name="cancelling">Cancelling</string>
218 237
219 <!-- GPU driver installation --> 238 <!-- GPU driver installation -->
220 <string name="select_gpu_driver">Select GPU driver</string> 239 <string name="select_gpu_driver">Select GPU driver</string>
@@ -281,6 +300,7 @@
281 <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string> 300 <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string>
282 <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string> 301 <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
283 <string name="memory_formatted">%1$s %2$s</string> 302 <string name="memory_formatted">%1$s %2$s</string>
303 <string name="no_game_present">No bootable game present!</string>
284 304
285 <!-- Region Names --> 305 <!-- Region Names -->
286 <string name="region_japan">Japan</string> 306 <string name="region_japan">Japan</string>
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 4ecaf550b..3fde3cae6 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -130,13 +130,17 @@ void LogSettings() {
130 log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir)); 130 log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
131} 131}
132 132
133void UpdateGPUAccuracy() {
134 values.current_gpu_accuracy = values.gpu_accuracy.GetValue();
135}
136
133bool IsGPULevelExtreme() { 137bool IsGPULevelExtreme() {
134 return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme; 138 return values.current_gpu_accuracy == GpuAccuracy::Extreme;
135} 139}
136 140
137bool IsGPULevelHigh() { 141bool IsGPULevelHigh() {
138 return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme || 142 return values.current_gpu_accuracy == GpuAccuracy::Extreme ||
139 values.gpu_accuracy.GetValue() == GpuAccuracy::High; 143 values.current_gpu_accuracy == GpuAccuracy::High;
140} 144}
141 145
142bool IsFastmemEnabled() { 146bool IsFastmemEnabled() {
diff --git a/src/common/settings.h b/src/common/settings.h
index 82ec9077e..98ab0ec2e 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -307,6 +307,7 @@ struct Values {
307 Specialization::Default, 307 Specialization::Default,
308 true, 308 true,
309 true}; 309 true};
310 GpuAccuracy current_gpu_accuracy{GpuAccuracy::High};
310 SwitchableSetting<AnisotropyMode, true> max_anisotropy{ 311 SwitchableSetting<AnisotropyMode, true> max_anisotropy{
311 linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16, 312 linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16,
312 "max_anisotropy", Category::RendererAdvanced}; 313 "max_anisotropy", Category::RendererAdvanced};
@@ -350,6 +351,8 @@ struct Values {
350 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; 351 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug};
351 Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey", 352 Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey",
352 Category::RendererDebug}; 353 Category::RendererDebug};
354 // TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control
355 bool renderer_amdvlk_depth_bias_workaround{};
353 356
354 // System 357 // System
355 SwitchableSetting<Language, true> language_index{linkage, 358 SwitchableSetting<Language, true> language_index{linkage,
@@ -522,6 +525,7 @@ struct Values {
522 525
523extern Values values; 526extern Values values;
524 527
528void UpdateGPUAccuracy();
525bool IsGPULevelExtreme(); 529bool IsGPULevelExtreme();
526bool IsGPULevelHigh(); 530bool IsGPULevelHigh();
527 531
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h
index 7be6f26f7..3175ab07d 100644
--- a/src/common/settings_setting.h
+++ b/src/common/settings_setting.h
@@ -187,6 +187,8 @@ public:
187 this->SetValue(input == "true"); 187 this->SetValue(input == "true");
188 } else if constexpr (std::is_same_v<Type, float>) { 188 } else if constexpr (std::is_same_v<Type, float>) {
189 this->SetValue(std::stof(input)); 189 this->SetValue(std::stof(input));
190 } else if constexpr (std::is_same_v<Type, AudioEngine>) {
191 this->SetValue(ToEnum<AudioEngine>(input));
190 } else { 192 } else {
191 this->SetValue(static_cast<Type>(std::stoll(input))); 193 this->SetValue(static_cast<Type>(std::stoll(input)));
192 } 194 }
diff --git a/src/core/core.cpp b/src/core/core.cpp
index e8300cd05..08cbb8978 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -381,6 +381,10 @@ struct System::Impl {
381 room_member->SendGameInfo(game_info); 381 room_member->SendGameInfo(game_info);
382 } 382 }
383 383
384 // Workarounds:
385 // Activate this in Super Smash Brothers Ultimate, it only affects AMD cards using AMDVLK
386 Settings::values.renderer_amdvlk_depth_bias_workaround = program_id == 0x1006A800016E000ULL;
387
384 status = SystemResultStatus::Success; 388 status = SystemResultStatus::Success;
385 return status; 389 return status;
386 } 390 }
@@ -440,6 +444,9 @@ struct System::Impl {
440 room_member->SendGameInfo(game_info); 444 room_member->SendGameInfo(game_info);
441 } 445 }
442 446
447 // Workarounds
448 Settings::values.renderer_amdvlk_depth_bias_workaround = false;
449
443 LOG_DEBUG(Core, "Shutdown OK"); 450 LOG_DEBUG(Core, "Shutdown OK");
444 } 451 }
445 452
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index 2527ae606..2422cb51b 100644
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -47,6 +47,7 @@ PartitionFilesystem::PartitionFilesystem(VirtualFile file) {
47 // Actually read in now... 47 // Actually read in now...
48 std::vector<u8> file_data = file->ReadBytes(metadata_size); 48 std::vector<u8> file_data = file->ReadBytes(metadata_size);
49 const std::size_t total_size = file_data.size(); 49 const std::size_t total_size = file_data.size();
50 file_data.push_back(0);
50 51
51 if (total_size != metadata_size) { 52 if (total_size != metadata_size) {
52 status = Loader::ResultStatus::ErrorIncorrectPFSFileSize; 53 status = Loader::ResultStatus::ErrorIncorrectPFSFileSize;
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 8ffdd19e7..a83622f7c 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -19,6 +19,7 @@
19#include "core/hle/service/am/am.h" 19#include "core/hle/service/am/am.h"
20#include "core/hle/service/am/applet_ae.h" 20#include "core/hle/service/am/applet_ae.h"
21#include "core/hle/service/am/applet_oe.h" 21#include "core/hle/service/am/applet_oe.h"
22#include "core/hle/service/am/applets/applet_mii_edit_types.h"
22#include "core/hle/service/am/applets/applet_profile_select.h" 23#include "core/hle/service/am/applets/applet_profile_select.h"
23#include "core/hle/service/am/applets/applet_web_browser.h" 24#include "core/hle/service/am/applets/applet_web_browser.h"
24#include "core/hle/service/am/applets/applets.h" 25#include "core/hle/service/am/applets/applets.h"
@@ -190,7 +191,7 @@ IDisplayController::IDisplayController(Core::System& system_)
190 {5, nullptr, "GetLastForegroundCaptureImageEx"}, 191 {5, nullptr, "GetLastForegroundCaptureImageEx"},
191 {6, nullptr, "GetLastApplicationCaptureImageEx"}, 192 {6, nullptr, "GetLastApplicationCaptureImageEx"},
192 {7, nullptr, "GetCallerAppletCaptureImageEx"}, 193 {7, nullptr, "GetCallerAppletCaptureImageEx"},
193 {8, nullptr, "TakeScreenShotOfOwnLayer"}, 194 {8, &IDisplayController::TakeScreenShotOfOwnLayer, "TakeScreenShotOfOwnLayer"},
194 {9, nullptr, "CopyBetweenCaptureBuffers"}, 195 {9, nullptr, "CopyBetweenCaptureBuffers"},
195 {10, nullptr, "AcquireLastApplicationCaptureBuffer"}, 196 {10, nullptr, "AcquireLastApplicationCaptureBuffer"},
196 {11, nullptr, "ReleaseLastApplicationCaptureBuffer"}, 197 {11, nullptr, "ReleaseLastApplicationCaptureBuffer"},
@@ -218,6 +219,13 @@ IDisplayController::IDisplayController(Core::System& system_)
218 219
219IDisplayController::~IDisplayController() = default; 220IDisplayController::~IDisplayController() = default;
220 221
222void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) {
223 LOG_WARNING(Service_AM, "(STUBBED) called");
224
225 IPC::ResponseBuilder rb{ctx, 2};
226 rb.Push(ResultSuccess);
227}
228
221IDebugFunctions::IDebugFunctions(Core::System& system_) 229IDebugFunctions::IDebugFunctions(Core::System& system_)
222 : ServiceFramework{system_, "IDebugFunctions"} { 230 : ServiceFramework{system_, "IDebugFunctions"} {
223 // clang-format off 231 // clang-format off
@@ -724,7 +732,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
724 {110, nullptr, "OpenMyGpuErrorHandler"}, 732 {110, nullptr, "OpenMyGpuErrorHandler"},
725 {120, nullptr, "GetAppletLaunchedHistory"}, 733 {120, nullptr, "GetAppletLaunchedHistory"},
726 {200, nullptr, "GetOperationModeSystemInfo"}, 734 {200, nullptr, "GetOperationModeSystemInfo"},
727 {300, nullptr, "GetSettingsPlatformRegion"}, 735 {300, &ICommonStateGetter::GetSettingsPlatformRegion, "GetSettingsPlatformRegion"},
728 {400, nullptr, "ActivateMigrationService"}, 736 {400, nullptr, "ActivateMigrationService"},
729 {401, nullptr, "DeactivateMigrationService"}, 737 {401, nullptr, "DeactivateMigrationService"},
730 {500, nullptr, "DisableSleepTillShutdown"}, 738 {500, nullptr, "DisableSleepTillShutdown"},
@@ -736,6 +744,10 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
736 // clang-format on 744 // clang-format on
737 745
738 RegisterHandlers(functions); 746 RegisterHandlers(functions);
747
748 // Configure applets to be in foreground state
749 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
750 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
739} 751}
740 752
741ICommonStateGetter::~ICommonStateGetter() = default; 753ICommonStateGetter::~ICommonStateGetter() = default;
@@ -867,6 +879,14 @@ void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext&
867 rb.Push(ResultSuccess); 879 rb.Push(ResultSuccess);
868} 880}
869 881
882void ICommonStateGetter::GetSettingsPlatformRegion(HLERequestContext& ctx) {
883 LOG_WARNING(Service_AM, "(STUBBED) called");
884
885 IPC::ResponseBuilder rb{ctx, 3};
886 rb.Push(ResultSuccess);
887 rb.PushEnum(SysPlatformRegion::Global);
888}
889
870void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled( 890void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(
871 HLERequestContext& ctx) { 891 HLERequestContext& ctx) {
872 LOG_WARNING(Service_AM, "(STUBBED) called"); 892 LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -1324,18 +1344,19 @@ void ILibraryAppletCreator::CreateHandleStorage(HLERequestContext& ctx) {
1324 1344
1325ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_) 1345ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1326 : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} { 1346 : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} {
1347 // clang-format off
1327 static const FunctionInfo functions[] = { 1348 static const FunctionInfo functions[] = {
1328 {0, nullptr, "PopInData"}, 1349 {0, &ILibraryAppletSelfAccessor::PopInData, "PopInData"},
1329 {1, nullptr, "PushOutData"}, 1350 {1, &ILibraryAppletSelfAccessor::PushOutData, "PushOutData"},
1330 {2, nullptr, "PopInteractiveInData"}, 1351 {2, nullptr, "PopInteractiveInData"},
1331 {3, nullptr, "PushInteractiveOutData"}, 1352 {3, nullptr, "PushInteractiveOutData"},
1332 {5, nullptr, "GetPopInDataEvent"}, 1353 {5, nullptr, "GetPopInDataEvent"},
1333 {6, nullptr, "GetPopInteractiveInDataEvent"}, 1354 {6, nullptr, "GetPopInteractiveInDataEvent"},
1334 {10, nullptr, "ExitProcessAndReturn"}, 1355 {10, &ILibraryAppletSelfAccessor::ExitProcessAndReturn, "ExitProcessAndReturn"},
1335 {11, nullptr, "GetLibraryAppletInfo"}, 1356 {11, &ILibraryAppletSelfAccessor::GetLibraryAppletInfo, "GetLibraryAppletInfo"},
1336 {12, nullptr, "GetMainAppletIdentityInfo"}, 1357 {12, nullptr, "GetMainAppletIdentityInfo"},
1337 {13, nullptr, "CanUseApplicationCore"}, 1358 {13, nullptr, "CanUseApplicationCore"},
1338 {14, nullptr, "GetCallerAppletIdentityInfo"}, 1359 {14, &ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo, "GetCallerAppletIdentityInfo"},
1339 {15, nullptr, "GetMainAppletApplicationControlProperty"}, 1360 {15, nullptr, "GetMainAppletApplicationControlProperty"},
1340 {16, nullptr, "GetMainAppletStorageId"}, 1361 {16, nullptr, "GetMainAppletStorageId"},
1341 {17, nullptr, "GetCallerAppletIdentityInfoStack"}, 1362 {17, nullptr, "GetCallerAppletIdentityInfoStack"},
@@ -1361,10 +1382,142 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1361 {140, nullptr, "SetApplicationMemoryReservation"}, 1382 {140, nullptr, "SetApplicationMemoryReservation"},
1362 {150, nullptr, "ShouldSetGpuTimeSliceManually"}, 1383 {150, nullptr, "ShouldSetGpuTimeSliceManually"},
1363 }; 1384 };
1385 // clang-format on
1364 RegisterHandlers(functions); 1386 RegisterHandlers(functions);
1387
1388 PushInShowMiiEditData();
1365} 1389}
1366 1390
1367ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default; 1391ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default;
1392void ILibraryAppletSelfAccessor::PopInData(HLERequestContext& ctx) {
1393 LOG_INFO(Service_AM, "called");
1394
1395 if (queue_data.empty()) {
1396 IPC::ResponseBuilder rb{ctx, 2};
1397 rb.Push(ResultNoDataInChannel);
1398 return;
1399 }
1400
1401 auto data = queue_data.front();
1402 queue_data.pop_front();
1403
1404 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
1405 rb.Push(ResultSuccess);
1406 rb.PushIpcInterface<IStorage>(system, std::move(data));
1407}
1408
1409void ILibraryAppletSelfAccessor::PushOutData(HLERequestContext& ctx) {
1410 LOG_WARNING(Service_AM, "(STUBBED) called");
1411
1412 IPC::ResponseBuilder rb{ctx, 2};
1413 rb.Push(ResultSuccess);
1414}
1415
1416void ILibraryAppletSelfAccessor::ExitProcessAndReturn(HLERequestContext& ctx) {
1417 LOG_WARNING(Service_AM, "(STUBBED) called");
1418
1419 system.Exit();
1420
1421 IPC::ResponseBuilder rb{ctx, 2};
1422 rb.Push(ResultSuccess);
1423}
1424
1425void ILibraryAppletSelfAccessor::GetLibraryAppletInfo(HLERequestContext& ctx) {
1426 struct LibraryAppletInfo {
1427 Applets::AppletId applet_id;
1428 Applets::LibraryAppletMode library_applet_mode;
1429 };
1430
1431 LOG_WARNING(Service_AM, "(STUBBED) called");
1432
1433 const LibraryAppletInfo applet_info{
1434 .applet_id = Applets::AppletId::MiiEdit,
1435 .library_applet_mode = Applets::LibraryAppletMode::AllForeground,
1436 };
1437
1438 IPC::ResponseBuilder rb{ctx, 4};
1439 rb.Push(ResultSuccess);
1440 rb.PushRaw(applet_info);
1441}
1442
1443void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext& ctx) {
1444 struct AppletIdentityInfo {
1445 Applets::AppletId applet_id;
1446 INSERT_PADDING_BYTES(0x4);
1447 u64 application_id;
1448 };
1449
1450 LOG_WARNING(Service_AM, "(STUBBED) called");
1451
1452 const AppletIdentityInfo applet_info{
1453 .applet_id = Applets::AppletId::QLaunch,
1454 .application_id = 0x0100000000001000ull,
1455 };
1456
1457 IPC::ResponseBuilder rb{ctx, 6};
1458 rb.Push(ResultSuccess);
1459 rb.PushRaw(applet_info);
1460}
1461
1462void ILibraryAppletSelfAccessor::PushInShowMiiEditData() {
1463 struct MiiEditV3 {
1464 Applets::MiiEditAppletInputCommon common;
1465 Applets::MiiEditAppletInputV3 input;
1466 };
1467 static_assert(sizeof(MiiEditV3) == 0x100, "MiiEditV3 has incorrect size.");
1468
1469 MiiEditV3 mii_arguments{
1470 .common =
1471 {
1472 .version = Applets::MiiEditAppletVersion::Version3,
1473 .applet_mode = Applets::MiiEditAppletMode::ShowMiiEdit,
1474 },
1475 .input{},
1476 };
1477
1478 std::vector<u8> argument_data(sizeof(mii_arguments));
1479 std::memcpy(argument_data.data(), &mii_arguments, sizeof(mii_arguments));
1480
1481 queue_data.emplace_back(std::move(argument_data));
1482}
1483
1484IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_)
1485 : ServiceFramework{system_, "IAppletCommonFunctions"} {
1486 // clang-format off
1487 static const FunctionInfo functions[] = {
1488 {0, nullptr, "SetTerminateResult"},
1489 {10, nullptr, "ReadThemeStorage"},
1490 {11, nullptr, "WriteThemeStorage"},
1491 {20, nullptr, "PushToAppletBoundChannel"},
1492 {21, nullptr, "TryPopFromAppletBoundChannel"},
1493 {40, nullptr, "GetDisplayLogicalResolution"},
1494 {42, nullptr, "SetDisplayMagnification"},
1495 {50, nullptr, "SetHomeButtonDoubleClickEnabled"},
1496 {51, nullptr, "GetHomeButtonDoubleClickEnabled"},
1497 {52, nullptr, "IsHomeButtonShortPressedBlocked"},
1498 {60, nullptr, "IsVrModeCurtainRequired"},
1499 {61, nullptr, "IsSleepRequiredByHighTemperature"},
1500 {62, nullptr, "IsSleepRequiredByLowBattery"},
1501 {70, &IAppletCommonFunctions::SetCpuBoostRequestPriority, "SetCpuBoostRequestPriority"},
1502 {80, nullptr, "SetHandlingCaptureButtonShortPressedMessageEnabledForApplet"},
1503 {81, nullptr, "SetHandlingCaptureButtonLongPressedMessageEnabledForApplet"},
1504 {90, nullptr, "OpenNamedChannelAsParent"},
1505 {91, nullptr, "OpenNamedChannelAsChild"},
1506 {100, nullptr, "SetApplicationCoreUsageMode"},
1507 };
1508 // clang-format on
1509
1510 RegisterHandlers(functions);
1511}
1512
1513IAppletCommonFunctions::~IAppletCommonFunctions() = default;
1514
1515void IAppletCommonFunctions::SetCpuBoostRequestPriority(HLERequestContext& ctx) {
1516 LOG_WARNING(Service_AM, "(STUBBED) called");
1517
1518 IPC::ResponseBuilder rb{ctx, 2};
1519 rb.Push(ResultSuccess);
1520}
1368 1521
1369IApplicationFunctions::IApplicationFunctions(Core::System& system_) 1522IApplicationFunctions::IApplicationFunctions(Core::System& system_)
1370 : ServiceFramework{system_, "IApplicationFunctions"}, service_context{system, 1523 : ServiceFramework{system_, "IApplicationFunctions"}, service_context{system,
@@ -1941,9 +2094,6 @@ void IApplicationFunctions::PrepareForJit(HLERequestContext& ctx) {
1941 2094
1942void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) { 2095void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) {
1943 auto message_queue = std::make_shared<AppletMessageQueue>(system); 2096 auto message_queue = std::make_shared<AppletMessageQueue>(system);
1944 // Needed on game boot
1945 message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
1946
1947 auto server_manager = std::make_unique<ServerManager>(system); 2097 auto server_manager = std::make_unique<ServerManager>(system);
1948 2098
1949 server_manager->RegisterNamedService( 2099 server_manager->RegisterNamedService(
@@ -2049,8 +2199,8 @@ IProcessWindingController::IProcessWindingController(Core::System& system_)
2049 : ServiceFramework{system_, "IProcessWindingController"} { 2199 : ServiceFramework{system_, "IProcessWindingController"} {
2050 // clang-format off 2200 // clang-format off
2051 static const FunctionInfo functions[] = { 2201 static const FunctionInfo functions[] = {
2052 {0, nullptr, "GetLaunchReason"}, 2202 {0, &IProcessWindingController::GetLaunchReason, "GetLaunchReason"},
2053 {11, nullptr, "OpenCallingLibraryApplet"}, 2203 {11, &IProcessWindingController::OpenCallingLibraryApplet, "OpenCallingLibraryApplet"},
2054 {21, nullptr, "PushContext"}, 2204 {21, nullptr, "PushContext"},
2055 {22, nullptr, "PopContext"}, 2205 {22, nullptr, "PopContext"},
2056 {23, nullptr, "CancelWindingReservation"}, 2206 {23, nullptr, "CancelWindingReservation"},
@@ -2064,4 +2214,46 @@ IProcessWindingController::IProcessWindingController(Core::System& system_)
2064} 2214}
2065 2215
2066IProcessWindingController::~IProcessWindingController() = default; 2216IProcessWindingController::~IProcessWindingController() = default;
2217
2218void IProcessWindingController::GetLaunchReason(HLERequestContext& ctx) {
2219 LOG_WARNING(Service_AM, "(STUBBED) called");
2220
2221 struct AppletProcessLaunchReason {
2222 u8 flag;
2223 INSERT_PADDING_BYTES(3);
2224 };
2225 static_assert(sizeof(AppletProcessLaunchReason) == 0x4,
2226 "AppletProcessLaunchReason is an invalid size");
2227
2228 AppletProcessLaunchReason reason{
2229 .flag = 0,
2230 };
2231
2232 IPC::ResponseBuilder rb{ctx, 3};
2233 rb.Push(ResultSuccess);
2234 rb.PushRaw(reason);
2235}
2236
2237void IProcessWindingController::OpenCallingLibraryApplet(HLERequestContext& ctx) {
2238 const auto applet_id = Applets::AppletId::MiiEdit;
2239 const auto applet_mode = Applets::LibraryAppletMode::AllForeground;
2240
2241 LOG_WARNING(Service_AM, "(STUBBED) called with applet_id={:08X}, applet_mode={:08X}", applet_id,
2242 applet_mode);
2243
2244 const auto& applet_manager{system.GetAppletManager()};
2245 const auto applet = applet_manager.GetApplet(applet_id, applet_mode);
2246
2247 if (applet == nullptr) {
2248 LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id);
2249
2250 IPC::ResponseBuilder rb{ctx, 2};
2251 rb.Push(ResultUnknown);
2252 return;
2253 }
2254
2255 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
2256 rb.Push(ResultSuccess);
2257 rb.PushIpcInterface<ILibraryAppletAccessor>(system, applet);
2258}
2067} // namespace Service::AM 2259} // namespace Service::AM
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index f86841c60..5b97eb5e3 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -120,6 +120,9 @@ class IDisplayController final : public ServiceFramework<IDisplayController> {
120public: 120public:
121 explicit IDisplayController(Core::System& system_); 121 explicit IDisplayController(Core::System& system_);
122 ~IDisplayController() override; 122 ~IDisplayController() override;
123
124private:
125 void TakeScreenShotOfOwnLayer(HLERequestContext& ctx);
123}; 126};
124 127
125class IDebugFunctions final : public ServiceFramework<IDebugFunctions> { 128class IDebugFunctions final : public ServiceFramework<IDebugFunctions> {
@@ -212,6 +215,11 @@ private:
212 CaptureButtonLongPressing, 215 CaptureButtonLongPressing,
213 }; 216 };
214 217
218 enum class SysPlatformRegion : s32 {
219 Global = 1,
220 Terra = 2,
221 };
222
215 void GetEventHandle(HLERequestContext& ctx); 223 void GetEventHandle(HLERequestContext& ctx);
216 void ReceiveMessage(HLERequestContext& ctx); 224 void ReceiveMessage(HLERequestContext& ctx);
217 void GetCurrentFocusState(HLERequestContext& ctx); 225 void GetCurrentFocusState(HLERequestContext& ctx);
@@ -227,6 +235,7 @@ private:
227 void GetDefaultDisplayResolution(HLERequestContext& ctx); 235 void GetDefaultDisplayResolution(HLERequestContext& ctx);
228 void SetCpuBoostMode(HLERequestContext& ctx); 236 void SetCpuBoostMode(HLERequestContext& ctx);
229 void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx); 237 void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx);
238 void GetSettingsPlatformRegion(HLERequestContext& ctx);
230 void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx); 239 void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx);
231 240
232 std::shared_ptr<AppletMessageQueue> msg_queue; 241 std::shared_ptr<AppletMessageQueue> msg_queue;
@@ -294,6 +303,26 @@ class ILibraryAppletSelfAccessor final : public ServiceFramework<ILibraryAppletS
294public: 303public:
295 explicit ILibraryAppletSelfAccessor(Core::System& system_); 304 explicit ILibraryAppletSelfAccessor(Core::System& system_);
296 ~ILibraryAppletSelfAccessor() override; 305 ~ILibraryAppletSelfAccessor() override;
306
307private:
308 void PopInData(HLERequestContext& ctx);
309 void PushOutData(HLERequestContext& ctx);
310 void GetLibraryAppletInfo(HLERequestContext& ctx);
311 void ExitProcessAndReturn(HLERequestContext& ctx);
312 void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
313
314 void PushInShowMiiEditData();
315
316 std::deque<std::vector<u8>> queue_data;
317};
318
319class IAppletCommonFunctions final : public ServiceFramework<IAppletCommonFunctions> {
320public:
321 explicit IAppletCommonFunctions(Core::System& system_);
322 ~IAppletCommonFunctions() override;
323
324private:
325 void SetCpuBoostRequestPriority(HLERequestContext& ctx);
297}; 326};
298 327
299class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { 328class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
@@ -378,6 +407,10 @@ class IProcessWindingController final : public ServiceFramework<IProcessWindingC
378public: 407public:
379 explicit IProcessWindingController(Core::System& system_); 408 explicit IProcessWindingController(Core::System& system_);
380 ~IProcessWindingController() override; 409 ~IProcessWindingController() override;
410
411private:
412 void GetLaunchReason(HLERequestContext& ctx);
413 void OpenCallingLibraryApplet(HLERequestContext& ctx);
381}; 414};
382 415
383void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system); 416void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system);
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index ee9d99a54..eb12312cc 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -27,7 +27,7 @@ public:
27 {10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"}, 27 {10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"},
28 {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"}, 28 {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
29 {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"}, 29 {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"},
30 {21, nullptr, "GetAppletCommonFunctions"}, 30 {21, &ILibraryAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"},
31 {22, nullptr, "GetHomeMenuFunctions"}, 31 {22, nullptr, "GetHomeMenuFunctions"},
32 {23, nullptr, "GetGlobalStateController"}, 32 {23, nullptr, "GetGlobalStateController"},
33 {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, 33 {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
@@ -86,28 +86,36 @@ private:
86 rb.PushIpcInterface<IProcessWindingController>(system); 86 rb.PushIpcInterface<IProcessWindingController>(system);
87 } 87 }
88 88
89 void GetDebugFunctions(HLERequestContext& ctx) { 89 void GetLibraryAppletCreator(HLERequestContext& ctx) {
90 LOG_DEBUG(Service_AM, "called"); 90 LOG_DEBUG(Service_AM, "called");
91 91
92 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 92 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
93 rb.Push(ResultSuccess); 93 rb.Push(ResultSuccess);
94 rb.PushIpcInterface<IDebugFunctions>(system); 94 rb.PushIpcInterface<ILibraryAppletCreator>(system);
95 } 95 }
96 96
97 void GetLibraryAppletCreator(HLERequestContext& ctx) { 97 void OpenLibraryAppletSelfAccessor(HLERequestContext& ctx) {
98 LOG_DEBUG(Service_AM, "called"); 98 LOG_DEBUG(Service_AM, "called");
99 99
100 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 100 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
101 rb.Push(ResultSuccess); 101 rb.Push(ResultSuccess);
102 rb.PushIpcInterface<ILibraryAppletCreator>(system); 102 rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system);
103 } 103 }
104 104
105 void OpenLibraryAppletSelfAccessor(HLERequestContext& ctx) { 105 void GetAppletCommonFunctions(HLERequestContext& ctx) {
106 LOG_DEBUG(Service_AM, "called"); 106 LOG_DEBUG(Service_AM, "called");
107 107
108 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 108 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
109 rb.Push(ResultSuccess); 109 rb.Push(ResultSuccess);
110 rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system); 110 rb.PushIpcInterface<IAppletCommonFunctions>(system);
111 }
112
113 void GetDebugFunctions(HLERequestContext& ctx) {
114 LOG_DEBUG(Service_AM, "called");
115
116 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
117 rb.Push(ResultSuccess);
118 rb.PushIpcInterface<IDebugFunctions>(system);
111 } 119 }
112 120
113 Nvnflinger::Nvnflinger& nvnflinger; 121 Nvnflinger::Nvnflinger& nvnflinger;
@@ -133,7 +141,7 @@ public:
133 {20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"}, 141 {20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
134 {21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"}, 142 {21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
135 {22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"}, 143 {22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"},
136 {23, nullptr, "GetAppletCommonFunctions"}, 144 {23, &ISystemAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"},
137 {1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, 145 {1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
138 }; 146 };
139 // clang-format on 147 // clang-format on
@@ -182,14 +190,6 @@ private:
182 rb.PushIpcInterface<IDisplayController>(system); 190 rb.PushIpcInterface<IDisplayController>(system);
183 } 191 }
184 192
185 void GetDebugFunctions(HLERequestContext& ctx) {
186 LOG_DEBUG(Service_AM, "called");
187
188 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
189 rb.Push(ResultSuccess);
190 rb.PushIpcInterface<IDebugFunctions>(system);
191 }
192
193 void GetLibraryAppletCreator(HLERequestContext& ctx) { 193 void GetLibraryAppletCreator(HLERequestContext& ctx) {
194 LOG_DEBUG(Service_AM, "called"); 194 LOG_DEBUG(Service_AM, "called");
195 195
@@ -222,6 +222,22 @@ private:
222 rb.PushIpcInterface<IApplicationCreator>(system); 222 rb.PushIpcInterface<IApplicationCreator>(system);
223 } 223 }
224 224
225 void GetAppletCommonFunctions(HLERequestContext& ctx) {
226 LOG_DEBUG(Service_AM, "called");
227
228 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
229 rb.Push(ResultSuccess);
230 rb.PushIpcInterface<IAppletCommonFunctions>(system);
231 }
232
233 void GetDebugFunctions(HLERequestContext& ctx) {
234 LOG_DEBUG(Service_AM, "called");
235
236 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
237 rb.Push(ResultSuccess);
238 rb.PushIpcInterface<IDebugFunctions>(system);
239 }
240
225 Nvnflinger::Nvnflinger& nvnflinger; 241 Nvnflinger::Nvnflinger& nvnflinger;
226 std::shared_ptr<AppletMessageQueue> msg_queue; 242 std::shared_ptr<AppletMessageQueue> msg_queue;
227}; 243};
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.cpp b/src/core/hle/service/am/applets/applet_mii_edit.cpp
index 350a90818..50adc7c02 100644
--- a/src/core/hle/service/am/applets/applet_mii_edit.cpp
+++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp
@@ -7,7 +7,9 @@
7#include "core/frontend/applets/mii_edit.h" 7#include "core/frontend/applets/mii_edit.h"
8#include "core/hle/service/am/am.h" 8#include "core/hle/service/am/am.h"
9#include "core/hle/service/am/applets/applet_mii_edit.h" 9#include "core/hle/service/am/applets/applet_mii_edit.h"
10#include "core/hle/service/mii/mii.h"
10#include "core/hle/service/mii/mii_manager.h" 11#include "core/hle/service/mii/mii_manager.h"
12#include "core/hle/service/sm/sm.h"
11 13
12namespace Service::AM::Applets { 14namespace Service::AM::Applets {
13 15
@@ -56,6 +58,12 @@ void MiiEdit::Initialize() {
56 sizeof(MiiEditAppletInputV4)); 58 sizeof(MiiEditAppletInputV4));
57 break; 59 break;
58 } 60 }
61
62 manager = system.ServiceManager().GetService<Mii::MiiDBModule>("mii:e")->GetMiiManager();
63 if (manager == nullptr) {
64 manager = std::make_shared<Mii::MiiManager>();
65 }
66 manager->Initialize(metadata);
59} 67}
60 68
61bool MiiEdit::TransactionComplete() const { 69bool MiiEdit::TransactionComplete() const {
@@ -78,22 +86,46 @@ void MiiEdit::Execute() {
78 // This is a default stub for each of the MiiEdit applet modes. 86 // This is a default stub for each of the MiiEdit applet modes.
79 switch (applet_input_common.applet_mode) { 87 switch (applet_input_common.applet_mode) {
80 case MiiEditAppletMode::ShowMiiEdit: 88 case MiiEditAppletMode::ShowMiiEdit:
81 case MiiEditAppletMode::AppendMii:
82 case MiiEditAppletMode::AppendMiiImage: 89 case MiiEditAppletMode::AppendMiiImage:
83 case MiiEditAppletMode::UpdateMiiImage: 90 case MiiEditAppletMode::UpdateMiiImage:
84 MiiEditOutput(MiiEditResult::Success, 0); 91 MiiEditOutput(MiiEditResult::Success, 0);
85 break; 92 break;
86 case MiiEditAppletMode::CreateMii: 93 case MiiEditAppletMode::AppendMii: {
87 case MiiEditAppletMode::EditMii: {
88 Mii::CharInfo char_info{};
89 Mii::StoreData store_data{}; 94 Mii::StoreData store_data{};
90 store_data.BuildBase(Mii::Gender::Male); 95 store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
91 char_info.SetFromStoreData(store_data); 96 store_data.SetNickname({u'y', u'u', u'z', u'u'});
97 store_data.SetChecksum();
98 const auto result = manager->AddOrReplace(metadata, store_data);
99
100 if (result.IsError()) {
101 MiiEditOutput(MiiEditResult::Cancel, 0);
102 break;
103 }
104
105 s32 index = manager->FindIndex(store_data.GetCreateId(), false);
106
107 if (index == -1) {
108 MiiEditOutput(MiiEditResult::Cancel, 0);
109 break;
110 }
111
112 MiiEditOutput(MiiEditResult::Success, index);
113 break;
114 }
115 case MiiEditAppletMode::CreateMii: {
116 Mii::CharInfo char_info{};
117 manager->BuildRandom(char_info, Mii::Age::All, Mii::Gender::All, Mii::Race::All);
92 118
93 const MiiEditCharInfo edit_char_info{ 119 const MiiEditCharInfo edit_char_info{
94 .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii 120 .mii_info{char_info},
95 ? applet_input_v4.char_info.mii_info 121 };
96 : char_info}, 122
123 MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
124 break;
125 }
126 case MiiEditAppletMode::EditMii: {
127 const MiiEditCharInfo edit_char_info{
128 .mii_info{applet_input_v4.char_info.mii_info},
97 }; 129 };
98 130
99 MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info); 131 MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
@@ -113,6 +145,8 @@ void MiiEdit::MiiEditOutput(MiiEditResult result, s32 index) {
113 .index{index}, 145 .index{index},
114 }; 146 };
115 147
148 LOG_INFO(Input, "called, result={}, index={}", result, index);
149
116 std::vector<u8> out_data(sizeof(MiiEditAppletOutput)); 150 std::vector<u8> out_data(sizeof(MiiEditAppletOutput));
117 std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutput)); 151 std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutput));
118 152
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.h b/src/core/hle/service/am/applets/applet_mii_edit.h
index 3f46fae1b..7ff34af49 100644
--- a/src/core/hle/service/am/applets/applet_mii_edit.h
+++ b/src/core/hle/service/am/applets/applet_mii_edit.h
@@ -11,6 +11,11 @@ namespace Core {
11class System; 11class System;
12} // namespace Core 12} // namespace Core
13 13
14namespace Service::Mii {
15struct DatabaseSessionMetadata;
16class MiiManager;
17} // namespace Service::Mii
18
14namespace Service::AM::Applets { 19namespace Service::AM::Applets {
15 20
16class MiiEdit final : public Applet { 21class MiiEdit final : public Applet {
@@ -40,6 +45,8 @@ private:
40 MiiEditAppletInputV4 applet_input_v4{}; 45 MiiEditAppletInputV4 applet_input_v4{};
41 46
42 bool is_complete{false}; 47 bool is_complete{false};
48 std::shared_ptr<Mii::MiiManager> manager = nullptr;
49 Mii::DatabaseSessionMetadata metadata{};
43}; 50};
44 51
45} // namespace Service::AM::Applets 52} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 6e4d26b1e..c2054e8a0 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -329,6 +329,7 @@ public:
329 {13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"}, 329 {13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"},
330 {14, &IFileSystem::GetFileTimeStampRaw, "GetFileTimeStampRaw"}, 330 {14, &IFileSystem::GetFileTimeStampRaw, "GetFileTimeStampRaw"},
331 {15, nullptr, "QueryEntry"}, 331 {15, nullptr, "QueryEntry"},
332 {16, &IFileSystem::GetFileSystemAttribute, "GetFileSystemAttribute"},
332 }; 333 };
333 RegisterHandlers(functions); 334 RegisterHandlers(functions);
334 } 335 }
@@ -521,6 +522,46 @@ public:
521 rb.PushRaw(vfs_timestamp); 522 rb.PushRaw(vfs_timestamp);
522 } 523 }
523 524
525 void GetFileSystemAttribute(HLERequestContext& ctx) {
526 LOG_WARNING(Service_FS, "(STUBBED) called");
527
528 struct FileSystemAttribute {
529 u8 dir_entry_name_length_max_defined;
530 u8 file_entry_name_length_max_defined;
531 u8 dir_path_name_length_max_defined;
532 u8 file_path_name_length_max_defined;
533 INSERT_PADDING_BYTES_NOINIT(0x5);
534 u8 utf16_dir_entry_name_length_max_defined;
535 u8 utf16_file_entry_name_length_max_defined;
536 u8 utf16_dir_path_name_length_max_defined;
537 u8 utf16_file_path_name_length_max_defined;
538 INSERT_PADDING_BYTES_NOINIT(0x18);
539 s32 dir_entry_name_length_max;
540 s32 file_entry_name_length_max;
541 s32 dir_path_name_length_max;
542 s32 file_path_name_length_max;
543 INSERT_PADDING_WORDS_NOINIT(0x5);
544 s32 utf16_dir_entry_name_length_max;
545 s32 utf16_file_entry_name_length_max;
546 s32 utf16_dir_path_name_length_max;
547 s32 utf16_file_path_name_length_max;
548 INSERT_PADDING_WORDS_NOINIT(0x18);
549 INSERT_PADDING_WORDS_NOINIT(0x1);
550 };
551 static_assert(sizeof(FileSystemAttribute) == 0xc0,
552 "FileSystemAttribute has incorrect size");
553
554 FileSystemAttribute savedata_attribute{};
555 savedata_attribute.dir_entry_name_length_max_defined = true;
556 savedata_attribute.file_entry_name_length_max_defined = true;
557 savedata_attribute.dir_entry_name_length_max = 0x40;
558 savedata_attribute.file_entry_name_length_max = 0x40;
559
560 IPC::ResponseBuilder rb{ctx, 50};
561 rb.Push(ResultSuccess);
562 rb.PushRaw(savedata_attribute);
563 }
564
524private: 565private:
525 VfsDirectoryServiceWrapper backend; 566 VfsDirectoryServiceWrapper backend;
526 SizeGetter size; 567 SizeGetter size;
@@ -698,7 +739,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
698 {19, nullptr, "FormatSdCardFileSystem"}, 739 {19, nullptr, "FormatSdCardFileSystem"},
699 {21, nullptr, "DeleteSaveDataFileSystem"}, 740 {21, nullptr, "DeleteSaveDataFileSystem"},
700 {22, &FSP_SRV::CreateSaveDataFileSystem, "CreateSaveDataFileSystem"}, 741 {22, &FSP_SRV::CreateSaveDataFileSystem, "CreateSaveDataFileSystem"},
701 {23, nullptr, "CreateSaveDataFileSystemBySystemSaveDataId"}, 742 {23, &FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId, "CreateSaveDataFileSystemBySystemSaveDataId"},
702 {24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"}, 743 {24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"},
703 {25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"}, 744 {25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"},
704 {26, nullptr, "FormatSdCardDryRun"}, 745 {26, nullptr, "FormatSdCardDryRun"},
@@ -712,7 +753,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
712 {35, nullptr, "CreateSaveDataFileSystemByHashSalt"}, 753 {35, nullptr, "CreateSaveDataFileSystemByHashSalt"},
713 {36, nullptr, "OpenHostFileSystemWithOption"}, 754 {36, nullptr, "OpenHostFileSystemWithOption"},
714 {51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"}, 755 {51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"},
715 {52, nullptr, "OpenSaveDataFileSystemBySystemSaveDataId"}, 756 {52, &FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId, "OpenSaveDataFileSystemBySystemSaveDataId"},
716 {53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"}, 757 {53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"},
717 {57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"}, 758 {57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"},
718 {58, nullptr, "ReadSaveDataFileSystemExtraData"}, 759 {58, nullptr, "ReadSaveDataFileSystemExtraData"},
@@ -870,6 +911,21 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) {
870 rb.Push(ResultSuccess); 911 rb.Push(ResultSuccess);
871} 912}
872 913
914void FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) {
915 IPC::RequestParser rp{ctx};
916
917 auto save_struct = rp.PopRaw<FileSys::SaveDataAttribute>();
918 [[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
919
920 LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo());
921
922 FileSys::VirtualDir save_data_dir{};
923 fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandSystem, save_struct);
924
925 IPC::ResponseBuilder rb{ctx, 2};
926 rb.Push(ResultSuccess);
927}
928
873void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) { 929void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
874 IPC::RequestParser rp{ctx}; 930 IPC::RequestParser rp{ctx};
875 931
@@ -916,6 +972,11 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
916 rb.PushIpcInterface<IFileSystem>(std::move(filesystem)); 972 rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
917} 973}
918 974
975void FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) {
976 LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
977 OpenSaveDataFileSystem(ctx);
978}
979
919void FSP_SRV::OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx) { 980void FSP_SRV::OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx) {
920 LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem"); 981 LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
921 OpenSaveDataFileSystem(ctx); 982 OpenSaveDataFileSystem(ctx);
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index 4f3c2f6de..280bc9867 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -39,7 +39,9 @@ private:
39 void OpenFileSystemWithPatch(HLERequestContext& ctx); 39 void OpenFileSystemWithPatch(HLERequestContext& ctx);
40 void OpenSdCardFileSystem(HLERequestContext& ctx); 40 void OpenSdCardFileSystem(HLERequestContext& ctx);
41 void CreateSaveDataFileSystem(HLERequestContext& ctx); 41 void CreateSaveDataFileSystem(HLERequestContext& ctx);
42 void CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx);
42 void OpenSaveDataFileSystem(HLERequestContext& ctx); 43 void OpenSaveDataFileSystem(HLERequestContext& ctx);
44 void OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx);
43 void OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx); 45 void OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx);
44 void OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx); 46 void OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx);
45 void OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx); 47 void OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx);
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 146bb486d..bc822f19e 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -346,6 +346,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
346 } 346 }
347 SignalStyleSetChangedEvent(npad_id); 347 SignalStyleSetChangedEvent(npad_id);
348 WriteEmptyEntry(controller.shared_memory); 348 WriteEmptyEntry(controller.shared_memory);
349 hid_core.SetLastActiveController(npad_id);
349} 350}
350 351
351void Controller_NPad::OnInit() { 352void Controller_NPad::OnInit() {
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp
index 8de806cfb..c28eed926 100644
--- a/src/core/hle/service/mii/mii.cpp
+++ b/src/core/hle/service/mii/mii.cpp
@@ -18,8 +18,10 @@ namespace Service::Mii {
18 18
19class IDatabaseService final : public ServiceFramework<IDatabaseService> { 19class IDatabaseService final : public ServiceFramework<IDatabaseService> {
20public: 20public:
21 explicit IDatabaseService(Core::System& system_, bool is_system_) 21 explicit IDatabaseService(Core::System& system_, std::shared_ptr<MiiManager> mii_manager,
22 : ServiceFramework{system_, "IDatabaseService"}, is_system{is_system_} { 22 bool is_system_)
23 : ServiceFramework{system_, "IDatabaseService"}, manager{mii_manager}, is_system{
24 is_system_} {
23 // clang-format off 25 // clang-format off
24 static const FunctionInfo functions[] = { 26 static const FunctionInfo functions[] = {
25 {0, &IDatabaseService::IsUpdated, "IsUpdated"}, 27 {0, &IDatabaseService::IsUpdated, "IsUpdated"},
@@ -54,7 +56,7 @@ public:
54 56
55 RegisterHandlers(functions); 57 RegisterHandlers(functions);
56 58
57 manager.Initialize(metadata); 59 manager->Initialize(metadata);
58 } 60 }
59 61
60private: 62private:
@@ -64,7 +66,7 @@ private:
64 66
65 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); 67 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
66 68
67 const bool is_updated = manager.IsUpdated(metadata, source_flag); 69 const bool is_updated = manager->IsUpdated(metadata, source_flag);
68 70
69 IPC::ResponseBuilder rb{ctx, 3}; 71 IPC::ResponseBuilder rb{ctx, 3};
70 rb.Push(ResultSuccess); 72 rb.Push(ResultSuccess);
@@ -74,7 +76,7 @@ private:
74 void IsFullDatabase(HLERequestContext& ctx) { 76 void IsFullDatabase(HLERequestContext& ctx) {
75 LOG_DEBUG(Service_Mii, "called"); 77 LOG_DEBUG(Service_Mii, "called");
76 78
77 const bool is_full_database = manager.IsFullDatabase(); 79 const bool is_full_database = manager->IsFullDatabase();
78 80
79 IPC::ResponseBuilder rb{ctx, 3}; 81 IPC::ResponseBuilder rb{ctx, 3};
80 rb.Push(ResultSuccess); 82 rb.Push(ResultSuccess);
@@ -85,7 +87,7 @@ private:
85 IPC::RequestParser rp{ctx}; 87 IPC::RequestParser rp{ctx};
86 const auto source_flag{rp.PopRaw<SourceFlag>()}; 88 const auto source_flag{rp.PopRaw<SourceFlag>()};
87 89
88 const u32 mii_count = manager.GetCount(metadata, source_flag); 90 const u32 mii_count = manager->GetCount(metadata, source_flag);
89 91
90 LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count); 92 LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count);
91 93
@@ -101,7 +103,7 @@ private:
101 103
102 u32 mii_count{}; 104 u32 mii_count{};
103 std::vector<CharInfoElement> char_info_elements(output_size); 105 std::vector<CharInfoElement> char_info_elements(output_size);
104 const auto result = manager.Get(metadata, char_info_elements, mii_count, source_flag); 106 const auto result = manager->Get(metadata, char_info_elements, mii_count, source_flag);
105 107
106 if (mii_count != 0) { 108 if (mii_count != 0) {
107 ctx.WriteBuffer(char_info_elements); 109 ctx.WriteBuffer(char_info_elements);
@@ -122,7 +124,7 @@ private:
122 124
123 u32 mii_count{}; 125 u32 mii_count{};
124 std::vector<CharInfo> char_info(output_size); 126 std::vector<CharInfo> char_info(output_size);
125 const auto result = manager.Get(metadata, char_info, mii_count, source_flag); 127 const auto result = manager->Get(metadata, char_info, mii_count, source_flag);
126 128
127 if (mii_count != 0) { 129 if (mii_count != 0) {
128 ctx.WriteBuffer(char_info); 130 ctx.WriteBuffer(char_info);
@@ -144,7 +146,7 @@ private:
144 LOG_INFO(Service_Mii, "called with source_flag={}", source_flag); 146 LOG_INFO(Service_Mii, "called with source_flag={}", source_flag);
145 147
146 CharInfo new_char_info{}; 148 CharInfo new_char_info{};
147 const auto result = manager.UpdateLatest(metadata, new_char_info, char_info, source_flag); 149 const auto result = manager->UpdateLatest(metadata, new_char_info, char_info, source_flag);
148 if (result.IsFailure()) { 150 if (result.IsFailure()) {
149 IPC::ResponseBuilder rb{ctx, 2}; 151 IPC::ResponseBuilder rb{ctx, 2};
150 rb.Push(result); 152 rb.Push(result);
@@ -183,7 +185,7 @@ private:
183 } 185 }
184 186
185 CharInfo char_info{}; 187 CharInfo char_info{};
186 manager.BuildRandom(char_info, age, gender, race); 188 manager->BuildRandom(char_info, age, gender, race);
187 189
188 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 190 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
189 rb.Push(ResultSuccess); 191 rb.Push(ResultSuccess);
@@ -203,7 +205,7 @@ private:
203 } 205 }
204 206
205 CharInfo char_info{}; 207 CharInfo char_info{};
206 manager.BuildDefault(char_info, index); 208 manager->BuildDefault(char_info, index);
207 209
208 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 210 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
209 rb.Push(ResultSuccess); 211 rb.Push(ResultSuccess);
@@ -217,7 +219,7 @@ private:
217 219
218 u32 mii_count{}; 220 u32 mii_count{};
219 std::vector<StoreDataElement> store_data_elements(output_size); 221 std::vector<StoreDataElement> store_data_elements(output_size);
220 const auto result = manager.Get(metadata, store_data_elements, mii_count, source_flag); 222 const auto result = manager->Get(metadata, store_data_elements, mii_count, source_flag);
221 223
222 if (mii_count != 0) { 224 if (mii_count != 0) {
223 ctx.WriteBuffer(store_data_elements); 225 ctx.WriteBuffer(store_data_elements);
@@ -238,7 +240,7 @@ private:
238 240
239 u32 mii_count{}; 241 u32 mii_count{};
240 std::vector<StoreData> store_data(output_size); 242 std::vector<StoreData> store_data(output_size);
241 const auto result = manager.Get(metadata, store_data, mii_count, source_flag); 243 const auto result = manager->Get(metadata, store_data, mii_count, source_flag);
242 244
243 if (mii_count != 0) { 245 if (mii_count != 0) {
244 ctx.WriteBuffer(store_data); 246 ctx.WriteBuffer(store_data);
@@ -266,7 +268,7 @@ private:
266 268
267 StoreData new_store_data{}; 269 StoreData new_store_data{};
268 if (result.IsSuccess()) { 270 if (result.IsSuccess()) {
269 result = manager.UpdateLatest(metadata, new_store_data, store_data, source_flag); 271 result = manager->UpdateLatest(metadata, new_store_data, store_data, source_flag);
270 } 272 }
271 273
272 if (result.IsFailure()) { 274 if (result.IsFailure()) {
@@ -288,7 +290,7 @@ private:
288 LOG_INFO(Service_Mii, "called with create_id={}, is_special={}", 290 LOG_INFO(Service_Mii, "called with create_id={}, is_special={}",
289 create_id.FormattedString(), is_special); 291 create_id.FormattedString(), is_special);
290 292
291 const s32 index = manager.FindIndex(create_id, is_special); 293 const s32 index = manager->FindIndex(create_id, is_special);
292 294
293 IPC::ResponseBuilder rb{ctx, 3}; 295 IPC::ResponseBuilder rb{ctx, 3};
294 rb.Push(ResultSuccess); 296 rb.Push(ResultSuccess);
@@ -309,14 +311,14 @@ private:
309 } 311 }
310 312
311 if (result.IsSuccess()) { 313 if (result.IsSuccess()) {
312 const u32 count = manager.GetCount(metadata, SourceFlag::Database); 314 const u32 count = manager->GetCount(metadata, SourceFlag::Database);
313 if (new_index < 0 || new_index >= static_cast<s32>(count)) { 315 if (new_index < 0 || new_index >= static_cast<s32>(count)) {
314 result = ResultInvalidArgument; 316 result = ResultInvalidArgument;
315 } 317 }
316 } 318 }
317 319
318 if (result.IsSuccess()) { 320 if (result.IsSuccess()) {
319 result = manager.Move(metadata, new_index, create_id); 321 result = manager->Move(metadata, new_index, create_id);
320 } 322 }
321 323
322 IPC::ResponseBuilder rb{ctx, 2}; 324 IPC::ResponseBuilder rb{ctx, 2};
@@ -336,7 +338,7 @@ private:
336 } 338 }
337 339
338 if (result.IsSuccess()) { 340 if (result.IsSuccess()) {
339 result = manager.AddOrReplace(metadata, store_data); 341 result = manager->AddOrReplace(metadata, store_data);
340 } 342 }
341 343
342 IPC::ResponseBuilder rb{ctx, 2}; 344 IPC::ResponseBuilder rb{ctx, 2};
@@ -356,7 +358,7 @@ private:
356 } 358 }
357 359
358 if (result.IsSuccess()) { 360 if (result.IsSuccess()) {
359 result = manager.Delete(metadata, create_id); 361 result = manager->Delete(metadata, create_id);
360 } 362 }
361 363
362 IPC::ResponseBuilder rb{ctx, 2}; 364 IPC::ResponseBuilder rb{ctx, 2};
@@ -376,7 +378,7 @@ private:
376 } 378 }
377 379
378 if (result.IsSuccess()) { 380 if (result.IsSuccess()) {
379 result = manager.DestroyFile(metadata); 381 result = manager->DestroyFile(metadata);
380 } 382 }
381 383
382 IPC::ResponseBuilder rb{ctx, 2}; 384 IPC::ResponseBuilder rb{ctx, 2};
@@ -396,7 +398,7 @@ private:
396 } 398 }
397 399
398 if (result.IsSuccess()) { 400 if (result.IsSuccess()) {
399 result = manager.DeleteFile(); 401 result = manager->DeleteFile();
400 } 402 }
401 403
402 IPC::ResponseBuilder rb{ctx, 2}; 404 IPC::ResponseBuilder rb{ctx, 2};
@@ -416,7 +418,7 @@ private:
416 } 418 }
417 419
418 if (result.IsSuccess()) { 420 if (result.IsSuccess()) {
419 result = manager.Format(metadata); 421 result = manager->Format(metadata);
420 } 422 }
421 423
422 IPC::ResponseBuilder rb{ctx, 2}; 424 IPC::ResponseBuilder rb{ctx, 2};
@@ -434,7 +436,7 @@ private:
434 } 436 }
435 437
436 if (result.IsSuccess()) { 438 if (result.IsSuccess()) {
437 is_broken_with_clear_flag = manager.IsBrokenWithClearFlag(metadata); 439 is_broken_with_clear_flag = manager->IsBrokenWithClearFlag(metadata);
438 } 440 }
439 441
440 IPC::ResponseBuilder rb{ctx, 3}; 442 IPC::ResponseBuilder rb{ctx, 3};
@@ -449,7 +451,7 @@ private:
449 LOG_DEBUG(Service_Mii, "called"); 451 LOG_DEBUG(Service_Mii, "called");
450 452
451 s32 index{}; 453 s32 index{};
452 const auto result = manager.GetIndex(metadata, info, index); 454 const auto result = manager->GetIndex(metadata, info, index);
453 455
454 IPC::ResponseBuilder rb{ctx, 3}; 456 IPC::ResponseBuilder rb{ctx, 3};
455 rb.Push(result); 457 rb.Push(result);
@@ -462,7 +464,7 @@ private:
462 464
463 LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version); 465 LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version);
464 466
465 manager.SetInterfaceVersion(metadata, interface_version); 467 manager->SetInterfaceVersion(metadata, interface_version);
466 468
467 IPC::ResponseBuilder rb{ctx, 2}; 469 IPC::ResponseBuilder rb{ctx, 2};
468 rb.Push(ResultSuccess); 470 rb.Push(ResultSuccess);
@@ -475,7 +477,7 @@ private:
475 LOG_INFO(Service_Mii, "called"); 477 LOG_INFO(Service_Mii, "called");
476 478
477 CharInfo char_info{}; 479 CharInfo char_info{};
478 const auto result = manager.ConvertV3ToCharInfo(char_info, mii_v3); 480 const auto result = manager->ConvertV3ToCharInfo(char_info, mii_v3);
479 481
480 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 482 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
481 rb.Push(result); 483 rb.Push(result);
@@ -489,7 +491,7 @@ private:
489 LOG_INFO(Service_Mii, "called"); 491 LOG_INFO(Service_Mii, "called");
490 492
491 CharInfo char_info{}; 493 CharInfo char_info{};
492 const auto result = manager.ConvertCoreDataToCharInfo(char_info, core_data); 494 const auto result = manager->ConvertCoreDataToCharInfo(char_info, core_data);
493 495
494 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 496 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
495 rb.Push(result); 497 rb.Push(result);
@@ -503,7 +505,7 @@ private:
503 LOG_INFO(Service_Mii, "called"); 505 LOG_INFO(Service_Mii, "called");
504 506
505 CoreData core_data{}; 507 CoreData core_data{};
506 const auto result = manager.ConvertCharInfoToCoreData(core_data, char_info); 508 const auto result = manager->ConvertCharInfoToCoreData(core_data, char_info);
507 509
508 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)}; 510 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)};
509 rb.Push(result); 511 rb.Push(result);
@@ -516,41 +518,46 @@ private:
516 518
517 LOG_INFO(Service_Mii, "called"); 519 LOG_INFO(Service_Mii, "called");
518 520
519 const auto result = manager.Append(metadata, char_info); 521 const auto result = manager->Append(metadata, char_info);
520 522
521 IPC::ResponseBuilder rb{ctx, 2}; 523 IPC::ResponseBuilder rb{ctx, 2};
522 rb.Push(result); 524 rb.Push(result);
523 } 525 }
524 526
525 MiiManager manager{}; 527 std::shared_ptr<MiiManager> manager = nullptr;
526 DatabaseSessionMetadata metadata{}; 528 DatabaseSessionMetadata metadata{};
527 bool is_system{}; 529 bool is_system{};
528}; 530};
529 531
530class MiiDBModule final : public ServiceFramework<MiiDBModule> { 532MiiDBModule::MiiDBModule(Core::System& system_, const char* name_,
531public: 533 std::shared_ptr<MiiManager> mii_manager, bool is_system_)
532 explicit MiiDBModule(Core::System& system_, const char* name_, bool is_system_) 534 : ServiceFramework{system_, name_}, manager{mii_manager}, is_system{is_system_} {
533 : ServiceFramework{system_, name_}, is_system{is_system_} { 535 // clang-format off
534 // clang-format off 536 static const FunctionInfo functions[] = {
535 static const FunctionInfo functions[] = { 537 {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
536 {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, 538 };
537 }; 539 // clang-format on
538 // clang-format on
539 540
540 RegisterHandlers(functions); 541 RegisterHandlers(functions);
542
543 if (manager == nullptr) {
544 manager = std::make_shared<MiiManager>();
541 } 545 }
546}
542 547
543private: 548MiiDBModule::~MiiDBModule() = default;
544 void GetDatabaseService(HLERequestContext& ctx) {
545 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
546 rb.Push(ResultSuccess);
547 rb.PushIpcInterface<IDatabaseService>(system, is_system);
548 549
549 LOG_DEBUG(Service_Mii, "called"); 550void MiiDBModule::GetDatabaseService(HLERequestContext& ctx) {
550 } 551 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
552 rb.Push(ResultSuccess);
553 rb.PushIpcInterface<IDatabaseService>(system, manager, is_system);
551 554
552 bool is_system{}; 555 LOG_DEBUG(Service_Mii, "called");
553}; 556}
557
558std::shared_ptr<MiiManager> MiiDBModule::GetMiiManager() {
559 return manager;
560}
554 561
555class MiiImg final : public ServiceFramework<MiiImg> { 562class MiiImg final : public ServiceFramework<MiiImg> {
556public: 563public:
@@ -596,11 +603,12 @@ private:
596 603
597void LoopProcess(Core::System& system) { 604void LoopProcess(Core::System& system) {
598 auto server_manager = std::make_unique<ServerManager>(system); 605 auto server_manager = std::make_unique<ServerManager>(system);
606 std::shared_ptr<MiiManager> manager = nullptr;
599 607
600 server_manager->RegisterNamedService("mii:e", 608 server_manager->RegisterNamedService(
601 std::make_shared<MiiDBModule>(system, "mii:e", true)); 609 "mii:e", std::make_shared<MiiDBModule>(system, "mii:e", manager, true));
602 server_manager->RegisterNamedService("mii:u", 610 server_manager->RegisterNamedService(
603 std::make_shared<MiiDBModule>(system, "mii:u", false)); 611 "mii:u", std::make_shared<MiiDBModule>(system, "mii:u", manager, false));
604 server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); 612 server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system));
605 ServerManager::RunServer(std::move(server_manager)); 613 ServerManager::RunServer(std::move(server_manager));
606} 614}
diff --git a/src/core/hle/service/mii/mii.h b/src/core/hle/service/mii/mii.h
index ed4e3f62b..9aa4426f6 100644
--- a/src/core/hle/service/mii/mii.h
+++ b/src/core/hle/service/mii/mii.h
@@ -3,11 +3,29 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "core/hle/service/service.h"
7
6namespace Core { 8namespace Core {
7class System; 9class System;
8} 10}
9 11
10namespace Service::Mii { 12namespace Service::Mii {
13class MiiManager;
14
15class MiiDBModule final : public ServiceFramework<MiiDBModule> {
16public:
17 explicit MiiDBModule(Core::System& system_, const char* name_,
18 std::shared_ptr<MiiManager> mii_manager, bool is_system_);
19 ~MiiDBModule() override;
20
21 std::shared_ptr<MiiManager> GetMiiManager();
22
23private:
24 void GetDatabaseService(HLERequestContext& ctx);
25
26 std::shared_ptr<MiiManager> manager = nullptr;
27 bool is_system{};
28};
11 29
12void LoopProcess(Core::System& system); 30void LoopProcess(Core::System& system);
13 31
diff --git a/src/core/hle/service/mii/mii_database_manager.cpp b/src/core/hle/service/mii/mii_database_manager.cpp
index c39898594..0080b6705 100644
--- a/src/core/hle/service/mii/mii_database_manager.cpp
+++ b/src/core/hle/service/mii/mii_database_manager.cpp
@@ -168,7 +168,7 @@ Result DatabaseManager::FindIndex(s32& out_index, const Common::UUID& create_id,
168 return ResultSuccess; 168 return ResultSuccess;
169 } 169 }
170 170
171 for (std::size_t i = 0; i <= index; ++i) { 171 for (std::size_t i = 0; i < index; ++i) {
172 if (database.Get(i).IsSpecial()) { 172 if (database.Get(i).IsSpecial()) {
173 continue; 173 continue;
174 } 174 }
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index a5a2a9232..dcfd6b2e2 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -130,11 +130,11 @@ Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharI
130 } 130 }
131 131
132 s32 index{}; 132 s32 index{};
133 Result result = {}; 133 const bool is_special = metadata.magic == MiiMagic;
134 // FindIndex(index); 134 const auto result = database_manager.FindIndex(index, char_info.GetCreateId(), is_special);
135 135
136 if (result.IsError()) { 136 if (result.IsError()) {
137 return ResultNotFound; 137 index = -1;
138 } 138 }
139 139
140 if (index == -1) { 140 if (index == -1) {
diff --git a/src/core/hle/service/mii/mii_types.h b/src/core/hle/service/mii/mii_types.h
index f43efd83c..08c6029df 100644
--- a/src/core/hle/service/mii/mii_types.h
+++ b/src/core/hle/service/mii/mii_types.h
@@ -614,7 +614,7 @@ struct Nickname {
614 } 614 }
615 615
616 std::size_t index = 1; 616 std::size_t index = 1;
617 while (data[index] != 0) { 617 while (index < MaxNameSize && data[index] != 0) {
618 index++; 618 index++;
619 } 619 }
620 while (index < MaxNameSize && data[index] == 0) { 620 while (index < MaxNameSize && data[index] == 0) {
diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp
index 465c6293a..970c748ca 100644
--- a/src/core/hle/service/mii/types/core_data.cpp
+++ b/src/core/hle/service/mii/types/core_data.cpp
@@ -113,7 +113,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
113 .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); 113 .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]);
114 114
115 const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; 115 const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0};
116 const auto eyebrow_y{race == Race::Asian ? 9 : 10}; 116 const auto eyebrow_y{race == Race::Asian ? 6 : 7};
117 const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6}; 117 const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6};
118 const auto eyebrow_rotate{ 118 const auto eyebrow_rotate{
119 32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]}; 119 32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]};
@@ -171,7 +171,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
171 u8 glasses_type{}; 171 u8 glasses_type{};
172 while (glasses_type_start < glasses_type_info.values[glasses_type]) { 172 while (glasses_type_start < glasses_type_info.values[glasses_type]) {
173 if (++glasses_type >= glasses_type_info.values_count) { 173 if (++glasses_type >= glasses_type_info.values_count) {
174 ASSERT(false); 174 glasses_type = 0;
175 break; 175 break;
176 } 176 }
177 } 177 }
@@ -179,6 +179,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
179 SetGlassType(static_cast<GlassType>(glasses_type)); 179 SetGlassType(static_cast<GlassType>(glasses_type));
180 SetGlassColor(RawData::GetGlassColorFromVer3(0)); 180 SetGlassColor(RawData::GetGlassColorFromVer3(0));
181 SetGlassScale(4); 181 SetGlassScale(4);
182 SetGlassY(static_cast<u8>(axis_y + 10));
182 183
183 SetMoleType(MoleType::None); 184 SetMoleType(MoleType::None);
184 SetMoleScale(4); 185 SetMoleScale(4);
diff --git a/src/core/hle/service/mii/types/raw_data.cpp b/src/core/hle/service/mii/types/raw_data.cpp
index 5143abcc8..0e1a07fd7 100644
--- a/src/core/hle/service/mii/types/raw_data.cpp
+++ b/src/core/hle/service/mii/types/raw_data.cpp
@@ -1716,18 +1716,18 @@ const std::array<RandomMiiData4, 18> RandomMiiMouthType{
1716const std::array<RandomMiiData2, 3> RandomMiiGlassType{ 1716const std::array<RandomMiiData2, 3> RandomMiiGlassType{
1717 RandomMiiData2{ 1717 RandomMiiData2{
1718 .arg_1 = 0, 1718 .arg_1 = 0,
1719 .values_count = 9, 1719 .values_count = 4,
1720 .values = {90, 94, 96, 100, 0, 0, 0, 0, 0}, 1720 .values = {90, 94, 96, 100},
1721 }, 1721 },
1722 RandomMiiData2{ 1722 RandomMiiData2{
1723 .arg_1 = 1, 1723 .arg_1 = 1,
1724 .values_count = 9, 1724 .values_count = 8,
1725 .values = {83, 86, 90, 93, 94, 96, 98, 100, 0}, 1725 .values = {83, 86, 90, 93, 94, 96, 98, 100},
1726 }, 1726 },
1727 RandomMiiData2{ 1727 RandomMiiData2{
1728 .arg_1 = 2, 1728 .arg_1 = 2,
1729 .values_count = 9, 1729 .values_count = 8,
1730 .values = {78, 83, 0, 93, 0, 0, 98, 100, 0}, 1730 .values = {78, 83, 0, 93, 0, 0, 98, 100},
1731 }, 1731 },
1732}; 1732};
1733 1733
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 05951d8cb..68c407f81 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -1374,7 +1374,7 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co
1374 1374
1375 // Convert from utf16 to utf8 1375 // Convert from utf16 to utf8
1376 const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data()); 1376 const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
1377 memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size() - 1); 1377 memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
1378 1378
1379 return amiibo_name; 1379 return amiibo_name;
1380} 1380}
diff --git a/src/core/hle/service/ns/iplatform_service_manager.cpp b/src/core/hle/service/ns/iplatform_service_manager.cpp
index 6c2f5e70b..46268be95 100644
--- a/src/core/hle/service/ns/iplatform_service_manager.cpp
+++ b/src/core/hle/service/ns/iplatform_service_manager.cpp
@@ -144,7 +144,7 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch
144 {3, &IPlatformServiceManager::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"}, 144 {3, &IPlatformServiceManager::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"},
145 {4, &IPlatformServiceManager::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}, 145 {4, &IPlatformServiceManager::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"},
146 {5, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"}, 146 {5, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
147 {6, nullptr, "GetSharedFontInOrderOfPriorityForSystem"}, 147 {6, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriorityForSystem"},
148 {100, nullptr, "RequestApplicationFunctionAuthorization"}, 148 {100, nullptr, "RequestApplicationFunctionAuthorization"},
149 {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"}, 149 {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"},
150 {102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"}, 150 {102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"},
@@ -262,8 +262,17 @@ void IPlatformServiceManager::GetSharedMemoryNativeHandle(HLERequestContext& ctx
262} 262}
263 263
264void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext& ctx) { 264void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext& ctx) {
265 // The maximum number of elements that can be returned is 6. Regardless of the available fonts
266 // or buffer size.
267 constexpr std::size_t MaxElementCount = 6;
265 IPC::RequestParser rp{ctx}; 268 IPC::RequestParser rp{ctx};
266 const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for 269 const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for
270 const std::size_t font_codes_count =
271 std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(0));
272 const std::size_t font_offsets_count =
273 std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(1));
274 const std::size_t font_sizes_count =
275 std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(2));
267 LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code); 276 LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code);
268 277
269 IPC::ResponseBuilder rb{ctx, 4}; 278 IPC::ResponseBuilder rb{ctx, 4};
@@ -280,9 +289,9 @@ void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext&
280 } 289 }
281 290
282 // Resize buffers if game requests smaller size output 291 // Resize buffers if game requests smaller size output
283 font_codes.resize(std::min(font_codes.size(), ctx.GetWriteBufferNumElements<u32>(0))); 292 font_codes.resize(std::min(font_codes.size(), font_codes_count));
284 font_offsets.resize(std::min(font_offsets.size(), ctx.GetWriteBufferNumElements<u32>(1))); 293 font_offsets.resize(std::min(font_offsets.size(), font_offsets_count));
285 font_sizes.resize(std::min(font_sizes.size(), ctx.GetWriteBufferNumElements<u32>(2))); 294 font_sizes.resize(std::min(font_sizes.size(), font_sizes_count));
286 295
287 ctx.WriteBuffer(font_codes, 0); 296 ctx.WriteBuffer(font_codes, 0);
288 ctx.WriteBuffer(font_offsets, 1); 297 ctx.WriteBuffer(font_offsets, 1);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp
index 2a12feddc..dde0f6e9c 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp
@@ -7,15 +7,12 @@
7 7
8namespace Shader::Backend::SPIRV { 8namespace Shader::Backend::SPIRV {
9namespace { 9namespace {
10Id Image(EmitContext& ctx, const IR::Value& index, IR::TextureInstInfo info) { 10Id Image(EmitContext& ctx, IR::TextureInstInfo info) {
11 if (!index.IsImmediate()) {
12 throw NotImplementedException("Indirect image indexing");
13 }
14 if (info.type == TextureType::Buffer) { 11 if (info.type == TextureType::Buffer) {
15 const ImageBufferDefinition def{ctx.image_buffers.at(index.U32())}; 12 const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)};
16 return def.id; 13 return def.id;
17 } else { 14 } else {
18 const ImageDefinition def{ctx.images.at(index.U32())}; 15 const ImageDefinition def{ctx.images.at(info.descriptor_index)};
19 return def.id; 16 return def.id;
20 } 17 }
21} 18}
@@ -28,8 +25,12 @@ std::pair<Id, Id> AtomicArgs(EmitContext& ctx) {
28 25
29Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id value, 26Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id value,
30 Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { 27 Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
28 if (!index.IsImmediate() || index.U32() != 0) {
29 // TODO: handle layers
30 throw NotImplementedException("Image indexing");
31 }
31 const auto info{inst->Flags<IR::TextureInstInfo>()}; 32 const auto info{inst->Flags<IR::TextureInstInfo>()};
32 const Id image{Image(ctx, index, info)}; 33 const Id image{Image(ctx, info)};
33 const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, image, coords, ctx.Const(0U))}; 34 const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, image, coords, ctx.Const(0U))};
34 const auto [scope, semantics]{AtomicArgs(ctx)}; 35 const auto [scope, semantics]{AtomicArgs(ctx)};
35 return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value); 36 return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value);
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 72f69b7aa..57df6fc34 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -74,11 +74,6 @@ spv::ImageFormat GetImageFormat(ImageFormat format) {
74 throw InvalidArgument("Invalid image format {}", format); 74 throw InvalidArgument("Invalid image format {}", format);
75} 75}
76 76
77spv::ImageFormat GetImageFormatForBuffer(ImageFormat format) {
78 const auto spv_format = GetImageFormat(format);
79 return spv_format == spv::ImageFormat::Unknown ? spv::ImageFormat::R32ui : spv_format;
80}
81
82Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) { 77Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) {
83 const spv::ImageFormat format{GetImageFormat(desc.format)}; 78 const spv::ImageFormat format{GetImageFormat(desc.format)};
84 const Id type{ctx.U32[1]}; 79 const Id type{ctx.U32[1]};
@@ -1275,7 +1270,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
1275 if (desc.count != 1) { 1270 if (desc.count != 1) {
1276 throw NotImplementedException("Array of image buffers"); 1271 throw NotImplementedException("Array of image buffers");
1277 } 1272 }
1278 const spv::ImageFormat format{GetImageFormatForBuffer(desc.format)}; 1273 const spv::ImageFormat format{GetImageFormat(desc.format)};
1279 const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)}; 1274 const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)};
1280 const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; 1275 const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)};
1281 const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; 1276 const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)};
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 9b13ccbab..cf9266d54 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -95,6 +95,12 @@ add_library(video_core STATIC
95 memory_manager.h 95 memory_manager.h
96 precompiled_headers.h 96 precompiled_headers.h
97 pte_kind.h 97 pte_kind.h
98 query_cache/bank_base.h
99 query_cache/query_base.h
100 query_cache/query_cache_base.h
101 query_cache/query_cache.h
102 query_cache/query_stream.h
103 query_cache/types.h
98 query_cache.h 104 query_cache.h
99 rasterizer_accelerated.cpp 105 rasterizer_accelerated.cpp
100 rasterizer_accelerated.h 106 rasterizer_accelerated.h
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 8be7bd594..9e90c587c 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -272,13 +272,19 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
272 if (!cpu_addr) { 272 if (!cpu_addr) {
273 return {&slot_buffers[NULL_BUFFER_ID], 0}; 273 return {&slot_buffers[NULL_BUFFER_ID], 0};
274 } 274 }
275 const BufferId buffer_id = FindBuffer(*cpu_addr, size); 275 return ObtainCPUBuffer(*cpu_addr, size, sync_info, post_op);
276}
277
278template <class P>
279std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainCPUBuffer(
280 VAddr cpu_addr, u32 size, ObtainBufferSynchronize sync_info, ObtainBufferOperation post_op) {
281 const BufferId buffer_id = FindBuffer(cpu_addr, size);
276 Buffer& buffer = slot_buffers[buffer_id]; 282 Buffer& buffer = slot_buffers[buffer_id];
277 283
278 // synchronize op 284 // synchronize op
279 switch (sync_info) { 285 switch (sync_info) {
280 case ObtainBufferSynchronize::FullSynchronize: 286 case ObtainBufferSynchronize::FullSynchronize:
281 SynchronizeBuffer(buffer, *cpu_addr, size); 287 SynchronizeBuffer(buffer, cpu_addr, size);
282 break; 288 break;
283 default: 289 default:
284 break; 290 break;
@@ -286,11 +292,11 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
286 292
287 switch (post_op) { 293 switch (post_op) {
288 case ObtainBufferOperation::MarkAsWritten: 294 case ObtainBufferOperation::MarkAsWritten:
289 MarkWrittenBuffer(buffer_id, *cpu_addr, size); 295 MarkWrittenBuffer(buffer_id, cpu_addr, size);
290 break; 296 break;
291 case ObtainBufferOperation::DiscardWrite: { 297 case ObtainBufferOperation::DiscardWrite: {
292 VAddr cpu_addr_start = Common::AlignDown(*cpu_addr, 64); 298 VAddr cpu_addr_start = Common::AlignDown(cpu_addr, 64);
293 VAddr cpu_addr_end = Common::AlignUp(*cpu_addr + size, 64); 299 VAddr cpu_addr_end = Common::AlignUp(cpu_addr + size, 64);
294 IntervalType interval{cpu_addr_start, cpu_addr_end}; 300 IntervalType interval{cpu_addr_start, cpu_addr_end};
295 ClearDownload(interval); 301 ClearDownload(interval);
296 common_ranges.subtract(interval); 302 common_ranges.subtract(interval);
@@ -300,7 +306,7 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
300 break; 306 break;
301 } 307 }
302 308
303 return {&buffer, buffer.Offset(*cpu_addr)}; 309 return {&buffer, buffer.Offset(cpu_addr)};
304} 310}
305 311
306template <class P> 312template <class P>
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index 0b7135d49..c4f6e8d12 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -295,6 +295,10 @@ public:
295 [[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(GPUVAddr gpu_addr, u32 size, 295 [[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(GPUVAddr gpu_addr, u32 size,
296 ObtainBufferSynchronize sync_info, 296 ObtainBufferSynchronize sync_info,
297 ObtainBufferOperation post_op); 297 ObtainBufferOperation post_op);
298
299 [[nodiscard]] std::pair<Buffer*, u32> ObtainCPUBuffer(VAddr gpu_addr, u32 size,
300 ObtainBufferSynchronize sync_info,
301 ObtainBufferOperation post_op);
298 void FlushCachedWrites(); 302 void FlushCachedWrites();
299 303
300 /// Return true when there are uncommitted buffers to be downloaded 304 /// Return true when there are uncommitted buffers to be downloaded
@@ -335,6 +339,14 @@ public:
335 339
336 [[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer(); 340 [[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer();
337 341
342 template <typename Func>
343 void BufferOperations(Func&& func) {
344 do {
345 channel_state->has_deleted_buffers = false;
346 func();
347 } while (channel_state->has_deleted_buffers);
348 }
349
338 std::recursive_mutex mutex; 350 std::recursive_mutex mutex;
339 Runtime& runtime; 351 Runtime& runtime;
340 352
diff --git a/src/video_core/control/channel_state_cache.h b/src/video_core/control/channel_state_cache.h
index 46bc9e322..5574e1fba 100644
--- a/src/video_core/control/channel_state_cache.h
+++ b/src/video_core/control/channel_state_cache.h
@@ -51,7 +51,7 @@ public:
51 virtual void CreateChannel(Tegra::Control::ChannelState& channel); 51 virtual void CreateChannel(Tegra::Control::ChannelState& channel);
52 52
53 /// Bind a channel for execution. 53 /// Bind a channel for execution.
54 void BindToChannel(s32 id); 54 virtual void BindToChannel(s32 id);
55 55
56 /// Erase channel's state. 56 /// Erase channel's state.
57 void EraseChannel(s32 id); 57 void EraseChannel(s32 id);
diff --git a/src/video_core/engines/draw_manager.h b/src/video_core/engines/draw_manager.h
index 7c22c49f1..18d959143 100644
--- a/src/video_core/engines/draw_manager.h
+++ b/src/video_core/engines/draw_manager.h
@@ -46,6 +46,7 @@ public:
46 }; 46 };
47 47
48 struct IndirectParams { 48 struct IndirectParams {
49 bool is_byte_count;
49 bool is_indexed; 50 bool is_indexed;
50 bool include_count; 51 bool include_count;
51 GPUVAddr count_start_address; 52 GPUVAddr count_start_address;
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 06e349e43..32d767d85 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -20,8 +20,6 @@
20 20
21namespace Tegra::Engines { 21namespace Tegra::Engines {
22 22
23using VideoCore::QueryType;
24
25/// First register id that is actually a Macro call. 23/// First register id that is actually a Macro call.
26constexpr u32 MacroRegistersStart = 0xE00; 24constexpr u32 MacroRegistersStart = 0xE00;
27 25
@@ -500,27 +498,21 @@ void Maxwell3D::StampQueryResult(u64 payload, bool long_query) {
500} 498}
501 499
502void Maxwell3D::ProcessQueryGet() { 500void Maxwell3D::ProcessQueryGet() {
501 VideoCommon::QueryPropertiesFlags flags{};
502 if (regs.report_semaphore.query.short_query == 0) {
503 flags |= VideoCommon::QueryPropertiesFlags::HasTimeout;
504 }
505 const GPUVAddr sequence_address{regs.report_semaphore.Address()};
506 const VideoCommon::QueryType query_type =
507 static_cast<VideoCommon::QueryType>(regs.report_semaphore.query.report.Value());
508 const u32 payload = regs.report_semaphore.payload;
509 const u32 subreport = regs.report_semaphore.query.sub_report;
503 switch (regs.report_semaphore.query.operation) { 510 switch (regs.report_semaphore.query.operation) {
504 case Regs::ReportSemaphore::Operation::Release: 511 case Regs::ReportSemaphore::Operation::Release:
505 if (regs.report_semaphore.query.short_query != 0) { 512 if (regs.report_semaphore.query.short_query != 0) {
506 const GPUVAddr sequence_address{regs.report_semaphore.Address()}; 513 flags |= VideoCommon::QueryPropertiesFlags::IsAFence;
507 const u32 payload = regs.report_semaphore.payload;
508 std::function<void()> operation([this, sequence_address, payload] {
509 memory_manager.Write<u32>(sequence_address, payload);
510 });
511 rasterizer->SignalFence(std::move(operation));
512 } else {
513 struct LongQueryResult {
514 u64_le value;
515 u64_le timestamp;
516 };
517 const GPUVAddr sequence_address{regs.report_semaphore.Address()};
518 const u32 payload = regs.report_semaphore.payload;
519 [this, sequence_address, payload] {
520 memory_manager.Write<u64>(sequence_address + sizeof(u64), system.GPU().GetTicks());
521 memory_manager.Write<u64>(sequence_address, payload);
522 }();
523 } 514 }
515 rasterizer->Query(sequence_address, query_type, flags, payload, subreport);
524 break; 516 break;
525 case Regs::ReportSemaphore::Operation::Acquire: 517 case Regs::ReportSemaphore::Operation::Acquire:
526 // TODO(Blinkhawk): Under this operation, the GPU waits for the CPU to write a value that 518 // TODO(Blinkhawk): Under this operation, the GPU waits for the CPU to write a value that
@@ -528,11 +520,7 @@ void Maxwell3D::ProcessQueryGet() {
528 UNIMPLEMENTED_MSG("Unimplemented query operation ACQUIRE"); 520 UNIMPLEMENTED_MSG("Unimplemented query operation ACQUIRE");
529 break; 521 break;
530 case Regs::ReportSemaphore::Operation::ReportOnly: 522 case Regs::ReportSemaphore::Operation::ReportOnly:
531 if (const std::optional<u64> result = GetQueryResult()) { 523 rasterizer->Query(sequence_address, query_type, flags, payload, subreport);
532 // If the query returns an empty optional it means it's cached and deferred.
533 // In this case we have a non-empty result, so we stamp it immediately.
534 StampQueryResult(*result, regs.report_semaphore.query.short_query == 0);
535 }
536 break; 524 break;
537 case Regs::ReportSemaphore::Operation::Trap: 525 case Regs::ReportSemaphore::Operation::Trap:
538 UNIMPLEMENTED_MSG("Unimplemented query operation TRAP"); 526 UNIMPLEMENTED_MSG("Unimplemented query operation TRAP");
@@ -544,6 +532,10 @@ void Maxwell3D::ProcessQueryGet() {
544} 532}
545 533
546void Maxwell3D::ProcessQueryCondition() { 534void Maxwell3D::ProcessQueryCondition() {
535 if (rasterizer->AccelerateConditionalRendering()) {
536 execute_on = true;
537 return;
538 }
547 const GPUVAddr condition_address{regs.render_enable.Address()}; 539 const GPUVAddr condition_address{regs.render_enable.Address()};
548 switch (regs.render_enable_override) { 540 switch (regs.render_enable_override) {
549 case Regs::RenderEnable::Override::AlwaysRender: 541 case Regs::RenderEnable::Override::AlwaysRender:
@@ -553,10 +545,6 @@ void Maxwell3D::ProcessQueryCondition() {
553 execute_on = false; 545 execute_on = false;
554 break; 546 break;
555 case Regs::RenderEnable::Override::UseRenderEnable: { 547 case Regs::RenderEnable::Override::UseRenderEnable: {
556 if (rasterizer->AccelerateConditionalRendering()) {
557 execute_on = true;
558 return;
559 }
560 switch (regs.render_enable.mode) { 548 switch (regs.render_enable.mode) {
561 case Regs::RenderEnable::Mode::True: { 549 case Regs::RenderEnable::Mode::True: {
562 execute_on = true; 550 execute_on = true;
@@ -598,15 +586,9 @@ void Maxwell3D::ProcessQueryCondition() {
598} 586}
599 587
600void Maxwell3D::ProcessCounterReset() { 588void Maxwell3D::ProcessCounterReset() {
601#if ANDROID
602 if (!Settings::IsGPULevelHigh()) {
603 // This is problematic on Android, disable on GPU Normal.
604 return;
605 }
606#endif
607 switch (regs.clear_report_value) { 589 switch (regs.clear_report_value) {
608 case Regs::ClearReport::ZPassPixelCount: 590 case Regs::ClearReport::ZPassPixelCount:
609 rasterizer->ResetCounter(QueryType::SamplesPassed); 591 rasterizer->ResetCounter(VideoCommon::QueryType::ZPassPixelCount64);
610 break; 592 break;
611 default: 593 default:
612 LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}", regs.clear_report_value); 594 LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}", regs.clear_report_value);
@@ -620,28 +602,6 @@ void Maxwell3D::ProcessSyncPoint() {
620 rasterizer->SignalSyncPoint(sync_point); 602 rasterizer->SignalSyncPoint(sync_point);
621} 603}
622 604
623std::optional<u64> Maxwell3D::GetQueryResult() {
624 switch (regs.report_semaphore.query.report) {
625 case Regs::ReportSemaphore::Report::Payload:
626 return regs.report_semaphore.payload;
627 case Regs::ReportSemaphore::Report::ZPassPixelCount64:
628#if ANDROID
629 if (!Settings::IsGPULevelHigh()) {
630 // This is problematic on Android, disable on GPU Normal.
631 return 120;
632 }
633#endif
634 // Deferred.
635 rasterizer->Query(regs.report_semaphore.Address(), QueryType::SamplesPassed,
636 system.GPU().GetTicks());
637 return std::nullopt;
638 default:
639 LOG_DEBUG(HW_GPU, "Unimplemented query report type {}",
640 regs.report_semaphore.query.report.Value());
641 return 1;
642 }
643}
644
645void Maxwell3D::ProcessCBBind(size_t stage_index) { 605void Maxwell3D::ProcessCBBind(size_t stage_index) {
646 // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader 606 // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader
647 // stage. 607 // stage.
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 6c19354e1..17faacc37 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -3182,9 +3182,6 @@ private:
3182 /// Handles writes to syncing register. 3182 /// Handles writes to syncing register.
3183 void ProcessSyncPoint(); 3183 void ProcessSyncPoint();
3184 3184
3185 /// Returns a query's value or an empty object if the value will be deferred through a cache.
3186 std::optional<u64> GetQueryResult();
3187
3188 void RefreshParametersImpl(); 3185 void RefreshParametersImpl();
3189 3186
3190 bool IsMethodExecutable(u32 method); 3187 bool IsMethodExecutable(u32 method);
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index da8eab7ee..422d4d859 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -109,10 +109,11 @@ void MaxwellDMA::Launch() {
109 const bool is_const_a_dst = regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A; 109 const bool is_const_a_dst = regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A;
110 if (regs.launch_dma.remap_enable != 0 && is_const_a_dst) { 110 if (regs.launch_dma.remap_enable != 0 && is_const_a_dst) {
111 ASSERT(regs.remap_const.component_size_minus_one == 3); 111 ASSERT(regs.remap_const.component_size_minus_one == 3);
112 accelerate.BufferClear(regs.offset_out, regs.line_length_in, regs.remap_consta_value); 112 accelerate.BufferClear(regs.offset_out, regs.line_length_in,
113 regs.remap_const.remap_consta_value);
113 read_buffer.resize_destructive(regs.line_length_in * sizeof(u32)); 114 read_buffer.resize_destructive(regs.line_length_in * sizeof(u32));
114 std::span<u32> span(reinterpret_cast<u32*>(read_buffer.data()), regs.line_length_in); 115 std::span<u32> span(reinterpret_cast<u32*>(read_buffer.data()), regs.line_length_in);
115 std::ranges::fill(span, regs.remap_consta_value); 116 std::ranges::fill(span, regs.remap_const.remap_consta_value);
116 memory_manager.WriteBlockUnsafe(regs.offset_out, 117 memory_manager.WriteBlockUnsafe(regs.offset_out,
117 reinterpret_cast<u8*>(read_buffer.data()), 118 reinterpret_cast<u8*>(read_buffer.data()),
118 regs.line_length_in * sizeof(u32)); 119 regs.line_length_in * sizeof(u32));
@@ -361,21 +362,17 @@ void MaxwellDMA::ReleaseSemaphore() {
361 const auto type = regs.launch_dma.semaphore_type; 362 const auto type = regs.launch_dma.semaphore_type;
362 const GPUVAddr address = regs.semaphore.address; 363 const GPUVAddr address = regs.semaphore.address;
363 const u32 payload = regs.semaphore.payload; 364 const u32 payload = regs.semaphore.payload;
365 VideoCommon::QueryPropertiesFlags flags{VideoCommon::QueryPropertiesFlags::IsAFence};
364 switch (type) { 366 switch (type) {
365 case LaunchDMA::SemaphoreType::NONE: 367 case LaunchDMA::SemaphoreType::NONE:
366 break; 368 break;
367 case LaunchDMA::SemaphoreType::RELEASE_ONE_WORD_SEMAPHORE: { 369 case LaunchDMA::SemaphoreType::RELEASE_ONE_WORD_SEMAPHORE: {
368 std::function<void()> operation( 370 rasterizer->Query(address, VideoCommon::QueryType::Payload, flags, payload, 0);
369 [this, address, payload] { memory_manager.Write<u32>(address, payload); });
370 rasterizer->SignalFence(std::move(operation));
371 break; 371 break;
372 } 372 }
373 case LaunchDMA::SemaphoreType::RELEASE_FOUR_WORD_SEMAPHORE: { 373 case LaunchDMA::SemaphoreType::RELEASE_FOUR_WORD_SEMAPHORE: {
374 std::function<void()> operation([this, address, payload] { 374 rasterizer->Query(address, VideoCommon::QueryType::Payload,
375 memory_manager.Write<u64>(address + sizeof(u64), system.GPU().GetTicks()); 375 flags | VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0);
376 memory_manager.Write<u64>(address, payload);
377 });
378 rasterizer->SignalFence(std::move(operation));
379 break; 376 break;
380 } 377 }
381 default: 378 default:
diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h
index 69e26cb32..1a43e24b6 100644
--- a/src/video_core/engines/maxwell_dma.h
+++ b/src/video_core/engines/maxwell_dma.h
@@ -214,14 +214,15 @@ public:
214 NO_WRITE = 6, 214 NO_WRITE = 6,
215 }; 215 };
216 216
217 PackedGPUVAddr address; 217 u32 remap_consta_value;
218 u32 remap_constb_value;
218 219
219 union { 220 union {
221 BitField<0, 12, u32> dst_components_raw;
220 BitField<0, 3, Swizzle> dst_x; 222 BitField<0, 3, Swizzle> dst_x;
221 BitField<4, 3, Swizzle> dst_y; 223 BitField<4, 3, Swizzle> dst_y;
222 BitField<8, 3, Swizzle> dst_z; 224 BitField<8, 3, Swizzle> dst_z;
223 BitField<12, 3, Swizzle> dst_w; 225 BitField<12, 3, Swizzle> dst_w;
224 BitField<0, 12, u32> dst_components_raw;
225 BitField<16, 2, u32> component_size_minus_one; 226 BitField<16, 2, u32> component_size_minus_one;
226 BitField<20, 2, u32> num_src_components_minus_one; 227 BitField<20, 2, u32> num_src_components_minus_one;
227 BitField<24, 2, u32> num_dst_components_minus_one; 228 BitField<24, 2, u32> num_dst_components_minus_one;
@@ -274,55 +275,57 @@ private:
274 struct Regs { 275 struct Regs {
275 union { 276 union {
276 struct { 277 struct {
277 u32 reserved[0x40]; 278 INSERT_PADDING_BYTES_NOINIT(0x100);
278 u32 nop; 279 u32 nop;
279 u32 reserved01[0xf]; 280 INSERT_PADDING_BYTES_NOINIT(0x3C);
280 u32 pm_trigger; 281 u32 pm_trigger;
281 u32 reserved02[0x3f]; 282 INSERT_PADDING_BYTES_NOINIT(0xFC);
282 Semaphore semaphore; 283 Semaphore semaphore;
283 u32 reserved03[0x2]; 284 INSERT_PADDING_BYTES_NOINIT(0x8);
284 RenderEnable render_enable; 285 RenderEnable render_enable;
285 PhysMode src_phys_mode; 286 PhysMode src_phys_mode;
286 PhysMode dst_phys_mode; 287 PhysMode dst_phys_mode;
287 u32 reserved04[0x26]; 288 INSERT_PADDING_BYTES_NOINIT(0x98);
288 LaunchDMA launch_dma; 289 LaunchDMA launch_dma;
289 u32 reserved05[0x3f]; 290 INSERT_PADDING_BYTES_NOINIT(0xFC);
290 PackedGPUVAddr offset_in; 291 PackedGPUVAddr offset_in;
291 PackedGPUVAddr offset_out; 292 PackedGPUVAddr offset_out;
292 s32 pitch_in; 293 s32 pitch_in;
293 s32 pitch_out; 294 s32 pitch_out;
294 u32 line_length_in; 295 u32 line_length_in;
295 u32 line_count; 296 u32 line_count;
296 u32 reserved06[0xb6]; 297 INSERT_PADDING_BYTES_NOINIT(0x2E0);
297 u32 remap_consta_value;
298 u32 remap_constb_value;
299 RemapConst remap_const; 298 RemapConst remap_const;
300 DMA::Parameters dst_params; 299 DMA::Parameters dst_params;
301 u32 reserved07[0x1]; 300 INSERT_PADDING_BYTES_NOINIT(0x4);
302 DMA::Parameters src_params; 301 DMA::Parameters src_params;
303 u32 reserved08[0x275]; 302 INSERT_PADDING_BYTES_NOINIT(0x9D4);
304 u32 pm_trigger_end; 303 u32 pm_trigger_end;
305 u32 reserved09[0x3ba]; 304 INSERT_PADDING_BYTES_NOINIT(0xEE8);
306 }; 305 };
307 std::array<u32, NUM_REGS> reg_array; 306 std::array<u32, NUM_REGS> reg_array;
308 }; 307 };
309 } regs{}; 308 } regs{};
309 static_assert(sizeof(Regs) == NUM_REGS * 4);
310 310
311#define ASSERT_REG_POSITION(field_name, position) \ 311#define ASSERT_REG_POSITION(field_name, position) \
312 static_assert(offsetof(MaxwellDMA::Regs, field_name) == position * 4, \ 312 static_assert(offsetof(MaxwellDMA::Regs, field_name) == position, \
313 "Field " #field_name " has invalid position") 313 "Field " #field_name " has invalid position")
314 314
315 ASSERT_REG_POSITION(launch_dma, 0xC0); 315 ASSERT_REG_POSITION(semaphore, 0x240);
316 ASSERT_REG_POSITION(offset_in, 0x100); 316 ASSERT_REG_POSITION(render_enable, 0x254);
317 ASSERT_REG_POSITION(offset_out, 0x102); 317 ASSERT_REG_POSITION(src_phys_mode, 0x260);
318 ASSERT_REG_POSITION(pitch_in, 0x104); 318 ASSERT_REG_POSITION(launch_dma, 0x300);
319 ASSERT_REG_POSITION(pitch_out, 0x105); 319 ASSERT_REG_POSITION(offset_in, 0x400);
320 ASSERT_REG_POSITION(line_length_in, 0x106); 320 ASSERT_REG_POSITION(offset_out, 0x408);
321 ASSERT_REG_POSITION(line_count, 0x107); 321 ASSERT_REG_POSITION(pitch_in, 0x410);
322 ASSERT_REG_POSITION(remap_const, 0x1C0); 322 ASSERT_REG_POSITION(pitch_out, 0x414);
323 ASSERT_REG_POSITION(dst_params, 0x1C3); 323 ASSERT_REG_POSITION(line_length_in, 0x418);
324 ASSERT_REG_POSITION(src_params, 0x1CA); 324 ASSERT_REG_POSITION(line_count, 0x41C);
325 325 ASSERT_REG_POSITION(remap_const, 0x700);
326 ASSERT_REG_POSITION(dst_params, 0x70C);
327 ASSERT_REG_POSITION(src_params, 0x728);
328 ASSERT_REG_POSITION(pm_trigger_end, 0x1114);
326#undef ASSERT_REG_POSITION 329#undef ASSERT_REG_POSITION
327}; 330};
328 331
diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp
index 6de2543b7..8dd34c04a 100644
--- a/src/video_core/engines/puller.cpp
+++ b/src/video_core/engines/puller.cpp
@@ -82,10 +82,8 @@ void Puller::ProcessSemaphoreTriggerMethod() {
82 if (op == GpuSemaphoreOperation::WriteLong) { 82 if (op == GpuSemaphoreOperation::WriteLong) {
83 const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()}; 83 const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
84 const u32 payload = regs.semaphore_sequence; 84 const u32 payload = regs.semaphore_sequence;
85 [this, sequence_address, payload] { 85 rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload,
86 memory_manager.Write<u64>(sequence_address + sizeof(u64), gpu.GetTicks()); 86 VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0);
87 memory_manager.Write<u64>(sequence_address, payload);
88 }();
89 } else { 87 } else {
90 do { 88 do {
91 const u32 word{memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress())}; 89 const u32 word{memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress())};
@@ -120,10 +118,8 @@ void Puller::ProcessSemaphoreTriggerMethod() {
120void Puller::ProcessSemaphoreRelease() { 118void Puller::ProcessSemaphoreRelease() {
121 const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()}; 119 const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
122 const u32 payload = regs.semaphore_release; 120 const u32 payload = regs.semaphore_release;
123 std::function<void()> operation([this, sequence_address, payload] { 121 rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload,
124 memory_manager.Write<u32>(sequence_address, payload); 122 VideoCommon::QueryPropertiesFlags::IsAFence, payload, 0);
125 });
126 rasterizer->SignalFence(std::move(operation));
127} 123}
128 124
129void Puller::ProcessSemaphoreAcquire() { 125void Puller::ProcessSemaphoreAcquire() {
@@ -132,7 +128,6 @@ void Puller::ProcessSemaphoreAcquire() {
132 while (word != value) { 128 while (word != value) {
133 regs.acquire_active = true; 129 regs.acquire_active = true;
134 regs.acquire_value = value; 130 regs.acquire_value = value;
135 std::this_thread::sleep_for(std::chrono::milliseconds(1));
136 rasterizer->ReleaseFences(); 131 rasterizer->ReleaseFences();
137 word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress()); 132 word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
138 // TODO(kemathe73) figure out how to do the acquire_timeout 133 // TODO(kemathe73) figure out how to do the acquire_timeout
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h
index ab20ff30f..805a89900 100644
--- a/src/video_core/fence_manager.h
+++ b/src/video_core/fence_manager.h
@@ -55,6 +55,9 @@ public:
55 55
56 // Unlike other fences, this one doesn't 56 // Unlike other fences, this one doesn't
57 void SignalOrdering() { 57 void SignalOrdering() {
58 if constexpr (!can_async_check) {
59 TryReleasePendingFences<false>();
60 }
58 std::scoped_lock lock{buffer_cache.mutex}; 61 std::scoped_lock lock{buffer_cache.mutex};
59 buffer_cache.AccumulateFlushes(); 62 buffer_cache.AccumulateFlushes();
60 } 63 }
@@ -104,9 +107,25 @@ public:
104 SignalFence(std::move(func)); 107 SignalFence(std::move(func));
105 } 108 }
106 109
107 void WaitPendingFences() { 110 void WaitPendingFences([[maybe_unused]] bool force) {
108 if constexpr (!can_async_check) { 111 if constexpr (!can_async_check) {
109 TryReleasePendingFences<true>(); 112 TryReleasePendingFences<true>();
113 } else {
114 if (!force) {
115 return;
116 }
117 std::mutex wait_mutex;
118 std::condition_variable wait_cv;
119 std::atomic<bool> wait_finished{};
120 std::function<void()> func([&] {
121 std::scoped_lock lk(wait_mutex);
122 wait_finished.store(true, std::memory_order_relaxed);
123 wait_cv.notify_all();
124 });
125 SignalFence(std::move(func));
126 std::unique_lock lk(wait_mutex);
127 wait_cv.wait(
128 lk, [&wait_finished] { return wait_finished.load(std::memory_order_relaxed); });
110 } 129 }
111 } 130 }
112 131
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index c192e33b2..11549d448 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -102,7 +102,8 @@ struct GPU::Impl {
102 102
103 /// Signal the ending of command list. 103 /// Signal the ending of command list.
104 void OnCommandListEnd() { 104 void OnCommandListEnd() {
105 rasterizer->ReleaseFences(); 105 rasterizer->ReleaseFences(false);
106 Settings::UpdateGPUAccuracy();
106 } 107 }
107 108
108 /// Request a host GPU memory flush from the CPU. 109 /// Request a host GPU memory flush from the CPU.
@@ -220,6 +221,7 @@ struct GPU::Impl {
220 /// This can be used to launch any necessary threads and register any necessary 221 /// This can be used to launch any necessary threads and register any necessary
221 /// core timing events. 222 /// core timing events.
222 void Start() { 223 void Start() {
224 Settings::UpdateGPUAccuracy();
223 gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler); 225 gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler);
224 } 226 }
225 227
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index c4d459077..6b912027f 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -41,6 +41,9 @@ set(SHADER_FILES
41 pitch_unswizzle.comp 41 pitch_unswizzle.comp
42 present_bicubic.frag 42 present_bicubic.frag
43 present_gaussian.frag 43 present_gaussian.frag
44 queries_prefix_scan_sum.comp
45 queries_prefix_scan_sum_nosubgroups.comp
46 resolve_conditional_render.comp
44 smaa_edge_detection.vert 47 smaa_edge_detection.vert
45 smaa_edge_detection.frag 48 smaa_edge_detection.frag
46 smaa_blending_weight_calculation.vert 49 smaa_blending_weight_calculation.vert
@@ -70,6 +73,7 @@ if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND")
70endif() 73endif()
71 74
72set(GLSL_FLAGS "") 75set(GLSL_FLAGS "")
76set(SPIR_V_VERSION "spirv1.3")
73set(QUIET_FLAG "--quiet") 77set(QUIET_FLAG "--quiet")
74 78
75set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include) 79set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
@@ -123,7 +127,7 @@ foreach(FILENAME IN ITEMS ${SHADER_FILES})
123 OUTPUT 127 OUTPUT
124 ${SPIRV_HEADER_FILE} 128 ${SPIRV_HEADER_FILE}
125 COMMAND 129 COMMAND
126 ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE} 130 ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE} --target-env ${SPIR_V_VERSION}
127 MAIN_DEPENDENCY 131 MAIN_DEPENDENCY
128 ${SOURCE_FILE} 132 ${SOURCE_FILE}
129 ) 133 )
diff --git a/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp b/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp
index fc3854d18..66f2ad483 100644
--- a/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp
+++ b/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp
@@ -15,11 +15,14 @@ void main() {
15 15
16 // TODO: Specialization constants for num_samples? 16 // TODO: Specialization constants for num_samples?
17 const int num_samples = imageSamples(msaa_in); 17 const int num_samples = imageSamples(msaa_in);
18 const ivec3 msaa_size = imageSize(msaa_in);
19 const ivec3 out_size = imageSize(output_img);
20 const ivec3 scale = out_size / msaa_size;
18 for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) { 21 for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) {
19 const vec4 pixel = imageLoad(msaa_in, coords, curr_sample); 22 const vec4 pixel = imageLoad(msaa_in, coords, curr_sample);
20 23
21 const int single_sample_x = 2 * coords.x + (curr_sample & 1); 24 const int single_sample_x = scale.x * coords.x + (curr_sample & 1);
22 const int single_sample_y = 2 * coords.y + ((curr_sample / 2) & 1); 25 const int single_sample_y = scale.y * coords.y + ((curr_sample / 2) & 1);
23 const ivec3 dest_coords = ivec3(single_sample_x, single_sample_y, coords.z); 26 const ivec3 dest_coords = ivec3(single_sample_x, single_sample_y, coords.z);
24 27
25 if (any(greaterThanEqual(dest_coords, imageSize(output_img)))) { 28 if (any(greaterThanEqual(dest_coords, imageSize(output_img)))) {
diff --git a/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp b/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp
index dedd962f1..c7ce38efa 100644
--- a/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp
+++ b/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp
@@ -15,9 +15,12 @@ void main() {
15 15
16 // TODO: Specialization constants for num_samples? 16 // TODO: Specialization constants for num_samples?
17 const int num_samples = imageSamples(output_msaa); 17 const int num_samples = imageSamples(output_msaa);
18 const ivec3 msaa_size = imageSize(output_msaa);
19 const ivec3 out_size = imageSize(img_in);
20 const ivec3 scale = out_size / msaa_size;
18 for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) { 21 for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) {
19 const int single_sample_x = 2 * coords.x + (curr_sample & 1); 22 const int single_sample_x = scale.x * coords.x + (curr_sample & 1);
20 const int single_sample_y = 2 * coords.y + ((curr_sample / 2) & 1); 23 const int single_sample_y = scale.y * coords.y + ((curr_sample / 2) & 1);
21 const ivec3 single_coords = ivec3(single_sample_x, single_sample_y, coords.z); 24 const ivec3 single_coords = ivec3(single_sample_x, single_sample_y, coords.z);
22 25
23 if (any(greaterThanEqual(single_coords, imageSize(img_in)))) { 26 if (any(greaterThanEqual(single_coords, imageSize(img_in)))) {
diff --git a/src/video_core/host_shaders/queries_prefix_scan_sum.comp b/src/video_core/host_shaders/queries_prefix_scan_sum.comp
new file mode 100644
index 000000000..6faa8981f
--- /dev/null
+++ b/src/video_core/host_shaders/queries_prefix_scan_sum.comp
@@ -0,0 +1,173 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#version 460 core
5
6#extension GL_KHR_shader_subgroup_basic : require
7#extension GL_KHR_shader_subgroup_shuffle : require
8#extension GL_KHR_shader_subgroup_shuffle_relative : require
9#extension GL_KHR_shader_subgroup_arithmetic : require
10
11#ifdef VULKAN
12
13#define HAS_EXTENDED_TYPES 1
14#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
15#define END_PUSH_CONSTANTS };
16#define UNIFORM(n)
17#define BINDING_INPUT_BUFFER 0
18#define BINDING_OUTPUT_IMAGE 1
19
20#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
21
22#extension GL_NV_gpu_shader5 : enable
23#ifdef GL_NV_gpu_shader5
24#define HAS_EXTENDED_TYPES 1
25#else
26#define HAS_EXTENDED_TYPES 0
27#endif
28#define BEGIN_PUSH_CONSTANTS
29#define END_PUSH_CONSTANTS
30#define UNIFORM(n) layout(location = n) uniform
31#define BINDING_INPUT_BUFFER 0
32#define BINDING_OUTPUT_IMAGE 0
33
34#endif
35
36BEGIN_PUSH_CONSTANTS
37UNIFORM(0) uint min_accumulation_base;
38UNIFORM(1) uint max_accumulation_base;
39UNIFORM(2) uint accumulation_limit;
40UNIFORM(3) uint buffer_offset;
41END_PUSH_CONSTANTS
42
43#define LOCAL_RESULTS 8
44#define QUERIES_PER_INVOC 2048
45
46layout(local_size_x = QUERIES_PER_INVOC / LOCAL_RESULTS) in;
47
48layout(std430, binding = 0) readonly buffer block1 {
49 uvec2 input_data[];
50};
51
52layout(std430, binding = 1) coherent buffer block2 {
53 uvec2 output_data[];
54};
55
56layout(std430, binding = 2) coherent buffer block3 {
57 uvec2 accumulated_data;
58};
59
60shared uvec2 shared_data[128];
61
62// Simple Uint64 add that uses 2 uint variables for GPUs that don't support uint64
63uvec2 AddUint64(uvec2 value_1, uvec2 value_2) {
64 uint carry = 0;
65 uvec2 result;
66 result.x = uaddCarry(value_1.x, value_2.x, carry);
67 result.y = value_1.y + value_2.y + carry;
68 return result;
69}
70
71// do subgroup Prefix Sum using Hillis and Steele's algorithm
72uvec2 subgroupInclusiveAddUint64(uvec2 value) {
73 uvec2 result = value;
74 for (uint i = 1; i < gl_SubgroupSize; i *= 2) {
75 uvec2 other = subgroupShuffleUp(result, i); // get value from subgroup_inv_id - i;
76 if (i <= gl_SubgroupInvocationID) {
77 result = AddUint64(result, other);
78 }
79 }
80 return result;
81}
82
83// Writes down the results to the output buffer and to the accumulation buffer
84void WriteResults(uvec2 results[LOCAL_RESULTS]) {
85 const uint current_id = gl_LocalInvocationID.x;
86 const uvec2 accum = accumulated_data;
87 for (uint i = 0; i < LOCAL_RESULTS; i++) {
88 uvec2 base_data = current_id * LOCAL_RESULTS + i < min_accumulation_base ? accum : uvec2(0, 0);
89 AddUint64(results[i], base_data);
90 }
91 for (uint i = 0; i < LOCAL_RESULTS; i++) {
92 output_data[buffer_offset + current_id * LOCAL_RESULTS + i] = results[i];
93 }
94 uint index = accumulation_limit % LOCAL_RESULTS;
95 uint base_id = accumulation_limit / LOCAL_RESULTS;
96 if (min_accumulation_base >= accumulation_limit + 1) {
97 if (current_id == base_id) {
98 accumulated_data = results[index];
99 }
100 return;
101 }
102 // We have that ugly case in which the accumulation data is reset in the middle somewhere.
103 barrier();
104 groupMemoryBarrier();
105
106 if (current_id == base_id) {
107 uvec2 reset_value = output_data[max_accumulation_base - 1];
108 // Calculate two complement / negate manually
109 reset_value = AddUint64(uvec2(1,0), ~reset_value);
110 accumulated_data = AddUint64(results[index], reset_value);
111 }
112}
113
114void main() {
115 const uint subgroup_inv_id = gl_SubgroupInvocationID;
116 const uint subgroup_id = gl_SubgroupID + gl_WorkGroupID.x * gl_NumSubgroups;
117 const uint last_subgroup_id = subgroupMax(subgroup_inv_id);
118 const uint current_id = gl_LocalInvocationID.x;
119 const uint total_work = accumulation_limit;
120 const uint last_result_id = LOCAL_RESULTS - 1;
121 uvec2 data[LOCAL_RESULTS];
122 for (uint i = 0; i < LOCAL_RESULTS; i++) {
123 data[i] = input_data[buffer_offset + current_id * LOCAL_RESULTS + i];
124 }
125 uvec2 results[LOCAL_RESULTS];
126 results[0] = data[0];
127 for (uint i = 1; i < LOCAL_RESULTS; i++) {
128 results[i] = AddUint64(data[i], results[i - 1]);
129 }
130 // make sure all input data has been loaded
131 subgroupBarrier();
132 subgroupMemoryBarrier();
133
134 // on the last local result, do a subgroup inclusive scan sum
135 results[last_result_id] = subgroupInclusiveAddUint64(results[last_result_id]);
136 // get the last local result from the subgroup behind the current
137 uvec2 result_behind = subgroupShuffleUp(results[last_result_id], 1);
138 if (subgroup_inv_id != 0) {
139 for (uint i = 1; i < LOCAL_RESULTS; i++) {
140 results[i - 1] = AddUint64(results[i - 1], result_behind);
141 }
142 }
143
144 // if we had less queries than our subgroup, just write down the results.
145 if (total_work <= gl_SubgroupSize * LOCAL_RESULTS) { // This condition is constant per dispatch.
146 WriteResults(results);
147 return;
148 }
149
150 // We now have more, so lets write the last result into shared memory.
151 // Only pick the last subgroup.
152 if (subgroup_inv_id == last_subgroup_id) {
153 shared_data[subgroup_id] = results[last_result_id];
154 }
155 // wait until everyone loaded their stuffs
156 barrier();
157 memoryBarrierShared();
158
159 // only if it's not the first subgroup
160 if (subgroup_id != 0) {
161 // get the results from some previous invocation
162 uvec2 tmp = shared_data[subgroup_inv_id];
163 subgroupBarrier();
164 subgroupMemoryBarrierShared();
165 tmp = subgroupInclusiveAddUint64(tmp);
166 // obtain the result that would be equivalent to the previous result
167 uvec2 shuffled_result = subgroupShuffle(tmp, subgroup_id - 1);
168 for (uint i = 0; i < LOCAL_RESULTS; i++) {
169 results[i] = AddUint64(results[i], shuffled_result);
170 }
171 }
172 WriteResults(results);
173} \ No newline at end of file
diff --git a/src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp b/src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp
new file mode 100644
index 000000000..559a213b9
--- /dev/null
+++ b/src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp
@@ -0,0 +1,138 @@
1// SPDX-FileCopyrightText: Copyright 2015 Graham Sellers, Richard Wright Jr. and Nicholas Haemel
2// SPDX-License-Identifier: MIT
3
4// Code obtained from OpenGL SuperBible, Seventh Edition by Graham Sellers, Richard Wright Jr. and
5// Nicholas Haemel. Modified to suit needs.
6
7#version 460 core
8
9#ifdef VULKAN
10
11#define HAS_EXTENDED_TYPES 1
12#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
13#define END_PUSH_CONSTANTS };
14#define UNIFORM(n)
15#define BINDING_INPUT_BUFFER 0
16#define BINDING_OUTPUT_IMAGE 1
17
18#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
19
20#extension GL_NV_gpu_shader5 : enable
21#ifdef GL_NV_gpu_shader5
22#define HAS_EXTENDED_TYPES 1
23#else
24#define HAS_EXTENDED_TYPES 0
25#endif
26#define BEGIN_PUSH_CONSTANTS
27#define END_PUSH_CONSTANTS
28#define UNIFORM(n) layout(location = n) uniform
29#define BINDING_INPUT_BUFFER 0
30#define BINDING_OUTPUT_IMAGE 0
31
32#endif
33
34BEGIN_PUSH_CONSTANTS
35UNIFORM(0) uint min_accumulation_base;
36UNIFORM(1) uint max_accumulation_base;
37UNIFORM(2) uint accumulation_limit;
38UNIFORM(3) uint buffer_offset;
39END_PUSH_CONSTANTS
40
41#define LOCAL_RESULTS 4
42#define QUERIES_PER_INVOC 2048
43
44layout(local_size_x = QUERIES_PER_INVOC / LOCAL_RESULTS) in;
45
46layout(std430, binding = 0) readonly buffer block1 {
47 uvec2 input_data[gl_WorkGroupSize.x * LOCAL_RESULTS];
48};
49
50layout(std430, binding = 1) writeonly coherent buffer block2 {
51 uvec2 output_data[gl_WorkGroupSize.x * LOCAL_RESULTS];
52};
53
54layout(std430, binding = 2) coherent buffer block3 {
55 uvec2 accumulated_data;
56};
57
58shared uvec2 shared_data[gl_WorkGroupSize.x * LOCAL_RESULTS];
59
60uvec2 AddUint64(uvec2 value_1, uvec2 value_2) {
61 uint carry = 0;
62 uvec2 result;
63 result.x = uaddCarry(value_1.x, value_2.x, carry);
64 result.y = value_1.y + value_2.y + carry;
65 return result;
66}
67
68void main(void) {
69 uint id = gl_LocalInvocationID.x;
70 uvec2 base_value[LOCAL_RESULTS];
71 const uvec2 accum = accumulated_data;
72 for (uint i = 0; i < LOCAL_RESULTS; i++) {
73 base_value[i] = (buffer_offset + id * LOCAL_RESULTS + i) < min_accumulation_base
74 ? accumulated_data
75 : uvec2(0);
76 }
77 uint work_size = gl_WorkGroupSize.x;
78 uint rd_id;
79 uint wr_id;
80 uint mask;
81 uvec2 inputs[LOCAL_RESULTS];
82 for (uint i = 0; i < LOCAL_RESULTS; i++) {
83 inputs[i] = input_data[buffer_offset + id * LOCAL_RESULTS + i];
84 }
85 // The number of steps is the log base 2 of the
86 // work group size, which should be a power of 2
87 const uint steps = uint(log2(work_size)) + uint(log2(LOCAL_RESULTS));
88 uint step = 0;
89
90 // Each invocation is responsible for the content of
91 // two elements of the output array
92 for (uint i = 0; i < LOCAL_RESULTS; i++) {
93 shared_data[id * LOCAL_RESULTS + i] = inputs[i];
94 }
95 // Synchronize to make sure that everyone has initialized
96 // their elements of shared_data[] with data loaded from
97 // the input arrays
98 barrier();
99 memoryBarrierShared();
100 // For each step...
101 for (step = 0; step < steps; step++) {
102 // Calculate the read and write index in the
103 // shared array
104 mask = (1 << step) - 1;
105 rd_id = ((id >> step) << (step + 1)) + mask;
106 wr_id = rd_id + 1 + (id & mask);
107 // Accumulate the read data into our element
108
109 shared_data[wr_id] = AddUint64(shared_data[rd_id], shared_data[wr_id]);
110 // Synchronize again to make sure that everyone
111 // has caught up with us
112 barrier();
113 memoryBarrierShared();
114 }
115 // Add the accumulation
116 for (uint i = 0; i < LOCAL_RESULTS; i++) {
117 shared_data[id * LOCAL_RESULTS + i] =
118 AddUint64(shared_data[id * LOCAL_RESULTS + i], base_value[i]);
119 }
120 barrier();
121 memoryBarrierShared();
122
123 // Finally write our data back to the output buffer
124 for (uint i = 0; i < LOCAL_RESULTS; i++) {
125 output_data[buffer_offset + id * LOCAL_RESULTS + i] = shared_data[id * LOCAL_RESULTS + i];
126 }
127 if (id == 0) {
128 if (min_accumulation_base >= accumulation_limit + 1) {
129 accumulated_data = shared_data[accumulation_limit];
130 return;
131 }
132 uvec2 reset_value = shared_data[max_accumulation_base - 1];
133 uvec2 final_value = shared_data[accumulation_limit];
134 // Two complements
135 reset_value = AddUint64(uvec2(1, 0), ~reset_value);
136 accumulated_data = AddUint64(final_value, reset_value);
137 }
138} \ No newline at end of file
diff --git a/src/video_core/host_shaders/resolve_conditional_render.comp b/src/video_core/host_shaders/resolve_conditional_render.comp
new file mode 100644
index 000000000..307e77d1a
--- /dev/null
+++ b/src/video_core/host_shaders/resolve_conditional_render.comp
@@ -0,0 +1,20 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#version 450
5
6layout(local_size_x = 1) in;
7
8layout(std430, binding = 0) buffer Query {
9 uvec2 initial;
10 uvec2 unknown;
11 uvec2 current;
12};
13
14layout(std430, binding = 1) buffer Result {
15 uint result;
16};
17
18void main() {
19 result = all(equal(initial, current)) ? 1 : 0;
20}
diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp
index 6272a4652..046c8085e 100644
--- a/src/video_core/macro/macro_hle.cpp
+++ b/src/video_core/macro/macro_hle.cpp
@@ -67,6 +67,7 @@ public:
67 } 67 }
68 68
69 auto& params = maxwell3d.draw_manager->GetIndirectParams(); 69 auto& params = maxwell3d.draw_manager->GetIndirectParams();
70 params.is_byte_count = false;
70 params.is_indexed = false; 71 params.is_indexed = false;
71 params.include_count = false; 72 params.include_count = false;
72 params.count_start_address = 0; 73 params.count_start_address = 0;
@@ -161,6 +162,7 @@ public:
161 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); 162 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance);
162 } 163 }
163 auto& params = maxwell3d.draw_manager->GetIndirectParams(); 164 auto& params = maxwell3d.draw_manager->GetIndirectParams();
165 params.is_byte_count = false;
164 params.is_indexed = true; 166 params.is_indexed = true;
165 params.include_count = false; 167 params.include_count = false;
166 params.count_start_address = 0; 168 params.count_start_address = 0;
@@ -256,6 +258,7 @@ public:
256 const u32 estimate = static_cast<u32>(maxwell3d.EstimateIndexBufferSize()); 258 const u32 estimate = static_cast<u32>(maxwell3d.EstimateIndexBufferSize());
257 maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; 259 maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
258 auto& params = maxwell3d.draw_manager->GetIndirectParams(); 260 auto& params = maxwell3d.draw_manager->GetIndirectParams();
261 params.is_byte_count = false;
259 params.is_indexed = true; 262 params.is_indexed = true;
260 params.include_count = true; 263 params.include_count = true;
261 params.count_start_address = maxwell3d.GetMacroAddress(4); 264 params.count_start_address = maxwell3d.GetMacroAddress(4);
@@ -319,6 +322,47 @@ private:
319 } 322 }
320}; 323};
321 324
325class HLE_DrawIndirectByteCount final : public HLEMacroImpl {
326public:
327 explicit HLE_DrawIndirectByteCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
328
329 void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
330 auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0] & 0xFFFFU);
331 if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) {
332 Fallback(parameters);
333 return;
334 }
335
336 auto& params = maxwell3d.draw_manager->GetIndirectParams();
337 params.is_byte_count = true;
338 params.is_indexed = false;
339 params.include_count = false;
340 params.count_start_address = 0;
341 params.indirect_start_address = maxwell3d.GetMacroAddress(2);
342 params.buffer_size = 4;
343 params.max_draw_counts = 1;
344 params.stride = parameters[1];
345 maxwell3d.regs.draw.begin = parameters[0];
346 maxwell3d.regs.draw_auto_stride = parameters[1];
347 maxwell3d.regs.draw_auto_byte_count = parameters[2];
348
349 maxwell3d.draw_manager->DrawArrayIndirect(topology);
350 }
351
352private:
353 void Fallback(const std::vector<u32>& parameters) {
354 maxwell3d.RefreshParameters();
355
356 maxwell3d.regs.draw.begin = parameters[0];
357 maxwell3d.regs.draw_auto_stride = parameters[1];
358 maxwell3d.regs.draw_auto_byte_count = parameters[2];
359
360 maxwell3d.draw_manager->DrawArray(
361 maxwell3d.regs.draw.topology, 0,
362 maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1);
363 }
364};
365
322class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl { 366class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl {
323public: 367public:
324 explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} 368 explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
@@ -536,6 +580,11 @@ HLEMacro::HLEMacro(Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {
536 [](Maxwell3D& maxwell3d__) -> std::unique_ptr<CachedMacro> { 580 [](Maxwell3D& maxwell3d__) -> std::unique_ptr<CachedMacro> {
537 return std::make_unique<HLE_TransformFeedbackSetup>(maxwell3d__); 581 return std::make_unique<HLE_TransformFeedbackSetup>(maxwell3d__);
538 })); 582 }));
583 builders.emplace(0xB5F74EDB717278ECULL,
584 std::function<std::unique_ptr<CachedMacro>(Maxwell3D&)>(
585 [](Maxwell3D& maxwell3d__) -> std::unique_ptr<CachedMacro> {
586 return std::make_unique<HLE_DrawIndirectByteCount>(maxwell3d__);
587 }));
539} 588}
540 589
541HLEMacro::~HLEMacro() = default; 590HLEMacro::~HLEMacro() = default;
diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h
index 7047e2e63..9fcaeeac7 100644
--- a/src/video_core/query_cache.h
+++ b/src/video_core/query_cache.h
@@ -25,6 +25,13 @@
25#include "video_core/rasterizer_interface.h" 25#include "video_core/rasterizer_interface.h"
26#include "video_core/texture_cache/slot_vector.h" 26#include "video_core/texture_cache/slot_vector.h"
27 27
28namespace VideoCore {
29enum class QueryType {
30 SamplesPassed,
31};
32constexpr std::size_t NumQueryTypes = 1;
33} // namespace VideoCore
34
28namespace VideoCommon { 35namespace VideoCommon {
29 36
30using AsyncJobId = SlotId; 37using AsyncJobId = SlotId;
@@ -98,10 +105,10 @@ private:
98}; 105};
99 106
100template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter> 107template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter>
101class QueryCacheBase : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> { 108class QueryCacheLegacy : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
102public: 109public:
103 explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_, 110 explicit QueryCacheLegacy(VideoCore::RasterizerInterface& rasterizer_,
104 Core::Memory::Memory& cpu_memory_) 111 Core::Memory::Memory& cpu_memory_)
105 : rasterizer{rasterizer_}, 112 : rasterizer{rasterizer_},
106 // Use reinterpret_cast instead of static_cast as workaround for 113 // Use reinterpret_cast instead of static_cast as workaround for
107 // UBSan bug (https://github.com/llvm/llvm-project/issues/59060) 114 // UBSan bug (https://github.com/llvm/llvm-project/issues/59060)
diff --git a/src/video_core/query_cache/bank_base.h b/src/video_core/query_cache/bank_base.h
new file mode 100644
index 000000000..420927091
--- /dev/null
+++ b/src/video_core/query_cache/bank_base.h
@@ -0,0 +1,104 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <atomic>
7#include <deque>
8#include <utility>
9
10#include "common/common_types.h"
11
12namespace VideoCommon {
13
14class BankBase {
15protected:
16 const size_t base_bank_size{};
17 size_t bank_size{};
18 std::atomic<size_t> references{};
19 size_t current_slot{};
20
21public:
22 explicit BankBase(size_t bank_size_) : base_bank_size{bank_size_}, bank_size(bank_size_) {}
23
24 virtual ~BankBase() = default;
25
26 virtual std::pair<bool, size_t> Reserve() {
27 if (IsClosed()) {
28 return {false, bank_size};
29 }
30 const size_t result = current_slot++;
31 return {true, result};
32 }
33
34 virtual void Reset() {
35 current_slot = 0;
36 references = 0;
37 bank_size = base_bank_size;
38 }
39
40 size_t Size() const {
41 return bank_size;
42 }
43
44 void AddReference(size_t how_many = 1) {
45 references.fetch_add(how_many, std::memory_order_relaxed);
46 }
47
48 void CloseReference(size_t how_many = 1) {
49 if (how_many > references.load(std::memory_order_relaxed)) {
50 UNREACHABLE();
51 }
52 references.fetch_sub(how_many, std::memory_order_relaxed);
53 }
54
55 void Close() {
56 bank_size = current_slot;
57 }
58
59 bool IsClosed() const {
60 return current_slot >= bank_size;
61 }
62
63 bool IsDead() const {
64 return IsClosed() && references == 0;
65 }
66};
67
68template <typename BankType>
69class BankPool {
70private:
71 std::deque<BankType> bank_pool;
72 std::deque<size_t> bank_indices;
73
74public:
75 BankPool() = default;
76 ~BankPool() = default;
77
78 // Reserve a bank from the pool and return its index
79 template <typename Func>
80 size_t ReserveBank(Func&& builder) {
81 if (!bank_indices.empty() && bank_pool[bank_indices.front()].IsDead()) {
82 size_t new_index = bank_indices.front();
83 bank_indices.pop_front();
84 bank_pool[new_index].Reset();
85 return new_index;
86 }
87 size_t new_index = bank_pool.size();
88 builder(bank_pool, new_index);
89 bank_indices.push_back(new_index);
90 return new_index;
91 }
92
93 // Get a reference to a bank using its index
94 BankType& GetBank(size_t index) {
95 return bank_pool[index];
96 }
97
98 // Get the total number of banks in the pool
99 size_t BankCount() const {
100 return bank_pool.size();
101 }
102};
103
104} // namespace VideoCommon
diff --git a/src/video_core/query_cache/query_base.h b/src/video_core/query_cache/query_base.h
new file mode 100644
index 000000000..1d786b3a7
--- /dev/null
+++ b/src/video_core/query_cache/query_base.h
@@ -0,0 +1,70 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8
9namespace VideoCommon {
10
11enum class QueryFlagBits : u32 {
12 HasTimestamp = 1 << 0, ///< Indicates if this query has a timestamp.
13 IsFinalValueSynced = 1 << 1, ///< Indicates if the query has been synced in the host
14 IsHostSynced = 1 << 2, ///< Indicates if the query has been synced in the host
15 IsGuestSynced = 1 << 3, ///< Indicates if the query has been synced with the guest.
16 IsHostManaged = 1 << 4, ///< Indicates if this query points to a host query
17 IsRewritten = 1 << 5, ///< Indicates if this query was rewritten by another query
18 IsInvalidated = 1 << 6, ///< Indicates the value of th query has been nullified.
19 IsOrphan = 1 << 7, ///< Indicates the query has not been set by a guest query.
20 IsFence = 1 << 8, ///< Indicates the query is a fence.
21};
22DECLARE_ENUM_FLAG_OPERATORS(QueryFlagBits)
23
24class QueryBase {
25public:
26 VAddr guest_address{};
27 QueryFlagBits flags{};
28 u64 value{};
29
30protected:
31 // Default constructor
32 QueryBase() = default;
33
34 // Parameterized constructor
35 QueryBase(VAddr address, QueryFlagBits flags_, u64 value_)
36 : guest_address(address), flags(flags_), value{value_} {}
37};
38
39class GuestQuery : public QueryBase {
40public:
41 // Parameterized constructor
42 GuestQuery(bool isLong, VAddr address, u64 queryValue)
43 : QueryBase(address, QueryFlagBits::IsFinalValueSynced, queryValue) {
44 if (isLong) {
45 flags |= QueryFlagBits::HasTimestamp;
46 }
47 }
48};
49
50class HostQueryBase : public QueryBase {
51public:
52 // Default constructor
53 HostQueryBase() : QueryBase(0, QueryFlagBits::IsHostManaged | QueryFlagBits::IsOrphan, 0) {}
54
55 // Parameterized constructor
56 HostQueryBase(bool has_timestamp, VAddr address)
57 : QueryBase(address, QueryFlagBits::IsHostManaged, 0), start_bank_id{}, size_banks{},
58 start_slot{}, size_slots{} {
59 if (has_timestamp) {
60 flags |= QueryFlagBits::HasTimestamp;
61 }
62 }
63
64 u32 start_bank_id{};
65 u32 size_banks{};
66 size_t start_slot{};
67 size_t size_slots{};
68};
69
70} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/query_cache/query_cache.h b/src/video_core/query_cache/query_cache.h
new file mode 100644
index 000000000..78b42b518
--- /dev/null
+++ b/src/video_core/query_cache/query_cache.h
@@ -0,0 +1,580 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <array>
7#include <deque>
8#include <memory>
9#include <mutex>
10#include <unordered_map>
11#include <utility>
12
13#include "common/assert.h"
14#include "common/common_types.h"
15#include "common/logging/log.h"
16#include "common/scope_exit.h"
17#include "common/settings.h"
18#include "core/memory.h"
19#include "video_core/engines/maxwell_3d.h"
20#include "video_core/gpu.h"
21#include "video_core/memory_manager.h"
22#include "video_core/query_cache/bank_base.h"
23#include "video_core/query_cache/query_base.h"
24#include "video_core/query_cache/query_cache_base.h"
25#include "video_core/query_cache/query_stream.h"
26#include "video_core/query_cache/types.h"
27
28namespace VideoCommon {
29
30using Maxwell = Tegra::Engines::Maxwell3D;
31
32struct SyncValuesStruct {
33 VAddr address;
34 u64 value;
35 u64 size;
36
37 static constexpr bool GeneratesBaseBuffer = true;
38};
39
40template <typename Traits>
41class GuestStreamer : public SimpleStreamer<GuestQuery> {
42public:
43 using RuntimeType = typename Traits::RuntimeType;
44
45 GuestStreamer(size_t id_, RuntimeType& runtime_)
46 : SimpleStreamer<GuestQuery>(id_), runtime{runtime_} {}
47
48 virtual ~GuestStreamer() = default;
49
50 size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
51 std::optional<u32> subreport = std::nullopt) override {
52 auto new_id = BuildQuery(has_timestamp, address, static_cast<u64>(value));
53 pending_sync.push_back(new_id);
54 return new_id;
55 }
56
57 bool HasPendingSync() const override {
58 return !pending_sync.empty();
59 }
60
61 void SyncWrites() override {
62 if (pending_sync.empty()) {
63 return;
64 }
65 std::vector<SyncValuesStruct> sync_values;
66 sync_values.reserve(pending_sync.size());
67 for (size_t pending_id : pending_sync) {
68 auto& query = slot_queries[pending_id];
69 if (True(query.flags & QueryFlagBits::IsRewritten) ||
70 True(query.flags & QueryFlagBits::IsInvalidated)) {
71 continue;
72 }
73 query.flags |= QueryFlagBits::IsHostSynced;
74 sync_values.emplace_back(SyncValuesStruct{
75 .address = query.guest_address,
76 .value = query.value,
77 .size = static_cast<u64>(True(query.flags & QueryFlagBits::HasTimestamp) ? 8 : 4)});
78 }
79 pending_sync.clear();
80 if (sync_values.size() > 0) {
81 runtime.template SyncValues<SyncValuesStruct>(sync_values);
82 }
83 }
84
85private:
86 RuntimeType& runtime;
87 std::deque<size_t> pending_sync;
88};
89
90template <typename Traits>
91class StubStreamer : public GuestStreamer<Traits> {
92public:
93 using RuntimeType = typename Traits::RuntimeType;
94
95 StubStreamer(size_t id_, RuntimeType& runtime_, u32 stub_value_)
96 : GuestStreamer<Traits>(id_, runtime_), stub_value{stub_value_} {}
97
98 ~StubStreamer() override = default;
99
100 size_t WriteCounter(VAddr address, bool has_timestamp, [[maybe_unused]] u32 value,
101 std::optional<u32> subreport = std::nullopt) override {
102 size_t new_id =
103 GuestStreamer<Traits>::WriteCounter(address, has_timestamp, stub_value, subreport);
104 return new_id;
105 }
106
107private:
108 u32 stub_value;
109};
110
111template <typename Traits>
112struct QueryCacheBase<Traits>::QueryCacheBaseImpl {
113 using RuntimeType = typename Traits::RuntimeType;
114
115 QueryCacheBaseImpl(QueryCacheBase<Traits>* owner_, VideoCore::RasterizerInterface& rasterizer_,
116 Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_, Tegra::GPU& gpu_)
117 : owner{owner_}, rasterizer{rasterizer_},
118 cpu_memory{cpu_memory_}, runtime{runtime_}, gpu{gpu_} {
119 streamer_mask = 0;
120 for (size_t i = 0; i < static_cast<size_t>(QueryType::MaxQueryTypes); i++) {
121 streamers[i] = runtime.GetStreamerInterface(static_cast<QueryType>(i));
122 if (streamers[i]) {
123 streamer_mask |= 1ULL << streamers[i]->GetId();
124 }
125 }
126 }
127
128 template <typename Func>
129 void ForEachStreamerIn(u64 mask, Func&& func) {
130 static constexpr bool RETURNS_BOOL =
131 std::is_same_v<std::invoke_result<Func, StreamerInterface*>, bool>;
132 while (mask != 0) {
133 size_t position = std::countr_zero(mask);
134 mask &= ~(1ULL << position);
135 if constexpr (RETURNS_BOOL) {
136 if (func(streamers[position])) {
137 return;
138 }
139 } else {
140 func(streamers[position]);
141 }
142 }
143 }
144
145 template <typename Func>
146 void ForEachStreamer(Func&& func) {
147 ForEachStreamerIn(streamer_mask, func);
148 }
149
150 QueryBase* ObtainQuery(QueryCacheBase<Traits>::QueryLocation location) {
151 size_t which_stream = location.stream_id.Value();
152 auto* streamer = streamers[which_stream];
153 if (!streamer) {
154 return nullptr;
155 }
156 return streamer->GetQuery(location.query_id.Value());
157 }
158
159 QueryCacheBase<Traits>* owner;
160 VideoCore::RasterizerInterface& rasterizer;
161 Core::Memory::Memory& cpu_memory;
162 RuntimeType& runtime;
163 Tegra::GPU& gpu;
164 std::array<StreamerInterface*, static_cast<size_t>(QueryType::MaxQueryTypes)> streamers;
165 u64 streamer_mask;
166 std::mutex flush_guard;
167 std::deque<u64> flushes_pending;
168 std::vector<QueryCacheBase<Traits>::QueryLocation> pending_unregister;
169};
170
171template <typename Traits>
172QueryCacheBase<Traits>::QueryCacheBase(Tegra::GPU& gpu_,
173 VideoCore::RasterizerInterface& rasterizer_,
174 Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_)
175 : cached_queries{} {
176 impl = std::make_unique<QueryCacheBase<Traits>::QueryCacheBaseImpl>(
177 this, rasterizer_, cpu_memory_, runtime_, gpu_);
178}
179
180template <typename Traits>
181QueryCacheBase<Traits>::~QueryCacheBase() = default;
182
183template <typename Traits>
184void QueryCacheBase<Traits>::CounterEnable(QueryType counter_type, bool is_enabled) {
185 size_t index = static_cast<size_t>(counter_type);
186 StreamerInterface* streamer = impl->streamers[index];
187 if (!streamer) [[unlikely]] {
188 UNREACHABLE();
189 return;
190 }
191 if (is_enabled) {
192 streamer->StartCounter();
193 } else {
194 streamer->PauseCounter();
195 }
196}
197
198template <typename Traits>
199void QueryCacheBase<Traits>::CounterClose(QueryType counter_type) {
200 size_t index = static_cast<size_t>(counter_type);
201 StreamerInterface* streamer = impl->streamers[index];
202 if (!streamer) [[unlikely]] {
203 UNREACHABLE();
204 return;
205 }
206 streamer->CloseCounter();
207}
208
209template <typename Traits>
210void QueryCacheBase<Traits>::CounterReset(QueryType counter_type) {
211 size_t index = static_cast<size_t>(counter_type);
212 StreamerInterface* streamer = impl->streamers[index];
213 if (!streamer) [[unlikely]] {
214 UNIMPLEMENTED();
215 return;
216 }
217 streamer->ResetCounter();
218}
219
220template <typename Traits>
221void QueryCacheBase<Traits>::BindToChannel(s32 id) {
222 VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo>::BindToChannel(id);
223 impl->runtime.Bind3DEngine(maxwell3d);
224}
225
226template <typename Traits>
227void QueryCacheBase<Traits>::CounterReport(GPUVAddr addr, QueryType counter_type,
228 QueryPropertiesFlags flags, u32 payload, u32 subreport) {
229 const bool has_timestamp = True(flags & QueryPropertiesFlags::HasTimeout);
230 const bool is_fence = True(flags & QueryPropertiesFlags::IsAFence);
231 size_t streamer_id = static_cast<size_t>(counter_type);
232 auto* streamer = impl->streamers[streamer_id];
233 if (streamer == nullptr) [[unlikely]] {
234 counter_type = QueryType::Payload;
235 payload = 1U;
236 streamer_id = static_cast<size_t>(counter_type);
237 streamer = impl->streamers[streamer_id];
238 }
239 auto cpu_addr_opt = gpu_memory->GpuToCpuAddress(addr);
240 if (!cpu_addr_opt) [[unlikely]] {
241 return;
242 }
243 VAddr cpu_addr = *cpu_addr_opt;
244 const size_t new_query_id = streamer->WriteCounter(cpu_addr, has_timestamp, payload, subreport);
245 auto* query = streamer->GetQuery(new_query_id);
246 if (is_fence) {
247 query->flags |= QueryFlagBits::IsFence;
248 }
249 QueryLocation query_location{};
250 query_location.stream_id.Assign(static_cast<u32>(streamer_id));
251 query_location.query_id.Assign(static_cast<u32>(new_query_id));
252 const auto gen_caching_indexing = [](VAddr cur_addr) {
253 return std::make_pair<u64, u32>(cur_addr >> Core::Memory::YUZU_PAGEBITS,
254 static_cast<u32>(cur_addr & Core::Memory::YUZU_PAGEMASK));
255 };
256 u8* pointer = impl->cpu_memory.GetPointer(cpu_addr);
257 u8* pointer_timestamp = impl->cpu_memory.GetPointer(cpu_addr + 8);
258 bool is_synced = !Settings::IsGPULevelHigh() && is_fence;
259
260 std::function<void()> operation([this, is_synced, streamer, query_base = query, query_location,
261 pointer, pointer_timestamp] {
262 if (True(query_base->flags & QueryFlagBits::IsInvalidated)) {
263 if (!is_synced) [[likely]] {
264 impl->pending_unregister.push_back(query_location);
265 }
266 return;
267 }
268 if (False(query_base->flags & QueryFlagBits::IsFinalValueSynced)) [[unlikely]] {
269 UNREACHABLE();
270 return;
271 }
272 query_base->value += streamer->GetAmmendValue();
273 streamer->SetAccumulationValue(query_base->value);
274 if (True(query_base->flags & QueryFlagBits::HasTimestamp)) {
275 u64 timestamp = impl->gpu.GetTicks();
276 std::memcpy(pointer_timestamp, &timestamp, sizeof(timestamp));
277 std::memcpy(pointer, &query_base->value, sizeof(query_base->value));
278 } else {
279 u32 value = static_cast<u32>(query_base->value);
280 std::memcpy(pointer, &value, sizeof(value));
281 }
282 if (!is_synced) [[likely]] {
283 impl->pending_unregister.push_back(query_location);
284 }
285 });
286 if (is_fence) {
287 impl->rasterizer.SignalFence(std::move(operation));
288 } else {
289 if (!Settings::IsGPULevelHigh() && counter_type == QueryType::Payload) {
290 if (has_timestamp) {
291 u64 timestamp = impl->gpu.GetTicks();
292 u64 value = static_cast<u64>(payload);
293 std::memcpy(pointer_timestamp, &timestamp, sizeof(timestamp));
294 std::memcpy(pointer, &value, sizeof(value));
295 } else {
296 std::memcpy(pointer, &payload, sizeof(payload));
297 }
298 streamer->Free(new_query_id);
299 return;
300 }
301 impl->rasterizer.SyncOperation(std::move(operation));
302 }
303 if (is_synced) {
304 streamer->Free(new_query_id);
305 return;
306 }
307 auto [cont_addr, base] = gen_caching_indexing(cpu_addr);
308 {
309 std::scoped_lock lock(cache_mutex);
310 auto it1 = cached_queries.try_emplace(cont_addr);
311 auto& sub_container = it1.first->second;
312 auto it_current = sub_container.find(base);
313 if (it_current == sub_container.end()) {
314 sub_container.insert_or_assign(base, query_location);
315 return;
316 }
317 auto* old_query = impl->ObtainQuery(it_current->second);
318 old_query->flags |= QueryFlagBits::IsRewritten;
319 sub_container.insert_or_assign(base, query_location);
320 }
321}
322
323template <typename Traits>
324void QueryCacheBase<Traits>::UnregisterPending() {
325 const auto gen_caching_indexing = [](VAddr cur_addr) {
326 return std::make_pair<u64, u32>(cur_addr >> Core::Memory::YUZU_PAGEBITS,
327 static_cast<u32>(cur_addr & Core::Memory::YUZU_PAGEMASK));
328 };
329 std::scoped_lock lock(cache_mutex);
330 for (QueryLocation loc : impl->pending_unregister) {
331 const auto [streamer_id, query_id] = loc.unpack();
332 auto* streamer = impl->streamers[streamer_id];
333 if (!streamer) [[unlikely]] {
334 continue;
335 }
336 auto* query = streamer->GetQuery(query_id);
337 auto [cont_addr, base] = gen_caching_indexing(query->guest_address);
338 auto it1 = cached_queries.find(cont_addr);
339 if (it1 != cached_queries.end()) {
340 auto it2 = it1->second.find(base);
341 if (it2 != it1->second.end()) {
342 if (it2->second.raw == loc.raw) {
343 it1->second.erase(it2);
344 }
345 }
346 }
347 streamer->Free(query_id);
348 }
349 impl->pending_unregister.clear();
350}
351
352template <typename Traits>
353void QueryCacheBase<Traits>::NotifyWFI() {
354 bool should_sync = false;
355 impl->ForEachStreamer(
356 [&should_sync](StreamerInterface* streamer) { should_sync |= streamer->HasPendingSync(); });
357 if (!should_sync) {
358 return;
359 }
360
361 impl->ForEachStreamer([](StreamerInterface* streamer) { streamer->PresyncWrites(); });
362 impl->runtime.Barriers(true);
363 impl->ForEachStreamer([](StreamerInterface* streamer) { streamer->SyncWrites(); });
364 impl->runtime.Barriers(false);
365}
366
367template <typename Traits>
368void QueryCacheBase<Traits>::NotifySegment(bool resume) {
369 if (resume) {
370 impl->runtime.ResumeHostConditionalRendering();
371 } else {
372 CounterClose(VideoCommon::QueryType::ZPassPixelCount64);
373 CounterClose(VideoCommon::QueryType::StreamingByteCount);
374 impl->runtime.PauseHostConditionalRendering();
375 }
376}
377
378template <typename Traits>
379bool QueryCacheBase<Traits>::AccelerateHostConditionalRendering() {
380 bool qc_dirty = false;
381 const auto gen_lookup = [this, &qc_dirty](GPUVAddr address) -> VideoCommon::LookupData {
382 auto cpu_addr_opt = gpu_memory->GpuToCpuAddress(address);
383 if (!cpu_addr_opt) [[unlikely]] {
384 return VideoCommon::LookupData{
385 .address = 0,
386 .found_query = nullptr,
387 };
388 }
389 VAddr cpu_addr = *cpu_addr_opt;
390 std::scoped_lock lock(cache_mutex);
391 auto it1 = cached_queries.find(cpu_addr >> Core::Memory::YUZU_PAGEBITS);
392 if (it1 == cached_queries.end()) {
393 return VideoCommon::LookupData{
394 .address = cpu_addr,
395 .found_query = nullptr,
396 };
397 }
398 auto& sub_container = it1->second;
399 auto it_current = sub_container.find(cpu_addr & Core::Memory::YUZU_PAGEMASK);
400
401 if (it_current == sub_container.end()) {
402 auto it_current_2 = sub_container.find((cpu_addr & Core::Memory::YUZU_PAGEMASK) + 4);
403 if (it_current_2 == sub_container.end()) {
404 return VideoCommon::LookupData{
405 .address = cpu_addr,
406 .found_query = nullptr,
407 };
408 }
409 }
410 auto* query = impl->ObtainQuery(it_current->second);
411 qc_dirty |= True(query->flags & QueryFlagBits::IsHostManaged) &&
412 False(query->flags & QueryFlagBits::IsGuestSynced);
413 return VideoCommon::LookupData{
414 .address = cpu_addr,
415 .found_query = query,
416 };
417 };
418
419 auto& regs = maxwell3d->regs;
420 if (regs.render_enable_override != Maxwell::Regs::RenderEnable::Override::UseRenderEnable) {
421 impl->runtime.EndHostConditionalRendering();
422 return false;
423 }
424 const ComparisonMode mode = static_cast<ComparisonMode>(regs.render_enable.mode);
425 const GPUVAddr address = regs.render_enable.Address();
426 switch (mode) {
427 case ComparisonMode::True:
428 impl->runtime.EndHostConditionalRendering();
429 return false;
430 case ComparisonMode::False:
431 impl->runtime.EndHostConditionalRendering();
432 return false;
433 case ComparisonMode::Conditional: {
434 VideoCommon::LookupData object_1{gen_lookup(address)};
435 return impl->runtime.HostConditionalRenderingCompareValue(object_1, qc_dirty);
436 }
437 case ComparisonMode::IfEqual: {
438 VideoCommon::LookupData object_1{gen_lookup(address)};
439 VideoCommon::LookupData object_2{gen_lookup(address + 16)};
440 return impl->runtime.HostConditionalRenderingCompareValues(object_1, object_2, qc_dirty,
441 true);
442 }
443 case ComparisonMode::IfNotEqual: {
444 VideoCommon::LookupData object_1{gen_lookup(address)};
445 VideoCommon::LookupData object_2{gen_lookup(address + 16)};
446 return impl->runtime.HostConditionalRenderingCompareValues(object_1, object_2, qc_dirty,
447 false);
448 }
449 default:
450 return false;
451 }
452}
453
454// Async downloads
455template <typename Traits>
456void QueryCacheBase<Traits>::CommitAsyncFlushes() {
457 // Make sure to have the results synced in Host.
458 NotifyWFI();
459
460 u64 mask{};
461 {
462 std::scoped_lock lk(impl->flush_guard);
463 impl->ForEachStreamer([&mask](StreamerInterface* streamer) {
464 bool local_result = streamer->HasUnsyncedQueries();
465 if (local_result) {
466 mask |= 1ULL << streamer->GetId();
467 }
468 });
469 impl->flushes_pending.push_back(mask);
470 }
471 std::function<void()> func([this] { UnregisterPending(); });
472 impl->rasterizer.SyncOperation(std::move(func));
473 if (mask == 0) {
474 return;
475 }
476 u64 ran_mask = ~mask;
477 while (mask) {
478 impl->ForEachStreamerIn(mask, [&mask, &ran_mask](StreamerInterface* streamer) {
479 u64 dep_mask = streamer->GetDependentMask();
480 if ((dep_mask & ~ran_mask) != 0) {
481 return;
482 }
483 u64 index = streamer->GetId();
484 ran_mask |= (1ULL << index);
485 mask &= ~(1ULL << index);
486 streamer->PushUnsyncedQueries();
487 });
488 }
489}
490
491template <typename Traits>
492bool QueryCacheBase<Traits>::HasUncommittedFlushes() const {
493 bool result = false;
494 impl->ForEachStreamer([&result](StreamerInterface* streamer) {
495 result |= streamer->HasUnsyncedQueries();
496 return result;
497 });
498 return result;
499}
500
501template <typename Traits>
502bool QueryCacheBase<Traits>::ShouldWaitAsyncFlushes() {
503 std::scoped_lock lk(impl->flush_guard);
504 return !impl->flushes_pending.empty() && impl->flushes_pending.front() != 0ULL;
505}
506
507template <typename Traits>
508void QueryCacheBase<Traits>::PopAsyncFlushes() {
509 u64 mask;
510 {
511 std::scoped_lock lk(impl->flush_guard);
512 mask = impl->flushes_pending.front();
513 impl->flushes_pending.pop_front();
514 }
515 if (mask == 0) {
516 return;
517 }
518 u64 ran_mask = ~mask;
519 while (mask) {
520 impl->ForEachStreamerIn(mask, [&mask, &ran_mask](StreamerInterface* streamer) {
521 u64 dep_mask = streamer->GetDependenceMask();
522 if ((dep_mask & ~ran_mask) != 0) {
523 return;
524 }
525 u64 index = streamer->GetId();
526 ran_mask |= (1ULL << index);
527 mask &= ~(1ULL << index);
528 streamer->PopUnsyncedQueries();
529 });
530 }
531}
532
533// Invalidation
534
535template <typename Traits>
536void QueryCacheBase<Traits>::InvalidateQuery(QueryCacheBase<Traits>::QueryLocation location) {
537 auto* query_base = impl->ObtainQuery(location);
538 if (!query_base) {
539 return;
540 }
541 query_base->flags |= QueryFlagBits::IsInvalidated;
542}
543
544template <typename Traits>
545bool QueryCacheBase<Traits>::IsQueryDirty(QueryCacheBase<Traits>::QueryLocation location) {
546 auto* query_base = impl->ObtainQuery(location);
547 if (!query_base) {
548 return false;
549 }
550 return True(query_base->flags & QueryFlagBits::IsHostManaged) &&
551 False(query_base->flags & QueryFlagBits::IsGuestSynced);
552}
553
554template <typename Traits>
555bool QueryCacheBase<Traits>::SemiFlushQueryDirty(QueryCacheBase<Traits>::QueryLocation location) {
556 auto* query_base = impl->ObtainQuery(location);
557 if (!query_base) {
558 return false;
559 }
560 if (True(query_base->flags & QueryFlagBits::IsFinalValueSynced) &&
561 False(query_base->flags & QueryFlagBits::IsGuestSynced)) {
562 auto* ptr = impl->cpu_memory.GetPointer(query_base->guest_address);
563 if (True(query_base->flags & QueryFlagBits::HasTimestamp)) {
564 std::memcpy(ptr, &query_base->value, sizeof(query_base->value));
565 return false;
566 }
567 u32 value_l = static_cast<u32>(query_base->value);
568 std::memcpy(ptr, &value_l, sizeof(value_l));
569 return false;
570 }
571 return True(query_base->flags & QueryFlagBits::IsHostManaged) &&
572 False(query_base->flags & QueryFlagBits::IsGuestSynced);
573}
574
575template <typename Traits>
576void QueryCacheBase<Traits>::RequestGuestHostSync() {
577 impl->rasterizer.ReleaseFences();
578}
579
580} // namespace VideoCommon
diff --git a/src/video_core/query_cache/query_cache_base.h b/src/video_core/query_cache/query_cache_base.h
new file mode 100644
index 000000000..07be421c6
--- /dev/null
+++ b/src/video_core/query_cache/query_cache_base.h
@@ -0,0 +1,181 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <functional>
7#include <mutex>
8#include <optional>
9#include <span>
10#include <unordered_map>
11#include <utility>
12
13#include "common/assert.h"
14#include "common/bit_field.h"
15#include "common/common_types.h"
16#include "core/memory.h"
17#include "video_core/control/channel_state_cache.h"
18#include "video_core/query_cache/query_base.h"
19#include "video_core/query_cache/types.h"
20
21namespace Core::Memory {
22class Memory;
23}
24
25namespace VideoCore {
26class RasterizerInterface;
27}
28
29namespace Tegra {
30class GPU;
31}
32
33namespace VideoCommon {
34
35struct LookupData {
36 VAddr address;
37 QueryBase* found_query;
38};
39
40template <typename Traits>
41class QueryCacheBase : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
42 using RuntimeType = typename Traits::RuntimeType;
43
44public:
45 union QueryLocation {
46 BitField<27, 5, u32> stream_id;
47 BitField<0, 27, u32> query_id;
48 u32 raw;
49
50 std::pair<size_t, size_t> unpack() const {
51 return {static_cast<size_t>(stream_id.Value()), static_cast<size_t>(query_id.Value())};
52 }
53 };
54
55 explicit QueryCacheBase(Tegra::GPU& gpu, VideoCore::RasterizerInterface& rasterizer_,
56 Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_);
57
58 ~QueryCacheBase();
59
60 void InvalidateRegion(VAddr addr, std::size_t size) {
61 IterateCache<true>(addr, size,
62 [this](QueryLocation location) { InvalidateQuery(location); });
63 }
64
65 void FlushRegion(VAddr addr, std::size_t size) {
66 bool result = false;
67 IterateCache<false>(addr, size, [this, &result](QueryLocation location) {
68 result |= SemiFlushQueryDirty(location);
69 return result;
70 });
71 if (result) {
72 RequestGuestHostSync();
73 }
74 }
75
76 static u64 BuildMask(std::span<const QueryType> types) {
77 u64 mask = 0;
78 for (auto query_type : types) {
79 mask |= 1ULL << (static_cast<u64>(query_type));
80 }
81 return mask;
82 }
83
84 /// Return true when a CPU region is modified from the GPU
85 [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size) {
86 bool result = false;
87 IterateCache<false>(addr, size, [this, &result](QueryLocation location) {
88 result |= IsQueryDirty(location);
89 return result;
90 });
91 return result;
92 }
93
94 void CounterEnable(QueryType counter_type, bool is_enabled);
95
96 void CounterReset(QueryType counter_type);
97
98 void CounterClose(QueryType counter_type);
99
100 void CounterReport(GPUVAddr addr, QueryType counter_type, QueryPropertiesFlags flags,
101 u32 payload, u32 subreport);
102
103 void NotifyWFI();
104
105 bool AccelerateHostConditionalRendering();
106
107 // Async downloads
108 void CommitAsyncFlushes();
109
110 bool HasUncommittedFlushes() const;
111
112 bool ShouldWaitAsyncFlushes();
113
114 void PopAsyncFlushes();
115
116 void NotifySegment(bool resume);
117
118 void BindToChannel(s32 id) override;
119
120protected:
121 template <bool remove_from_cache, typename Func>
122 void IterateCache(VAddr addr, std::size_t size, Func&& func) {
123 static constexpr bool RETURNS_BOOL =
124 std::is_same_v<std::invoke_result<Func, QueryLocation>, bool>;
125 const u64 addr_begin = addr;
126 const u64 addr_end = addr_begin + size;
127
128 const u64 page_end = addr_end >> Core::Memory::YUZU_PAGEBITS;
129 std::scoped_lock lock(cache_mutex);
130 for (u64 page = addr_begin >> Core::Memory::YUZU_PAGEBITS; page <= page_end; ++page) {
131 const u64 page_start = page << Core::Memory::YUZU_PAGEBITS;
132 const auto in_range = [page_start, addr_begin, addr_end](const u32 query_location) {
133 const u64 cache_begin = page_start + query_location;
134 const u64 cache_end = cache_begin + sizeof(u32);
135 return cache_begin < addr_end && addr_begin < cache_end;
136 };
137 const auto& it = cached_queries.find(page);
138 if (it == std::end(cached_queries)) {
139 continue;
140 }
141 auto& contents = it->second;
142 for (auto& query : contents) {
143 if (!in_range(query.first)) {
144 continue;
145 }
146 if constexpr (RETURNS_BOOL) {
147 if (func(query.second)) {
148 return;
149 }
150 } else {
151 func(query.second);
152 }
153 }
154 if constexpr (remove_from_cache) {
155 const auto in_range2 = [&](const std::pair<u32, QueryLocation>& pair) {
156 return in_range(pair.first);
157 };
158 std::erase_if(contents, in_range2);
159 }
160 }
161 }
162
163 using ContentCache = std::unordered_map<u64, std::unordered_map<u32, QueryLocation>>;
164
165 void InvalidateQuery(QueryLocation location);
166 bool IsQueryDirty(QueryLocation location);
167 bool SemiFlushQueryDirty(QueryLocation location);
168 void RequestGuestHostSync();
169 void UnregisterPending();
170
171 std::unordered_map<u64, std::unordered_map<u32, QueryLocation>> cached_queries;
172 std::mutex cache_mutex;
173
174 struct QueryCacheBaseImpl;
175 friend struct QueryCacheBaseImpl;
176 friend RuntimeType;
177
178 std::unique_ptr<QueryCacheBaseImpl> impl;
179};
180
181} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/query_cache/query_stream.h b/src/video_core/query_cache/query_stream.h
new file mode 100644
index 000000000..39da6ac07
--- /dev/null
+++ b/src/video_core/query_cache/query_stream.h
@@ -0,0 +1,149 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <deque>
7#include <optional>
8#include <vector>
9
10#include "common/assert.h"
11#include "common/common_types.h"
12#include "video_core/query_cache/bank_base.h"
13#include "video_core/query_cache/query_base.h"
14
15namespace VideoCommon {
16
17class StreamerInterface {
18public:
19 explicit StreamerInterface(size_t id_) : id{id_}, dependence_mask{}, dependent_mask{} {}
20 virtual ~StreamerInterface() = default;
21
22 virtual QueryBase* GetQuery(size_t id) = 0;
23
24 virtual void StartCounter() {
25 /* Do Nothing */
26 }
27
28 virtual void PauseCounter() {
29 /* Do Nothing */
30 }
31
32 virtual void ResetCounter() {
33 /* Do Nothing */
34 }
35
36 virtual void CloseCounter() {
37 /* Do Nothing */
38 }
39
40 virtual bool HasPendingSync() const {
41 return false;
42 }
43
44 virtual void PresyncWrites() {
45 /* Do Nothing */
46 }
47
48 virtual void SyncWrites() {
49 /* Do Nothing */
50 }
51
52 virtual size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
53 std::optional<u32> subreport = std::nullopt) = 0;
54
55 virtual bool HasUnsyncedQueries() const {
56 return false;
57 }
58
59 virtual void PushUnsyncedQueries() {
60 /* Do Nothing */
61 }
62
63 virtual void PopUnsyncedQueries() {
64 /* Do Nothing */
65 }
66
67 virtual void Free(size_t query_id) = 0;
68
69 size_t GetId() const {
70 return id;
71 }
72
73 u64 GetDependenceMask() const {
74 return dependence_mask;
75 }
76
77 u64 GetDependentMask() const {
78 return dependence_mask;
79 }
80
81 u64 GetAmmendValue() const {
82 return ammend_value;
83 }
84
85 void SetAccumulationValue(u64 new_value) {
86 acumulation_value = new_value;
87 }
88
89protected:
90 void MakeDependent(StreamerInterface* depend_on) {
91 dependence_mask |= 1ULL << depend_on->id;
92 depend_on->dependent_mask |= 1ULL << id;
93 }
94
95 const size_t id;
96 u64 dependence_mask;
97 u64 dependent_mask;
98 u64 ammend_value{};
99 u64 acumulation_value{};
100};
101
102template <typename QueryType>
103class SimpleStreamer : public StreamerInterface {
104public:
105 explicit SimpleStreamer(size_t id_) : StreamerInterface{id_} {}
106 virtual ~SimpleStreamer() = default;
107
108protected:
109 virtual QueryType* GetQuery(size_t query_id) override {
110 if (query_id < slot_queries.size()) {
111 return &slot_queries[query_id];
112 }
113 return nullptr;
114 }
115
116 virtual void Free(size_t query_id) override {
117 std::scoped_lock lk(guard);
118 ReleaseQuery(query_id);
119 }
120
121 template <typename... Args, typename = decltype(QueryType(std::declval<Args>()...))>
122 size_t BuildQuery(Args&&... args) {
123 std::scoped_lock lk(guard);
124 if (!old_queries.empty()) {
125 size_t new_id = old_queries.front();
126 old_queries.pop_front();
127 new (&slot_queries[new_id]) QueryType(std::forward<Args>(args)...);
128 return new_id;
129 }
130 size_t new_id = slot_queries.size();
131 slot_queries.emplace_back(std::forward<Args>(args)...);
132 return new_id;
133 }
134
135 void ReleaseQuery(size_t query_id) {
136
137 if (query_id < slot_queries.size()) {
138 old_queries.push_back(query_id);
139 return;
140 }
141 UNREACHABLE();
142 }
143
144 std::mutex guard;
145 std::deque<QueryType> slot_queries;
146 std::deque<size_t> old_queries;
147};
148
149} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/query_cache/types.h b/src/video_core/query_cache/types.h
new file mode 100644
index 000000000..e9226bbfc
--- /dev/null
+++ b/src/video_core/query_cache/types.h
@@ -0,0 +1,74 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8
9namespace VideoCommon {
10
11enum class QueryPropertiesFlags : u32 {
12 HasTimeout = 1 << 0,
13 IsAFence = 1 << 1,
14};
15DECLARE_ENUM_FLAG_OPERATORS(QueryPropertiesFlags)
16
17// This should always be equivalent to maxwell3d Report Semaphore Reports
18enum class QueryType : u32 {
19 Payload = 0, // "None" in docs, but confirmed via hardware to return the payload
20 VerticesGenerated = 1,
21 ZPassPixelCount = 2,
22 PrimitivesGenerated = 3,
23 AlphaBetaClocks = 4,
24 VertexShaderInvocations = 5,
25 StreamingPrimitivesNeededMinusSucceeded = 6,
26 GeometryShaderInvocations = 7,
27 GeometryShaderPrimitivesGenerated = 9,
28 ZCullStats0 = 10,
29 StreamingPrimitivesSucceeded = 11,
30 ZCullStats1 = 12,
31 StreamingPrimitivesNeeded = 13,
32 ZCullStats2 = 14,
33 ClipperInvocations = 15,
34 ZCullStats3 = 16,
35 ClipperPrimitivesGenerated = 17,
36 VtgPrimitivesOut = 18,
37 PixelShaderInvocations = 19,
38 ZPassPixelCount64 = 21,
39 IEEECleanColorTarget = 24,
40 IEEECleanZetaTarget = 25,
41 StreamingByteCount = 26,
42 TessellationInitInvocations = 27,
43 BoundingRectangle = 28,
44 TessellationShaderInvocations = 29,
45 TotalStreamingPrimitivesNeededMinusSucceeded = 30,
46 TessellationShaderPrimitivesGenerated = 31,
47 // max.
48 MaxQueryTypes,
49};
50
51// Comparison modes for Host Conditional Rendering
52enum class ComparisonMode : u32 {
53 False = 0,
54 True = 1,
55 Conditional = 2,
56 IfEqual = 3,
57 IfNotEqual = 4,
58 MaxComparisonMode,
59};
60
61// Reduction ops.
62enum class ReductionOp : u32 {
63 RedAdd = 0,
64 RedMin = 1,
65 RedMax = 2,
66 RedInc = 3,
67 RedDec = 4,
68 RedAnd = 5,
69 RedOr = 6,
70 RedXor = 7,
71 MaxReductionOp,
72};
73
74} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index cb8029a4f..af1469147 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -12,6 +12,7 @@
12#include "video_core/cache_types.h" 12#include "video_core/cache_types.h"
13#include "video_core/engines/fermi_2d.h" 13#include "video_core/engines/fermi_2d.h"
14#include "video_core/gpu.h" 14#include "video_core/gpu.h"
15#include "video_core/query_cache/types.h"
15#include "video_core/rasterizer_download_area.h" 16#include "video_core/rasterizer_download_area.h"
16 17
17namespace Tegra { 18namespace Tegra {
@@ -26,11 +27,6 @@ struct ChannelState;
26 27
27namespace VideoCore { 28namespace VideoCore {
28 29
29enum class QueryType {
30 SamplesPassed,
31};
32constexpr std::size_t NumQueryTypes = 1;
33
34enum class LoadCallbackStage { 30enum class LoadCallbackStage {
35 Prepare, 31 Prepare,
36 Build, 32 Build,
@@ -58,10 +54,11 @@ public:
58 virtual void DispatchCompute() = 0; 54 virtual void DispatchCompute() = 0;
59 55
60 /// Resets the counter of a query 56 /// Resets the counter of a query
61 virtual void ResetCounter(QueryType type) = 0; 57 virtual void ResetCounter(VideoCommon::QueryType type) = 0;
62 58
63 /// Records a GPU query and caches it 59 /// Records a GPU query and caches it
64 virtual void Query(GPUVAddr gpu_addr, QueryType type, std::optional<u64> timestamp) = 0; 60 virtual void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
61 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) = 0;
65 62
66 /// Signal an uniform buffer binding 63 /// Signal an uniform buffer binding
67 virtual void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, 64 virtual void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@@ -83,7 +80,7 @@ public:
83 virtual void SignalReference() = 0; 80 virtual void SignalReference() = 0;
84 81
85 /// Release all pending fences. 82 /// Release all pending fences.
86 virtual void ReleaseFences() = 0; 83 virtual void ReleaseFences(bool force = true) = 0;
87 84
88 /// Notify rasterizer that all caches should be flushed to Switch memory 85 /// Notify rasterizer that all caches should be flushed to Switch memory
89 virtual void FlushAll() = 0; 86 virtual void FlushAll() = 0;
diff --git a/src/video_core/renderer_null/null_rasterizer.cpp b/src/video_core/renderer_null/null_rasterizer.cpp
index 92ecf6682..65cd5aa06 100644
--- a/src/video_core/renderer_null/null_rasterizer.cpp
+++ b/src/video_core/renderer_null/null_rasterizer.cpp
@@ -26,16 +26,18 @@ void RasterizerNull::Draw(bool is_indexed, u32 instance_count) {}
26void RasterizerNull::DrawTexture() {} 26void RasterizerNull::DrawTexture() {}
27void RasterizerNull::Clear(u32 layer_count) {} 27void RasterizerNull::Clear(u32 layer_count) {}
28void RasterizerNull::DispatchCompute() {} 28void RasterizerNull::DispatchCompute() {}
29void RasterizerNull::ResetCounter(VideoCore::QueryType type) {} 29void RasterizerNull::ResetCounter(VideoCommon::QueryType type) {}
30void RasterizerNull::Query(GPUVAddr gpu_addr, VideoCore::QueryType type, 30void RasterizerNull::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
31 std::optional<u64> timestamp) { 31 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) {
32 if (!gpu_memory) { 32 if (!gpu_memory) {
33 return; 33 return;
34 } 34 }
35 35 if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) {
36 gpu_memory->Write(gpu_addr, u64{0}); 36 u64 ticks = m_gpu.GetTicks();
37 if (timestamp) { 37 gpu_memory->Write<u64>(gpu_addr + 8, ticks);
38 gpu_memory->Write(gpu_addr + 8, *timestamp); 38 gpu_memory->Write<u64>(gpu_addr, static_cast<u64>(payload));
39 } else {
40 gpu_memory->Write<u32>(gpu_addr, payload);
39 } 41 }
40} 42}
41void RasterizerNull::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, 43void RasterizerNull::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@@ -74,7 +76,7 @@ void RasterizerNull::SignalSyncPoint(u32 value) {
74 syncpoint_manager.IncrementHost(value); 76 syncpoint_manager.IncrementHost(value);
75} 77}
76void RasterizerNull::SignalReference() {} 78void RasterizerNull::SignalReference() {}
77void RasterizerNull::ReleaseFences() {} 79void RasterizerNull::ReleaseFences(bool) {}
78void RasterizerNull::FlushAndInvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType) {} 80void RasterizerNull::FlushAndInvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType) {}
79void RasterizerNull::WaitForIdle() {} 81void RasterizerNull::WaitForIdle() {}
80void RasterizerNull::FragmentBarrier() {} 82void RasterizerNull::FragmentBarrier() {}
diff --git a/src/video_core/renderer_null/null_rasterizer.h b/src/video_core/renderer_null/null_rasterizer.h
index 93b9a6971..23001eeb8 100644
--- a/src/video_core/renderer_null/null_rasterizer.h
+++ b/src/video_core/renderer_null/null_rasterizer.h
@@ -42,8 +42,9 @@ public:
42 void DrawTexture() override; 42 void DrawTexture() override;
43 void Clear(u32 layer_count) override; 43 void Clear(u32 layer_count) override;
44 void DispatchCompute() override; 44 void DispatchCompute() override;
45 void ResetCounter(VideoCore::QueryType type) override; 45 void ResetCounter(VideoCommon::QueryType type) override;
46 void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; 46 void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
47 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override;
47 void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; 48 void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override;
48 void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; 49 void DisableGraphicsUniformBuffer(size_t stage, u32 index) override;
49 void FlushAll() override; 50 void FlushAll() override;
@@ -63,7 +64,7 @@ public:
63 void SyncOperation(std::function<void()>&& func) override; 64 void SyncOperation(std::function<void()>&& func) override;
64 void SignalSyncPoint(u32 value) override; 65 void SignalSyncPoint(u32 value) override;
65 void SignalReference() override; 66 void SignalReference() override;
66 void ReleaseFences() override; 67 void ReleaseFences(bool force) override;
67 void FlushAndInvalidateRegion( 68 void FlushAndInvalidateRegion(
68 VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; 69 VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
69 void WaitForIdle() override; 70 void WaitForIdle() override;
diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp
index 99d7347f5..ec142d48e 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_query_cache.cpp
@@ -27,7 +27,7 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) {
27} // Anonymous namespace 27} // Anonymous namespace
28 28
29QueryCache::QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_) 29QueryCache::QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_)
30 : QueryCacheBase(rasterizer_, cpu_memory_), gl_rasterizer{rasterizer_} {} 30 : QueryCacheLegacy(rasterizer_, cpu_memory_), gl_rasterizer{rasterizer_} {}
31 31
32QueryCache::~QueryCache() = default; 32QueryCache::~QueryCache() = default;
33 33
diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h
index 872513f22..0721e0b3d 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.h
+++ b/src/video_core/renderer_opengl/gl_query_cache.h
@@ -26,7 +26,7 @@ class RasterizerOpenGL;
26using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>; 26using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
27 27
28class QueryCache final 28class QueryCache final
29 : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> { 29 : public VideoCommon::QueryCacheLegacy<QueryCache, CachedQuery, CounterStream, HostCounter> {
30public: 30public:
31 explicit QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_); 31 explicit QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_);
32 ~QueryCache(); 32 ~QueryCache();
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index dd03efecd..27e2de1bf 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -396,13 +396,39 @@ void RasterizerOpenGL::DispatchCompute() {
396 has_written_global_memory |= pipeline->WritesGlobalMemory(); 396 has_written_global_memory |= pipeline->WritesGlobalMemory();
397} 397}
398 398
399void RasterizerOpenGL::ResetCounter(VideoCore::QueryType type) { 399void RasterizerOpenGL::ResetCounter(VideoCommon::QueryType type) {
400 query_cache.ResetCounter(type); 400 if (type == VideoCommon::QueryType::ZPassPixelCount64) {
401 query_cache.ResetCounter(VideoCore::QueryType::SamplesPassed);
402 }
401} 403}
402 404
403void RasterizerOpenGL::Query(GPUVAddr gpu_addr, VideoCore::QueryType type, 405void RasterizerOpenGL::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
404 std::optional<u64> timestamp) { 406 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) {
405 query_cache.Query(gpu_addr, type, timestamp); 407 if (type == VideoCommon::QueryType::ZPassPixelCount64) {
408 if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) {
409 query_cache.Query(gpu_addr, VideoCore::QueryType::SamplesPassed, {gpu.GetTicks()});
410 } else {
411 query_cache.Query(gpu_addr, VideoCore::QueryType::SamplesPassed, std::nullopt);
412 }
413 return;
414 }
415 if (type != VideoCommon::QueryType::Payload) {
416 payload = 1u;
417 }
418 std::function<void()> func([this, gpu_addr, flags, memory_manager = gpu_memory, payload]() {
419 if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) {
420 u64 ticks = gpu.GetTicks();
421 memory_manager->Write<u64>(gpu_addr + 8, ticks);
422 memory_manager->Write<u64>(gpu_addr, static_cast<u64>(payload));
423 } else {
424 memory_manager->Write<u32>(gpu_addr, payload);
425 }
426 });
427 if (True(flags & VideoCommon::QueryPropertiesFlags::IsAFence)) {
428 SignalFence(std::move(func));
429 return;
430 }
431 func();
406} 432}
407 433
408void RasterizerOpenGL::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, 434void RasterizerOpenGL::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@@ -573,8 +599,8 @@ void RasterizerOpenGL::SignalReference() {
573 fence_manager.SignalOrdering(); 599 fence_manager.SignalOrdering();
574} 600}
575 601
576void RasterizerOpenGL::ReleaseFences() { 602void RasterizerOpenGL::ReleaseFences(bool force) {
577 fence_manager.WaitPendingFences(); 603 fence_manager.WaitPendingFences(force);
578} 604}
579 605
580void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size, 606void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size,
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 8eda2ddba..ceffe1f1e 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -86,8 +86,9 @@ public:
86 void DrawTexture() override; 86 void DrawTexture() override;
87 void Clear(u32 layer_count) override; 87 void Clear(u32 layer_count) override;
88 void DispatchCompute() override; 88 void DispatchCompute() override;
89 void ResetCounter(VideoCore::QueryType type) override; 89 void ResetCounter(VideoCommon::QueryType type) override;
90 void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; 90 void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
91 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override;
91 void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; 92 void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override;
92 void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; 93 void DisableGraphicsUniformBuffer(size_t stage, u32 index) override;
93 void FlushAll() override; 94 void FlushAll() override;
@@ -107,7 +108,7 @@ public:
107 void SyncOperation(std::function<void()>&& func) override; 108 void SyncOperation(std::function<void()>&& func) override;
108 void SignalSyncPoint(u32 value) override; 109 void SignalSyncPoint(u32 value) override;
109 void SignalReference() override; 110 void SignalReference() override;
110 void ReleaseFences() override; 111 void ReleaseFences(bool force = true) override;
111 void FlushAndInvalidateRegion( 112 void FlushAndInvalidateRegion(
112 VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; 113 VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
113 void WaitForIdle() override; 114 void WaitForIdle() override;
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 35bf80ea3..208e88533 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -185,7 +185,7 @@ struct FormatTuple {
185 {VK_FORMAT_BC2_SRGB_BLOCK}, // BC2_SRGB 185 {VK_FORMAT_BC2_SRGB_BLOCK}, // BC2_SRGB
186 {VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB 186 {VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB
187 {VK_FORMAT_BC7_SRGB_BLOCK}, // BC7_SRGB 187 {VK_FORMAT_BC7_SRGB_BLOCK}, // BC7_SRGB
188 {VK_FORMAT_R4G4B4A4_UNORM_PACK16}, // A4B4G4R4_UNORM 188 {VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT}, // A4B4G4R4_UNORM
189 {VK_FORMAT_R4G4_UNORM_PACK8}, // G4R4_UNORM 189 {VK_FORMAT_R4G4_UNORM_PACK8}, // G4R4_UNORM
190 {VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB 190 {VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB
191 {VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB 191 {VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index e15865d16..d8148e89a 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -61,6 +61,9 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
61 if (device.IsExtTransformFeedbackSupported()) { 61 if (device.IsExtTransformFeedbackSupported()) {
62 flags |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT; 62 flags |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
63 } 63 }
64 if (device.IsExtConditionalRendering()) {
65 flags |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT;
66 }
64 const VkBufferCreateInfo buffer_ci = { 67 const VkBufferCreateInfo buffer_ci = {
65 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 68 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
66 .pNext = nullptr, 69 .pNext = nullptr,
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
index 54ee030ce..617f92910 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -3,6 +3,7 @@
3 3
4#include <array> 4#include <array>
5#include <memory> 5#include <memory>
6#include <numeric>
6#include <optional> 7#include <optional>
7#include <utility> 8#include <utility>
8 9
@@ -11,7 +12,13 @@
11#include "common/assert.h" 12#include "common/assert.h"
12#include "common/common_types.h" 13#include "common/common_types.h"
13#include "common/div_ceil.h" 14#include "common/div_ceil.h"
15#include "common/vector_math.h"
14#include "video_core/host_shaders/astc_decoder_comp_spv.h" 16#include "video_core/host_shaders/astc_decoder_comp_spv.h"
17#include "video_core/host_shaders/convert_msaa_to_non_msaa_comp_spv.h"
18#include "video_core/host_shaders/convert_non_msaa_to_msaa_comp_spv.h"
19#include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h"
20#include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h"
21#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
15#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h" 22#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
16#include "video_core/host_shaders/vulkan_uint8_comp_spv.h" 23#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
17#include "video_core/renderer_vulkan/vk_compute_pass.h" 24#include "video_core/renderer_vulkan/vk_compute_pass.h"
@@ -57,6 +64,30 @@ constexpr std::array<VkDescriptorSetLayoutBinding, 2> INPUT_OUTPUT_DESCRIPTOR_SE
57 }, 64 },
58}}; 65}};
59 66
67constexpr std::array<VkDescriptorSetLayoutBinding, 3> QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS{{
68 {
69 .binding = 0,
70 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
71 .descriptorCount = 1,
72 .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
73 .pImmutableSamplers = nullptr,
74 },
75 {
76 .binding = 1,
77 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
78 .descriptorCount = 1,
79 .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
80 .pImmutableSamplers = nullptr,
81 },
82 {
83 .binding = 2,
84 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
85 .descriptorCount = 1,
86 .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
87 .pImmutableSamplers = nullptr,
88 },
89}};
90
60constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{ 91constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{
61 .uniform_buffers = 0, 92 .uniform_buffers = 0,
62 .storage_buffers = 2, 93 .storage_buffers = 2,
@@ -67,6 +98,16 @@ constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{
67 .score = 2, 98 .score = 2,
68}; 99};
69 100
101constexpr DescriptorBankInfo QUERIES_SCAN_BANK_INFO{
102 .uniform_buffers = 0,
103 .storage_buffers = 3,
104 .texture_buffers = 0,
105 .image_buffers = 0,
106 .textures = 0,
107 .images = 0,
108 .score = 3,
109};
110
70constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> ASTC_DESCRIPTOR_SET_BINDINGS{{ 111constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> ASTC_DESCRIPTOR_SET_BINDINGS{{
71 { 112 {
72 .binding = ASTC_BINDING_INPUT_BUFFER, 113 .binding = ASTC_BINDING_INPUT_BUFFER,
@@ -94,6 +135,33 @@ constexpr DescriptorBankInfo ASTC_BANK_INFO{
94 .score = 2, 135 .score = 2,
95}; 136};
96 137
138constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> MSAA_DESCRIPTOR_SET_BINDINGS{{
139 {
140 .binding = 0,
141 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
142 .descriptorCount = 1,
143 .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
144 .pImmutableSamplers = nullptr,
145 },
146 {
147 .binding = 1,
148 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
149 .descriptorCount = 1,
150 .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
151 .pImmutableSamplers = nullptr,
152 },
153}};
154
155constexpr DescriptorBankInfo MSAA_BANK_INFO{
156 .uniform_buffers = 0,
157 .storage_buffers = 0,
158 .texture_buffers = 0,
159 .image_buffers = 0,
160 .textures = 0,
161 .images = 2,
162 .score = 2,
163};
164
97constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE{ 165constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE{
98 .dstBinding = 0, 166 .dstBinding = 0,
99 .dstArrayElement = 0, 167 .dstArrayElement = 0,
@@ -103,6 +171,24 @@ constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLAT
103 .stride = sizeof(DescriptorUpdateEntry), 171 .stride = sizeof(DescriptorUpdateEntry),
104}; 172};
105 173
174constexpr VkDescriptorUpdateTemplateEntry QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLATE{
175 .dstBinding = 0,
176 .dstArrayElement = 0,
177 .descriptorCount = 3,
178 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
179 .offset = 0,
180 .stride = sizeof(DescriptorUpdateEntry),
181};
182
183constexpr VkDescriptorUpdateTemplateEntry MSAA_DESCRIPTOR_UPDATE_TEMPLATE{
184 .dstBinding = 0,
185 .dstArrayElement = 0,
186 .descriptorCount = 2,
187 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
188 .offset = 0,
189 .stride = sizeof(DescriptorUpdateEntry),
190};
191
106constexpr std::array<VkDescriptorUpdateTemplateEntry, ASTC_NUM_BINDINGS> 192constexpr std::array<VkDescriptorUpdateTemplateEntry, ASTC_NUM_BINDINGS>
107 ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{ 193 ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{
108 { 194 {
@@ -131,13 +217,21 @@ struct AstcPushConstants {
131 u32 block_height; 217 u32 block_height;
132 u32 block_height_mask; 218 u32 block_height_mask;
133}; 219};
220
221struct QueriesPrefixScanPushConstants {
222 u32 min_accumulation_base;
223 u32 max_accumulation_base;
224 u32 accumulation_limit;
225 u32 buffer_offset;
226};
134} // Anonymous namespace 227} // Anonymous namespace
135 228
136ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool, 229ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
137 vk::Span<VkDescriptorSetLayoutBinding> bindings, 230 vk::Span<VkDescriptorSetLayoutBinding> bindings,
138 vk::Span<VkDescriptorUpdateTemplateEntry> templates, 231 vk::Span<VkDescriptorUpdateTemplateEntry> templates,
139 const DescriptorBankInfo& bank_info, 232 const DescriptorBankInfo& bank_info,
140 vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code) 233 vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code,
234 std::optional<u32> optional_subgroup_size)
141 : device{device_} { 235 : device{device_} {
142 descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout({ 236 descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout({
143 .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, 237 .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
@@ -170,6 +264,9 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
170 }); 264 });
171 descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, bank_info); 265 descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, bank_info);
172 } 266 }
267 if (code.empty()) {
268 return;
269 }
173 module = device.GetLogical().CreateShaderModule({ 270 module = device.GetLogical().CreateShaderModule({
174 .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, 271 .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
175 .pNext = nullptr, 272 .pNext = nullptr,
@@ -178,13 +275,19 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
178 .pCode = code.data(), 275 .pCode = code.data(),
179 }); 276 });
180 device.SaveShader(code); 277 device.SaveShader(code);
278 const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
279 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT,
280 .pNext = nullptr,
281 .requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U,
282 };
283 bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size;
181 pipeline = device.GetLogical().CreateComputePipeline({ 284 pipeline = device.GetLogical().CreateComputePipeline({
182 .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, 285 .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
183 .pNext = nullptr, 286 .pNext = nullptr,
184 .flags = 0, 287 .flags = 0,
185 .stage{ 288 .stage{
186 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 289 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
187 .pNext = nullptr, 290 .pNext = use_setup_size ? &subgroup_size_ci : nullptr,
188 .flags = 0, 291 .flags = 0,
189 .stage = VK_SHADER_STAGE_COMPUTE_BIT, 292 .stage = VK_SHADER_STAGE_COMPUTE_BIT,
190 .module = *module, 293 .module = *module,
@@ -302,6 +405,123 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
302 return {staging.buffer, staging.offset}; 405 return {staging.buffer, staging.offset};
303} 406}
304 407
408ConditionalRenderingResolvePass::ConditionalRenderingResolvePass(
409 const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
410 ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
411 : ComputePass(device_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS,
412 INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO, nullptr,
413 RESOLVE_CONDITIONAL_RENDER_COMP_SPV),
414 scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
415
416void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_buffer,
417 u32 src_offset, bool compare_to_zero) {
418 const size_t compare_size = compare_to_zero ? 8 : 24;
419
420 compute_pass_descriptor_queue.Acquire();
421 compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, compare_size);
422 compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, sizeof(u32));
423 const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
424
425 scheduler.RequestOutsideRenderPassOperationContext();
426 scheduler.Record([this, descriptor_data](vk::CommandBuffer cmdbuf) {
427 static constexpr VkMemoryBarrier read_barrier{
428 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
429 .pNext = nullptr,
430 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT,
431 .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
432 };
433 static constexpr VkMemoryBarrier write_barrier{
434 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
435 .pNext = nullptr,
436 .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
437 .dstAccessMask = VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT,
438 };
439 const VkDescriptorSet set = descriptor_allocator.Commit();
440 device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
441
442 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
443 VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
444 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
445 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
446 cmdbuf.Dispatch(1, 1, 1);
447 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
448 VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0, write_barrier);
449 });
450}
451
452QueriesPrefixScanPass::QueriesPrefixScanPass(
453 const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
454 ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
455 : ComputePass(
456 device_, descriptor_pool_, QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS,
457 QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLATE, QUERIES_SCAN_BANK_INFO,
458 COMPUTE_PUSH_CONSTANT_RANGE<sizeof(QueriesPrefixScanPushConstants)>,
459 device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_BASIC_BIT) &&
460 device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_ARITHMETIC_BIT) &&
461 device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_BIT) &&
462 device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT)
463 ? std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_COMP_SPV)
464 : std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_NOSUBGROUPS_COMP_SPV)),
465 scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
466
467void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffer,
468 VkBuffer src_buffer, size_t number_of_sums,
469 size_t min_accumulation_limit, size_t max_accumulation_limit) {
470 size_t current_runs = number_of_sums;
471 size_t offset = 0;
472 while (current_runs != 0) {
473 static constexpr size_t DISPATCH_SIZE = 2048U;
474 size_t runs_to_do = std::min<size_t>(current_runs, DISPATCH_SIZE);
475 current_runs -= runs_to_do;
476 compute_pass_descriptor_queue.Acquire();
477 compute_pass_descriptor_queue.AddBuffer(src_buffer, 0, number_of_sums * sizeof(u64));
478 compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, number_of_sums * sizeof(u64));
479 compute_pass_descriptor_queue.AddBuffer(accumulation_buffer, 0, sizeof(u64));
480 const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
481 size_t used_offset = offset;
482 offset += runs_to_do;
483
484 scheduler.RequestOutsideRenderPassOperationContext();
485 scheduler.Record([this, descriptor_data, min_accumulation_limit, max_accumulation_limit,
486 runs_to_do, used_offset](vk::CommandBuffer cmdbuf) {
487 static constexpr VkMemoryBarrier read_barrier{
488 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
489 .pNext = nullptr,
490 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
491 .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
492 };
493 static constexpr VkMemoryBarrier write_barrier{
494 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
495 .pNext = nullptr,
496 .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
497 .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
498 VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT |
499 VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_INDEX_READ_BIT |
500 VK_ACCESS_UNIFORM_READ_BIT |
501 VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT,
502 };
503 const QueriesPrefixScanPushConstants uniforms{
504 .min_accumulation_base = static_cast<u32>(min_accumulation_limit),
505 .max_accumulation_base = static_cast<u32>(max_accumulation_limit),
506 .accumulation_limit = static_cast<u32>(runs_to_do - 1),
507 .buffer_offset = static_cast<u32>(used_offset),
508 };
509 const VkDescriptorSet set = descriptor_allocator.Commit();
510 device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
511
512 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
513 VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
514 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
515 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
516 cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, uniforms);
517 cmdbuf.Dispatch(1, 1, 1);
518 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
519 VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0,
520 write_barrier);
521 });
522 }
523}
524
305ASTCDecoderPass::ASTCDecoderPass(const Device& device_, Scheduler& scheduler_, 525ASTCDecoderPass::ASTCDecoderPass(const Device& device_, Scheduler& scheduler_,
306 DescriptorPool& descriptor_pool_, 526 DescriptorPool& descriptor_pool_,
307 StagingBufferPool& staging_buffer_pool_, 527 StagingBufferPool& staging_buffer_pool_,
@@ -413,4 +633,100 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
413 scheduler.Finish(); 633 scheduler.Finish();
414} 634}
415 635
636MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
637 DescriptorPool& descriptor_pool_,
638 StagingBufferPool& staging_buffer_pool_,
639 ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
640 : ComputePass(device_, descriptor_pool_, MSAA_DESCRIPTOR_SET_BINDINGS,
641 MSAA_DESCRIPTOR_UPDATE_TEMPLATE, MSAA_BANK_INFO, {},
642 CONVERT_NON_MSAA_TO_MSAA_COMP_SPV),
643 scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
644 compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {
645 const auto make_msaa_pipeline = [this](size_t i, std::span<const u32> code) {
646 modules[i] = device.GetLogical().CreateShaderModule({
647 .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
648 .pNext = nullptr,
649 .flags = 0,
650 .codeSize = static_cast<u32>(code.size_bytes()),
651 .pCode = code.data(),
652 });
653 pipelines[i] = device.GetLogical().CreateComputePipeline({
654 .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
655 .pNext = nullptr,
656 .flags = 0,
657 .stage{
658 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
659 .pNext = nullptr,
660 .flags = 0,
661 .stage = VK_SHADER_STAGE_COMPUTE_BIT,
662 .module = *modules[i],
663 .pName = "main",
664 .pSpecializationInfo = nullptr,
665 },
666 .layout = *layout,
667 .basePipelineHandle = nullptr,
668 .basePipelineIndex = 0,
669 });
670 };
671 make_msaa_pipeline(0, CONVERT_NON_MSAA_TO_MSAA_COMP_SPV);
672 make_msaa_pipeline(1, CONVERT_MSAA_TO_NON_MSAA_COMP_SPV);
673}
674
675MSAACopyPass::~MSAACopyPass() = default;
676
677void MSAACopyPass::CopyImage(Image& dst_image, Image& src_image,
678 std::span<const VideoCommon::ImageCopy> copies,
679 bool msaa_to_non_msaa) {
680 const VkPipeline msaa_pipeline = *pipelines[msaa_to_non_msaa ? 1 : 0];
681 scheduler.RequestOutsideRenderPassOperationContext();
682 for (const VideoCommon::ImageCopy& copy : copies) {
683 ASSERT(copy.src_subresource.base_layer == 0);
684 ASSERT(copy.src_subresource.num_layers == 1);
685 ASSERT(copy.dst_subresource.base_layer == 0);
686 ASSERT(copy.dst_subresource.num_layers == 1);
687
688 compute_pass_descriptor_queue.Acquire();
689 compute_pass_descriptor_queue.AddImage(
690 src_image.StorageImageView(copy.src_subresource.base_level));
691 compute_pass_descriptor_queue.AddImage(
692 dst_image.StorageImageView(copy.dst_subresource.base_level));
693 const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
694
695 const Common::Vec3<u32> num_dispatches = {
696 Common::DivCeil(copy.extent.width, 8U),
697 Common::DivCeil(copy.extent.height, 8U),
698 copy.extent.depth,
699 };
700
701 scheduler.Record([this, dst = dst_image.Handle(), msaa_pipeline, num_dispatches,
702 descriptor_data](vk::CommandBuffer cmdbuf) {
703 const VkDescriptorSet set = descriptor_allocator.Commit();
704 device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
705 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, msaa_pipeline);
706 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
707 cmdbuf.Dispatch(num_dispatches.x, num_dispatches.y, num_dispatches.z);
708 const VkImageMemoryBarrier write_barrier{
709 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
710 .pNext = nullptr,
711 .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
712 .dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
713 .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
714 .newLayout = VK_IMAGE_LAYOUT_GENERAL,
715 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
716 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
717 .image = dst,
718 .subresourceRange{
719 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
720 .baseMipLevel = 0,
721 .levelCount = VK_REMAINING_MIP_LEVELS,
722 .baseArrayLayer = 0,
723 .layerCount = VK_REMAINING_ARRAY_LAYERS,
724 },
725 };
726 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
727 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier);
728 });
729 }
730}
731
416} // namespace Vulkan 732} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h
index dd3927376..7b8f938c1 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.h
@@ -3,6 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <optional>
6#include <span> 7#include <span>
7#include <utility> 8#include <utility>
8 9
@@ -10,6 +11,7 @@
10#include "video_core/engines/maxwell_3d.h" 11#include "video_core/engines/maxwell_3d.h"
11#include "video_core/renderer_vulkan/vk_descriptor_pool.h" 12#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
12#include "video_core/renderer_vulkan/vk_update_descriptor.h" 13#include "video_core/renderer_vulkan/vk_update_descriptor.h"
14#include "video_core/texture_cache/types.h"
13#include "video_core/vulkan_common/vulkan_memory_allocator.h" 15#include "video_core/vulkan_common/vulkan_memory_allocator.h"
14#include "video_core/vulkan_common/vulkan_wrapper.h" 16#include "video_core/vulkan_common/vulkan_wrapper.h"
15 17
@@ -31,7 +33,8 @@ public:
31 vk::Span<VkDescriptorSetLayoutBinding> bindings, 33 vk::Span<VkDescriptorSetLayoutBinding> bindings,
32 vk::Span<VkDescriptorUpdateTemplateEntry> templates, 34 vk::Span<VkDescriptorUpdateTemplateEntry> templates,
33 const DescriptorBankInfo& bank_info, 35 const DescriptorBankInfo& bank_info,
34 vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code); 36 vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code,
37 std::optional<u32> optional_subgroup_size = std::nullopt);
35 ~ComputePass(); 38 ~ComputePass();
36 39
37protected: 40protected:
@@ -82,6 +85,33 @@ private:
82 ComputePassDescriptorQueue& compute_pass_descriptor_queue; 85 ComputePassDescriptorQueue& compute_pass_descriptor_queue;
83}; 86};
84 87
88class ConditionalRenderingResolvePass final : public ComputePass {
89public:
90 explicit ConditionalRenderingResolvePass(
91 const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
92 ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
93
94 void Resolve(VkBuffer dst_buffer, VkBuffer src_buffer, u32 src_offset, bool compare_to_zero);
95
96private:
97 Scheduler& scheduler;
98 ComputePassDescriptorQueue& compute_pass_descriptor_queue;
99};
100
101class QueriesPrefixScanPass final : public ComputePass {
102public:
103 explicit QueriesPrefixScanPass(const Device& device_, Scheduler& scheduler_,
104 DescriptorPool& descriptor_pool_,
105 ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
106
107 void Run(VkBuffer accumulation_buffer, VkBuffer dst_buffer, VkBuffer src_buffer,
108 size_t number_of_sums, size_t min_accumulation_limit, size_t max_accumulation_limit);
109
110private:
111 Scheduler& scheduler;
112 ComputePassDescriptorQueue& compute_pass_descriptor_queue;
113};
114
85class ASTCDecoderPass final : public ComputePass { 115class ASTCDecoderPass final : public ComputePass {
86public: 116public:
87 explicit ASTCDecoderPass(const Device& device_, Scheduler& scheduler_, 117 explicit ASTCDecoderPass(const Device& device_, Scheduler& scheduler_,
@@ -101,4 +131,22 @@ private:
101 MemoryAllocator& memory_allocator; 131 MemoryAllocator& memory_allocator;
102}; 132};
103 133
134class MSAACopyPass final : public ComputePass {
135public:
136 explicit MSAACopyPass(const Device& device_, Scheduler& scheduler_,
137 DescriptorPool& descriptor_pool_, StagingBufferPool& staging_buffer_pool_,
138 ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
139 ~MSAACopyPass();
140
141 void CopyImage(Image& dst_image, Image& src_image,
142 std::span<const VideoCommon::ImageCopy> copies, bool msaa_to_non_msaa);
143
144private:
145 Scheduler& scheduler;
146 StagingBufferPool& staging_buffer_pool;
147 ComputePassDescriptorQueue& compute_pass_descriptor_queue;
148 std::array<vk::ShaderModule, 2> modules;
149 std::array<vk::Pipeline, 2> pipelines;
150};
151
104} // namespace Vulkan 152} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.h b/src/video_core/renderer_vulkan/vk_fence_manager.h
index 145359d4e..336573574 100644
--- a/src/video_core/renderer_vulkan/vk_fence_manager.h
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.h
@@ -7,6 +7,7 @@
7 7
8#include "video_core/fence_manager.h" 8#include "video_core/fence_manager.h"
9#include "video_core/renderer_vulkan/vk_buffer_cache.h" 9#include "video_core/renderer_vulkan/vk_buffer_cache.h"
10#include "video_core/renderer_vulkan/vk_query_cache.h"
10#include "video_core/renderer_vulkan/vk_texture_cache.h" 11#include "video_core/renderer_vulkan/vk_texture_cache.h"
11 12
12namespace Core { 13namespace Core {
@@ -20,7 +21,6 @@ class RasterizerInterface;
20namespace Vulkan { 21namespace Vulkan {
21 22
22class Device; 23class Device;
23class QueryCache;
24class Scheduler; 24class Scheduler;
25 25
26class InnerFence : public VideoCommon::FenceBase { 26class InnerFence : public VideoCommon::FenceBase {
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
index 29e0b797b..17b2587ad 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -1,139 +1,1554 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-3.0-or-later
3 3
4#include <algorithm>
5#include <cstddef> 4#include <cstddef>
5#include <limits>
6#include <map>
7#include <memory>
8#include <span>
9#include <type_traits>
10#include <unordered_map>
6#include <utility> 11#include <utility>
7#include <vector> 12#include <vector>
8 13
14#include "common/bit_util.h"
15#include "common/common_types.h"
16#include "core/memory.h"
17#include "video_core/engines/draw_manager.h"
18#include "video_core/query_cache/query_cache.h"
19#include "video_core/renderer_vulkan/vk_buffer_cache.h"
20#include "video_core/renderer_vulkan/vk_compute_pass.h"
9#include "video_core/renderer_vulkan/vk_query_cache.h" 21#include "video_core/renderer_vulkan/vk_query_cache.h"
10#include "video_core/renderer_vulkan/vk_resource_pool.h" 22#include "video_core/renderer_vulkan/vk_resource_pool.h"
11#include "video_core/renderer_vulkan/vk_scheduler.h" 23#include "video_core/renderer_vulkan/vk_scheduler.h"
24#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
25#include "video_core/renderer_vulkan/vk_update_descriptor.h"
12#include "video_core/vulkan_common/vulkan_device.h" 26#include "video_core/vulkan_common/vulkan_device.h"
27#include "video_core/vulkan_common/vulkan_memory_allocator.h"
13#include "video_core/vulkan_common/vulkan_wrapper.h" 28#include "video_core/vulkan_common/vulkan_wrapper.h"
14 29
15namespace Vulkan { 30namespace Vulkan {
16 31
17using VideoCore::QueryType; 32using Tegra::Engines::Maxwell3D;
33using VideoCommon::QueryType;
18 34
19namespace { 35namespace {
36class SamplesQueryBank : public VideoCommon::BankBase {
37public:
38 static constexpr size_t BANK_SIZE = 256;
39 static constexpr size_t QUERY_SIZE = 8;
40 explicit SamplesQueryBank(const Device& device_, size_t index_)
41 : BankBase(BANK_SIZE), device{device_}, index{index_} {
42 const auto& dev = device.GetLogical();
43 query_pool = dev.CreateQueryPool({
44 .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
45 .pNext = nullptr,
46 .flags = 0,
47 .queryType = VK_QUERY_TYPE_OCCLUSION,
48 .queryCount = BANK_SIZE,
49 .pipelineStatistics = 0,
50 });
51 Reset();
52 }
20 53
21constexpr std::array QUERY_TARGETS = {VK_QUERY_TYPE_OCCLUSION}; 54 ~SamplesQueryBank() = default;
22 55
23constexpr VkQueryType GetTarget(QueryType type) { 56 void Reset() override {
24 return QUERY_TARGETS[static_cast<std::size_t>(type)]; 57 ASSERT(references == 0);
25} 58 VideoCommon::BankBase::Reset();
59 const auto& dev = device.GetLogical();
60 dev.ResetQueryPool(*query_pool, 0, BANK_SIZE);
61 host_results.fill(0ULL);
62 next_bank = 0;
63 }
64
65 void Sync(size_t start, size_t size) {
66 const auto& dev = device.GetLogical();
67 const VkResult query_result = dev.GetQueryResults(
68 *query_pool, static_cast<u32>(start), static_cast<u32>(size), sizeof(u64) * size,
69 &host_results[start], sizeof(u64), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
70 switch (query_result) {
71 case VK_SUCCESS:
72 return;
73 case VK_ERROR_DEVICE_LOST:
74 device.ReportLoss();
75 [[fallthrough]];
76 default:
77 throw vk::Exception(query_result);
78 }
79 }
80
81 VkQueryPool GetInnerPool() {
82 return *query_pool;
83 }
84
85 size_t GetIndex() const {
86 return index;
87 }
88
89 const std::array<u64, BANK_SIZE>& GetResults() const {
90 return host_results;
91 }
92
93 size_t next_bank;
94
95private:
96 const Device& device;
97 const size_t index;
98 vk::QueryPool query_pool;
99 std::array<u64, BANK_SIZE> host_results;
100};
101
102using BaseStreamer = VideoCommon::SimpleStreamer<VideoCommon::HostQueryBase>;
103
104struct HostSyncValues {
105 VAddr address;
106 size_t size;
107 size_t offset;
108
109 static constexpr bool GeneratesBaseBuffer = false;
110};
111
112class SamplesStreamer : public BaseStreamer {
113public:
114 explicit SamplesStreamer(size_t id_, QueryCacheRuntime& runtime_,
115 VideoCore::RasterizerInterface* rasterizer_, const Device& device_,
116 Scheduler& scheduler_, const MemoryAllocator& memory_allocator_,
117 ComputePassDescriptorQueue& compute_pass_descriptor_queue,
118 DescriptorPool& descriptor_pool)
119 : BaseStreamer(id_), runtime{runtime_}, rasterizer{rasterizer_}, device{device_},
120 scheduler{scheduler_}, memory_allocator{memory_allocator_} {
121 current_bank = nullptr;
122 current_query = nullptr;
123 ammend_value = 0;
124 acumulation_value = 0;
125 queries_prefix_scan_pass = std::make_unique<QueriesPrefixScanPass>(
126 device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
127
128 const VkBufferCreateInfo buffer_ci = {
129 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
130 .pNext = nullptr,
131 .flags = 0,
132 .size = 8,
133 .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
134 VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
135 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
136 .queueFamilyIndexCount = 0,
137 .pQueueFamilyIndices = nullptr,
138 };
139 accumulation_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
140 scheduler.RequestOutsideRenderPassOperationContext();
141 scheduler.Record([buffer = *accumulation_buffer](vk::CommandBuffer cmdbuf) {
142 cmdbuf.FillBuffer(buffer, 0, 8, 0);
143 });
144 }
145
146 ~SamplesStreamer() = default;
147
148 void StartCounter() override {
149 if (has_started) {
150 return;
151 }
152 ReserveHostQuery();
153 scheduler.Record([query_pool = current_query_pool,
154 query_index = current_bank_slot](vk::CommandBuffer cmdbuf) {
155 const bool use_precise = Settings::IsGPULevelHigh();
156 cmdbuf.BeginQuery(query_pool, static_cast<u32>(query_index),
157 use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0);
158 });
159 has_started = true;
160 }
161
162 void PauseCounter() override {
163 if (!has_started) {
164 return;
165 }
166 scheduler.Record([query_pool = current_query_pool,
167 query_index = current_bank_slot](vk::CommandBuffer cmdbuf) {
168 cmdbuf.EndQuery(query_pool, static_cast<u32>(query_index));
169 });
170 has_started = false;
171 }
172
173 void ResetCounter() override {
174 if (has_started) {
175 PauseCounter();
176 }
177 AbandonCurrentQuery();
178 std::function<void()> func([this, counts = pending_flush_queries.size()] {
179 ammend_value = 0;
180 acumulation_value = 0;
181 });
182 rasterizer->SyncOperation(std::move(func));
183 accumulation_since_last_sync = false;
184 first_accumulation_checkpoint = std::min(first_accumulation_checkpoint, num_slots_used);
185 last_accumulation_checkpoint = std::max(last_accumulation_checkpoint, num_slots_used);
186 }
187
188 void CloseCounter() override {
189 PauseCounter();
190 }
26 191
27} // Anonymous namespace 192 bool HasPendingSync() const override {
193 return !pending_sync.empty();
194 }
195
196 void SyncWrites() override {
197 if (sync_values_stash.empty()) {
198 return;
199 }
28 200
29QueryPool::QueryPool(const Device& device_, Scheduler& scheduler, QueryType type_) 201 for (size_t i = 0; i < sync_values_stash.size(); i++) {
30 : ResourcePool{scheduler.GetMasterSemaphore(), GROW_STEP}, device{device_}, type{type_} {} 202 runtime.template SyncValues<HostSyncValues>(sync_values_stash[i],
203 *buffers[resolve_buffers[i]]);
204 }
205
206 sync_values_stash.clear();
207 }
31 208
32QueryPool::~QueryPool() = default; 209 void PresyncWrites() override {
210 if (pending_sync.empty()) {
211 return;
212 }
213 PauseCounter();
214 sync_values_stash.clear();
215 sync_values_stash.emplace_back();
216 std::vector<HostSyncValues>* sync_values = &sync_values_stash.back();
217 sync_values->reserve(num_slots_used);
218 std::unordered_map<size_t, std::pair<size_t, size_t>> offsets;
219 resolve_buffers.clear();
220 size_t resolve_buffer_index = ObtainBuffer<true>(num_slots_used);
221 resolve_buffers.push_back(resolve_buffer_index);
222 size_t base_offset = 0;
33 223
34std::pair<VkQueryPool, u32> QueryPool::Commit() { 224 ApplyBanksWideOp<true>(pending_sync, [&](SamplesQueryBank* bank, size_t start,
35 std::size_t index; 225 size_t amount) {
36 do { 226 size_t bank_id = bank->GetIndex();
37 index = CommitResource(); 227 auto& resolve_buffer = buffers[resolve_buffer_index];
38 } while (usage[index]); 228 VkQueryPool query_pool = bank->GetInnerPool();
39 usage[index] = true; 229 scheduler.RequestOutsideRenderPassOperationContext();
230 scheduler.Record([start, amount, base_offset, query_pool,
231 buffer = *resolve_buffer](vk::CommandBuffer cmdbuf) {
232 const VkBufferMemoryBarrier copy_query_pool_barrier{
233 .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
234 .pNext = nullptr,
235 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
236 .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
237 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
238 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
239 .buffer = buffer,
240 .offset = base_offset,
241 .size = amount * SamplesQueryBank::QUERY_SIZE,
242 };
243
244 cmdbuf.CopyQueryPoolResults(
245 query_pool, static_cast<u32>(start), static_cast<u32>(amount), buffer,
246 static_cast<u32>(base_offset), SamplesQueryBank::QUERY_SIZE,
247 VK_QUERY_RESULT_WAIT_BIT | VK_QUERY_RESULT_64_BIT);
248 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
249 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, copy_query_pool_barrier);
250 });
251 offsets[bank_id] = {start, base_offset};
252 base_offset += amount * SamplesQueryBank::QUERY_SIZE;
253 });
254
255 // Convert queries
256 bool has_multi_queries = false;
257 for (auto q : pending_sync) {
258 auto* query = GetQuery(q);
259 size_t sync_value_slot = 0;
260 if (True(query->flags & VideoCommon::QueryFlagBits::IsRewritten)) {
261 continue;
262 }
263 if (True(query->flags & VideoCommon::QueryFlagBits::IsInvalidated)) {
264 continue;
265 }
266 if (accumulation_since_last_sync || query->size_slots > 1) {
267 if (!has_multi_queries) {
268 has_multi_queries = true;
269 sync_values_stash.emplace_back();
270 }
271 sync_value_slot = 1;
272 }
273 query->flags |= VideoCommon::QueryFlagBits::IsHostSynced;
274 auto loc_data = offsets[query->start_bank_id];
275 sync_values_stash[sync_value_slot].emplace_back(HostSyncValues{
276 .address = query->guest_address,
277 .size = SamplesQueryBank::QUERY_SIZE,
278 .offset =
279 loc_data.second + (query->start_slot - loc_data.first + query->size_slots - 1) *
280 SamplesQueryBank::QUERY_SIZE,
281 });
282 }
283
284 if (has_multi_queries) {
285 size_t intermediary_buffer_index = ObtainBuffer<false>(num_slots_used);
286 resolve_buffers.push_back(intermediary_buffer_index);
287 queries_prefix_scan_pass->Run(*accumulation_buffer, *buffers[intermediary_buffer_index],
288 *buffers[resolve_buffer_index], num_slots_used,
289 std::min(first_accumulation_checkpoint, num_slots_used),
290 last_accumulation_checkpoint);
291
292 } else {
293 scheduler.RequestOutsideRenderPassOperationContext();
294 scheduler.Record([buffer = *accumulation_buffer](vk::CommandBuffer cmdbuf) {
295 cmdbuf.FillBuffer(buffer, 0, 8, 0);
296 });
297 }
298
299 ReplicateCurrentQueryIfNeeded();
300 std::function<void()> func([this] { ammend_value = acumulation_value; });
301 rasterizer->SyncOperation(std::move(func));
302 AbandonCurrentQuery();
303 num_slots_used = 0;
304 first_accumulation_checkpoint = std::numeric_limits<size_t>::max();
305 last_accumulation_checkpoint = 0;
306 accumulation_since_last_sync = has_multi_queries;
307 pending_sync.clear();
308 }
40 309
41 return {*pools[index / GROW_STEP], static_cast<u32>(index % GROW_STEP)}; 310 size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
311 [[maybe_unused]] std::optional<u32> subreport) override {
312 PauseCounter();
313 auto index = BuildQuery();
314 auto* new_query = GetQuery(index);
315 new_query->guest_address = address;
316 new_query->value = 0;
317 new_query->flags &= ~VideoCommon::QueryFlagBits::IsOrphan;
318 if (has_timestamp) {
319 new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp;
320 }
321 if (!current_query) {
322 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
323 return index;
324 }
325 new_query->start_bank_id = current_query->start_bank_id;
326 new_query->size_banks = current_query->size_banks;
327 new_query->start_slot = current_query->start_slot;
328 new_query->size_slots = current_query->size_slots;
329 ApplyBankOp(new_query, [](SamplesQueryBank* bank, size_t start, size_t amount) {
330 bank->AddReference(amount);
331 });
332 pending_sync.push_back(index);
333 pending_flush_queries.push_back(index);
334 return index;
335 }
336
337 bool HasUnsyncedQueries() const override {
338 return !pending_flush_queries.empty();
339 }
340
341 void PushUnsyncedQueries() override {
342 PauseCounter();
343 current_bank->Close();
344 {
345 std::scoped_lock lk(flush_guard);
346 pending_flush_sets.emplace_back(std::move(pending_flush_queries));
347 }
348 }
349
350 void PopUnsyncedQueries() override {
351 std::vector<size_t> current_flush_queries;
352 {
353 std::scoped_lock lk(flush_guard);
354 current_flush_queries = std::move(pending_flush_sets.front());
355 pending_flush_sets.pop_front();
356 }
357 ApplyBanksWideOp<false>(
358 current_flush_queries,
359 [](SamplesQueryBank* bank, size_t start, size_t amount) { bank->Sync(start, amount); });
360 for (auto q : current_flush_queries) {
361 auto* query = GetQuery(q);
362 u64 total = 0;
363 ApplyBankOp(query, [&total](SamplesQueryBank* bank, size_t start, size_t amount) {
364 const auto& results = bank->GetResults();
365 for (size_t i = 0; i < amount; i++) {
366 total += results[start + i];
367 }
368 });
369 query->value = total;
370 query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
371 }
372 }
373
374private:
375 template <typename Func>
376 void ApplyBankOp(VideoCommon::HostQueryBase* query, Func&& func) {
377 size_t size_slots = query->size_slots;
378 if (size_slots == 0) {
379 return;
380 }
381 size_t bank_id = query->start_bank_id;
382 size_t banks_set = query->size_banks;
383 size_t start_slot = query->start_slot;
384 for (size_t i = 0; i < banks_set; i++) {
385 auto& the_bank = bank_pool.GetBank(bank_id);
386 size_t amount = std::min(the_bank.Size() - start_slot, size_slots);
387 func(&the_bank, start_slot, amount);
388 bank_id = the_bank.next_bank - 1;
389 start_slot = 0;
390 size_slots -= amount;
391 }
392 }
393
394 template <bool is_ordered, typename Func>
395 void ApplyBanksWideOp(std::vector<size_t>& queries, Func&& func) {
396 std::conditional_t<is_ordered, std::map<size_t, std::pair<size_t, size_t>>,
397 std::unordered_map<size_t, std::pair<size_t, size_t>>>
398 indexer;
399 for (auto q : queries) {
400 auto* query = GetQuery(q);
401 ApplyBankOp(query, [&indexer](SamplesQueryBank* bank, size_t start, size_t amount) {
402 auto id_ = bank->GetIndex();
403 auto pair = indexer.try_emplace(id_, std::numeric_limits<size_t>::max(),
404 std::numeric_limits<size_t>::min());
405 auto& current_pair = pair.first->second;
406 current_pair.first = std::min(current_pair.first, start);
407 current_pair.second = std::max(current_pair.second, amount + start);
408 });
409 }
410 for (auto& cont : indexer) {
411 func(&bank_pool.GetBank(cont.first), cont.second.first,
412 cont.second.second - cont.second.first);
413 }
414 }
415
416 void ReserveBank() {
417 current_bank_id =
418 bank_pool.ReserveBank([this](std::deque<SamplesQueryBank>& queue, size_t index) {
419 queue.emplace_back(device, index);
420 });
421 if (current_bank) {
422 current_bank->next_bank = current_bank_id + 1;
423 }
424 current_bank = &bank_pool.GetBank(current_bank_id);
425 current_query_pool = current_bank->GetInnerPool();
426 }
427
428 size_t ReserveBankSlot() {
429 if (!current_bank || current_bank->IsClosed()) {
430 ReserveBank();
431 }
432 auto [built, index] = current_bank->Reserve();
433 current_bank_slot = index;
434 return index;
435 }
436
437 void ReserveHostQuery() {
438 size_t new_slot = ReserveBankSlot();
439 current_bank->AddReference(1);
440 num_slots_used++;
441 if (current_query) {
442 size_t bank_id = current_query->start_bank_id;
443 size_t banks_set = current_query->size_banks - 1;
444 bool found = bank_id == current_bank_id;
445 while (!found && banks_set > 0) {
446 SamplesQueryBank& some_bank = bank_pool.GetBank(bank_id);
447 bank_id = some_bank.next_bank - 1;
448 found = bank_id == current_bank_id;
449 banks_set--;
450 }
451 if (!found) {
452 current_query->size_banks++;
453 }
454 current_query->size_slots++;
455 } else {
456 current_query_id = BuildQuery();
457 current_query = GetQuery(current_query_id);
458 current_query->start_bank_id = static_cast<u32>(current_bank_id);
459 current_query->size_banks = 1;
460 current_query->start_slot = new_slot;
461 current_query->size_slots = 1;
462 }
463 }
464
465 void Free(size_t query_id) override {
466 std::scoped_lock lk(guard);
467 auto* query = GetQuery(query_id);
468 ApplyBankOp(query, [](SamplesQueryBank* bank, size_t start, size_t amount) {
469 bank->CloseReference(amount);
470 });
471 ReleaseQuery(query_id);
472 }
473
474 void AbandonCurrentQuery() {
475 if (!current_query) {
476 return;
477 }
478 Free(current_query_id);
479 current_query = nullptr;
480 current_query_id = 0;
481 }
482
483 void ReplicateCurrentQueryIfNeeded() {
484 if (pending_sync.empty()) {
485 return;
486 }
487 if (!current_query) {
488 return;
489 }
490 auto index = BuildQuery();
491 auto* new_query = GetQuery(index);
492 new_query->guest_address = 0;
493 new_query->value = 0;
494 new_query->flags &= ~VideoCommon::QueryFlagBits::IsOrphan;
495 new_query->start_bank_id = current_query->start_bank_id;
496 new_query->size_banks = current_query->size_banks;
497 new_query->start_slot = current_query->start_slot;
498 new_query->size_slots = current_query->size_slots;
499 ApplyBankOp(new_query, [](SamplesQueryBank* bank, size_t start, size_t amount) {
500 bank->AddReference(amount);
501 });
502 pending_flush_queries.push_back(index);
503 std::function<void()> func([this, index] {
504 auto* query = GetQuery(index);
505 query->value += GetAmmendValue();
506 SetAccumulationValue(query->value);
507 Free(index);
508 });
509 }
510
511 template <bool is_resolve>
512 size_t ObtainBuffer(size_t num_needed) {
513 const size_t log_2 = std::max<size_t>(11U, Common::Log2Ceil64(num_needed));
514 if constexpr (is_resolve) {
515 if (resolve_table[log_2] != 0) {
516 return resolve_table[log_2] - 1;
517 }
518 } else {
519 if (intermediary_table[log_2] != 0) {
520 return intermediary_table[log_2] - 1;
521 }
522 }
523 const VkBufferCreateInfo buffer_ci = {
524 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
525 .pNext = nullptr,
526 .flags = 0,
527 .size = SamplesQueryBank::QUERY_SIZE * (1ULL << log_2),
528 .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
529 VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
530 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
531 .queueFamilyIndexCount = 0,
532 .pQueueFamilyIndices = nullptr,
533 };
534 buffers.emplace_back(memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal));
535 if constexpr (is_resolve) {
536 resolve_table[log_2] = buffers.size();
537 } else {
538 intermediary_table[log_2] = buffers.size();
539 }
540 return buffers.size() - 1;
541 }
542
543 QueryCacheRuntime& runtime;
544 VideoCore::RasterizerInterface* rasterizer;
545 const Device& device;
546 Scheduler& scheduler;
547 const MemoryAllocator& memory_allocator;
548 VideoCommon::BankPool<SamplesQueryBank> bank_pool;
549 std::deque<vk::Buffer> buffers;
550 std::array<size_t, 32> resolve_table{};
551 std::array<size_t, 32> intermediary_table{};
552 vk::Buffer accumulation_buffer;
553 std::deque<std::vector<HostSyncValues>> sync_values_stash;
554 std::vector<size_t> resolve_buffers;
555
556 // syncing queue
557 std::vector<size_t> pending_sync;
558
559 // flush levels
560 std::vector<size_t> pending_flush_queries;
561 std::deque<std::vector<size_t>> pending_flush_sets;
562
563 // State Machine
564 size_t current_bank_slot;
565 size_t current_bank_id;
566 SamplesQueryBank* current_bank;
567 VkQueryPool current_query_pool;
568 size_t current_query_id;
569 size_t num_slots_used{};
570 size_t first_accumulation_checkpoint{};
571 size_t last_accumulation_checkpoint{};
572 bool accumulation_since_last_sync{};
573 VideoCommon::HostQueryBase* current_query;
574 bool has_started{};
575 std::mutex flush_guard;
576
577 std::unique_ptr<QueriesPrefixScanPass> queries_prefix_scan_pass;
578};
579
580// Transform feedback queries
581class TFBQueryBank : public VideoCommon::BankBase {
582public:
583 static constexpr size_t BANK_SIZE = 1024;
584 static constexpr size_t QUERY_SIZE = 4;
585 explicit TFBQueryBank(Scheduler& scheduler_, const MemoryAllocator& memory_allocator,
586 size_t index_)
587 : BankBase(BANK_SIZE), scheduler{scheduler_}, index{index_} {
588 const VkBufferCreateInfo buffer_ci = {
589 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
590 .pNext = nullptr,
591 .flags = 0,
592 .size = QUERY_SIZE * BANK_SIZE,
593 .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
594 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
595 .queueFamilyIndexCount = 0,
596 .pQueueFamilyIndices = nullptr,
597 };
598 buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
599 }
600
601 ~TFBQueryBank() = default;
602
603 void Reset() override {
604 ASSERT(references == 0);
605 VideoCommon::BankBase::Reset();
606 }
607
608 void Sync(StagingBufferRef& stagging_buffer, size_t extra_offset, size_t start, size_t size) {
609 scheduler.RequestOutsideRenderPassOperationContext();
610 scheduler.Record([this, dst_buffer = stagging_buffer.buffer, extra_offset, start,
611 size](vk::CommandBuffer cmdbuf) {
612 std::array<VkBufferCopy, 1> copy{VkBufferCopy{
613 .srcOffset = start * QUERY_SIZE,
614 .dstOffset = extra_offset,
615 .size = size * QUERY_SIZE,
616 }};
617 cmdbuf.CopyBuffer(*buffer, dst_buffer, copy);
618 });
619 }
620
621 size_t GetIndex() const {
622 return index;
623 }
624
625 VkBuffer GetBuffer() const {
626 return *buffer;
627 }
628
629private:
630 Scheduler& scheduler;
631 const size_t index;
632 vk::Buffer buffer;
633};
634
635class PrimitivesSucceededStreamer;
636
637class TFBCounterStreamer : public BaseStreamer {
638public:
639 explicit TFBCounterStreamer(size_t id_, QueryCacheRuntime& runtime_, const Device& device_,
640 Scheduler& scheduler_, const MemoryAllocator& memory_allocator_,
641 StagingBufferPool& staging_pool_)
642 : BaseStreamer(id_), runtime{runtime_}, device{device_}, scheduler{scheduler_},
643 memory_allocator{memory_allocator_}, staging_pool{staging_pool_} {
644 buffers_count = 0;
645 current_bank = nullptr;
646 counter_buffers.fill(VK_NULL_HANDLE);
647 offsets.fill(0);
648 last_queries.fill(0);
649 last_queries_stride.fill(1);
650 const VkBufferCreateInfo buffer_ci = {
651 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
652 .pNext = nullptr,
653 .flags = 0,
654 .size = TFBQueryBank::QUERY_SIZE * NUM_STREAMS,
655 .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
656 VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT,
657 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
658 .queueFamilyIndexCount = 0,
659 .pQueueFamilyIndices = nullptr,
660 };
661
662 counters_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
663 for (auto& c : counter_buffers) {
664 c = *counters_buffer;
665 }
666 size_t base_offset = 0;
667 for (auto& o : offsets) {
668 o = base_offset;
669 base_offset += TFBQueryBank::QUERY_SIZE;
670 }
671 }
672
673 ~TFBCounterStreamer() = default;
674
675 void StartCounter() override {
676 FlushBeginTFB();
677 has_started = true;
678 }
679
680 void PauseCounter() override {
681 CloseCounter();
682 }
683
684 void ResetCounter() override {
685 CloseCounter();
686 }
687
688 void CloseCounter() override {
689 if (has_flushed_end_pending) {
690 FlushEndTFB();
691 }
692 runtime.View3DRegs([this](Maxwell3D& maxwell3d) {
693 if (maxwell3d.regs.transform_feedback_enabled == 0) {
694 streams_mask = 0;
695 has_started = false;
696 }
697 });
698 }
699
700 bool HasPendingSync() const override {
701 return !pending_sync.empty();
702 }
703
704 void SyncWrites() override {
705 CloseCounter();
706 std::unordered_map<size_t, std::vector<HostSyncValues>> sync_values_stash;
707 for (auto q : pending_sync) {
708 auto* query = GetQuery(q);
709 if (True(query->flags & VideoCommon::QueryFlagBits::IsRewritten)) {
710 continue;
711 }
712 if (True(query->flags & VideoCommon::QueryFlagBits::IsInvalidated)) {
713 continue;
714 }
715 query->flags |= VideoCommon::QueryFlagBits::IsHostSynced;
716 sync_values_stash.try_emplace(query->start_bank_id);
717 sync_values_stash[query->start_bank_id].emplace_back(HostSyncValues{
718 .address = query->guest_address,
719 .size = TFBQueryBank::QUERY_SIZE,
720 .offset = query->start_slot * TFBQueryBank::QUERY_SIZE,
721 });
722 }
723 for (auto& p : sync_values_stash) {
724 auto& bank = bank_pool.GetBank(p.first);
725 runtime.template SyncValues<HostSyncValues>(p.second, bank.GetBuffer());
726 }
727 pending_sync.clear();
728 }
729
730 size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
731 std::optional<u32> subreport_) override {
732 auto index = BuildQuery();
733 auto* new_query = GetQuery(index);
734 new_query->guest_address = address;
735 new_query->value = 0;
736 new_query->flags &= ~VideoCommon::QueryFlagBits::IsOrphan;
737 if (has_timestamp) {
738 new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp;
739 }
740 if (!subreport_) {
741 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
742 return index;
743 }
744 const size_t subreport = static_cast<size_t>(*subreport_);
745 last_queries[subreport] = address;
746 if ((streams_mask & (1ULL << subreport)) == 0) {
747 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
748 return index;
749 }
750 CloseCounter();
751 auto [bank_slot, data_slot] = ProduceCounterBuffer(subreport);
752 new_query->start_bank_id = static_cast<u32>(bank_slot);
753 new_query->size_banks = 1;
754 new_query->start_slot = static_cast<u32>(data_slot);
755 new_query->size_slots = 1;
756 pending_sync.push_back(index);
757 pending_flush_queries.push_back(index);
758 return index;
759 }
760
761 std::optional<std::pair<VAddr, size_t>> GetLastQueryStream(size_t stream) {
762 if (last_queries[stream] != 0) {
763 std::pair<VAddr, size_t> result(last_queries[stream], last_queries_stride[stream]);
764 return result;
765 }
766 return std::nullopt;
767 }
768
769 Maxwell3D::Regs::PrimitiveTopology GetOutputTopology() const {
770 return out_topology;
771 }
772
773 bool HasUnsyncedQueries() const override {
774 return !pending_flush_queries.empty();
775 }
776
777 void PushUnsyncedQueries() override {
778 CloseCounter();
779 auto staging_ref = staging_pool.Request(
780 pending_flush_queries.size() * TFBQueryBank::QUERY_SIZE, MemoryUsage::Download, true);
781 size_t offset_base = staging_ref.offset;
782 for (auto q : pending_flush_queries) {
783 auto* query = GetQuery(q);
784 auto& bank = bank_pool.GetBank(query->start_bank_id);
785 bank.Sync(staging_ref, offset_base, query->start_slot, 1);
786 offset_base += TFBQueryBank::QUERY_SIZE;
787 bank.CloseReference();
788 }
789 static constexpr VkMemoryBarrier WRITE_BARRIER{
790 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
791 .pNext = nullptr,
792 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
793 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
794 };
795 scheduler.RequestOutsideRenderPassOperationContext();
796 scheduler.Record([](vk::CommandBuffer cmdbuf) {
797 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
798 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
799 });
800
801 std::scoped_lock lk(flush_guard);
802 for (auto& str : free_queue) {
803 staging_pool.FreeDeferred(str);
804 }
805 free_queue.clear();
806 download_buffers.emplace_back(staging_ref);
807 pending_flush_sets.emplace_back(std::move(pending_flush_queries));
808 }
809
810 void PopUnsyncedQueries() override {
811 StagingBufferRef staging_ref;
812 std::vector<size_t> flushed_queries;
813 {
814 std::scoped_lock lk(flush_guard);
815 staging_ref = download_buffers.front();
816 flushed_queries = std::move(pending_flush_sets.front());
817 download_buffers.pop_front();
818 pending_flush_sets.pop_front();
819 }
820
821 size_t offset_base = staging_ref.offset;
822 for (auto q : flushed_queries) {
823 auto* query = GetQuery(q);
824 u32 result = 0;
825 std::memcpy(&result, staging_ref.mapped_span.data() + offset_base, sizeof(u32));
826 query->value = static_cast<u64>(result);
827 query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
828 offset_base += TFBQueryBank::QUERY_SIZE;
829 }
830
831 {
832 std::scoped_lock lk(flush_guard);
833 free_queue.emplace_back(staging_ref);
834 }
835 }
836
837private:
838 void FlushBeginTFB() {
839 if (has_flushed_end_pending) [[unlikely]] {
840 return;
841 }
842 has_flushed_end_pending = true;
843 if (!has_started || buffers_count == 0) {
844 scheduler.Record([](vk::CommandBuffer cmdbuf) {
845 cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr);
846 });
847 UpdateBuffers();
848 return;
849 }
850 scheduler.Record([this, total = static_cast<u32>(buffers_count)](vk::CommandBuffer cmdbuf) {
851 cmdbuf.BeginTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data());
852 });
853 UpdateBuffers();
854 }
855
856 void FlushEndTFB() {
857 if (!has_flushed_end_pending) [[unlikely]] {
858 UNREACHABLE();
859 return;
860 }
861 has_flushed_end_pending = false;
862
863 if (buffers_count == 0) {
864 scheduler.Record([](vk::CommandBuffer cmdbuf) {
865 cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr);
866 });
867 } else {
868 scheduler.Record([this,
869 total = static_cast<u32>(buffers_count)](vk::CommandBuffer cmdbuf) {
870 cmdbuf.EndTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data());
871 });
872 }
873 }
874
875 void UpdateBuffers() {
876 last_queries.fill(0);
877 last_queries_stride.fill(1);
878 runtime.View3DRegs([this](Maxwell3D& maxwell3d) {
879 buffers_count = 0;
880 out_topology = maxwell3d.draw_manager->GetDrawState().topology;
881 for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) {
882 const auto& tf = maxwell3d.regs.transform_feedback;
883 if (tf.buffers[i].enable == 0) {
884 continue;
885 }
886 const size_t stream = tf.controls[i].stream;
887 last_queries_stride[stream] = tf.controls[i].stride;
888 streams_mask |= 1ULL << stream;
889 buffers_count = std::max<size_t>(buffers_count, stream + 1);
890 }
891 });
892 }
893
894 std::pair<size_t, size_t> ProduceCounterBuffer(size_t stream) {
895 if (current_bank == nullptr || current_bank->IsClosed()) {
896 current_bank_id =
897 bank_pool.ReserveBank([this](std::deque<TFBQueryBank>& queue, size_t index) {
898 queue.emplace_back(scheduler, memory_allocator, index);
899 });
900 current_bank = &bank_pool.GetBank(current_bank_id);
901 }
902 auto [dont_care, other] = current_bank->Reserve();
903 const size_t slot = other; // workaround to compile bug.
904 current_bank->AddReference();
905
906 static constexpr VkMemoryBarrier READ_BARRIER{
907 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
908 .pNext = nullptr,
909 .srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT,
910 .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
911 };
912 static constexpr VkMemoryBarrier WRITE_BARRIER{
913 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
914 .pNext = nullptr,
915 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
916 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
917 };
918 scheduler.RequestOutsideRenderPassOperationContext();
919 scheduler.Record([dst_buffer = current_bank->GetBuffer(),
920 src_buffer = counter_buffers[stream], src_offset = offsets[stream],
921 slot](vk::CommandBuffer cmdbuf) {
922 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
923 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
924 std::array<VkBufferCopy, 1> copy{VkBufferCopy{
925 .srcOffset = src_offset,
926 .dstOffset = slot * TFBQueryBank::QUERY_SIZE,
927 .size = TFBQueryBank::QUERY_SIZE,
928 }};
929 cmdbuf.CopyBuffer(src_buffer, dst_buffer, copy);
930 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
931 0, WRITE_BARRIER);
932 });
933 return {current_bank_id, slot};
934 }
935
936 friend class PrimitivesSucceededStreamer;
937
938 static constexpr size_t NUM_STREAMS = 4;
939
940 QueryCacheRuntime& runtime;
941 const Device& device;
942 Scheduler& scheduler;
943 const MemoryAllocator& memory_allocator;
944 StagingBufferPool& staging_pool;
945 VideoCommon::BankPool<TFBQueryBank> bank_pool;
946 size_t current_bank_id;
947 TFBQueryBank* current_bank;
948 vk::Buffer counters_buffer;
949
950 // syncing queue
951 std::vector<size_t> pending_sync;
952
953 // flush levels
954 std::vector<size_t> pending_flush_queries;
955 std::deque<StagingBufferRef> download_buffers;
956 std::deque<std::vector<size_t>> pending_flush_sets;
957 std::vector<StagingBufferRef> free_queue;
958 std::mutex flush_guard;
959
960 // state machine
961 bool has_started{};
962 bool has_flushed_end_pending{};
963 size_t buffers_count{};
964 std::array<VkBuffer, NUM_STREAMS> counter_buffers{};
965 std::array<VkDeviceSize, NUM_STREAMS> offsets{};
966 std::array<VAddr, NUM_STREAMS> last_queries;
967 std::array<size_t, NUM_STREAMS> last_queries_stride;
968 Maxwell3D::Regs::PrimitiveTopology out_topology;
969 u64 streams_mask;
970};
971
972class PrimitivesQueryBase : public VideoCommon::QueryBase {
973public:
974 // Default constructor
975 PrimitivesQueryBase()
976 : VideoCommon::QueryBase(0, VideoCommon::QueryFlagBits::IsHostManaged, 0) {}
977
978 // Parameterized constructor
979 PrimitivesQueryBase(bool has_timestamp, VAddr address)
980 : VideoCommon::QueryBase(address, VideoCommon::QueryFlagBits::IsHostManaged, 0) {
981 if (has_timestamp) {
982 flags |= VideoCommon::QueryFlagBits::HasTimestamp;
983 }
984 }
985
986 u64 stride{};
987 VAddr dependant_address{};
988 Maxwell3D::Regs::PrimitiveTopology topology{Maxwell3D::Regs::PrimitiveTopology::Points};
989 size_t dependant_index{};
990 bool dependant_manage{};
991};
992
993class PrimitivesSucceededStreamer : public VideoCommon::SimpleStreamer<PrimitivesQueryBase> {
994public:
995 explicit PrimitivesSucceededStreamer(size_t id_, QueryCacheRuntime& runtime_,
996 TFBCounterStreamer& tfb_streamer_,
997 Core::Memory::Memory& cpu_memory_)
998 : VideoCommon::SimpleStreamer<PrimitivesQueryBase>(id_), runtime{runtime_},
999 tfb_streamer{tfb_streamer_}, cpu_memory{cpu_memory_} {
1000 MakeDependent(&tfb_streamer);
1001 }
1002
1003 ~PrimitivesSucceededStreamer() = default;
1004
1005 size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
1006 std::optional<u32> subreport_) override {
1007 auto index = BuildQuery();
1008 auto* new_query = GetQuery(index);
1009 new_query->guest_address = address;
1010 new_query->value = 0;
1011 if (has_timestamp) {
1012 new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp;
1013 }
1014 if (!subreport_) {
1015 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
1016 return index;
1017 }
1018 const size_t subreport = static_cast<size_t>(*subreport_);
1019 auto dependant_address_opt = tfb_streamer.GetLastQueryStream(subreport);
1020 bool must_manage_dependance = false;
1021 new_query->topology = tfb_streamer.GetOutputTopology();
1022 if (dependant_address_opt) {
1023 auto [dep_address, stride] = *dependant_address_opt;
1024 new_query->dependant_address = dep_address;
1025 new_query->stride = stride;
1026 } else {
1027 new_query->dependant_index =
1028 tfb_streamer.WriteCounter(address, has_timestamp, value, subreport_);
1029 auto* dependant_query = tfb_streamer.GetQuery(new_query->dependant_index);
1030 dependant_query->flags |= VideoCommon::QueryFlagBits::IsInvalidated;
1031 must_manage_dependance = true;
1032 if (True(dependant_query->flags & VideoCommon::QueryFlagBits::IsFinalValueSynced)) {
1033 new_query->value = 0;
1034 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
1035 if (must_manage_dependance) {
1036 tfb_streamer.Free(new_query->dependant_index);
1037 }
1038 return index;
1039 }
1040 new_query->stride = 1;
1041 runtime.View3DRegs([new_query, subreport](Maxwell3D& maxwell3d) {
1042 for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) {
1043 const auto& tf = maxwell3d.regs.transform_feedback;
1044 if (tf.buffers[i].enable == 0) {
1045 continue;
1046 }
1047 if (tf.controls[i].stream != subreport) {
1048 continue;
1049 }
1050 new_query->stride = tf.controls[i].stride;
1051 break;
1052 }
1053 });
1054 }
1055
1056 new_query->dependant_manage = must_manage_dependance;
1057 pending_flush_queries.push_back(index);
1058 return index;
1059 }
1060
1061 bool HasUnsyncedQueries() const override {
1062 return !pending_flush_queries.empty();
1063 }
1064
1065 void PushUnsyncedQueries() override {
1066 std::scoped_lock lk(flush_guard);
1067 pending_flush_sets.emplace_back(std::move(pending_flush_queries));
1068 pending_flush_queries.clear();
1069 }
1070
1071 void PopUnsyncedQueries() override {
1072 std::vector<size_t> flushed_queries;
1073 {
1074 std::scoped_lock lk(flush_guard);
1075 flushed_queries = std::move(pending_flush_sets.front());
1076 pending_flush_sets.pop_front();
1077 }
1078
1079 for (auto q : flushed_queries) {
1080 auto* query = GetQuery(q);
1081 if (True(query->flags & VideoCommon::QueryFlagBits::IsFinalValueSynced)) {
1082 continue;
1083 }
1084
1085 query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
1086 u64 num_vertices = 0;
1087 if (query->dependant_manage) {
1088 auto* dependant_query = tfb_streamer.GetQuery(query->dependant_index);
1089 num_vertices = dependant_query->value / query->stride;
1090 tfb_streamer.Free(query->dependant_index);
1091 } else {
1092 u8* pointer = cpu_memory.GetPointer(query->dependant_address);
1093 u32 result;
1094 std::memcpy(&result, pointer, sizeof(u32));
1095 num_vertices = static_cast<u64>(result) / query->stride;
1096 }
1097 query->value = [&]() -> u64 {
1098 switch (query->topology) {
1099 case Maxwell3D::Regs::PrimitiveTopology::Points:
1100 return num_vertices;
1101 case Maxwell3D::Regs::PrimitiveTopology::Lines:
1102 return num_vertices / 2;
1103 case Maxwell3D::Regs::PrimitiveTopology::LineLoop:
1104 return (num_vertices / 2) + 1;
1105 case Maxwell3D::Regs::PrimitiveTopology::LineStrip:
1106 return num_vertices - 1;
1107 case Maxwell3D::Regs::PrimitiveTopology::Patches:
1108 case Maxwell3D::Regs::PrimitiveTopology::Triangles:
1109 case Maxwell3D::Regs::PrimitiveTopology::TrianglesAdjacency:
1110 return num_vertices / 3;
1111 case Maxwell3D::Regs::PrimitiveTopology::TriangleFan:
1112 case Maxwell3D::Regs::PrimitiveTopology::TriangleStrip:
1113 case Maxwell3D::Regs::PrimitiveTopology::TriangleStripAdjacency:
1114 return num_vertices - 2;
1115 case Maxwell3D::Regs::PrimitiveTopology::Quads:
1116 return num_vertices / 4;
1117 case Maxwell3D::Regs::PrimitiveTopology::Polygon:
1118 return 1U;
1119 default:
1120 return num_vertices;
1121 }
1122 }();
1123 }
1124 }
1125
1126private:
1127 QueryCacheRuntime& runtime;
1128 TFBCounterStreamer& tfb_streamer;
1129 Core::Memory::Memory& cpu_memory;
1130
1131 // syncing queue
1132 std::vector<size_t> pending_sync;
1133
1134 // flush levels
1135 std::vector<size_t> pending_flush_queries;
1136 std::deque<std::vector<size_t>> pending_flush_sets;
1137 std::mutex flush_guard;
1138};
1139
1140} // namespace
1141
1142struct QueryCacheRuntimeImpl {
1143 QueryCacheRuntimeImpl(QueryCacheRuntime& runtime, VideoCore::RasterizerInterface* rasterizer_,
1144 Core::Memory::Memory& cpu_memory_, Vulkan::BufferCache& buffer_cache_,
1145 const Device& device_, const MemoryAllocator& memory_allocator_,
1146 Scheduler& scheduler_, StagingBufferPool& staging_pool_,
1147 ComputePassDescriptorQueue& compute_pass_descriptor_queue,
1148 DescriptorPool& descriptor_pool)
1149 : rasterizer{rasterizer_}, cpu_memory{cpu_memory_},
1150 buffer_cache{buffer_cache_}, device{device_},
1151 memory_allocator{memory_allocator_}, scheduler{scheduler_}, staging_pool{staging_pool_},
1152 guest_streamer(0, runtime),
1153 sample_streamer(static_cast<size_t>(QueryType::ZPassPixelCount64), runtime, rasterizer,
1154 device, scheduler, memory_allocator, compute_pass_descriptor_queue,
1155 descriptor_pool),
1156 tfb_streamer(static_cast<size_t>(QueryType::StreamingByteCount), runtime, device,
1157 scheduler, memory_allocator, staging_pool),
1158 primitives_succeeded_streamer(
1159 static_cast<size_t>(QueryType::StreamingPrimitivesSucceeded), runtime, tfb_streamer,
1160 cpu_memory_),
1161 primitives_needed_minus_suceeded_streamer(
1162 static_cast<size_t>(QueryType::StreamingPrimitivesNeededMinusSucceeded), runtime, 0u),
1163 hcr_setup{}, hcr_is_set{}, is_hcr_running{}, maxwell3d{} {
1164
1165 hcr_setup.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT;
1166 hcr_setup.pNext = nullptr;
1167 hcr_setup.flags = 0;
1168
1169 conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>(
1170 device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
1171
1172 const VkBufferCreateInfo buffer_ci = {
1173 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
1174 .pNext = nullptr,
1175 .flags = 0,
1176 .size = sizeof(u32),
1177 .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
1178 VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
1179 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
1180 .queueFamilyIndexCount = 0,
1181 .pQueueFamilyIndices = nullptr,
1182 };
1183 hcr_resolve_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
1184 }
1185
1186 VideoCore::RasterizerInterface* rasterizer;
1187 Core::Memory::Memory& cpu_memory;
1188 Vulkan::BufferCache& buffer_cache;
1189
1190 const Device& device;
1191 const MemoryAllocator& memory_allocator;
1192 Scheduler& scheduler;
1193 StagingBufferPool& staging_pool;
1194
1195 // Streamers
1196 VideoCommon::GuestStreamer<QueryCacheParams> guest_streamer;
1197 SamplesStreamer sample_streamer;
1198 TFBCounterStreamer tfb_streamer;
1199 PrimitivesSucceededStreamer primitives_succeeded_streamer;
1200 VideoCommon::StubStreamer<QueryCacheParams> primitives_needed_minus_suceeded_streamer;
1201
1202 std::vector<std::pair<VAddr, VAddr>> little_cache;
1203 std::vector<std::pair<VkBuffer, VkDeviceSize>> buffers_to_upload_to;
1204 std::vector<size_t> redirect_cache;
1205 std::vector<std::vector<VkBufferCopy>> copies_setup;
1206
1207 // Host conditional rendering data
1208 std::unique_ptr<ConditionalRenderingResolvePass> conditional_resolve_pass;
1209 vk::Buffer hcr_resolve_buffer;
1210 VkConditionalRenderingBeginInfoEXT hcr_setup;
1211 VkBuffer hcr_buffer;
1212 size_t hcr_offset;
1213 bool hcr_is_set;
1214 bool is_hcr_running;
1215
1216 // maxwell3d
1217 Maxwell3D* maxwell3d;
1218};
1219
1220QueryCacheRuntime::QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer,
1221 Core::Memory::Memory& cpu_memory_,
1222 Vulkan::BufferCache& buffer_cache_, const Device& device_,
1223 const MemoryAllocator& memory_allocator_,
1224 Scheduler& scheduler_, StagingBufferPool& staging_pool_,
1225 ComputePassDescriptorQueue& compute_pass_descriptor_queue,
1226 DescriptorPool& descriptor_pool) {
1227 impl = std::make_unique<QueryCacheRuntimeImpl>(
1228 *this, rasterizer, cpu_memory_, buffer_cache_, device_, memory_allocator_, scheduler_,
1229 staging_pool_, compute_pass_descriptor_queue, descriptor_pool);
42} 1230}
43 1231
44void QueryPool::Allocate(std::size_t begin, std::size_t end) { 1232void QueryCacheRuntime::Bind3DEngine(Maxwell3D* maxwell3d) {
45 usage.resize(end); 1233 impl->maxwell3d = maxwell3d;
1234}
46 1235
47 pools.push_back(device.GetLogical().CreateQueryPool({ 1236template <typename Func>
48 .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, 1237void QueryCacheRuntime::View3DRegs(Func&& func) {
49 .pNext = nullptr, 1238 if (impl->maxwell3d) {
50 .flags = 0, 1239 func(*impl->maxwell3d);
51 .queryType = GetTarget(type), 1240 }
52 .queryCount = static_cast<u32>(end - begin), 1241}
53 .pipelineStatistics = 0, 1242
54 })); 1243void QueryCacheRuntime::EndHostConditionalRendering() {
1244 PauseHostConditionalRendering();
1245 impl->hcr_is_set = false;
1246 impl->is_hcr_running = false;
1247 impl->hcr_buffer = nullptr;
1248 impl->hcr_offset = 0;
1249}
1250
1251void QueryCacheRuntime::PauseHostConditionalRendering() {
1252 if (!impl->hcr_is_set) {
1253 return;
1254 }
1255 if (impl->is_hcr_running) {
1256 impl->scheduler.Record(
1257 [](vk::CommandBuffer cmdbuf) { cmdbuf.EndConditionalRenderingEXT(); });
1258 }
1259 impl->is_hcr_running = false;
55} 1260}
56 1261
57void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) { 1262void QueryCacheRuntime::ResumeHostConditionalRendering() {
58 const auto it = 1263 if (!impl->hcr_is_set) {
59 std::find_if(pools.begin(), pools.end(), [query_pool = query.first](vk::QueryPool& pool) { 1264 return;
60 return query_pool == *pool; 1265 }
1266 if (!impl->is_hcr_running) {
1267 impl->scheduler.Record([hcr_setup = impl->hcr_setup](vk::CommandBuffer cmdbuf) {
1268 cmdbuf.BeginConditionalRenderingEXT(hcr_setup);
61 }); 1269 });
1270 }
1271 impl->is_hcr_running = true;
1272}
62 1273
63 if (it != std::end(pools)) { 1274void QueryCacheRuntime::HostConditionalRenderingCompareValueImpl(VideoCommon::LookupData object,
64 const std::ptrdiff_t pool_index = std::distance(std::begin(pools), it); 1275 bool is_equal) {
65 usage[pool_index * GROW_STEP + static_cast<std::ptrdiff_t>(query.second)] = false; 1276 {
1277 std::scoped_lock lk(impl->buffer_cache.mutex);
1278 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
1279 const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
1280 const auto [buffer, offset] =
1281 impl->buffer_cache.ObtainCPUBuffer(object.address, 8, sync_info, post_op);
1282 impl->hcr_buffer = buffer->Handle();
1283 impl->hcr_offset = offset;
1284 }
1285 if (impl->hcr_is_set) {
1286 if (impl->hcr_setup.buffer == impl->hcr_buffer &&
1287 impl->hcr_setup.offset == impl->hcr_offset) {
1288 ResumeHostConditionalRendering();
1289 return;
1290 }
1291 PauseHostConditionalRendering();
66 } 1292 }
1293 impl->hcr_setup.buffer = impl->hcr_buffer;
1294 impl->hcr_setup.offset = impl->hcr_offset;
1295 impl->hcr_setup.flags = is_equal ? VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT : 0;
1296 impl->hcr_is_set = true;
1297 impl->is_hcr_running = false;
1298 ResumeHostConditionalRendering();
67} 1299}
68 1300
69QueryCache::QueryCache(VideoCore::RasterizerInterface& rasterizer_, 1301void QueryCacheRuntime::HostConditionalRenderingCompareBCImpl(VAddr address, bool is_equal) {
70 Core::Memory::Memory& cpu_memory_, const Device& device_, 1302 VkBuffer to_resolve;
71 Scheduler& scheduler_) 1303 u32 to_resolve_offset;
72 : QueryCacheBase{rasterizer_, cpu_memory_}, device{device_}, scheduler{scheduler_}, 1304 {
73 query_pools{ 1305 std::scoped_lock lk(impl->buffer_cache.mutex);
74 QueryPool{device_, scheduler_, QueryType::SamplesPassed}, 1306 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::NoSynchronize;
75 } {} 1307 const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
76 1308 const auto [buffer, offset] =
77QueryCache::~QueryCache() { 1309 impl->buffer_cache.ObtainCPUBuffer(address, 24, sync_info, post_op);
78 // TODO(Rodrigo): This is a hack to destroy all HostCounter instances before the base class 1310 to_resolve = buffer->Handle();
79 // destructor is called. The query cache should be redesigned to have a proper ownership model 1311 to_resolve_offset = static_cast<u32>(offset);
80 // instead of using shared pointers.
81 for (size_t query_type = 0; query_type < VideoCore::NumQueryTypes; ++query_type) {
82 auto& stream = Stream(static_cast<QueryType>(query_type));
83 stream.Update(false);
84 stream.Reset();
85 } 1312 }
1313 if (impl->is_hcr_running) {
1314 PauseHostConditionalRendering();
1315 }
1316 impl->conditional_resolve_pass->Resolve(*impl->hcr_resolve_buffer, to_resolve,
1317 to_resolve_offset, false);
1318 impl->hcr_setup.buffer = *impl->hcr_resolve_buffer;
1319 impl->hcr_setup.offset = 0;
1320 impl->hcr_setup.flags = is_equal ? 0 : VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT;
1321 impl->hcr_is_set = true;
1322 impl->is_hcr_running = false;
1323 ResumeHostConditionalRendering();
86} 1324}
87 1325
88std::pair<VkQueryPool, u32> QueryCache::AllocateQuery(QueryType type) { 1326bool QueryCacheRuntime::HostConditionalRenderingCompareValue(VideoCommon::LookupData object_1,
89 return query_pools[static_cast<std::size_t>(type)].Commit(); 1327 [[maybe_unused]] bool qc_dirty) {
1328 if (!impl->device.IsExtConditionalRendering()) {
1329 return false;
1330 }
1331 HostConditionalRenderingCompareValueImpl(object_1, false);
1332 return true;
90} 1333}
91 1334
92void QueryCache::Reserve(QueryType type, std::pair<VkQueryPool, u32> query) { 1335bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::LookupData object_1,
93 query_pools[static_cast<std::size_t>(type)].Reserve(query); 1336 VideoCommon::LookupData object_2,
1337 bool qc_dirty, bool equal_check) {
1338 if (!impl->device.IsExtConditionalRendering()) {
1339 return false;
1340 }
1341
1342 const auto check_in_bc = [&](VAddr address) {
1343 return impl->buffer_cache.IsRegionGpuModified(address, 8);
1344 };
1345 const auto check_value = [&](VAddr address) {
1346 u8* ptr = impl->cpu_memory.GetPointer(address);
1347 u64 value{};
1348 std::memcpy(&value, ptr, sizeof(value));
1349 return value == 0;
1350 };
1351 std::array<VideoCommon::LookupData*, 2> objects{&object_1, &object_2};
1352 std::array<bool, 2> is_in_bc{};
1353 std::array<bool, 2> is_in_qc{};
1354 std::array<bool, 2> is_in_ac{};
1355 std::array<bool, 2> is_null{};
1356 {
1357 std::scoped_lock lk(impl->buffer_cache.mutex);
1358 for (size_t i = 0; i < 2; i++) {
1359 is_in_qc[i] = objects[i]->found_query != nullptr;
1360 is_in_bc[i] = !is_in_qc[i] && check_in_bc(objects[i]->address);
1361 is_in_ac[i] = is_in_qc[i] || is_in_bc[i];
1362 }
1363 }
1364
1365 if (!is_in_ac[0] && !is_in_ac[1]) {
1366 EndHostConditionalRendering();
1367 return false;
1368 }
1369
1370 if (!qc_dirty && !is_in_bc[0] && !is_in_bc[1]) {
1371 EndHostConditionalRendering();
1372 return false;
1373 }
1374
1375 const bool is_gpu_high = Settings::IsGPULevelHigh();
1376 if (!is_gpu_high && impl->device.GetDriverID() == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
1377 return true;
1378 }
1379
1380 for (size_t i = 0; i < 2; i++) {
1381 is_null[i] = !is_in_ac[i] && check_value(objects[i]->address);
1382 }
1383
1384 for (size_t i = 0; i < 2; i++) {
1385 if (is_null[i]) {
1386 size_t j = (i + 1) % 2;
1387 HostConditionalRenderingCompareValueImpl(*objects[j], equal_check);
1388 return true;
1389 }
1390 }
1391
1392 if (!is_gpu_high) {
1393 return true;
1394 }
1395
1396 if (!is_in_bc[0] && !is_in_bc[1]) {
1397 // Both queries are in query cache, it's best to just flush.
1398 return true;
1399 }
1400 HostConditionalRenderingCompareBCImpl(object_1.address, equal_check);
1401 return true;
94} 1402}
95 1403
96HostCounter::HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_, 1404QueryCacheRuntime::~QueryCacheRuntime() = default;
97 QueryType type_) 1405
98 : HostCounterBase{std::move(dependency_)}, cache{cache_}, type{type_}, 1406VideoCommon::StreamerInterface* QueryCacheRuntime::GetStreamerInterface(QueryType query_type) {
99 query{cache_.AllocateQuery(type_)}, tick{cache_.GetScheduler().CurrentTick()} { 1407 switch (query_type) {
100 const vk::Device* logical = &cache.GetDevice().GetLogical(); 1408 case QueryType::Payload:
101 cache.GetScheduler().Record([logical, query_ = query](vk::CommandBuffer cmdbuf) { 1409 return &impl->guest_streamer;
102 const bool use_precise = Settings::IsGPULevelHigh(); 1410 case QueryType::ZPassPixelCount64:
103 logical->ResetQueryPool(query_.first, query_.second, 1); 1411 return &impl->sample_streamer;
104 cmdbuf.BeginQuery(query_.first, query_.second, 1412 case QueryType::StreamingByteCount:
105 use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0); 1413 return &impl->tfb_streamer;
106 }); 1414 case QueryType::StreamingPrimitivesNeeded:
1415 case QueryType::VtgPrimitivesOut:
1416 case QueryType::StreamingPrimitivesSucceeded:
1417 return &impl->primitives_succeeded_streamer;
1418 case QueryType::StreamingPrimitivesNeededMinusSucceeded:
1419 return &impl->primitives_needed_minus_suceeded_streamer;
1420 default:
1421 return nullptr;
1422 }
107} 1423}
108 1424
109HostCounter::~HostCounter() { 1425void QueryCacheRuntime::Barriers(bool is_prebarrier) {
110 cache.Reserve(type, query); 1426 static constexpr VkMemoryBarrier READ_BARRIER{
1427 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
1428 .pNext = nullptr,
1429 .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
1430 .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
1431 };
1432 static constexpr VkMemoryBarrier WRITE_BARRIER{
1433 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
1434 .pNext = nullptr,
1435 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
1436 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
1437 };
1438 if (is_prebarrier) {
1439 impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
1440 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
1441 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
1442 });
1443 } else {
1444 impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
1445 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
1446 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
1447 });
1448 }
111} 1449}
112 1450
113void HostCounter::EndQuery() { 1451template <typename SyncValuesType>
114 cache.GetScheduler().Record([query_ = query](vk::CommandBuffer cmdbuf) { 1452void QueryCacheRuntime::SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer) {
115 cmdbuf.EndQuery(query_.first, query_.second); 1453 if (values.size() == 0) {
1454 return;
1455 }
1456 impl->redirect_cache.clear();
1457 impl->little_cache.clear();
1458 size_t total_size = 0;
1459 for (auto& sync_val : values) {
1460 total_size += sync_val.size;
1461 bool found = false;
1462 VAddr base = Common::AlignDown(sync_val.address, Core::Memory::YUZU_PAGESIZE);
1463 VAddr base_end = base + Core::Memory::YUZU_PAGESIZE;
1464 for (size_t i = 0; i < impl->little_cache.size(); i++) {
1465 const auto set_found = [&] {
1466 impl->redirect_cache.push_back(i);
1467 found = true;
1468 };
1469 auto& loc = impl->little_cache[i];
1470 if (base < loc.second && loc.first < base_end) {
1471 set_found();
1472 break;
1473 }
1474 if (loc.first == base_end) {
1475 loc.first = base;
1476 set_found();
1477 break;
1478 }
1479 if (loc.second == base) {
1480 loc.second = base_end;
1481 set_found();
1482 break;
1483 }
1484 }
1485 if (!found) {
1486 impl->redirect_cache.push_back(impl->little_cache.size());
1487 impl->little_cache.emplace_back(base, base_end);
1488 }
1489 }
1490
1491 // Vulkan part.
1492 std::scoped_lock lk(impl->buffer_cache.mutex);
1493 impl->buffer_cache.BufferOperations([&] {
1494 impl->buffers_to_upload_to.clear();
1495 for (auto& pair : impl->little_cache) {
1496 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
1497 const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
1498 const auto [buffer, offset] = impl->buffer_cache.ObtainCPUBuffer(
1499 pair.first, static_cast<u32>(pair.second - pair.first), sync_info, post_op);
1500 impl->buffers_to_upload_to.emplace_back(buffer->Handle(), offset);
1501 }
116 }); 1502 });
117}
118 1503
119u64 HostCounter::BlockingQuery(bool async) const { 1504 VkBuffer src_buffer;
120 if (!async) { 1505 [[maybe_unused]] StagingBufferRef ref;
121 cache.GetScheduler().Wait(tick); 1506 impl->copies_setup.clear();
122 } 1507 impl->copies_setup.resize(impl->little_cache.size());
123 u64 data; 1508 if constexpr (SyncValuesType::GeneratesBaseBuffer) {
124 const VkResult query_result = cache.GetDevice().GetLogical().GetQueryResults( 1509 ref = impl->staging_pool.Request(total_size, MemoryUsage::Upload);
125 query.first, query.second, 1, sizeof(data), &data, sizeof(data), 1510 size_t current_offset = ref.offset;
126 VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); 1511 size_t accumulated_size = 0;
127 1512 for (size_t i = 0; i < values.size(); i++) {
128 switch (query_result) { 1513 size_t which_copy = impl->redirect_cache[i];
129 case VK_SUCCESS: 1514 impl->copies_setup[which_copy].emplace_back(VkBufferCopy{
130 return data; 1515 .srcOffset = current_offset + accumulated_size,
131 case VK_ERROR_DEVICE_LOST: 1516 .dstOffset = impl->buffers_to_upload_to[which_copy].second + values[i].address -
132 cache.GetDevice().ReportLoss(); 1517 impl->little_cache[which_copy].first,
133 [[fallthrough]]; 1518 .size = values[i].size,
134 default: 1519 });
135 throw vk::Exception(query_result); 1520 std::memcpy(ref.mapped_span.data() + accumulated_size, &values[i].value,
1521 values[i].size);
1522 accumulated_size += values[i].size;
1523 }
1524 src_buffer = ref.buffer;
1525 } else {
1526 for (size_t i = 0; i < values.size(); i++) {
1527 size_t which_copy = impl->redirect_cache[i];
1528 impl->copies_setup[which_copy].emplace_back(VkBufferCopy{
1529 .srcOffset = values[i].offset,
1530 .dstOffset = impl->buffers_to_upload_to[which_copy].second + values[i].address -
1531 impl->little_cache[which_copy].first,
1532 .size = values[i].size,
1533 });
1534 }
1535 src_buffer = base_src_buffer;
136 } 1536 }
1537
1538 impl->scheduler.RequestOutsideRenderPassOperationContext();
1539 impl->scheduler.Record([src_buffer, dst_buffers = std::move(impl->buffers_to_upload_to),
1540 vk_copies = std::move(impl->copies_setup)](vk::CommandBuffer cmdbuf) {
1541 size_t size = dst_buffers.size();
1542 for (size_t i = 0; i < size; i++) {
1543 cmdbuf.CopyBuffer(src_buffer, dst_buffers[i].first, vk_copies[i]);
1544 }
1545 });
137} 1546}
138 1547
139} // namespace Vulkan 1548} // namespace Vulkan
1549
1550namespace VideoCommon {
1551
1552template class QueryCacheBase<Vulkan::QueryCacheParams>;
1553
1554} // namespace VideoCommon
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h
index c1b9552eb..e9a1ea169 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.h
+++ b/src/video_core/renderer_vulkan/vk_query_cache.h
@@ -1,101 +1,75 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-3.0-or-later
3 3
4#pragma once 4#pragma once
5 5
6#include <cstddef>
7#include <memory> 6#include <memory>
8#include <utility>
9#include <vector>
10 7
11#include "common/common_types.h" 8#include "video_core/query_cache/query_cache_base.h"
12#include "video_core/query_cache.h" 9#include "video_core/renderer_vulkan/vk_buffer_cache.h"
13#include "video_core/renderer_vulkan/vk_resource_pool.h"
14#include "video_core/vulkan_common/vulkan_wrapper.h"
15 10
16namespace VideoCore { 11namespace VideoCore {
17class RasterizerInterface; 12class RasterizerInterface;
18} 13}
19 14
15namespace VideoCommon {
16class StreamerInterface;
17}
18
20namespace Vulkan { 19namespace Vulkan {
21 20
22class CachedQuery;
23class Device; 21class Device;
24class HostCounter;
25class QueryCache;
26class Scheduler; 22class Scheduler;
23class StagingBufferPool;
27 24
28using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>; 25struct QueryCacheRuntimeImpl;
29 26
30class QueryPool final : public ResourcePool { 27class QueryCacheRuntime {
31public: 28public:
32 explicit QueryPool(const Device& device, Scheduler& scheduler, VideoCore::QueryType type); 29 explicit QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer,
33 ~QueryPool() override; 30 Core::Memory::Memory& cpu_memory_,
31 Vulkan::BufferCache& buffer_cache_, const Device& device_,
32 const MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
33 StagingBufferPool& staging_pool_,
34 ComputePassDescriptorQueue& compute_pass_descriptor_queue,
35 DescriptorPool& descriptor_pool);
36 ~QueryCacheRuntime();
34 37
35 std::pair<VkQueryPool, u32> Commit(); 38 template <typename SyncValuesType>
39 void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = nullptr);
36 40
37 void Reserve(std::pair<VkQueryPool, u32> query); 41 void Barriers(bool is_prebarrier);
38 42
39protected: 43 void EndHostConditionalRendering();
40 void Allocate(std::size_t begin, std::size_t end) override;
41 44
42private: 45 void PauseHostConditionalRendering();
43 static constexpr std::size_t GROW_STEP = 512;
44 46
45 const Device& device; 47 void ResumeHostConditionalRendering();
46 const VideoCore::QueryType type;
47 48
48 std::vector<vk::QueryPool> pools; 49 bool HostConditionalRenderingCompareValue(VideoCommon::LookupData object_1, bool qc_dirty);
49 std::vector<bool> usage;
50};
51 50
52class QueryCache final 51 bool HostConditionalRenderingCompareValues(VideoCommon::LookupData object_1,
53 : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> { 52 VideoCommon::LookupData object_2, bool qc_dirty,
54public: 53 bool equal_check);
55 explicit QueryCache(VideoCore::RasterizerInterface& rasterizer_,
56 Core::Memory::Memory& cpu_memory_, const Device& device_,
57 Scheduler& scheduler_);
58 ~QueryCache();
59
60 std::pair<VkQueryPool, u32> AllocateQuery(VideoCore::QueryType type);
61 54
62 void Reserve(VideoCore::QueryType type, std::pair<VkQueryPool, u32> query); 55 VideoCommon::StreamerInterface* GetStreamerInterface(VideoCommon::QueryType query_type);
63 56
64 const Device& GetDevice() const noexcept { 57 void Bind3DEngine(Tegra::Engines::Maxwell3D* maxwell3d);
65 return device;
66 }
67 58
68 Scheduler& GetScheduler() const noexcept { 59 template <typename Func>
69 return scheduler; 60 void View3DRegs(Func&& func);
70 }
71 61
72private: 62private:
73 const Device& device; 63 void HostConditionalRenderingCompareValueImpl(VideoCommon::LookupData object, bool is_equal);
74 Scheduler& scheduler; 64 void HostConditionalRenderingCompareBCImpl(VAddr address, bool is_equal);
75 std::array<QueryPool, VideoCore::NumQueryTypes> query_pools; 65 friend struct QueryCacheRuntimeImpl;
66 std::unique_ptr<QueryCacheRuntimeImpl> impl;
76}; 67};
77 68
78class HostCounter final : public VideoCommon::HostCounterBase<QueryCache, HostCounter> { 69struct QueryCacheParams {
79public: 70 using RuntimeType = typename Vulkan::QueryCacheRuntime;
80 explicit HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
81 VideoCore::QueryType type_);
82 ~HostCounter();
83
84 void EndQuery();
85
86private:
87 u64 BlockingQuery(bool async = false) const override;
88
89 QueryCache& cache;
90 const VideoCore::QueryType type;
91 const std::pair<VkQueryPool, u32> query;
92 const u64 tick;
93}; 71};
94 72
95class CachedQuery : public VideoCommon::CachedQueryBase<HostCounter> { 73using QueryCache = VideoCommon::QueryCacheBase<QueryCacheParams>;
96public:
97 explicit CachedQuery(QueryCache&, VideoCore::QueryType, VAddr cpu_addr_, u8* host_ptr_)
98 : CachedQueryBase{cpu_addr_, host_ptr_} {}
99};
100 74
101} // namespace Vulkan 75} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 01e76a82c..1628d76d6 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -24,6 +24,7 @@
24#include "video_core/renderer_vulkan/vk_compute_pipeline.h" 24#include "video_core/renderer_vulkan/vk_compute_pipeline.h"
25#include "video_core/renderer_vulkan/vk_descriptor_pool.h" 25#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
26#include "video_core/renderer_vulkan/vk_pipeline_cache.h" 26#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
27#include "video_core/renderer_vulkan/vk_query_cache.h"
27#include "video_core/renderer_vulkan/vk_rasterizer.h" 28#include "video_core/renderer_vulkan/vk_rasterizer.h"
28#include "video_core/renderer_vulkan/vk_scheduler.h" 29#include "video_core/renderer_vulkan/vk_scheduler.h"
29#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" 30#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -170,9 +171,11 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
170 buffer_cache_runtime(device, memory_allocator, scheduler, staging_pool, 171 buffer_cache_runtime(device, memory_allocator, scheduler, staging_pool,
171 guest_descriptor_queue, compute_pass_descriptor_queue, descriptor_pool), 172 guest_descriptor_queue, compute_pass_descriptor_queue, descriptor_pool),
172 buffer_cache(*this, cpu_memory_, buffer_cache_runtime), 173 buffer_cache(*this, cpu_memory_, buffer_cache_runtime),
174 query_cache_runtime(this, cpu_memory_, buffer_cache, device, memory_allocator, scheduler,
175 staging_pool, compute_pass_descriptor_queue, descriptor_pool),
176 query_cache(gpu, *this, cpu_memory_, query_cache_runtime),
173 pipeline_cache(*this, device, scheduler, descriptor_pool, guest_descriptor_queue, 177 pipeline_cache(*this, device, scheduler, descriptor_pool, guest_descriptor_queue,
174 render_pass_cache, buffer_cache, texture_cache, gpu.ShaderNotify()), 178 render_pass_cache, buffer_cache, texture_cache, gpu.ShaderNotify()),
175 query_cache{*this, cpu_memory_, device, scheduler},
176 accelerate_dma(buffer_cache, texture_cache, scheduler), 179 accelerate_dma(buffer_cache, texture_cache, scheduler),
177 fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler), 180 fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler),
178 wfi_event(device.GetLogical().CreateEvent()) { 181 wfi_event(device.GetLogical().CreateEvent()) {
@@ -189,14 +192,7 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
189 FlushWork(); 192 FlushWork();
190 gpu_memory->FlushCaching(); 193 gpu_memory->FlushCaching();
191 194
192#if ANDROID 195 query_cache.NotifySegment(true);
193 if (Settings::IsGPULevelHigh()) {
194 // This is problematic on Android, disable on GPU Normal.
195 query_cache.UpdateCounters();
196 }
197#else
198 query_cache.UpdateCounters();
199#endif
200 196
201 GraphicsPipeline* const pipeline{pipeline_cache.CurrentGraphicsPipeline()}; 197 GraphicsPipeline* const pipeline{pipeline_cache.CurrentGraphicsPipeline()};
202 if (!pipeline) { 198 if (!pipeline) {
@@ -207,13 +203,12 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
207 pipeline->SetEngine(maxwell3d, gpu_memory); 203 pipeline->SetEngine(maxwell3d, gpu_memory);
208 pipeline->Configure(is_indexed); 204 pipeline->Configure(is_indexed);
209 205
210 BeginTransformFeedback();
211
212 UpdateDynamicStates(); 206 UpdateDynamicStates();
213 207
208 HandleTransformFeedback();
209 query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64,
210 maxwell3d->regs.zpass_pixel_count_enable);
214 draw_func(); 211 draw_func();
215
216 EndTransformFeedback();
217} 212}
218 213
219void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) { 214void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
@@ -241,6 +236,14 @@ void RasterizerVulkan::DrawIndirect() {
241 const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer(); 236 const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer();
242 const auto& buffer = indirect_buffer.first; 237 const auto& buffer = indirect_buffer.first;
243 const auto& offset = indirect_buffer.second; 238 const auto& offset = indirect_buffer.second;
239 if (params.is_byte_count) {
240 scheduler.Record([buffer_obj = buffer->Handle(), offset,
241 stride = params.stride](vk::CommandBuffer cmdbuf) {
242 cmdbuf.DrawIndirectByteCountEXT(1, 0, buffer_obj, offset, 0,
243 static_cast<u32>(stride));
244 });
245 return;
246 }
244 if (params.include_count) { 247 if (params.include_count) {
245 const auto count = buffer_cache.GetDrawIndirectCount(); 248 const auto count = buffer_cache.GetDrawIndirectCount();
246 const auto& draw_buffer = count.first; 249 const auto& draw_buffer = count.first;
@@ -280,20 +283,15 @@ void RasterizerVulkan::DrawTexture() {
280 SCOPE_EXIT({ gpu.TickWork(); }); 283 SCOPE_EXIT({ gpu.TickWork(); });
281 FlushWork(); 284 FlushWork();
282 285
283#if ANDROID 286 query_cache.NotifySegment(true);
284 if (Settings::IsGPULevelHigh()) {
285 // This is problematic on Android, disable on GPU Normal.
286 query_cache.UpdateCounters();
287 }
288#else
289 query_cache.UpdateCounters();
290#endif
291 287
292 texture_cache.SynchronizeGraphicsDescriptors(); 288 texture_cache.SynchronizeGraphicsDescriptors();
293 texture_cache.UpdateRenderTargets(false); 289 texture_cache.UpdateRenderTargets(false);
294 290
295 UpdateDynamicStates(); 291 UpdateDynamicStates();
296 292
293 query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64,
294 maxwell3d->regs.zpass_pixel_count_enable);
297 const auto& draw_texture_state = maxwell3d->draw_manager->GetDrawTextureState(); 295 const auto& draw_texture_state = maxwell3d->draw_manager->GetDrawTextureState();
298 const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler); 296 const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler);
299 const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture); 297 const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture);
@@ -316,14 +314,9 @@ void RasterizerVulkan::Clear(u32 layer_count) {
316 FlushWork(); 314 FlushWork();
317 gpu_memory->FlushCaching(); 315 gpu_memory->FlushCaching();
318 316
319#if ANDROID 317 query_cache.NotifySegment(true);
320 if (Settings::IsGPULevelHigh()) { 318 query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64,
321 // This is problematic on Android, disable on GPU Normal. 319 maxwell3d->regs.zpass_pixel_count_enable);
322 query_cache.UpdateCounters();
323 }
324#else
325 query_cache.UpdateCounters();
326#endif
327 320
328 auto& regs = maxwell3d->regs; 321 auto& regs = maxwell3d->regs;
329 const bool use_color = regs.clear_surface.R || regs.clear_surface.G || regs.clear_surface.B || 322 const bool use_color = regs.clear_surface.R || regs.clear_surface.G || regs.clear_surface.B ||
@@ -482,13 +475,13 @@ void RasterizerVulkan::DispatchCompute() {
482 scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); 475 scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); });
483} 476}
484 477
485void RasterizerVulkan::ResetCounter(VideoCore::QueryType type) { 478void RasterizerVulkan::ResetCounter(VideoCommon::QueryType type) {
486 query_cache.ResetCounter(type); 479 query_cache.CounterReset(type);
487} 480}
488 481
489void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCore::QueryType type, 482void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
490 std::optional<u64> timestamp) { 483 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) {
491 query_cache.Query(gpu_addr, type, timestamp); 484 query_cache.CounterReport(gpu_addr, type, flags, payload, subreport);
492} 485}
493 486
494void RasterizerVulkan::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, 487void RasterizerVulkan::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@@ -669,8 +662,8 @@ void RasterizerVulkan::SignalReference() {
669 fence_manager.SignalReference(); 662 fence_manager.SignalReference();
670} 663}
671 664
672void RasterizerVulkan::ReleaseFences() { 665void RasterizerVulkan::ReleaseFences(bool force) {
673 fence_manager.WaitPendingFences(); 666 fence_manager.WaitPendingFences(force);
674} 667}
675 668
676void RasterizerVulkan::FlushAndInvalidateRegion(VAddr addr, u64 size, 669void RasterizerVulkan::FlushAndInvalidateRegion(VAddr addr, u64 size,
@@ -694,6 +687,8 @@ void RasterizerVulkan::WaitForIdle() {
694 flags |= VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT; 687 flags |= VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT;
695 } 688 }
696 689
690 query_cache.NotifyWFI();
691
697 scheduler.RequestOutsideRenderPassOperationContext(); 692 scheduler.RequestOutsideRenderPassOperationContext();
698 scheduler.Record([event = *wfi_event, flags](vk::CommandBuffer cmdbuf) { 693 scheduler.Record([event = *wfi_event, flags](vk::CommandBuffer cmdbuf) {
699 cmdbuf.SetEvent(event, flags); 694 cmdbuf.SetEvent(event, flags);
@@ -737,19 +732,7 @@ void RasterizerVulkan::TickFrame() {
737 732
738bool RasterizerVulkan::AccelerateConditionalRendering() { 733bool RasterizerVulkan::AccelerateConditionalRendering() {
739 gpu_memory->FlushCaching(); 734 gpu_memory->FlushCaching();
740 if (Settings::IsGPULevelHigh()) { 735 return query_cache.AccelerateHostConditionalRendering();
741 // TODO(Blinkhawk): Reimplement Host conditional rendering.
742 return false;
743 }
744 // Medium / Low Hack: stub any checks on queries written into the buffer cache.
745 const GPUVAddr condition_address{maxwell3d->regs.render_enable.Address()};
746 Maxwell::ReportSemaphore::Compare cmp;
747 if (gpu_memory->IsMemoryDirty(condition_address, sizeof(cmp),
748 VideoCommon::CacheType::BufferCache |
749 VideoCommon::CacheType::QueryCache)) {
750 return true;
751 }
752 return false;
753} 736}
754 737
755bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src, 738bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
@@ -795,6 +778,7 @@ bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config,
795 if (!image_view) { 778 if (!image_view) {
796 return false; 779 return false;
797 } 780 }
781 query_cache.NotifySegment(false);
798 screen_info.image = image_view->ImageHandle(); 782 screen_info.image = image_view->ImageHandle();
799 screen_info.image_view = image_view->Handle(Shader::TextureType::Color2D); 783 screen_info.image_view = image_view->Handle(Shader::TextureType::Color2D);
800 screen_info.width = image_view->size.width; 784 screen_info.width = image_view->size.width;
@@ -933,31 +917,18 @@ void RasterizerVulkan::UpdateDynamicStates() {
933 } 917 }
934} 918}
935 919
936void RasterizerVulkan::BeginTransformFeedback() { 920void RasterizerVulkan::HandleTransformFeedback() {
937 const auto& regs = maxwell3d->regs; 921 const auto& regs = maxwell3d->regs;
938 if (regs.transform_feedback_enabled == 0) {
939 return;
940 }
941 if (!device.IsExtTransformFeedbackSupported()) { 922 if (!device.IsExtTransformFeedbackSupported()) {
942 LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported"); 923 LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported");
943 return; 924 return;
944 } 925 }
945 UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) || 926 query_cache.CounterEnable(VideoCommon::QueryType::StreamingByteCount,
946 regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation)); 927 regs.transform_feedback_enabled);
947 scheduler.Record( 928 if (regs.transform_feedback_enabled != 0) {
948 [](vk::CommandBuffer cmdbuf) { cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr); }); 929 UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) ||
949} 930 regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation));
950
951void RasterizerVulkan::EndTransformFeedback() {
952 const auto& regs = maxwell3d->regs;
953 if (regs.transform_feedback_enabled == 0) {
954 return;
955 }
956 if (!device.IsExtTransformFeedbackSupported()) {
957 return;
958 } 931 }
959 scheduler.Record(
960 [](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); });
961} 932}
962 933
963void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs) { 934void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs) {
@@ -1043,15 +1014,37 @@ void RasterizerVulkan::UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs) {
1043 regs.zeta.format == Tegra::DepthFormat::X8Z24_UNORM || 1014 regs.zeta.format == Tegra::DepthFormat::X8Z24_UNORM ||
1044 regs.zeta.format == Tegra::DepthFormat::S8Z24_UNORM || 1015 regs.zeta.format == Tegra::DepthFormat::S8Z24_UNORM ||
1045 regs.zeta.format == Tegra::DepthFormat::V8Z24_UNORM; 1016 regs.zeta.format == Tegra::DepthFormat::V8Z24_UNORM;
1046 if (is_d24 && !device.SupportsD24DepthBuffer()) { 1017 bool force_unorm = ([&] {
1018 if (!is_d24 || device.SupportsD24DepthBuffer()) {
1019 return false;
1020 }
1021 if (device.IsExtDepthBiasControlSupported()) {
1022 return true;
1023 }
1024 if (!Settings::values.renderer_amdvlk_depth_bias_workaround) {
1025 return false;
1026 }
1047 // the base formulas can be obtained from here: 1027 // the base formulas can be obtained from here:
1048 // https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias 1028 // https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias
1049 const double rescale_factor = 1029 const double rescale_factor =
1050 static_cast<double>(1ULL << (32 - 24)) / (static_cast<double>(0x1.ep+127)); 1030 static_cast<double>(1ULL << (32 - 24)) / (static_cast<double>(0x1.ep+127));
1051 units = static_cast<float>(static_cast<double>(units) * rescale_factor); 1031 units = static_cast<float>(static_cast<double>(units) * rescale_factor);
1052 } 1032 return false;
1033 })();
1053 scheduler.Record([constant = units, clamp = regs.depth_bias_clamp, 1034 scheduler.Record([constant = units, clamp = regs.depth_bias_clamp,
1054 factor = regs.slope_scale_depth_bias](vk::CommandBuffer cmdbuf) { 1035 factor = regs.slope_scale_depth_bias, force_unorm,
1036 precise = device.HasExactDepthBiasControl()](vk::CommandBuffer cmdbuf) {
1037 if (force_unorm) {
1038 VkDepthBiasRepresentationInfoEXT info{
1039 .sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_REPRESENTATION_INFO_EXT,
1040 .pNext = nullptr,
1041 .depthBiasRepresentation =
1042 VK_DEPTH_BIAS_REPRESENTATION_LEAST_REPRESENTABLE_VALUE_FORCE_UNORM_EXT,
1043 .depthBiasExact = precise ? VK_TRUE : VK_FALSE,
1044 };
1045 cmdbuf.SetDepthBias(constant, clamp, factor, &info);
1046 return;
1047 }
1055 cmdbuf.SetDepthBias(constant, clamp, factor); 1048 cmdbuf.SetDepthBias(constant, clamp, factor);
1056 }); 1049 });
1057} 1050}
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index b31982485..ad069556c 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -84,8 +84,9 @@ public:
84 void DrawTexture() override; 84 void DrawTexture() override;
85 void Clear(u32 layer_count) override; 85 void Clear(u32 layer_count) override;
86 void DispatchCompute() override; 86 void DispatchCompute() override;
87 void ResetCounter(VideoCore::QueryType type) override; 87 void ResetCounter(VideoCommon::QueryType type) override;
88 void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; 88 void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
89 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override;
89 void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; 90 void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override;
90 void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; 91 void DisableGraphicsUniformBuffer(size_t stage, u32 index) override;
91 void FlushAll() override; 92 void FlushAll() override;
@@ -106,7 +107,7 @@ public:
106 void SyncOperation(std::function<void()>&& func) override; 107 void SyncOperation(std::function<void()>&& func) override;
107 void SignalSyncPoint(u32 value) override; 108 void SignalSyncPoint(u32 value) override;
108 void SignalReference() override; 109 void SignalReference() override;
109 void ReleaseFences() override; 110 void ReleaseFences(bool force = true) override;
110 void FlushAndInvalidateRegion( 111 void FlushAndInvalidateRegion(
111 VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; 112 VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
112 void WaitForIdle() override; 113 void WaitForIdle() override;
@@ -146,9 +147,7 @@ private:
146 147
147 void UpdateDynamicStates(); 148 void UpdateDynamicStates();
148 149
149 void BeginTransformFeedback(); 150 void HandleTransformFeedback();
150
151 void EndTransformFeedback();
152 151
153 void UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs); 152 void UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs);
154 void UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs); 153 void UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs);
@@ -195,8 +194,9 @@ private:
195 TextureCache texture_cache; 194 TextureCache texture_cache;
196 BufferCacheRuntime buffer_cache_runtime; 195 BufferCacheRuntime buffer_cache_runtime;
197 BufferCache buffer_cache; 196 BufferCache buffer_cache;
198 PipelineCache pipeline_cache; 197 QueryCacheRuntime query_cache_runtime;
199 QueryCache query_cache; 198 QueryCache query_cache;
199 PipelineCache pipeline_cache;
200 AccelerateDMA accelerate_dma; 200 AccelerateDMA accelerate_dma;
201 FenceManager fence_manager; 201 FenceManager fence_manager;
202 202
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index 89fd31b4f..3be7837f4 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -243,10 +243,10 @@ void Scheduler::AllocateNewContext() {
243#if ANDROID 243#if ANDROID
244 if (Settings::IsGPULevelHigh()) { 244 if (Settings::IsGPULevelHigh()) {
245 // This is problematic on Android, disable on GPU Normal. 245 // This is problematic on Android, disable on GPU Normal.
246 query_cache->UpdateCounters(); 246 query_cache->NotifySegment(true);
247 } 247 }
248#else 248#else
249 query_cache->UpdateCounters(); 249 query_cache->NotifySegment(true);
250#endif 250#endif
251 } 251 }
252} 252}
@@ -261,11 +261,12 @@ void Scheduler::EndPendingOperations() {
261#if ANDROID 261#if ANDROID
262 if (Settings::IsGPULevelHigh()) { 262 if (Settings::IsGPULevelHigh()) {
263 // This is problematic on Android, disable on GPU Normal. 263 // This is problematic on Android, disable on GPU Normal.
264 query_cache->DisableStreams(); 264 // query_cache->DisableStreams();
265 } 265 }
266#else 266#else
267 query_cache->DisableStreams(); 267 // query_cache->DisableStreams();
268#endif 268#endif
269 query_cache->NotifySegment(false);
269 EndRenderPass(); 270 EndRenderPass();
270} 271}
271 272
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index 475c682eb..da03803aa 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -17,6 +17,11 @@
17#include "video_core/renderer_vulkan/vk_master_semaphore.h" 17#include "video_core/renderer_vulkan/vk_master_semaphore.h"
18#include "video_core/vulkan_common/vulkan_wrapper.h" 18#include "video_core/vulkan_common/vulkan_wrapper.h"
19 19
20namespace VideoCommon {
21template <typename Trait>
22class QueryCacheBase;
23}
24
20namespace Vulkan { 25namespace Vulkan {
21 26
22class CommandPool; 27class CommandPool;
@@ -24,7 +29,8 @@ class Device;
24class Framebuffer; 29class Framebuffer;
25class GraphicsPipeline; 30class GraphicsPipeline;
26class StateTracker; 31class StateTracker;
27class QueryCache; 32
33struct QueryCacheParams;
28 34
29/// The scheduler abstracts command buffer and fence management with an interface that's able to do 35/// The scheduler abstracts command buffer and fence management with an interface that's able to do
30/// OpenGL-like operations on Vulkan command buffers. 36/// OpenGL-like operations on Vulkan command buffers.
@@ -63,7 +69,7 @@ public:
63 void InvalidateState(); 69 void InvalidateState();
64 70
65 /// Assigns the query cache. 71 /// Assigns the query cache.
66 void SetQueryCache(QueryCache& query_cache_) { 72 void SetQueryCache(VideoCommon::QueryCacheBase<QueryCacheParams>& query_cache_) {
67 query_cache = &query_cache_; 73 query_cache = &query_cache_;
68 } 74 }
69 75
@@ -219,7 +225,7 @@ private:
219 std::unique_ptr<MasterSemaphore> master_semaphore; 225 std::unique_ptr<MasterSemaphore> master_semaphore;
220 std::unique_ptr<CommandPool> command_pool; 226 std::unique_ptr<CommandPool> command_pool;
221 227
222 QueryCache* query_cache = nullptr; 228 VideoCommon::QueryCacheBase<QueryCacheParams>* query_cache = nullptr;
223 229
224 vk::CommandBuffer current_cmdbuf; 230 vk::CommandBuffer current_cmdbuf;
225 231
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index b3e17c332..71fdec809 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -120,19 +120,9 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
120 return usage; 120 return usage;
121} 121}
122 122
123/// Returns the preferred format for a VkImage
124[[nodiscard]] PixelFormat StorageFormat(PixelFormat format) {
125 switch (format) {
126 case PixelFormat::A8B8G8R8_SRGB:
127 return PixelFormat::A8B8G8R8_UNORM;
128 default:
129 return format;
130 }
131}
132
133[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) { 123[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) {
134 const PixelFormat format = StorageFormat(info.format); 124 const auto format_info =
135 const auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, format); 125 MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, info.format);
136 VkImageCreateFlags flags{}; 126 VkImageCreateFlags flags{};
137 if (info.type == ImageType::e2D && info.resources.layers >= 6 && 127 if (info.type == ImageType::e2D && info.resources.layers >= 6 &&
138 info.size.width == info.size.height && !device.HasBrokenCubeImageCompability()) { 128 info.size.width == info.size.height && !device.HasBrokenCubeImageCompability()) {
@@ -157,7 +147,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
157 .arrayLayers = static_cast<u32>(info.resources.layers), 147 .arrayLayers = static_cast<u32>(info.resources.layers),
158 .samples = ConvertSampleCount(info.num_samples), 148 .samples = ConvertSampleCount(info.num_samples),
159 .tiling = VK_IMAGE_TILING_OPTIMAL, 149 .tiling = VK_IMAGE_TILING_OPTIMAL,
160 .usage = ImageUsageFlags(format_info, format), 150 .usage = ImageUsageFlags(format_info, info.format),
161 .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 151 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
162 .queueFamilyIndexCount = 0, 152 .queueFamilyIndexCount = 0,
163 .pQueueFamilyIndices = nullptr, 153 .pQueueFamilyIndices = nullptr,
@@ -186,6 +176,36 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
186 return allocator.CreateImage(image_ci); 176 return allocator.CreateImage(image_ci);
187} 177}
188 178
179[[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image,
180 VkFormat format) {
181 static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{
182 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
183 .pNext = nullptr,
184 .usage = VK_IMAGE_USAGE_STORAGE_BIT,
185 };
186 return device.CreateImageView(VkImageViewCreateInfo{
187 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
188 .pNext = &storage_image_view_usage_create_info,
189 .flags = 0,
190 .image = image,
191 .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
192 .format = format,
193 .components{
194 .r = VK_COMPONENT_SWIZZLE_IDENTITY,
195 .g = VK_COMPONENT_SWIZZLE_IDENTITY,
196 .b = VK_COMPONENT_SWIZZLE_IDENTITY,
197 .a = VK_COMPONENT_SWIZZLE_IDENTITY,
198 },
199 .subresourceRange{
200 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
201 .baseMipLevel = level,
202 .levelCount = 1,
203 .baseArrayLayer = 0,
204 .layerCount = VK_REMAINING_ARRAY_LAYERS,
205 },
206 });
207}
208
189[[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) { 209[[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) {
190 switch (VideoCore::Surface::GetFormatType(format)) { 210 switch (VideoCore::Surface::GetFormatType(format)) {
191 case VideoCore::Surface::SurfaceType::ColorTexture: 211 case VideoCore::Surface::SurfaceType::ColorTexture:
@@ -600,7 +620,7 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
600} 620}
601 621
602void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle, 622void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle,
603 bool emulate_bgr565) { 623 bool emulate_bgr565, bool emulate_a4b4g4r4) {
604 switch (format) { 624 switch (format) {
605 case PixelFormat::A1B5G5R5_UNORM: 625 case PixelFormat::A1B5G5R5_UNORM:
606 std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed); 626 std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed);
@@ -616,6 +636,11 @@ void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4
616 case PixelFormat::G4R4_UNORM: 636 case PixelFormat::G4R4_UNORM:
617 std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed); 637 std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed);
618 break; 638 break;
639 case PixelFormat::A4B4G4R4_UNORM:
640 if (emulate_a4b4g4r4) {
641 std::ranges::reverse(swizzle);
642 }
643 break;
619 default: 644 default:
620 break; 645 break;
621 } 646 }
@@ -822,6 +847,10 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
822 astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, 847 astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
823 compute_pass_descriptor_queue, memory_allocator); 848 compute_pass_descriptor_queue, memory_allocator);
824 } 849 }
850 if (device.IsStorageImageMultisampleSupported()) {
851 msaa_copy_pass = std::make_unique<MSAACopyPass>(
852 device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue);
853 }
825 if (!device.IsKhrImageFormatListSupported()) { 854 if (!device.IsKhrImageFormatListSupported()) {
826 return; 855 return;
827 } 856 }
@@ -1044,15 +1073,27 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
1044 dst_region, src_region, filter, operation); 1073 dst_region, src_region, filter, operation);
1045 return; 1074 return;
1046 } 1075 }
1076 ASSERT(src.format == dst.format);
1047 if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { 1077 if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
1048 if (!device.IsBlitDepthStencilSupported()) { 1078 const auto format = src.format;
1079 const auto can_blit_depth_stencil = [this, format] {
1080 switch (format) {
1081 case VideoCore::Surface::PixelFormat::D24_UNORM_S8_UINT:
1082 case VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM:
1083 return device.IsBlitDepth24Stencil8Supported();
1084 case VideoCore::Surface::PixelFormat::D32_FLOAT_S8_UINT:
1085 return device.IsBlitDepth32Stencil8Supported();
1086 default:
1087 UNREACHABLE();
1088 }
1089 }();
1090 if (!can_blit_depth_stencil) {
1049 UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa); 1091 UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa);
1050 blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(), 1092 blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(),
1051 dst_region, src_region, filter, operation); 1093 dst_region, src_region, filter, operation);
1052 return; 1094 return;
1053 } 1095 }
1054 } 1096 }
1055 ASSERT(src.format == dst.format);
1056 ASSERT(!(is_dst_msaa && !is_src_msaa)); 1097 ASSERT(!(is_dst_msaa && !is_src_msaa));
1057 ASSERT(operation == Fermi2D::Operation::SrcCopy); 1098 ASSERT(operation == Fermi2D::Operation::SrcCopy);
1058 1099
@@ -1278,7 +1319,11 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src,
1278 1319
1279void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src, 1320void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src,
1280 std::span<const VideoCommon::ImageCopy> copies) { 1321 std::span<const VideoCommon::ImageCopy> copies) {
1281 UNIMPLEMENTED_MSG("Copying images with different samples is not implemented in Vulkan."); 1322 const bool msaa_to_non_msaa = src.info.num_samples > 1 && dst.info.num_samples == 1;
1323 if (msaa_copy_pass) {
1324 return msaa_copy_pass->CopyImage(dst, src, copies, msaa_to_non_msaa);
1325 }
1326 UNIMPLEMENTED_MSG("Copying images with different samples is not supported.");
1282} 1327}
1283 1328
1284u64 TextureCacheRuntime::GetDeviceLocalMemory() const { 1329u64 TextureCacheRuntime::GetDeviceLocalMemory() const {
@@ -1326,39 +1371,15 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
1326 if (runtime->device.HasDebuggingToolAttached()) { 1371 if (runtime->device.HasDebuggingToolAttached()) {
1327 original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); 1372 original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
1328 } 1373 }
1329 static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{
1330 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
1331 .pNext = nullptr,
1332 .usage = VK_IMAGE_USAGE_STORAGE_BIT,
1333 };
1334 current_image = *original_image; 1374 current_image = *original_image;
1375 storage_image_views.resize(info.resources.levels);
1335 if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() && 1376 if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() &&
1336 Settings::values.astc_recompression.GetValue() == 1377 Settings::values.astc_recompression.GetValue() ==
1337 Settings::AstcRecompression::Uncompressed) { 1378 Settings::AstcRecompression::Uncompressed) {
1338 const auto& device = runtime->device.GetLogical(); 1379 const auto& device = runtime->device.GetLogical();
1339 storage_image_views.reserve(info.resources.levels);
1340 for (s32 level = 0; level < info.resources.levels; ++level) { 1380 for (s32 level = 0; level < info.resources.levels; ++level) {
1341 storage_image_views.push_back(device.CreateImageView(VkImageViewCreateInfo{ 1381 storage_image_views[level] =
1342 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 1382 MakeStorageView(device, level, *original_image, VK_FORMAT_A8B8G8R8_UNORM_PACK32);
1343 .pNext = &storage_image_view_usage_create_info,
1344 .flags = 0,
1345 .image = *original_image,
1346 .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
1347 .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
1348 .components{
1349 .r = VK_COMPONENT_SWIZZLE_IDENTITY,
1350 .g = VK_COMPONENT_SWIZZLE_IDENTITY,
1351 .b = VK_COMPONENT_SWIZZLE_IDENTITY,
1352 .a = VK_COMPONENT_SWIZZLE_IDENTITY,
1353 },
1354 .subresourceRange{
1355 .aspectMask = aspect_mask,
1356 .baseMipLevel = static_cast<u32>(level),
1357 .levelCount = 1,
1358 .baseArrayLayer = 0,
1359 .layerCount = VK_REMAINING_ARRAY_LAYERS,
1360 },
1361 }));
1362 } 1383 }
1363 } 1384 }
1364} 1385}
@@ -1489,6 +1510,17 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferIm
1489 DownloadMemory(buffers, offsets, copies); 1510 DownloadMemory(buffers, offsets, copies);
1490} 1511}
1491 1512
1513VkImageView Image::StorageImageView(s32 level) noexcept {
1514 auto& view = storage_image_views[level];
1515 if (!view) {
1516 const auto format_info =
1517 MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format);
1518 view =
1519 MakeStorageView(runtime->device.GetLogical(), level, current_image, format_info.format);
1520 }
1521 return *view;
1522}
1523
1492bool Image::IsRescaled() const noexcept { 1524bool Image::IsRescaled() const noexcept {
1493 return True(flags & ImageFlagBits::Rescaled); 1525 return True(flags & ImageFlagBits::Rescaled);
1494} 1526}
@@ -1626,8 +1658,8 @@ bool Image::NeedsScaleHelper() const {
1626 return true; 1658 return true;
1627 } 1659 }
1628 static constexpr auto OPTIMAL_FORMAT = FormatType::Optimal; 1660 static constexpr auto OPTIMAL_FORMAT = FormatType::Optimal;
1629 const PixelFormat format = StorageFormat(info.format); 1661 const auto vk_format =
1630 const auto vk_format = MaxwellToVK::SurfaceFormat(device, OPTIMAL_FORMAT, false, format).format; 1662 MaxwellToVK::SurfaceFormat(device, OPTIMAL_FORMAT, false, info.format).format;
1631 const auto blit_usage = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; 1663 const auto blit_usage = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
1632 const bool needs_blit_helper = !device.IsFormatSupported(vk_format, blit_usage, OPTIMAL_FORMAT); 1664 const bool needs_blit_helper = !device.IsFormatSupported(vk_format, blit_usage, OPTIMAL_FORMAT);
1633 return needs_blit_helper; 1665 return needs_blit_helper;
@@ -1649,7 +1681,8 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
1649 }; 1681 };
1650 if (!info.IsRenderTarget()) { 1682 if (!info.IsRenderTarget()) {
1651 swizzle = info.Swizzle(); 1683 swizzle = info.Swizzle();
1652 TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565()); 1684 TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565(),
1685 !device->IsExt4444FormatsSupported());
1653 if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { 1686 if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
1654 std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed); 1687 std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
1655 } 1688 }
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 565ce19a9..d6c5a15cc 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -117,6 +117,7 @@ public:
117 BlitImageHelper& blit_image_helper; 117 BlitImageHelper& blit_image_helper;
118 RenderPassCache& render_pass_cache; 118 RenderPassCache& render_pass_cache;
119 std::optional<ASTCDecoderPass> astc_decoder_pass; 119 std::optional<ASTCDecoderPass> astc_decoder_pass;
120 std::unique_ptr<MSAACopyPass> msaa_copy_pass;
120 const Settings::ResolutionScalingInfo& resolution; 121 const Settings::ResolutionScalingInfo& resolution;
121 std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats; 122 std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
122 123
@@ -161,15 +162,13 @@ public:
161 return aspect_mask; 162 return aspect_mask;
162 } 163 }
163 164
164 [[nodiscard]] VkImageView StorageImageView(s32 level) const noexcept {
165 return *storage_image_views[level];
166 }
167
168 /// Returns true when the image is already initialized and mark it as initialized 165 /// Returns true when the image is already initialized and mark it as initialized
169 [[nodiscard]] bool ExchangeInitialization() noexcept { 166 [[nodiscard]] bool ExchangeInitialization() noexcept {
170 return std::exchange(initialized, true); 167 return std::exchange(initialized, true);
171 } 168 }
172 169
170 VkImageView StorageImageView(s32 level) noexcept;
171
173 bool IsRescaled() const noexcept; 172 bool IsRescaled() const noexcept;
174 173
175 bool ScaleUp(bool ignore = false); 174 bool ScaleUp(bool ignore = false);
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 617417040..3960b135a 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -76,6 +76,11 @@ constexpr std::array VK_FORMAT_R32G32B32_SFLOAT{
76 VK_FORMAT_UNDEFINED, 76 VK_FORMAT_UNDEFINED,
77}; 77};
78 78
79constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{
80 VK_FORMAT_R4G4B4A4_UNORM_PACK16,
81 VK_FORMAT_UNDEFINED,
82};
83
79} // namespace Alternatives 84} // namespace Alternatives
80 85
81enum class NvidiaArchitecture { 86enum class NvidiaArchitecture {
@@ -110,6 +115,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
110 return Alternatives::R8G8B8_SSCALED.data(); 115 return Alternatives::R8G8B8_SSCALED.data();
111 case VK_FORMAT_R32G32B32_SFLOAT: 116 case VK_FORMAT_R32G32B32_SFLOAT:
112 return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data(); 117 return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data();
118 case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT:
119 return Alternatives::VK_FORMAT_A4B4G4R4_UNORM_PACK16.data();
113 default: 120 default:
114 return nullptr; 121 return nullptr;
115 } 122 }
@@ -238,6 +245,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
238 VK_FORMAT_R32_SINT, 245 VK_FORMAT_R32_SINT,
239 VK_FORMAT_R32_UINT, 246 VK_FORMAT_R32_UINT,
240 VK_FORMAT_R4G4B4A4_UNORM_PACK16, 247 VK_FORMAT_R4G4B4A4_UNORM_PACK16,
248 VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT,
241 VK_FORMAT_R4G4_UNORM_PACK8, 249 VK_FORMAT_R4G4_UNORM_PACK8,
242 VK_FORMAT_R5G5B5A1_UNORM_PACK16, 250 VK_FORMAT_R5G5B5A1_UNORM_PACK16,
243 VK_FORMAT_R5G6B5_UNORM_PACK16, 251 VK_FORMAT_R5G6B5_UNORM_PACK16,
@@ -420,7 +428,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
420 first_next = &diagnostics_nv; 428 first_next = &diagnostics_nv;
421 } 429 }
422 430
423 is_blit_depth_stencil_supported = TestDepthStencilBlits(); 431 is_blit_depth24_stencil8_supported = TestDepthStencilBlits(VK_FORMAT_D24_UNORM_S8_UINT);
432 is_blit_depth32_stencil8_supported = TestDepthStencilBlits(VK_FORMAT_D32_SFLOAT_S8_UINT);
424 is_optimal_astc_supported = ComputeIsOptimalAstcSupported(); 433 is_optimal_astc_supported = ComputeIsOptimalAstcSupported();
425 is_warp_potentially_bigger = !extensions.subgroup_size_control || 434 is_warp_potentially_bigger = !extensions.subgroup_size_control ||
426 properties.subgroup_size_control.maxSubgroupSize > GuestWarpSize; 435 properties.subgroup_size_control.maxSubgroupSize > GuestWarpSize;
@@ -774,14 +783,13 @@ bool Device::ComputeIsOptimalAstcSupported() const {
774 return true; 783 return true;
775} 784}
776 785
777bool Device::TestDepthStencilBlits() const { 786bool Device::TestDepthStencilBlits(VkFormat format) const {
778 static constexpr VkFormatFeatureFlags required_features = 787 static constexpr VkFormatFeatureFlags required_features =
779 VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; 788 VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
780 const auto test_features = [](VkFormatProperties props) { 789 const auto test_features = [](VkFormatProperties props) {
781 return (props.optimalTilingFeatures & required_features) == required_features; 790 return (props.optimalTilingFeatures & required_features) == required_features;
782 }; 791 };
783 return test_features(format_properties.at(VK_FORMAT_D32_SFLOAT_S8_UINT)) && 792 return test_features(format_properties.at(format));
784 test_features(format_properties.at(VK_FORMAT_D24_UNORM_S8_UINT));
785} 793}
786 794
787bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, 795bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
@@ -1051,6 +1059,13 @@ void Device::RemoveUnsuitableExtensions() {
1051 RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color, 1059 RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color,
1052 VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); 1060 VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
1053 1061
1062 // VK_EXT_depth_bias_control
1063 extensions.depth_bias_control =
1064 features.depth_bias_control.depthBiasControl &&
1065 features.depth_bias_control.leastRepresentableValueForceUnormRepresentation;
1066 RemoveExtensionFeatureIfUnsuitable(extensions.depth_bias_control, features.depth_bias_control,
1067 VK_EXT_DEPTH_BIAS_CONTROL_EXTENSION_NAME);
1068
1054 // VK_EXT_depth_clip_control 1069 // VK_EXT_depth_clip_control
1055 extensions.depth_clip_control = features.depth_clip_control.depthClipControl; 1070 extensions.depth_clip_control = features.depth_clip_control.depthClipControl;
1056 RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control, 1071 RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control,
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 488fdd313..9be612392 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -41,10 +41,12 @@ VK_DEFINE_HANDLE(VmaAllocator)
41// Define all features which may be used by the implementation and require an extension here. 41// Define all features which may be used by the implementation and require an extension here.
42#define FOR_EACH_VK_FEATURE_EXT(FEATURE) \ 42#define FOR_EACH_VK_FEATURE_EXT(FEATURE) \
43 FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \ 43 FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \
44 FEATURE(EXT, DepthBiasControl, DEPTH_BIAS_CONTROL, depth_bias_control) \
44 FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \ 45 FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \
45 FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \ 46 FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \
46 FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \ 47 FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \
47 FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \ 48 FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \
49 FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \
48 FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \ 50 FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \
49 FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \ 51 FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \
50 FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \ 52 FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \
@@ -60,6 +62,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
60 62
61// Define miscellaneous extensions which may be used by the implementation here. 63// Define miscellaneous extensions which may be used by the implementation here.
62#define FOR_EACH_VK_EXTENSION(EXTENSION) \ 64#define FOR_EACH_VK_EXTENSION(EXTENSION) \
65 EXTENSION(EXT, CONDITIONAL_RENDERING, conditional_rendering) \
63 EXTENSION(EXT, CONSERVATIVE_RASTERIZATION, conservative_rasterization) \ 66 EXTENSION(EXT, CONSERVATIVE_RASTERIZATION, conservative_rasterization) \
64 EXTENSION(EXT, DEPTH_RANGE_UNRESTRICTED, depth_range_unrestricted) \ 67 EXTENSION(EXT, DEPTH_RANGE_UNRESTRICTED, depth_range_unrestricted) \
65 EXTENSION(EXT, MEMORY_BUDGET, memory_budget) \ 68 EXTENSION(EXT, MEMORY_BUDGET, memory_budget) \
@@ -92,11 +95,14 @@ VK_DEFINE_HANDLE(VmaAllocator)
92 95
93// Define extensions where the absence of the extension may result in a degraded experience. 96// Define extensions where the absence of the extension may result in a degraded experience.
94#define FOR_EACH_VK_RECOMMENDED_EXTENSION(EXTENSION_NAME) \ 97#define FOR_EACH_VK_RECOMMENDED_EXTENSION(EXTENSION_NAME) \
98 EXTENSION_NAME(VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME) \
95 EXTENSION_NAME(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME) \ 99 EXTENSION_NAME(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME) \
100 EXTENSION_NAME(VK_EXT_DEPTH_BIAS_CONTROL_EXTENSION_NAME) \
96 EXTENSION_NAME(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME) \ 101 EXTENSION_NAME(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME) \
97 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) \ 102 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) \
98 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \ 103 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \
99 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \ 104 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \
105 EXTENSION_NAME(VK_EXT_4444_FORMATS_EXTENSION_NAME) \
100 EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \ 106 EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \
101 EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \ 107 EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \
102 EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \ 108 EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \
@@ -143,7 +149,11 @@ VK_DEFINE_HANDLE(VmaAllocator)
143// Define features where the absence of the feature may result in a degraded experience. 149// Define features where the absence of the feature may result in a degraded experience.
144#define FOR_EACH_VK_RECOMMENDED_FEATURE(FEATURE_NAME) \ 150#define FOR_EACH_VK_RECOMMENDED_FEATURE(FEATURE_NAME) \
145 FEATURE_NAME(custom_border_color, customBorderColors) \ 151 FEATURE_NAME(custom_border_color, customBorderColors) \
152 FEATURE_NAME(depth_bias_control, depthBiasControl) \
153 FEATURE_NAME(depth_bias_control, leastRepresentableValueForceUnormRepresentation) \
154 FEATURE_NAME(depth_bias_control, depthBiasExact) \
146 FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \ 155 FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \
156 FEATURE_NAME(format_a4b4g4r4, formatA4B4G4R4) \
147 FEATURE_NAME(index_type_uint8, indexTypeUint8) \ 157 FEATURE_NAME(index_type_uint8, indexTypeUint8) \
148 FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \ 158 FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \
149 FEATURE_NAME(provoking_vertex, provokingVertexLast) \ 159 FEATURE_NAME(provoking_vertex, provokingVertexLast) \
@@ -319,6 +329,11 @@ public:
319 return features.shader_float16_int8.shaderInt8; 329 return features.shader_float16_int8.shaderInt8;
320 } 330 }
321 331
332 /// Returns true if the device supports binding multisample images as storage images.
333 bool IsStorageImageMultisampleSupported() const {
334 return features.features.shaderStorageImageMultisample;
335 }
336
322 /// Returns true if the device warp size can potentially be bigger than guest's warp size. 337 /// Returns true if the device warp size can potentially be bigger than guest's warp size.
323 bool IsWarpSizePotentiallyBiggerThanGuest() const { 338 bool IsWarpSizePotentiallyBiggerThanGuest() const {
324 return is_warp_potentially_bigger; 339 return is_warp_potentially_bigger;
@@ -359,9 +374,14 @@ public:
359 return features.features.depthBounds; 374 return features.features.depthBounds;
360 } 375 }
361 376
362 /// Returns true when blitting from and to depth stencil images is supported. 377 /// Returns true when blitting from and to D24S8 images is supported.
363 bool IsBlitDepthStencilSupported() const { 378 bool IsBlitDepth24Stencil8Supported() const {
364 return is_blit_depth_stencil_supported; 379 return is_blit_depth24_stencil8_supported;
380 }
381
382 /// Returns true when blitting from and to D32S8 images is supported.
383 bool IsBlitDepth32Stencil8Supported() const {
384 return is_blit_depth32_stencil8_supported;
365 } 385 }
366 386
367 /// Returns true if the device supports VK_NV_viewport_swizzle. 387 /// Returns true if the device supports VK_NV_viewport_swizzle.
@@ -449,6 +469,11 @@ public:
449 return extensions.depth_clip_control; 469 return extensions.depth_clip_control;
450 } 470 }
451 471
472 /// Returns true if the device supports VK_EXT_depth_bias_control.
473 bool IsExtDepthBiasControlSupported() const {
474 return extensions.depth_bias_control;
475 }
476
452 /// Returns true if the device supports VK_EXT_shader_viewport_index_layer. 477 /// Returns true if the device supports VK_EXT_shader_viewport_index_layer.
453 bool IsExtShaderViewportIndexLayerSupported() const { 478 bool IsExtShaderViewportIndexLayerSupported() const {
454 return extensions.shader_viewport_index_layer; 479 return extensions.shader_viewport_index_layer;
@@ -488,6 +513,11 @@ public:
488 return extensions.extended_dynamic_state3; 513 return extensions.extended_dynamic_state3;
489 } 514 }
490 515
516 /// Returns true if the device supports VK_EXT_4444_formats.
517 bool IsExt4444FormatsSupported() const {
518 return features.format_a4b4g4r4.formatA4B4G4R4;
519 }
520
491 /// Returns true if the device supports VK_EXT_extended_dynamic_state3. 521 /// Returns true if the device supports VK_EXT_extended_dynamic_state3.
492 bool IsExtExtendedDynamicState3BlendingSupported() const { 522 bool IsExtExtendedDynamicState3BlendingSupported() const {
493 return dynamic_state3_blending; 523 return dynamic_state3_blending;
@@ -528,6 +558,10 @@ public:
528 return extensions.shader_atomic_int64; 558 return extensions.shader_atomic_int64;
529 } 559 }
530 560
561 bool IsExtConditionalRendering() const {
562 return extensions.conditional_rendering;
563 }
564
531 bool HasTimelineSemaphore() const; 565 bool HasTimelineSemaphore() const;
532 566
533 /// Returns the minimum supported version of SPIR-V. 567 /// Returns the minimum supported version of SPIR-V.
@@ -600,6 +634,10 @@ public:
600 return features.robustness2.nullDescriptor; 634 return features.robustness2.nullDescriptor;
601 } 635 }
602 636
637 bool HasExactDepthBiasControl() const {
638 return features.depth_bias_control.depthBiasExact;
639 }
640
603 u32 GetMaxVertexInputAttributes() const { 641 u32 GetMaxVertexInputAttributes() const {
604 return properties.properties.limits.maxVertexInputAttributes; 642 return properties.properties.limits.maxVertexInputAttributes;
605 } 643 }
@@ -666,7 +704,7 @@ private:
666 bool ComputeIsOptimalAstcSupported() const; 704 bool ComputeIsOptimalAstcSupported() const;
667 705
668 /// Returns true if the device natively supports blitting depth stencil images. 706 /// Returns true if the device natively supports blitting depth stencil images.
669 bool TestDepthStencilBlits() const; 707 bool TestDepthStencilBlits(VkFormat format) const;
670 708
671private: 709private:
672 VkInstance instance; ///< Vulkan instance. 710 VkInstance instance; ///< Vulkan instance.
@@ -730,25 +768,26 @@ private:
730 VkPhysicalDeviceProperties2 properties2{}; 768 VkPhysicalDeviceProperties2 properties2{};
731 769
732 // Misc features 770 // Misc features
733 bool is_optimal_astc_supported{}; ///< Support for all guest ASTC formats. 771 bool is_optimal_astc_supported{}; ///< Support for all guest ASTC formats.
734 bool is_blit_depth_stencil_supported{}; ///< Support for blitting from and to depth stencil. 772 bool is_blit_depth24_stencil8_supported{}; ///< Support for blitting from and to D24S8.
735 bool is_warp_potentially_bigger{}; ///< Host warp size can be bigger than guest. 773 bool is_blit_depth32_stencil8_supported{}; ///< Support for blitting from and to D32S8.
736 bool is_integrated{}; ///< Is GPU an iGPU. 774 bool is_warp_potentially_bigger{}; ///< Host warp size can be bigger than guest.
737 bool is_virtual{}; ///< Is GPU a virtual GPU. 775 bool is_integrated{}; ///< Is GPU an iGPU.
738 bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device. 776 bool is_virtual{}; ///< Is GPU a virtual GPU.
739 bool has_broken_compute{}; ///< Compute shaders can cause crashes 777 bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device.
740 bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit 778 bool has_broken_compute{}; ///< Compute shaders can cause crashes
741 bool has_renderdoc{}; ///< Has RenderDoc attached 779 bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit
742 bool has_nsight_graphics{}; ///< Has Nsight Graphics attached 780 bool has_renderdoc{}; ///< Has RenderDoc attached
743 bool supports_d24_depth{}; ///< Supports D24 depth buffers. 781 bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
744 bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting. 782 bool supports_d24_depth{}; ///< Supports D24 depth buffers.
745 bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation 783 bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting.
746 bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format. 784 bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation
747 bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3. 785 bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format.
748 bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3. 786 bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3.
749 bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow. 787 bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3.
750 u64 device_access_memory{}; ///< Total size of device local memory in bytes. 788 bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow.
751 u32 sets_per_pool{}; ///< Sets per Description Pool 789 u64 device_access_memory{}; ///< Total size of device local memory in bytes.
790 u32 sets_per_pool{}; ///< Sets per Description Pool
752 791
753 // Telemetry parameters 792 // Telemetry parameters
754 std::set<std::string, std::less<>> supported_extensions; ///< Reported Vulkan extensions. 793 std::set<std::string, std::less<>> supported_extensions; ///< Reported Vulkan extensions.
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index c3f388d89..2f3254a97 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -75,6 +75,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
75 X(vkBeginCommandBuffer); 75 X(vkBeginCommandBuffer);
76 X(vkBindBufferMemory); 76 X(vkBindBufferMemory);
77 X(vkBindImageMemory); 77 X(vkBindImageMemory);
78 X(vkCmdBeginConditionalRenderingEXT);
78 X(vkCmdBeginQuery); 79 X(vkCmdBeginQuery);
79 X(vkCmdBeginRenderPass); 80 X(vkCmdBeginRenderPass);
80 X(vkCmdBeginTransformFeedbackEXT); 81 X(vkCmdBeginTransformFeedbackEXT);
@@ -91,6 +92,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
91 X(vkCmdCopyBufferToImage); 92 X(vkCmdCopyBufferToImage);
92 X(vkCmdCopyImage); 93 X(vkCmdCopyImage);
93 X(vkCmdCopyImageToBuffer); 94 X(vkCmdCopyImageToBuffer);
95 X(vkCmdCopyQueryPoolResults);
94 X(vkCmdDispatch); 96 X(vkCmdDispatch);
95 X(vkCmdDispatchIndirect); 97 X(vkCmdDispatchIndirect);
96 X(vkCmdDraw); 98 X(vkCmdDraw);
@@ -99,6 +101,8 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
99 X(vkCmdDrawIndexedIndirect); 101 X(vkCmdDrawIndexedIndirect);
100 X(vkCmdDrawIndirectCount); 102 X(vkCmdDrawIndirectCount);
101 X(vkCmdDrawIndexedIndirectCount); 103 X(vkCmdDrawIndexedIndirectCount);
104 X(vkCmdDrawIndirectByteCountEXT);
105 X(vkCmdEndConditionalRenderingEXT);
102 X(vkCmdEndQuery); 106 X(vkCmdEndQuery);
103 X(vkCmdEndRenderPass); 107 X(vkCmdEndRenderPass);
104 X(vkCmdEndTransformFeedbackEXT); 108 X(vkCmdEndTransformFeedbackEXT);
@@ -109,6 +113,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
109 X(vkCmdPushDescriptorSetWithTemplateKHR); 113 X(vkCmdPushDescriptorSetWithTemplateKHR);
110 X(vkCmdSetBlendConstants); 114 X(vkCmdSetBlendConstants);
111 X(vkCmdSetDepthBias); 115 X(vkCmdSetDepthBias);
116 X(vkCmdSetDepthBias2EXT);
112 X(vkCmdSetDepthBounds); 117 X(vkCmdSetDepthBounds);
113 X(vkCmdSetEvent); 118 X(vkCmdSetEvent);
114 X(vkCmdSetScissor); 119 X(vkCmdSetScissor);
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 049fa8038..1e3c0fa64 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -185,6 +185,7 @@ struct DeviceDispatch : InstanceDispatch {
185 PFN_vkBeginCommandBuffer vkBeginCommandBuffer{}; 185 PFN_vkBeginCommandBuffer vkBeginCommandBuffer{};
186 PFN_vkBindBufferMemory vkBindBufferMemory{}; 186 PFN_vkBindBufferMemory vkBindBufferMemory{};
187 PFN_vkBindImageMemory vkBindImageMemory{}; 187 PFN_vkBindImageMemory vkBindImageMemory{};
188 PFN_vkCmdBeginConditionalRenderingEXT vkCmdBeginConditionalRenderingEXT{};
188 PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT{}; 189 PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT{};
189 PFN_vkCmdBeginQuery vkCmdBeginQuery{}; 190 PFN_vkCmdBeginQuery vkCmdBeginQuery{};
190 PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass{}; 191 PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass{};
@@ -202,6 +203,7 @@ struct DeviceDispatch : InstanceDispatch {
202 PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage{}; 203 PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage{};
203 PFN_vkCmdCopyImage vkCmdCopyImage{}; 204 PFN_vkCmdCopyImage vkCmdCopyImage{};
204 PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; 205 PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{};
206 PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults{};
205 PFN_vkCmdDispatch vkCmdDispatch{}; 207 PFN_vkCmdDispatch vkCmdDispatch{};
206 PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{}; 208 PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{};
207 PFN_vkCmdDraw vkCmdDraw{}; 209 PFN_vkCmdDraw vkCmdDraw{};
@@ -210,6 +212,8 @@ struct DeviceDispatch : InstanceDispatch {
210 PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect{}; 212 PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect{};
211 PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount{}; 213 PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount{};
212 PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount{}; 214 PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount{};
215 PFN_vkCmdDrawIndirectByteCountEXT vkCmdDrawIndirectByteCountEXT{};
216 PFN_vkCmdEndConditionalRenderingEXT vkCmdEndConditionalRenderingEXT{};
213 PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{}; 217 PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{};
214 PFN_vkCmdEndQuery vkCmdEndQuery{}; 218 PFN_vkCmdEndQuery vkCmdEndQuery{};
215 PFN_vkCmdEndRenderPass vkCmdEndRenderPass{}; 219 PFN_vkCmdEndRenderPass vkCmdEndRenderPass{};
@@ -222,6 +226,7 @@ struct DeviceDispatch : InstanceDispatch {
222 PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{}; 226 PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{};
223 PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{}; 227 PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{};
224 PFN_vkCmdSetDepthBias vkCmdSetDepthBias{}; 228 PFN_vkCmdSetDepthBias vkCmdSetDepthBias{};
229 PFN_vkCmdSetDepthBias2EXT vkCmdSetDepthBias2EXT{};
225 PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds{}; 230 PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds{};
226 PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT{}; 231 PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT{};
227 PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{}; 232 PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{};
@@ -1182,6 +1187,13 @@ public:
1182 count_offset, draw_count, stride); 1187 count_offset, draw_count, stride);
1183 } 1188 }
1184 1189
1190 void DrawIndirectByteCountEXT(u32 instance_count, u32 first_instance, VkBuffer counter_buffer,
1191 VkDeviceSize counter_buffer_offset, u32 counter_offset,
1192 u32 stride) {
1193 dld->vkCmdDrawIndirectByteCountEXT(handle, instance_count, first_instance, counter_buffer,
1194 counter_buffer_offset, counter_offset, stride);
1195 }
1196
1185 void ClearAttachments(Span<VkClearAttachment> attachments, 1197 void ClearAttachments(Span<VkClearAttachment> attachments,
1186 Span<VkClearRect> rects) const noexcept { 1198 Span<VkClearRect> rects) const noexcept {
1187 dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(), 1199 dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(),
@@ -1270,6 +1282,13 @@ public:
1270 regions.data()); 1282 regions.data());
1271 } 1283 }
1272 1284
1285 void CopyQueryPoolResults(VkQueryPool query_pool, u32 first_query, u32 query_count,
1286 VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize stride,
1287 VkQueryResultFlags flags) const noexcept {
1288 dld->vkCmdCopyQueryPoolResults(handle, query_pool, first_query, query_count, dst_buffer,
1289 dst_offset, stride, flags);
1290 }
1291
1273 void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size, 1292 void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size,
1274 u32 data) const noexcept { 1293 u32 data) const noexcept {
1275 dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data); 1294 dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data);
@@ -1315,6 +1334,18 @@ public:
1315 dld->vkCmdSetDepthBias(handle, constant_factor, clamp, slope_factor); 1334 dld->vkCmdSetDepthBias(handle, constant_factor, clamp, slope_factor);
1316 } 1335 }
1317 1336
1337 void SetDepthBias(float constant_factor, float clamp, float slope_factor,
1338 VkDepthBiasRepresentationInfoEXT* extra) const noexcept {
1339 VkDepthBiasInfoEXT info{
1340 .sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_INFO_EXT,
1341 .pNext = extra,
1342 .depthBiasConstantFactor = constant_factor,
1343 .depthBiasClamp = clamp,
1344 .depthBiasSlopeFactor = slope_factor,
1345 };
1346 dld->vkCmdSetDepthBias2EXT(handle, &info);
1347 }
1348
1318 void SetDepthBounds(float min_depth_bounds, float max_depth_bounds) const noexcept { 1349 void SetDepthBounds(float min_depth_bounds, float max_depth_bounds) const noexcept {
1319 dld->vkCmdSetDepthBounds(handle, min_depth_bounds, max_depth_bounds); 1350 dld->vkCmdSetDepthBounds(handle, min_depth_bounds, max_depth_bounds);
1320 } 1351 }
@@ -1448,6 +1479,15 @@ public:
1448 counter_buffers, counter_buffer_offsets); 1479 counter_buffers, counter_buffer_offsets);
1449 } 1480 }
1450 1481
1482 void BeginConditionalRenderingEXT(
1483 const VkConditionalRenderingBeginInfoEXT& info) const noexcept {
1484 dld->vkCmdBeginConditionalRenderingEXT(handle, &info);
1485 }
1486
1487 void EndConditionalRenderingEXT() const noexcept {
1488 dld->vkCmdEndConditionalRenderingEXT(handle);
1489 }
1490
1451 void BeginDebugUtilsLabelEXT(const char* label, std::span<float, 4> color) const noexcept { 1491 void BeginDebugUtilsLabelEXT(const char* label, std::span<float, 4> color) const noexcept {
1452 const VkDebugUtilsLabelEXT label_info{ 1492 const VkDebugUtilsLabelEXT label_info{
1453 .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, 1493 .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index d32aa9615..adb7b332f 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1551,6 +1551,7 @@ void GMainWindow::ConnectMenuEvents() {
1551 // Tools 1551 // Tools
1552 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, 1552 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
1553 ReinitializeKeyBehavior::Warning)); 1553 ReinitializeKeyBehavior::Warning));
1554 connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit);
1554 connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot); 1555 connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
1555 1556
1556 // TAS 1557 // TAS
@@ -1590,6 +1591,8 @@ void GMainWindow::UpdateMenuState() {
1590 } 1591 }
1591 1592
1592 multiplayer_state->UpdateNotificationStatus(); 1593 multiplayer_state->UpdateNotificationStatus();
1594
1595 ui->action_Load_Mii_Edit->setEnabled(CheckFirmwarePresence());
1593} 1596}
1594 1597
1595void GMainWindow::OnDisplayTitleBars(bool show) { 1598void GMainWindow::OnDisplayTitleBars(bool show) {
@@ -3110,10 +3113,9 @@ void GMainWindow::OnMenuInstallToNAND() {
3110 QFuture<InstallResult> future; 3113 QFuture<InstallResult> future;
3111 InstallResult result; 3114 InstallResult result;
3112 3115
3113 if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || 3116 if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
3114 file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
3115 3117
3116 future = QtConcurrent::run([this, &file] { return InstallNSPXCI(file); }); 3118 future = QtConcurrent::run([this, &file] { return InstallNSP(file); });
3117 3119
3118 while (!future.isFinished()) { 3120 while (!future.isFinished()) {
3119 QCoreApplication::processEvents(); 3121 QCoreApplication::processEvents();
@@ -3172,7 +3174,7 @@ void GMainWindow::OnMenuInstallToNAND() {
3172 ui->action_Install_File_NAND->setEnabled(true); 3174 ui->action_Install_File_NAND->setEnabled(true);
3173} 3175}
3174 3176
3175InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { 3177InstallResult GMainWindow::InstallNSP(const QString& filename) {
3176 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, 3178 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
3177 const FileSys::VirtualFile& dest, std::size_t block_size) { 3179 const FileSys::VirtualFile& dest, std::size_t block_size) {
3178 if (src == nullptr || dest == nullptr) { 3180 if (src == nullptr || dest == nullptr) {
@@ -3206,9 +3208,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) {
3206 return InstallResult::Failure; 3208 return InstallResult::Failure;
3207 } 3209 }
3208 } else { 3210 } else {
3209 const auto xci = std::make_shared<FileSys::XCI>( 3211 return InstallResult::Failure;
3210 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
3211 nsp = xci->GetSecurePartitionNSP();
3212 } 3212 }
3213 3213
3214 if (nsp->GetStatus() != Loader::ResultStatus::Success) { 3214 if (nsp->GetStatus() != Loader::ResultStatus::Success) {
@@ -4134,6 +4134,27 @@ void GMainWindow::OnToggleStatusBar() {
4134 statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); 4134 statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
4135} 4135}
4136 4136
4137void GMainWindow::OnMiiEdit() {
4138 constexpr u64 MiiEditId = 0x0100000000001009ull;
4139 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4140 if (!bis_system) {
4141 QMessageBox::warning(this, tr("No firmware available"),
4142 tr("Please install the firmware to use the Mii editor."));
4143 return;
4144 }
4145
4146 auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
4147 if (!mii_applet_nca) {
4148 QMessageBox::warning(this, tr("Mii Edit Applet"),
4149 tr("Mii editor is not available. Please reinstall firmware."));
4150 return;
4151 }
4152
4153 const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath()));
4154 UISettings::values.roms_path = QFileInfo(filename).path();
4155 BootGame(filename);
4156}
4157
4137void GMainWindow::OnCaptureScreenshot() { 4158void GMainWindow::OnCaptureScreenshot() {
4138 if (emu_thread == nullptr || !emu_thread->IsRunning()) { 4159 if (emu_thread == nullptr || !emu_thread->IsRunning()) {
4139 return; 4160 return;
@@ -4540,6 +4561,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
4540 if (behavior == ReinitializeKeyBehavior::Warning) { 4561 if (behavior == ReinitializeKeyBehavior::Warning) {
4541 game_list->PopulateAsync(UISettings::values.game_dirs); 4562 game_list->PopulateAsync(UISettings::values.game_dirs);
4542 } 4563 }
4564
4565 UpdateMenuState();
4543} 4566}
4544 4567
4545bool GMainWindow::CheckSystemArchiveDecryption() { 4568bool GMainWindow::CheckSystemArchiveDecryption() {
@@ -4561,6 +4584,22 @@ bool GMainWindow::CheckSystemArchiveDecryption() {
4561 return mii_nca->GetRomFS().get() != nullptr; 4584 return mii_nca->GetRomFS().get() != nullptr;
4562} 4585}
4563 4586
4587bool GMainWindow::CheckFirmwarePresence() {
4588 constexpr u64 MiiEditId = 0x0100000000001009ull;
4589
4590 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4591 if (!bis_system) {
4592 return false;
4593 }
4594
4595 auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
4596 if (!mii_applet_nca) {
4597 return false;
4598 }
4599
4600 return true;
4601}
4602
4564bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, 4603bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
4565 u64* selected_title_id, u8* selected_content_record_type) { 4604 u64* selected_title_id, u8* selected_content_record_type) {
4566 using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>; 4605 using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index cf191f698..ba318eb11 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -365,6 +365,7 @@ private slots:
365 void ResetWindowSize720(); 365 void ResetWindowSize720();
366 void ResetWindowSize900(); 366 void ResetWindowSize900();
367 void ResetWindowSize1080(); 367 void ResetWindowSize1080();
368 void OnMiiEdit();
368 void OnCaptureScreenshot(); 369 void OnCaptureScreenshot();
369 void OnReinitializeKeys(ReinitializeKeyBehavior behavior); 370 void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
370 void OnLanguageChanged(const QString& locale); 371 void OnLanguageChanged(const QString& locale);
@@ -386,7 +387,7 @@ private:
386 void RemoveCacheStorage(u64 program_id); 387 void RemoveCacheStorage(u64 program_id);
387 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, 388 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
388 u64* selected_title_id, u8* selected_content_record_type); 389 u64* selected_title_id, u8* selected_content_record_type);
389 InstallResult InstallNSPXCI(const QString& filename); 390 InstallResult InstallNSP(const QString& filename);
390 InstallResult InstallNCA(const QString& filename); 391 InstallResult InstallNCA(const QString& filename);
391 void MigrateConfigFiles(); 392 void MigrateConfigFiles();
392 void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, 393 void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
@@ -409,6 +410,7 @@ private:
409 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); 410 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
410 bool CheckDarkMode(); 411 bool CheckDarkMode();
411 bool CheckSystemArchiveDecryption(); 412 bool CheckSystemArchiveDecryption();
413 bool CheckFirmwarePresence();
412 void ConfigureFilesystemProvider(const std::string& filepath); 414 void ConfigureFilesystemProvider(const std::string& filepath);
413 415
414 QString GetTasStateDescription() const; 416 QString GetTasStateDescription() const;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index e54d7d75d..91d6c5ef3 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -150,6 +150,8 @@
150 <addaction name="action_Rederive"/> 150 <addaction name="action_Rederive"/>
151 <addaction name="action_Verify_installed_contents"/> 151 <addaction name="action_Verify_installed_contents"/>
152 <addaction name="separator"/> 152 <addaction name="separator"/>
153 <addaction name="action_Load_Mii_Edit"/>
154 <addaction name="separator"/>
153 <addaction name="action_Capture_Screenshot"/> 155 <addaction name="action_Capture_Screenshot"/>
154 <addaction name="menuTAS"/> 156 <addaction name="menuTAS"/>
155 </widget> 157 </widget>
@@ -217,7 +219,7 @@
217 </action> 219 </action>
218 <action name="action_Verify_installed_contents"> 220 <action name="action_Verify_installed_contents">
219 <property name="text"> 221 <property name="text">
220 <string>Verify installed contents</string> 222 <string>&amp;Verify Installed Contents</string>
221 </property> 223 </property>
222 </action> 224 </action>
223 <action name="action_About"> 225 <action name="action_About">
@@ -368,6 +370,11 @@
368 <string>&amp;Capture Screenshot</string> 370 <string>&amp;Capture Screenshot</string>
369 </property> 371 </property>
370 </action> 372 </action>
373 <action name="action_Load_Mii_Edit">
374 <property name="text">
375 <string>Open &amp;Mii Editor</string>
376 </property>
377 </action>
371 <action name="action_Configure_Tas"> 378 <action name="action_Configure_Tas">
372 <property name="text"> 379 <property name="text">
373 <string>&amp;Configure TAS...</string> 380 <string>&amp;Configure TAS...</string>