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/adapters/HomeSettingAdapter.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt59
-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/model/MessageDialogViewModel.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt133
-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/dialog_progress_bar.xml28
-rw-r--r--src/android/app/src/main/res/layout/fragment_about.xml61
-rw-r--r--src/android/app/src/main/res/values/strings.xml12
-rw-r--r--src/core/file_sys/partition_filesystem.cpp1
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.cpp50
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.h7
-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_manager.cpp6
-rw-r--r--src/core/hle/service/mii/types/char_info.cpp2
-rw-r--r--src/core/hle/service/mii/types/core_data.cpp3
-rw-r--r--src/core/hle/service/mii/types/raw_data.cpp12
-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/engines/maxwell_dma.cpp5
-rw-r--r--src/video_core/engines/maxwell_dma.h55
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp26
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp16
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h60
31 files changed, 575 insertions, 187 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/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/fragments/AboutFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
index 2ff827c6b..7b8f99872 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
@@ -26,6 +26,7 @@ import org.yuzu.yuzu_emu.BuildConfig
26import org.yuzu.yuzu_emu.R 26import org.yuzu.yuzu_emu.R
27import org.yuzu.yuzu_emu.databinding.FragmentAboutBinding 27import org.yuzu.yuzu_emu.databinding.FragmentAboutBinding
28import org.yuzu.yuzu_emu.model.HomeViewModel 28import org.yuzu.yuzu_emu.model.HomeViewModel
29import org.yuzu.yuzu_emu.ui.main.MainActivity
29 30
30class AboutFragment : Fragment() { 31class AboutFragment : Fragment() {
31 private var _binding: FragmentAboutBinding? = null 32 private var _binding: FragmentAboutBinding? = null
@@ -92,6 +93,12 @@ class AboutFragment : Fragment() {
92 } 93 }
93 } 94 }
94 95
96 val mainActivity = requireActivity() as MainActivity
97 binding.buttonExport.setOnClickListener { mainActivity.exportUserData.launch("export.zip") }
98 binding.buttonImport.setOnClickListener {
99 mainActivity.importUserData.launch(arrayOf("application/zip"))
100 }
101
95 binding.buttonDiscord.setOnClickListener { openLink(getString(R.string.support_link)) } 102 binding.buttonDiscord.setOnClickListener { openLink(getString(R.string.support_link)) }
96 binding.buttonWebsite.setOnClickListener { openLink(getString(R.string.website_link)) } 103 binding.buttonWebsite.setOnClickListener { openLink(getString(R.string.website_link)) }
97 binding.buttonGithub.setOnClickListener { openLink(getString(R.string.github_link)) } 104 binding.buttonGithub.setOnClickListener { openLink(getString(R.string.github_link)) }
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
index f38aeea53..ee2d44718 100644
--- 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
@@ -187,6 +187,7 @@ class ImportExportSavesFragment : DialogFragment() {
187 withContext(Dispatchers.Main) { 187 withContext(Dispatchers.Main) {
188 if (!validZip) { 188 if (!validZip) {
189 MessageDialogFragment.newInstance( 189 MessageDialogFragment.newInstance(
190 requireActivity(),
190 titleId = R.string.save_file_invalid_zip_structure, 191 titleId = R.string.save_file_invalid_zip_structure,
191 descriptionId = R.string.save_file_invalid_zip_structure_description 192 descriptionId = R.string.save_file_invalid_zip_structure_description
192 ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) 193 ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
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..0d16a7d37 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
@@ -4,6 +4,7 @@
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.os.Bundle 8import android.os.Bundle
8import android.view.LayoutInflater 9import android.view.LayoutInflater
9import android.view.View 10import android.view.View
@@ -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,27 @@ 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) { _: DialogInterface, _: Int ->
43 taskViewModel.setCancelled(true)
44 }
45 }
46
47 val alertDialog = dialog.create()
48 alertDialog.setCanceledOnTouchOutside(false)
39 49
40 if (!taskViewModel.isRunning.value) { 50 if (!taskViewModel.isRunning.value) {
41 taskViewModel.runTask() 51 taskViewModel.runTask()
42 } 52 }
43 return dialog 53 return alertDialog
44 } 54 }
45 55
46 override fun onCreateView( 56 override fun onCreateView(
@@ -53,21 +63,35 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
53 63
54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 64 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
55 super.onViewCreated(view, savedInstanceState) 65 super.onViewCreated(view, savedInstanceState)
56 viewLifecycleOwner.lifecycleScope.launch { 66 viewLifecycleOwner.lifecycleScope.apply {
57 repeatOnLifecycle(Lifecycle.State.CREATED) { 67 launch {
58 taskViewModel.isComplete.collect { 68 repeatOnLifecycle(Lifecycle.State.CREATED) {
59 if (it) { 69 taskViewModel.isComplete.collect {
60 dismiss() 70 if (it) {
61 when (val result = taskViewModel.result.value) { 71 dismiss()
62 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG) 72 when (val result = taskViewModel.result.value) {
63 .show() 73 is String -> Toast.makeText(
74 requireContext(),
75 result,
76 Toast.LENGTH_LONG
77 ).show()
64 78
65 is MessageDialogFragment -> result.show( 79 is MessageDialogFragment -> result.show(
66 requireActivity().supportFragmentManager, 80 requireActivity().supportFragmentManager,
67 MessageDialogFragment.TAG 81 MessageDialogFragment.TAG
68 ) 82 )
83 }
84 taskViewModel.clear()
85 }
86 }
87 }
88 }
89 launch {
90 repeatOnLifecycle(Lifecycle.State.CREATED) {
91 taskViewModel.cancelled.collect {
92 if (it) {
93 dialog?.setTitle(R.string.cancelling)
69 } 94 }
70 taskViewModel.clear()
71 } 95 }
72 } 96 }
73 } 97 }
@@ -78,16 +102,19 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
78 const val TAG = "IndeterminateProgressDialogFragment" 102 const val TAG = "IndeterminateProgressDialogFragment"
79 103
80 private const val TITLE = "Title" 104 private const val TITLE = "Title"
105 private const val CANCELLABLE = "Cancellable"
81 106
82 fun newInstance( 107 fun newInstance(
83 activity: AppCompatActivity, 108 activity: AppCompatActivity,
84 titleId: Int, 109 titleId: Int,
110 cancellable: Boolean = false,
85 task: () -> Any 111 task: () -> Any
86 ): IndeterminateProgressDialogFragment { 112 ): IndeterminateProgressDialogFragment {
87 val dialog = IndeterminateProgressDialogFragment() 113 val dialog = IndeterminateProgressDialogFragment()
88 val args = Bundle() 114 val args = Bundle()
89 ViewModelProvider(activity)[TaskViewModel::class.java].task = task 115 ViewModelProvider(activity)[TaskViewModel::class.java].task = task
90 args.putInt(TITLE, titleId) 116 args.putInt(TITLE, titleId)
117 args.putBoolean(CANCELLABLE, cancellable)
91 dialog.arguments = args 118 dialog.arguments = args
92 return dialog 119 return dialog
93 } 120 }
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/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..d6418a666 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() {
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..6fa847631 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
@@ -46,13 +46,22 @@ import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
46import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 46import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
47import org.yuzu.yuzu_emu.model.GamesViewModel 47import org.yuzu.yuzu_emu.model.GamesViewModel
48import org.yuzu.yuzu_emu.model.HomeViewModel 48import org.yuzu.yuzu_emu.model.HomeViewModel
49import org.yuzu.yuzu_emu.model.TaskViewModel
49import org.yuzu.yuzu_emu.utils.* 50import org.yuzu.yuzu_emu.utils.*
51import java.io.BufferedInputStream
52import java.io.BufferedOutputStream
53import java.io.FileInputStream
54import java.io.FileOutputStream
55import java.util.zip.ZipEntry
56import java.util.zip.ZipInputStream
57import java.util.zip.ZipOutputStream
50 58
51class MainActivity : AppCompatActivity(), ThemeProvider { 59class MainActivity : AppCompatActivity(), ThemeProvider {
52 private lateinit var binding: ActivityMainBinding 60 private lateinit var binding: ActivityMainBinding
53 61
54 private val homeViewModel: HomeViewModel by viewModels() 62 private val homeViewModel: HomeViewModel by viewModels()
55 private val gamesViewModel: GamesViewModel by viewModels() 63 private val gamesViewModel: GamesViewModel by viewModels()
64 private val taskViewModel: TaskViewModel by viewModels()
56 65
57 override var themeId: Int = 0 66 override var themeId: Int = 0
58 67
@@ -307,6 +316,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
307 fun processKey(result: Uri): Boolean { 316 fun processKey(result: Uri): Boolean {
308 if (FileUtil.getExtension(result) != "keys") { 317 if (FileUtil.getExtension(result) != "keys") {
309 MessageDialogFragment.newInstance( 318 MessageDialogFragment.newInstance(
319 this,
310 titleId = R.string.reading_keys_failure, 320 titleId = R.string.reading_keys_failure,
311 descriptionId = R.string.install_prod_keys_failure_extension_description 321 descriptionId = R.string.install_prod_keys_failure_extension_description
312 ).show(supportFragmentManager, MessageDialogFragment.TAG) 322 ).show(supportFragmentManager, MessageDialogFragment.TAG)
@@ -336,6 +346,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
336 return true 346 return true
337 } else { 347 } else {
338 MessageDialogFragment.newInstance( 348 MessageDialogFragment.newInstance(
349 this,
339 titleId = R.string.invalid_keys_error, 350 titleId = R.string.invalid_keys_error,
340 descriptionId = R.string.install_keys_failure_description, 351 descriptionId = R.string.install_keys_failure_description,
341 helpLinkId = R.string.dumping_keys_quickstart_link 352 helpLinkId = R.string.dumping_keys_quickstart_link
@@ -376,6 +387,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
376 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 387 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
377 messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { 388 messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
378 MessageDialogFragment.newInstance( 389 MessageDialogFragment.newInstance(
390 this,
379 titleId = R.string.firmware_installed_failure, 391 titleId = R.string.firmware_installed_failure,
380 descriptionId = R.string.firmware_installed_failure_description 392 descriptionId = R.string.firmware_installed_failure_description
381 ) 393 )
@@ -395,7 +407,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
395 IndeterminateProgressDialogFragment.newInstance( 407 IndeterminateProgressDialogFragment.newInstance(
396 this, 408 this,
397 R.string.firmware_installing, 409 R.string.firmware_installing,
398 task 410 task = task
399 ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) 411 ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
400 } 412 }
401 413
@@ -407,6 +419,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
407 419
408 if (FileUtil.getExtension(result) != "bin") { 420 if (FileUtil.getExtension(result) != "bin") {
409 MessageDialogFragment.newInstance( 421 MessageDialogFragment.newInstance(
422 this,
410 titleId = R.string.reading_keys_failure, 423 titleId = R.string.reading_keys_failure,
411 descriptionId = R.string.install_amiibo_keys_failure_extension_description 424 descriptionId = R.string.install_amiibo_keys_failure_extension_description
412 ).show(supportFragmentManager, MessageDialogFragment.TAG) 425 ).show(supportFragmentManager, MessageDialogFragment.TAG)
@@ -434,6 +447,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
434 ).show() 447 ).show()
435 } else { 448 } else {
436 MessageDialogFragment.newInstance( 449 MessageDialogFragment.newInstance(
450 this,
437 titleId = R.string.invalid_keys_error, 451 titleId = R.string.invalid_keys_error,
438 descriptionId = R.string.install_keys_failure_description, 452 descriptionId = R.string.install_keys_failure_description,
439 helpLinkId = R.string.dumping_keys_quickstart_link 453 helpLinkId = R.string.dumping_keys_quickstart_link
@@ -583,12 +597,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
583 installResult.append(separator) 597 installResult.append(separator)
584 } 598 }
585 return@newInstance MessageDialogFragment.newInstance( 599 return@newInstance MessageDialogFragment.newInstance(
600 this,
586 titleId = R.string.install_game_content_failure, 601 titleId = R.string.install_game_content_failure,
587 descriptionString = installResult.toString().trim(), 602 descriptionString = installResult.toString().trim(),
588 helpLinkId = R.string.install_game_content_help_link 603 helpLinkId = R.string.install_game_content_help_link
589 ) 604 )
590 } else { 605 } else {
591 return@newInstance MessageDialogFragment.newInstance( 606 return@newInstance MessageDialogFragment.newInstance(
607 this,
592 titleId = R.string.install_game_content_success, 608 titleId = R.string.install_game_content_success,
593 descriptionString = installResult.toString().trim() 609 descriptionString = installResult.toString().trim()
594 ) 610 )
@@ -596,4 +612,119 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
596 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) 612 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
597 } 613 }
598 } 614 }
615
616 val exportUserData = registerForActivityResult(
617 ActivityResultContracts.CreateDocument("application/zip")
618 ) { result ->
619 if (result == null) {
620 return@registerForActivityResult
621 }
622
623 IndeterminateProgressDialogFragment.newInstance(
624 this,
625 R.string.exporting_user_data,
626 true
627 ) {
628 val zos = ZipOutputStream(
629 BufferedOutputStream(contentResolver.openOutputStream(result))
630 )
631 zos.use { stream ->
632 File(DirectoryInitialization.userDirectory!!).walkTopDown().forEach { file ->
633 if (taskViewModel.cancelled.value) {
634 return@newInstance R.string.user_data_export_cancelled
635 }
636
637 if (!file.isDirectory) {
638 val newPath = file.path.substring(
639 DirectoryInitialization.userDirectory!!.length,
640 file.path.length
641 )
642 stream.putNextEntry(ZipEntry(newPath))
643
644 val buffer = ByteArray(8096)
645 var read: Int
646 FileInputStream(file).use { fis ->
647 while (fis.read(buffer).also { read = it } != -1) {
648 stream.write(buffer, 0, read)
649 }
650 }
651
652 stream.closeEntry()
653 }
654 }
655 }
656 return@newInstance getString(R.string.user_data_export_success)
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 getString(R.string.invalid_yuzu_backup)
685 }
686
687 File(DirectoryInitialization.userDirectory!!).deleteRecursively()
688
689 val zis =
690 ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
691 val userDirectory = File(DirectoryInitialization.userDirectory!!)
692 val canonicalPath = userDirectory.canonicalPath + '/'
693 zis.use { stream ->
694 var ze: ZipEntry? = stream.nextEntry
695 while (ze != null) {
696 val newFile = File(userDirectory, ze!!.name)
697 val destinationDirectory =
698 if (ze!!.isDirectory) newFile else newFile.parentFile
699
700 if (!newFile.canonicalPath.startsWith(canonicalPath)) {
701 throw SecurityException(
702 "Zip file attempted path traversal! ${ze!!.name}"
703 )
704 }
705
706 if (!destinationDirectory.isDirectory && !destinationDirectory.mkdirs()) {
707 throw IOException("Failed to create directory $destinationDirectory")
708 }
709
710 if (!ze!!.isDirectory) {
711 val buffer = ByteArray(8096)
712 var read: Int
713 BufferedOutputStream(FileOutputStream(newFile)).use { bos ->
714 while (zis.read(buffer).also { read = it } != -1) {
715 bos.write(buffer, 0, read)
716 }
717 }
718 }
719 ze = stream.nextEntry
720 }
721 }
722
723 // Reinitialize relevant data
724 NativeLibrary.initializeEmulation()
725 gamesViewModel.reloadGames(false)
726
727 return@newInstance getString(R.string.user_data_import_success)
728 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
729 }
599} 730}
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/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_about.xml b/src/android/app/src/main/res/layout/fragment_about.xml
index 3e1d98451..36b350338 100644
--- a/src/android/app/src/main/res/layout/fragment_about.xml
+++ b/src/android/app/src/main/res/layout/fragment_about.xml
@@ -184,6 +184,67 @@
184 <LinearLayout 184 <LinearLayout
185 android:layout_width="match_parent" 185 android:layout_width="match_parent"
186 android:layout_height="wrap_content" 186 android:layout_height="wrap_content"
187 android:orientation="horizontal">
188
189 <LinearLayout
190 android:layout_width="match_parent"
191 android:layout_height="wrap_content"
192 android:paddingVertical="16dp"
193 android:paddingHorizontal="16dp"
194 android:orientation="vertical"
195 android:layout_weight="1">
196
197 <com.google.android.material.textview.MaterialTextView
198 style="@style/TextAppearance.Material3.TitleMedium"
199 android:layout_width="match_parent"
200 android:layout_height="wrap_content"
201 android:layout_marginHorizontal="24dp"
202 android:textAlignment="viewStart"
203 android:text="@string/user_data" />
204
205 <com.google.android.material.textview.MaterialTextView
206 style="@style/TextAppearance.Material3.BodyMedium"
207 android:layout_width="match_parent"
208 android:layout_height="wrap_content"
209 android:layout_marginHorizontal="24dp"
210 android:layout_marginTop="6dp"
211 android:textAlignment="viewStart"
212 android:text="@string/user_data_description" />
213
214 </LinearLayout>
215
216 <Button
217 android:id="@+id/button_import"
218 style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
219 android:layout_width="wrap_content"
220 android:layout_height="wrap_content"
221 android:layout_gravity="center_vertical"
222 android:contentDescription="@string/string_import"
223 android:tooltipText="@string/string_import"
224 app:icon="@drawable/ic_import" />
225
226 <Button
227 android:id="@+id/button_export"
228 style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
229 android:layout_width="wrap_content"
230 android:layout_height="wrap_content"
231 android:layout_marginStart="12dp"
232 android:layout_marginEnd="24dp"
233 android:layout_gravity="center_vertical"
234 android:contentDescription="@string/export"
235 android:tooltipText="@string/export"
236 app:icon="@drawable/ic_export" />
237
238 </LinearLayout>
239
240 <com.google.android.material.divider.MaterialDivider
241 android:layout_width="match_parent"
242 android:layout_height="wrap_content"
243 android:layout_marginHorizontal="20dp" />
244
245 <LinearLayout
246 android:layout_width="match_parent"
247 android:layout_height="wrap_content"
187 android:orientation="horizontal" 248 android:orientation="horizontal"
188 android:gravity="center_horizontal" 249 android:gravity="center_horizontal"
189 android:layout_marginTop="12dp" 250 android:layout_marginTop="12dp"
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index b163e6fc1..0730143bd 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -128,6 +128,15 @@
128 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string> 128 <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> 129 <string name="licenses_description">Projects that make yuzu for Android possible</string>
130 <string name="build">Build</string> 130 <string name="build">Build</string>
131 <string name="user_data">User data</string>
132 <string name="user_data_description">Import/export all app data.\n\nWhen importing user data, all existing user data will be deleted!</string>
133 <string name="exporting_user_data">Exporting user data…</string>
134 <string name="importing_user_data">Importing user data…</string>
135 <string name="import_user_data">Import user data</string>
136 <string name="invalid_yuzu_backup">Invalid yuzu backup</string>
137 <string name="user_data_export_success">User data exported successfully</string>
138 <string name="user_data_import_success">User data imported successfully</string>
139 <string name="user_data_export_cancelled">Export cancelled</string>
131 <string name="support_link">https://discord.gg/u77vRWY</string> 140 <string name="support_link">https://discord.gg/u77vRWY</string>
132 <string name="website_link">https://yuzu-emu.org/</string> 141 <string name="website_link">https://yuzu-emu.org/</string>
133 <string name="github_link">https://github.com/yuzu-emu</string> 142 <string name="github_link">https://github.com/yuzu-emu</string>
@@ -215,6 +224,9 @@
215 <string name="auto">Auto</string> 224 <string name="auto">Auto</string>
216 <string name="submit">Submit</string> 225 <string name="submit">Submit</string>
217 <string name="string_null">Null</string> 226 <string name="string_null">Null</string>
227 <string name="string_import">Import</string>
228 <string name="export">Export</string>
229 <string name="cancelling">Cancelling</string>
218 230
219 <!-- GPU driver installation --> 231 <!-- GPU driver installation -->
220 <string name="select_gpu_driver">Select GPU driver</string> 232 <string name="select_gpu_driver">Select GPU driver</string>
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/applets/applet_mii_edit.cpp b/src/core/hle/service/am/applets/applet_mii_edit.cpp
index 350a90818..ff77830d2 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);
118
119 const MiiEditCharInfo edit_char_info{
120 .mii_info{char_info},
121 };
92 122
123 MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
124 break;
125 }
126 case MiiEditAppletMode::EditMii: {
93 const MiiEditCharInfo edit_char_info{ 127 const MiiEditCharInfo edit_char_info{
94 .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii 128 .mii_info{applet_input_v4.char_info.mii_info},
95 ? applet_input_v4.char_info.mii_info
96 : char_info},
97 }; 129 };
98 130
99 MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info); 131 MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
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/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_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/types/char_info.cpp b/src/core/hle/service/mii/types/char_info.cpp
index e90124af4..c82186c73 100644
--- a/src/core/hle/service/mii/types/char_info.cpp
+++ b/src/core/hle/service/mii/types/char_info.cpp
@@ -37,7 +37,7 @@ void CharInfo::SetFromStoreData(const StoreData& store_data) {
37 eyebrow_aspect = store_data.GetEyebrowAspect(); 37 eyebrow_aspect = store_data.GetEyebrowAspect();
38 eyebrow_rotate = store_data.GetEyebrowRotate(); 38 eyebrow_rotate = store_data.GetEyebrowRotate();
39 eyebrow_x = store_data.GetEyebrowX(); 39 eyebrow_x = store_data.GetEyebrowX();
40 eyebrow_y = store_data.GetEyebrowY() + 3; 40 eyebrow_y = store_data.GetEyebrowY();
41 nose_type = store_data.GetNoseType(); 41 nose_type = store_data.GetNoseType();
42 nose_scale = store_data.GetNoseScale(); 42 nose_scale = store_data.GetNoseScale();
43 nose_y = store_data.GetNoseY(); 43 nose_y = store_data.GetNoseY();
diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp
index 465c6293a..1068031e3 100644
--- a/src/core/hle/service/mii/types/core_data.cpp
+++ b/src/core/hle/service/mii/types/core_data.cpp
@@ -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/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/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index da8eab7ee..279f0daa1 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));
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/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_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index d935dd43e..1f9e7acaa 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -590,7 +590,7 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
590} 590}
591 591
592void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle, 592void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle,
593 bool emulate_bgr565) { 593 bool emulate_bgr565, bool emulate_a4b4g4r4) {
594 switch (format) { 594 switch (format) {
595 case PixelFormat::A1B5G5R5_UNORM: 595 case PixelFormat::A1B5G5R5_UNORM:
596 std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed); 596 std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed);
@@ -606,6 +606,11 @@ void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4
606 case PixelFormat::G4R4_UNORM: 606 case PixelFormat::G4R4_UNORM:
607 std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed); 607 std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed);
608 break; 608 break;
609 case PixelFormat::A4B4G4R4_UNORM:
610 if (emulate_a4b4g4r4) {
611 std::ranges::reverse(swizzle);
612 }
613 break;
609 default: 614 default:
610 break; 615 break;
611 } 616 }
@@ -1034,15 +1039,27 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
1034 dst_region, src_region, filter, operation); 1039 dst_region, src_region, filter, operation);
1035 return; 1040 return;
1036 } 1041 }
1042 ASSERT(src.format == dst.format);
1037 if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { 1043 if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
1038 if (!device.IsBlitDepthStencilSupported()) { 1044 const auto format = src.format;
1045 const auto can_blit_depth_stencil = [this, format] {
1046 switch (format) {
1047 case VideoCore::Surface::PixelFormat::D24_UNORM_S8_UINT:
1048 case VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM:
1049 return device.IsBlitDepth24Stencil8Supported();
1050 case VideoCore::Surface::PixelFormat::D32_FLOAT_S8_UINT:
1051 return device.IsBlitDepth32Stencil8Supported();
1052 default:
1053 UNREACHABLE();
1054 }
1055 }();
1056 if (!can_blit_depth_stencil) {
1039 UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa); 1057 UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa);
1040 blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(), 1058 blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(),
1041 dst_region, src_region, filter, operation); 1059 dst_region, src_region, filter, operation);
1042 return; 1060 return;
1043 } 1061 }
1044 } 1062 }
1045 ASSERT(src.format == dst.format);
1046 ASSERT(!(is_dst_msaa && !is_src_msaa)); 1063 ASSERT(!(is_dst_msaa && !is_src_msaa));
1047 ASSERT(operation == Fermi2D::Operation::SrcCopy); 1064 ASSERT(operation == Fermi2D::Operation::SrcCopy);
1048 1065
@@ -1639,7 +1656,8 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
1639 }; 1656 };
1640 if (!info.IsRenderTarget()) { 1657 if (!info.IsRenderTarget()) {
1641 swizzle = info.Swizzle(); 1658 swizzle = info.Swizzle();
1642 TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565()); 1659 TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565(),
1660 !device->IsExt4444FormatsSupported());
1643 if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { 1661 if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
1644 std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed); 1662 std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
1645 } 1663 }
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 617417040..18185610f 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,
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 488fdd313..8c5355a28 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -45,6 +45,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
45 FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \ 45 FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \
46 FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \ 46 FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \
47 FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \ 47 FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \
48 FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \
48 FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \ 49 FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \
49 FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \ 50 FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \
50 FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \ 51 FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \
@@ -97,6 +98,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
97 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) \ 98 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) \
98 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \ 99 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \
99 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \ 100 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \
101 EXTENSION_NAME(VK_EXT_4444_FORMATS_EXTENSION_NAME) \
100 EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \ 102 EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \
101 EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \ 103 EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \
102 EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \ 104 EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \
@@ -144,6 +146,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
144#define FOR_EACH_VK_RECOMMENDED_FEATURE(FEATURE_NAME) \ 146#define FOR_EACH_VK_RECOMMENDED_FEATURE(FEATURE_NAME) \
145 FEATURE_NAME(custom_border_color, customBorderColors) \ 147 FEATURE_NAME(custom_border_color, customBorderColors) \
146 FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \ 148 FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \
149 FEATURE_NAME(format_a4b4g4r4, formatA4B4G4R4) \
147 FEATURE_NAME(index_type_uint8, indexTypeUint8) \ 150 FEATURE_NAME(index_type_uint8, indexTypeUint8) \
148 FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \ 151 FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \
149 FEATURE_NAME(provoking_vertex, provokingVertexLast) \ 152 FEATURE_NAME(provoking_vertex, provokingVertexLast) \
@@ -359,9 +362,14 @@ public:
359 return features.features.depthBounds; 362 return features.features.depthBounds;
360 } 363 }
361 364
362 /// Returns true when blitting from and to depth stencil images is supported. 365 /// Returns true when blitting from and to D24S8 images is supported.
363 bool IsBlitDepthStencilSupported() const { 366 bool IsBlitDepth24Stencil8Supported() const {
364 return is_blit_depth_stencil_supported; 367 return is_blit_depth24_stencil8_supported;
368 }
369
370 /// Returns true when blitting from and to D32S8 images is supported.
371 bool IsBlitDepth32Stencil8Supported() const {
372 return is_blit_depth32_stencil8_supported;
365 } 373 }
366 374
367 /// Returns true if the device supports VK_NV_viewport_swizzle. 375 /// Returns true if the device supports VK_NV_viewport_swizzle.
@@ -488,6 +496,11 @@ public:
488 return extensions.extended_dynamic_state3; 496 return extensions.extended_dynamic_state3;
489 } 497 }
490 498
499 /// Returns true if the device supports VK_EXT_4444_formats.
500 bool IsExt4444FormatsSupported() const {
501 return features.format_a4b4g4r4.formatA4B4G4R4;
502 }
503
491 /// Returns true if the device supports VK_EXT_extended_dynamic_state3. 504 /// Returns true if the device supports VK_EXT_extended_dynamic_state3.
492 bool IsExtExtendedDynamicState3BlendingSupported() const { 505 bool IsExtExtendedDynamicState3BlendingSupported() const {
493 return dynamic_state3_blending; 506 return dynamic_state3_blending;
@@ -666,7 +679,7 @@ private:
666 bool ComputeIsOptimalAstcSupported() const; 679 bool ComputeIsOptimalAstcSupported() const;
667 680
668 /// Returns true if the device natively supports blitting depth stencil images. 681 /// Returns true if the device natively supports blitting depth stencil images.
669 bool TestDepthStencilBlits() const; 682 bool TestDepthStencilBlits(VkFormat format) const;
670 683
671private: 684private:
672 VkInstance instance; ///< Vulkan instance. 685 VkInstance instance; ///< Vulkan instance.
@@ -730,25 +743,26 @@ private:
730 VkPhysicalDeviceProperties2 properties2{}; 743 VkPhysicalDeviceProperties2 properties2{};
731 744
732 // Misc features 745 // Misc features
733 bool is_optimal_astc_supported{}; ///< Support for all guest ASTC formats. 746 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. 747 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. 748 bool is_blit_depth32_stencil8_supported{}; ///< Support for blitting from and to D32S8.
736 bool is_integrated{}; ///< Is GPU an iGPU. 749 bool is_warp_potentially_bigger{}; ///< Host warp size can be bigger than guest.
737 bool is_virtual{}; ///< Is GPU a virtual GPU. 750 bool is_integrated{}; ///< Is GPU an iGPU.
738 bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device. 751 bool is_virtual{}; ///< Is GPU a virtual GPU.
739 bool has_broken_compute{}; ///< Compute shaders can cause crashes 752 bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device.
740 bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit 753 bool has_broken_compute{}; ///< Compute shaders can cause crashes
741 bool has_renderdoc{}; ///< Has RenderDoc attached 754 bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit
742 bool has_nsight_graphics{}; ///< Has Nsight Graphics attached 755 bool has_renderdoc{}; ///< Has RenderDoc attached
743 bool supports_d24_depth{}; ///< Supports D24 depth buffers. 756 bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
744 bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting. 757 bool supports_d24_depth{}; ///< Supports D24 depth buffers.
745 bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation 758 bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting.
746 bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format. 759 bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation
747 bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3. 760 bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format.
748 bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3. 761 bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3.
749 bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow. 762 bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3.
750 u64 device_access_memory{}; ///< Total size of device local memory in bytes. 763 bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow.
751 u32 sets_per_pool{}; ///< Sets per Description Pool 764 u64 device_access_memory{}; ///< Total size of device local memory in bytes.
765 u32 sets_per_pool{}; ///< Sets per Description Pool
752 766
753 // Telemetry parameters 767 // Telemetry parameters
754 std::set<std::string, std::less<>> supported_extensions; ///< Reported Vulkan extensions. 768 std::set<std::string, std::less<>> supported_extensions; ///< Reported Vulkan extensions.