summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt22
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt15
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt23
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt9
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt145
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt109
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt35
-rw-r--r--src/android/app/src/main/res/layout-w600dp/fragment_setup.xml60
-rw-r--r--src/android/app/src/main/res/layout-w600dp/page_setup.xml69
-rw-r--r--src/android/app/src/main/res/layout/dialog_slider.xml13
-rw-r--r--src/android/app/src/main/res/layout/fragment_setup.xml62
-rw-r--r--src/android/app/src/main/res/layout/list_item_setting.xml62
-rw-r--r--src/android/app/src/main/res/layout/page_setup.xml30
-rw-r--r--src/android/app/src/main/res/values/strings.xml2
-rw-r--r--src/common/settings_common.cpp2
-rw-r--r--src/common/settings_enums.h16
-rw-r--r--src/common/settings_setting.h10
-rw-r--r--src/core/hid/hid_types.h26
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h16
-rw-r--r--src/core/hle/service/hid/hid.cpp68
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_producer.cpp1
-rw-r--r--src/core/hle/service/nvnflinger/window.h1
-rw-r--r--src/core/hle/service/ssl/ssl_backend_securetransport.cpp2
-rw-r--r--src/core/memory.h125
-rw-r--r--src/video_core/engines/maxwell_dma.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.cpp18
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.h2
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp179
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h9
-rw-r--r--src/yuzu/game_list.cpp8
38 files changed, 769 insertions, 434 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
index 7006651d0..bc6ff1364 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
@@ -49,6 +49,7 @@ class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List
49 val context = YuzuApplication.appContext 49 val context = YuzuApplication.appContext
50 binding.textSettingName.text = context.getString(license.titleId) 50 binding.textSettingName.text = context.getString(license.titleId)
51 binding.textSettingDescription.text = context.getString(license.descriptionId) 51 binding.textSettingDescription.text = context.getString(license.descriptionId)
52 binding.textSettingValue.visibility = View.GONE
52 } 53 }
53 } 54 }
54} 55}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
index 481ddd5a5..6b46d359e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
@@ -5,13 +5,19 @@ package org.yuzu.yuzu_emu.adapters
5 5
6import android.text.Html 6import android.text.Html
7import android.view.LayoutInflater 7import android.view.LayoutInflater
8import android.view.View
8import android.view.ViewGroup 9import android.view.ViewGroup
9import androidx.appcompat.app.AppCompatActivity 10import androidx.appcompat.app.AppCompatActivity
10import androidx.core.content.res.ResourcesCompat 11import androidx.core.content.res.ResourcesCompat
12import androidx.lifecycle.ViewModelProvider
11import androidx.recyclerview.widget.RecyclerView 13import androidx.recyclerview.widget.RecyclerView
12import com.google.android.material.button.MaterialButton 14import com.google.android.material.button.MaterialButton
13import org.yuzu.yuzu_emu.databinding.PageSetupBinding 15import org.yuzu.yuzu_emu.databinding.PageSetupBinding
16import org.yuzu.yuzu_emu.model.HomeViewModel
17import org.yuzu.yuzu_emu.model.SetupCallback
14import org.yuzu.yuzu_emu.model.SetupPage 18import org.yuzu.yuzu_emu.model.SetupPage
19import org.yuzu.yuzu_emu.model.StepState
20import org.yuzu.yuzu_emu.utils.ViewUtils
15 21
16class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : 22class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
17 RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() { 23 RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
@@ -26,7 +32,7 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
26 holder.bind(pages[position]) 32 holder.bind(pages[position])
27 33
28 inner class SetupPageViewHolder(val binding: PageSetupBinding) : 34 inner class SetupPageViewHolder(val binding: PageSetupBinding) :
29 RecyclerView.ViewHolder(binding.root) { 35 RecyclerView.ViewHolder(binding.root), SetupCallback {
30 lateinit var page: SetupPage 36 lateinit var page: SetupPage
31 37
32 init { 38 init {
@@ -35,6 +41,12 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
35 41
36 fun bind(page: SetupPage) { 42 fun bind(page: SetupPage) {
37 this.page = page 43 this.page = page
44
45 if (page.stepCompleted.invoke() == StepState.COMPLETE) {
46 binding.buttonAction.visibility = View.INVISIBLE
47 binding.textConfirmation.visibility = View.VISIBLE
48 }
49
38 binding.icon.setImageDrawable( 50 binding.icon.setImageDrawable(
39 ResourcesCompat.getDrawable( 51 ResourcesCompat.getDrawable(
40 activity.resources, 52 activity.resources,
@@ -62,9 +74,15 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
62 MaterialButton.ICON_GRAVITY_END 74 MaterialButton.ICON_GRAVITY_END
63 } 75 }
64 setOnClickListener { 76 setOnClickListener {
65 page.buttonAction.invoke() 77 page.buttonAction.invoke(this@SetupPageViewHolder)
66 } 78 }
67 } 79 }
68 } 80 }
81
82 override fun onStepCompleted() {
83 ViewUtils.hideView(binding.buttonAction, 200)
84 ViewUtils.showView(binding.textConfirmation, 200)
85 ViewModelProvider(activity)[HomeViewModel::class.java].setShouldPageForward(true)
86 }
69 } 87 }
70} 88}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index ce0b92c90..9711e2c51 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -207,8 +207,11 @@ class SettingsAdapter(
207 val sliderBinding = DialogSliderBinding.inflate(inflater) 207 val sliderBinding = DialogSliderBinding.inflate(inflater)
208 208
209 textSliderValue = sliderBinding.textValue 209 textSliderValue = sliderBinding.textValue
210 textSliderValue!!.text = sliderProgress.toString() 210 textSliderValue!!.text = String.format(
211 sliderBinding.textUnits.text = item.units 211 context.getString(R.string.value_with_units),
212 sliderProgress.toString(),
213 item.units
214 )
212 215
213 sliderBinding.slider.apply { 216 sliderBinding.slider.apply {
214 valueFrom = item.min.toFloat() 217 valueFrom = item.min.toFloat()
@@ -216,7 +219,11 @@ class SettingsAdapter(
216 value = sliderProgress.toFloat() 219 value = sliderProgress.toFloat()
217 addOnChangeListener { _: Slider, value: Float, _: Boolean -> 220 addOnChangeListener { _: Slider, value: Float, _: Boolean ->
218 sliderProgress = value.toInt() 221 sliderProgress = value.toInt()
219 textSliderValue!!.text = sliderProgress.toString() 222 textSliderValue!!.text = String.format(
223 context.getString(R.string.value_with_units),
224 sliderProgress.toString(),
225 item.units
226 )
220 } 227 }
221 } 228 }
222 229
@@ -225,10 +232,6 @@ class SettingsAdapter(
225 .setView(sliderBinding.root) 232 .setView(sliderBinding.root)
226 .setPositiveButton(android.R.string.ok, this) 233 .setPositiveButton(android.R.string.ok, this)
227 .setNegativeButton(android.R.string.cancel, defaultCancelListener) 234 .setNegativeButton(android.R.string.cancel, defaultCancelListener)
228 .setNeutralButton(R.string.slider_default) { dialog: DialogInterface, which: Int ->
229 sliderBinding.slider.value = item.defaultValue!!.toFloat()
230 onClick(dialog, which)
231 }
232 .show() 235 .show()
233 } 236 }
234 237
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
index 7955532ee..79572fc06 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
@@ -25,12 +25,17 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
25 binding.textSettingDescription.setText(item.descriptionId) 25 binding.textSettingDescription.setText(item.descriptionId)
26 binding.textSettingDescription.visibility = View.VISIBLE 26 binding.textSettingDescription.visibility = View.VISIBLE
27 } else { 27 } else {
28 val epochTime = setting.value.toLong() 28 binding.textSettingDescription.visibility = View.GONE
29 val instant = Instant.ofEpochMilli(epochTime * 1000)
30 val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
31 val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
32 binding.textSettingDescription.text = dateFormatter.format(zonedTime)
33 } 29 }
30
31 binding.textSettingValue.visibility = View.VISIBLE
32 val epochTime = setting.value.toLong()
33 val instant = Instant.ofEpochMilli(epochTime * 1000)
34 val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
35 val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
36 binding.textSettingValue.text = dateFormatter.format(zonedTime)
37
38 setStyle(setting.isEditable, binding)
34 } 39 }
35 40
36 override fun onClick(clicked: View) { 41 override fun onClick(clicked: View) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
index 5dad5945f..83a2e94f1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
@@ -23,6 +23,9 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
23 } else { 23 } else {
24 binding.textSettingDescription.visibility = View.GONE 24 binding.textSettingDescription.visibility = View.GONE
25 } 25 }
26 binding.textSettingValue.visibility = View.GONE
27
28 setStyle(setting.isEditable, binding)
26 } 29 }
27 30
28 override fun onClick(clicked: View) { 31 override fun onClick(clicked: View) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt
index f56460893..0fd1d2eaa 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt
@@ -5,6 +5,8 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder
5 5
6import android.view.View 6import android.view.View
7import androidx.recyclerview.widget.RecyclerView 7import androidx.recyclerview.widget.RecyclerView
8import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
9import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
8import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 10import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
9import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter 11import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
10 12
@@ -33,4 +35,18 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
33 abstract override fun onClick(clicked: View) 35 abstract override fun onClick(clicked: View)
34 36
35 abstract override fun onLongClick(clicked: View): Boolean 37 abstract override fun onLongClick(clicked: View): Boolean
38
39 fun setStyle(isEditable: Boolean, binding: ListItemSettingBinding) {
40 val opacity = if (isEditable) 1.0f else 0.5f
41 binding.textSettingName.alpha = opacity
42 binding.textSettingDescription.alpha = opacity
43 binding.textSettingValue.alpha = opacity
44 }
45
46 fun setStyle(isEditable: Boolean, binding: ListItemSettingSwitchBinding) {
47 binding.switchWidget.isEnabled = isEditable
48 val opacity = if (isEditable) 1.0f else 0.5f
49 binding.textSettingName.alpha = opacity
50 binding.textSettingDescription.alpha = opacity
51 }
36} 52}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
index e4e321bd3..b42d955aa 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
@@ -17,28 +17,33 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
17 override fun bind(item: SettingsItem) { 17 override fun bind(item: SettingsItem) {
18 setting = item 18 setting = item
19 binding.textSettingName.setText(item.nameId) 19 binding.textSettingName.setText(item.nameId)
20 binding.textSettingDescription.visibility = View.VISIBLE
21 if (item.descriptionId != 0) { 20 if (item.descriptionId != 0) {
22 binding.textSettingDescription.setText(item.descriptionId) 21 binding.textSettingDescription.setText(item.descriptionId)
23 } else if (item is SingleChoiceSetting) { 22 binding.textSettingDescription.visibility = View.VISIBLE
24 val resMgr = binding.textSettingDescription.context.resources 23 } else {
24 binding.textSettingDescription.visibility = View.GONE
25 }
26
27 binding.textSettingValue.visibility = View.VISIBLE
28 if (item is SingleChoiceSetting) {
29 val resMgr = binding.textSettingValue.context.resources
25 val values = resMgr.getIntArray(item.valuesId) 30 val values = resMgr.getIntArray(item.valuesId)
26 for (i in values.indices) { 31 for (i in values.indices) {
27 if (values[i] == item.selectedValue) { 32 if (values[i] == item.selectedValue) {
28 binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i] 33 binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i]
29 return 34 break
30 } 35 }
31 } 36 }
32 } else if (item is StringSingleChoiceSetting) { 37 } else if (item is StringSingleChoiceSetting) {
33 for (i in item.values!!.indices) { 38 for (i in item.values!!.indices) {
34 if (item.values[i] == item.selectedValue) { 39 if (item.values[i] == item.selectedValue) {
35 binding.textSettingDescription.text = item.choices[i] 40 binding.textSettingValue.text = item.choices[i]
36 return 41 break
37 } 42 }
38 } 43 }
39 } else {
40 binding.textSettingDescription.visibility = View.GONE
41 } 44 }
45
46 setStyle(setting.isEditable, binding)
42 } 47 }
43 48
44 override fun onClick(clicked: View) { 49 override fun onClick(clicked: View) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
index cc3f39aa5..a23b5d109 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
@@ -4,6 +4,7 @@
4package org.yuzu.yuzu_emu.features.settings.ui.viewholder 4package org.yuzu.yuzu_emu.features.settings.ui.viewholder
5 5
6import android.view.View 6import android.view.View
7import org.yuzu.yuzu_emu.R
7import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding 8import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
8import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 9import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
9import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting 10import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
@@ -22,6 +23,14 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
22 } else { 23 } else {
23 binding.textSettingDescription.visibility = View.GONE 24 binding.textSettingDescription.visibility = View.GONE
24 } 25 }
26 binding.textSettingValue.visibility = View.VISIBLE
27 binding.textSettingValue.text = String.format(
28 binding.textSettingValue.context.getString(R.string.value_with_units),
29 setting.selectedValue,
30 setting.units
31 )
32
33 setStyle(setting.isEditable, binding)
25 } 34 }
26 35
27 override fun onClick(clicked: View) { 36 override fun onClick(clicked: View) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
index c545b4174..1cf581a9d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
@@ -22,6 +22,7 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
22 } else { 22 } else {
23 binding.textSettingDescription.visibility = View.GONE 23 binding.textSettingDescription.visibility = View.GONE
24 } 24 }
25 binding.textSettingValue.visibility = View.GONE
25 } 26 }
26 27
27 override fun onClick(clicked: View) { 28 override fun onClick(clicked: View) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
index 54f531795..ef34bf5f4 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
@@ -25,12 +25,12 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
25 binding.textSettingDescription.text = "" 25 binding.textSettingDescription.text = ""
26 binding.textSettingDescription.visibility = View.GONE 26 binding.textSettingDescription.visibility = View.GONE
27 } 27 }
28 binding.switchWidget.isChecked = setting.isChecked
29 binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> 28 binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
30 adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked) 29 adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked)
31 } 30 }
31 binding.switchWidget.isChecked = setting.isChecked
32 32
33 binding.switchWidget.isEnabled = setting.isEditable 33 setStyle(setting.isEditable, binding)
34 } 34 }
35 35
36 override fun onClick(clicked: View) { 36 override fun onClick(clicked: View) {
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 6c4ddaf6b..d50c421a0 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
@@ -19,6 +19,7 @@ import androidx.core.content.ContextCompat
19import androidx.core.view.ViewCompat 19import androidx.core.view.ViewCompat
20import androidx.core.view.WindowInsetsCompat 20import androidx.core.view.WindowInsetsCompat
21import androidx.core.view.isVisible 21import androidx.core.view.isVisible
22import androidx.core.view.updatePadding
22import androidx.fragment.app.Fragment 23import androidx.fragment.app.Fragment
23import androidx.fragment.app.activityViewModels 24import androidx.fragment.app.activityViewModels
24import androidx.navigation.findNavController 25import androidx.navigation.findNavController
@@ -32,10 +33,13 @@ import org.yuzu.yuzu_emu.adapters.SetupAdapter
32import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding 33import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding
33import org.yuzu.yuzu_emu.features.settings.model.Settings 34import org.yuzu.yuzu_emu.features.settings.model.Settings
34import org.yuzu.yuzu_emu.model.HomeViewModel 35import org.yuzu.yuzu_emu.model.HomeViewModel
36import org.yuzu.yuzu_emu.model.SetupCallback
35import org.yuzu.yuzu_emu.model.SetupPage 37import org.yuzu.yuzu_emu.model.SetupPage
38import org.yuzu.yuzu_emu.model.StepState
36import org.yuzu.yuzu_emu.ui.main.MainActivity 39import org.yuzu.yuzu_emu.ui.main.MainActivity
37import org.yuzu.yuzu_emu.utils.DirectoryInitialization 40import org.yuzu.yuzu_emu.utils.DirectoryInitialization
38import org.yuzu.yuzu_emu.utils.GameHelper 41import org.yuzu.yuzu_emu.utils.GameHelper
42import org.yuzu.yuzu_emu.utils.ViewUtils
39 43
40class SetupFragment : Fragment() { 44class SetupFragment : Fragment() {
41 private var _binding: FragmentSetupBinding? = null 45 private var _binding: FragmentSetupBinding? = null
@@ -112,14 +116,22 @@ class SetupFragment : Fragment() {
112 0, 116 0,
113 false, 117 false,
114 R.string.give_permission, 118 R.string.give_permission,
115 { permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) }, 119 {
120 notificationCallback = it
121 permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
122 },
116 true, 123 true,
117 R.string.notification_warning, 124 R.string.notification_warning,
118 R.string.notification_warning_description, 125 R.string.notification_warning_description,
119 0, 126 0,
120 { 127 {
121 NotificationManagerCompat.from(requireContext()) 128 if (NotificationManagerCompat.from(requireContext())
122 .areNotificationsEnabled() 129 .areNotificationsEnabled()
130 ) {
131 StepState.COMPLETE
132 } else {
133 StepState.INCOMPLETE
134 }
123 } 135 }
124 ) 136 )
125 ) 137 )
@@ -133,12 +145,22 @@ class SetupFragment : Fragment() {
133 R.drawable.ic_add, 145 R.drawable.ic_add,
134 true, 146 true,
135 R.string.select_keys, 147 R.string.select_keys,
136 { mainActivity.getProdKey.launch(arrayOf("*/*")) }, 148 {
149 keyCallback = it
150 getProdKey.launch(arrayOf("*/*"))
151 },
137 true, 152 true,
138 R.string.install_prod_keys_warning, 153 R.string.install_prod_keys_warning,
139 R.string.install_prod_keys_warning_description, 154 R.string.install_prod_keys_warning_description,
140 R.string.install_prod_keys_warning_help, 155 R.string.install_prod_keys_warning_help,
141 { File(DirectoryInitialization.userDirectory + "/keys/prod.keys").exists() } 156 {
157 val file = File(DirectoryInitialization.userDirectory + "/keys/prod.keys")
158 if (file.exists()) {
159 StepState.COMPLETE
160 } else {
161 StepState.INCOMPLETE
162 }
163 }
142 ) 164 )
143 ) 165 )
144 add( 166 add(
@@ -150,9 +172,8 @@ class SetupFragment : Fragment() {
150 true, 172 true,
151 R.string.add_games, 173 R.string.add_games,
152 { 174 {
153 mainActivity.getGamesDirectory.launch( 175 gamesDirCallback = it
154 Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data 176 getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
155 )
156 }, 177 },
157 true, 178 true,
158 R.string.add_games_warning, 179 R.string.add_games_warning,
@@ -163,7 +184,11 @@ class SetupFragment : Fragment() {
163 PreferenceManager.getDefaultSharedPreferences( 184 PreferenceManager.getDefaultSharedPreferences(
164 YuzuApplication.appContext 185 YuzuApplication.appContext
165 ) 186 )
166 preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() 187 if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) {
188 StepState.COMPLETE
189 } else {
190 StepState.INCOMPLETE
191 }
167 } 192 }
168 ) 193 )
169 ) 194 )
@@ -181,6 +206,13 @@ class SetupFragment : Fragment() {
181 ) 206 )
182 } 207 }
183 208
209 homeViewModel.shouldPageForward.observe(viewLifecycleOwner) {
210 if (it) {
211 pageForward()
212 homeViewModel.setShouldPageForward(false)
213 }
214 }
215
184 binding.viewPager2.apply { 216 binding.viewPager2.apply {
185 adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) 217 adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
186 offscreenPageLimit = 2 218 offscreenPageLimit = 2
@@ -194,15 +226,15 @@ class SetupFragment : Fragment() {
194 super.onPageSelected(position) 226 super.onPageSelected(position)
195 227
196 if (position == 1 && previousPosition == 0) { 228 if (position == 1 && previousPosition == 0) {
197 showView(binding.buttonNext) 229 ViewUtils.showView(binding.buttonNext)
198 showView(binding.buttonBack) 230 ViewUtils.showView(binding.buttonBack)
199 } else if (position == 0 && previousPosition == 1) { 231 } else if (position == 0 && previousPosition == 1) {
200 hideView(binding.buttonBack) 232 ViewUtils.hideView(binding.buttonBack)
201 hideView(binding.buttonNext) 233 ViewUtils.hideView(binding.buttonNext)
202 } else if (position == pages.size - 1 && previousPosition == pages.size - 2) { 234 } else if (position == pages.size - 1 && previousPosition == pages.size - 2) {
203 hideView(binding.buttonNext) 235 ViewUtils.hideView(binding.buttonNext)
204 } else if (position == pages.size - 2 && previousPosition == pages.size - 1) { 236 } else if (position == pages.size - 2 && previousPosition == pages.size - 1) {
205 showView(binding.buttonNext) 237 ViewUtils.showView(binding.buttonNext)
206 } 238 }
207 239
208 previousPosition = position 240 previousPosition = position
@@ -215,7 +247,8 @@ class SetupFragment : Fragment() {
215 247
216 // Checks if the user has completed the task on the current page 248 // Checks if the user has completed the task on the current page
217 if (currentPage.hasWarning) { 249 if (currentPage.hasWarning) {
218 if (currentPage.taskCompleted.invoke()) { 250 val stepState = currentPage.stepCompleted.invoke()
251 if (stepState != StepState.INCOMPLETE) {
219 pageForward() 252 pageForward()
220 return@setOnClickListener 253 return@setOnClickListener
221 } 254 }
@@ -264,9 +297,15 @@ class SetupFragment : Fragment() {
264 _binding = null 297 _binding = null
265 } 298 }
266 299
300 private lateinit var notificationCallback: SetupCallback
301
267 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 302 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
268 private val permissionLauncher = 303 private val permissionLauncher =
269 registerForActivityResult(ActivityResultContracts.RequestPermission()) { 304 registerForActivityResult(ActivityResultContracts.RequestPermission()) {
305 if (it) {
306 notificationCallback.onStepCompleted()
307 }
308
270 if (!it && 309 if (!it &&
271 !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) 310 !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
272 ) { 311 ) {
@@ -277,6 +316,27 @@ class SetupFragment : Fragment() {
277 } 316 }
278 } 317 }
279 318
319 private lateinit var keyCallback: SetupCallback
320
321 val getProdKey =
322 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
323 if (result != null) {
324 if (mainActivity.processKey(result)) {
325 keyCallback.onStepCompleted()
326 }
327 }
328 }
329
330 private lateinit var gamesDirCallback: SetupCallback
331
332 val getGamesDirectory =
333 registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
334 if (result != null) {
335 mainActivity.processGamesDir(result)
336 gamesDirCallback.onStepCompleted()
337 }
338 }
339
280 private fun finishSetup() { 340 private fun finishSetup() {
281 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() 341 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
282 .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false) 342 .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false)
@@ -284,33 +344,6 @@ class SetupFragment : Fragment() {
284 mainActivity.finishSetup(binding.root.findNavController()) 344 mainActivity.finishSetup(binding.root.findNavController())
285 } 345 }
286 346
287 private fun showView(view: View) {
288 view.apply {
289 alpha = 0f
290 visibility = View.VISIBLE
291 isClickable = true
292 }.animate().apply {
293 duration = 300
294 alpha(1f)
295 }.start()
296 }
297
298 private fun hideView(view: View) {
299 if (view.visibility == View.INVISIBLE) {
300 return
301 }
302
303 view.apply {
304 alpha = 1f
305 isClickable = false
306 }.animate().apply {
307 duration = 300
308 alpha(0f)
309 }.withEndAction {
310 view.visibility = View.INVISIBLE
311 }
312 }
313
314 fun pageForward() { 347 fun pageForward() {
315 binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 348 binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1
316 } 349 }
@@ -326,15 +359,29 @@ class SetupFragment : Fragment() {
326 private fun setInsets() = 359 private fun setInsets() =
327 ViewCompat.setOnApplyWindowInsetsListener( 360 ViewCompat.setOnApplyWindowInsetsListener(
328 binding.root 361 binding.root
329 ) { view: View, windowInsets: WindowInsetsCompat -> 362 ) { _: View, windowInsets: WindowInsetsCompat ->
330 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 363 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
331 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) 364 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
332 view.setPadding( 365
333 barInsets.left + cutoutInsets.left, 366 val leftPadding = barInsets.left + cutoutInsets.left
334 barInsets.top + cutoutInsets.top, 367 val topPadding = barInsets.top + cutoutInsets.top
335 barInsets.right + cutoutInsets.right, 368 val rightPadding = barInsets.right + cutoutInsets.right
336 barInsets.bottom + cutoutInsets.bottom 369 val bottomPadding = barInsets.bottom + cutoutInsets.bottom
337 ) 370
371 if (resources.getBoolean(R.bool.small_layout)) {
372 binding.viewPager2
373 .updatePadding(left = leftPadding, top = topPadding, right = rightPadding)
374 binding.constraintButtons
375 .updatePadding(left = leftPadding, right = rightPadding, bottom = bottomPadding)
376 } else {
377 binding.viewPager2.updatePadding(top = topPadding, bottom = bottomPadding)
378 binding.constraintButtons
379 .updatePadding(
380 left = leftPadding,
381 right = rightPadding,
382 bottom = bottomPadding
383 )
384 }
338 windowInsets 385 windowInsets
339 } 386 }
340} 387}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
index 263ee7144..e13d84c9c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -14,6 +14,9 @@ class HomeViewModel : ViewModel() {
14 private val _statusBarShadeVisible = MutableLiveData(true) 14 private val _statusBarShadeVisible = MutableLiveData(true)
15 val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible 15 val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible
16 16
17 private val _shouldPageForward = MutableLiveData(false)
18 val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward
19
17 var navigatedToSetup = false 20 var navigatedToSetup = false
18 21
19 init { 22 init {
@@ -33,4 +36,8 @@ class HomeViewModel : ViewModel() {
33 } 36 }
34 _statusBarShadeVisible.value = visible 37 _statusBarShadeVisible.value = visible
35 } 38 }
39
40 fun setShouldPageForward(pageForward: Boolean) {
41 _shouldPageForward.value = pageForward
42 }
36} 43}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt
index a0c878e1c..09a128ae6 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt
@@ -10,10 +10,20 @@ data class SetupPage(
10 val buttonIconId: Int, 10 val buttonIconId: Int,
11 val leftAlignedIcon: Boolean, 11 val leftAlignedIcon: Boolean,
12 val buttonTextId: Int, 12 val buttonTextId: Int,
13 val buttonAction: () -> Unit, 13 val buttonAction: (callback: SetupCallback) -> Unit,
14 val hasWarning: Boolean, 14 val hasWarning: Boolean,
15 val warningTitleId: Int = 0, 15 val warningTitleId: Int = 0,
16 val warningDescriptionId: Int = 0, 16 val warningDescriptionId: Int = 0,
17 val warningHelpLinkId: Int = 0, 17 val warningHelpLinkId: Int = 0,
18 val taskCompleted: () -> Boolean = { true } 18 val stepCompleted: () -> StepState = { StepState.UNDEFINED }
19) 19)
20
21interface SetupCallback {
22 fun onStepCompleted()
23}
24
25enum class StepState {
26 COMPLETE,
27 INCOMPLETE,
28 UNDEFINED
29}
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 f7d7aed1e..f77d06262 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
@@ -266,73 +266,80 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
266 266
267 val getGamesDirectory = 267 val getGamesDirectory =
268 registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> 268 registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
269 if (result == null) { 269 if (result != null) {
270 return@registerForActivityResult 270 processGamesDir(result)
271 } 271 }
272 }
272 273
273 contentResolver.takePersistableUriPermission( 274 fun processGamesDir(result: Uri) {
274 result, 275 contentResolver.takePersistableUriPermission(
275 Intent.FLAG_GRANT_READ_URI_PERMISSION 276 result,
276 ) 277 Intent.FLAG_GRANT_READ_URI_PERMISSION
278 )
277 279
278 // When a new directory is picked, we currently will reset the existing games 280 // When a new directory is picked, we currently will reset the existing games
279 // database. This effectively means that only one game directory is supported. 281 // database. This effectively means that only one game directory is supported.
280 PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() 282 PreferenceManager.getDefaultSharedPreferences(applicationContext).edit()
281 .putString(GameHelper.KEY_GAME_PATH, result.toString()) 283 .putString(GameHelper.KEY_GAME_PATH, result.toString())
282 .apply() 284 .apply()
283 285
284 Toast.makeText( 286 Toast.makeText(
285 applicationContext, 287 applicationContext,
286 R.string.games_dir_selected, 288 R.string.games_dir_selected,
287 Toast.LENGTH_LONG 289 Toast.LENGTH_LONG
288 ).show() 290 ).show()
289 291
290 gamesViewModel.reloadGames(true) 292 gamesViewModel.reloadGames(true)
291 } 293 }
292 294
293 val getProdKey = 295 val getProdKey =
294 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> 296 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
295 if (result == null) { 297 if (result != null) {
296 return@registerForActivityResult 298 processKey(result)
297 } 299 }
300 }
298 301
299 if (FileUtil.getExtension(result) != "keys") { 302 fun processKey(result: Uri): Boolean {
300 MessageDialogFragment.newInstance( 303 if (FileUtil.getExtension(result) != "keys") {
301 R.string.reading_keys_failure, 304 MessageDialogFragment.newInstance(
302 R.string.install_prod_keys_failure_extension_description 305 R.string.reading_keys_failure,
303 ).show(supportFragmentManager, MessageDialogFragment.TAG) 306 R.string.install_prod_keys_failure_extension_description
304 return@registerForActivityResult 307 ).show(supportFragmentManager, MessageDialogFragment.TAG)
305 } 308 return false
309 }
306 310
307 contentResolver.takePersistableUriPermission( 311 contentResolver.takePersistableUriPermission(
312 result,
313 Intent.FLAG_GRANT_READ_URI_PERMISSION
314 )
315
316 val dstPath = DirectoryInitialization.userDirectory + "/keys/"
317 if (FileUtil.copyUriToInternalStorage(
318 applicationContext,
308 result, 319 result,
309 Intent.FLAG_GRANT_READ_URI_PERMISSION 320 dstPath,
321 "prod.keys"
310 ) 322 )
311 323 ) {
312 val dstPath = DirectoryInitialization.userDirectory + "/keys/" 324 if (NativeLibrary.reloadKeys()) {
313 if (FileUtil.copyUriToInternalStorage( 325 Toast.makeText(
314 applicationContext, 326 applicationContext,
315 result, 327 R.string.install_keys_success,
316 dstPath, 328 Toast.LENGTH_SHORT
317 "prod.keys" 329 ).show()
318 ) 330 gamesViewModel.reloadGames(true)
319 ) { 331 return true
320 if (NativeLibrary.reloadKeys()) { 332 } else {
321 Toast.makeText( 333 MessageDialogFragment.newInstance(
322 applicationContext, 334 R.string.invalid_keys_error,
323 R.string.install_keys_success, 335 R.string.install_keys_failure_description,
324 Toast.LENGTH_SHORT 336 R.string.dumping_keys_quickstart_link
325 ).show() 337 ).show(supportFragmentManager, MessageDialogFragment.TAG)
326 gamesViewModel.reloadGames(true) 338 return false
327 } else {
328 MessageDialogFragment.newInstance(
329 R.string.invalid_keys_error,
330 R.string.install_keys_failure_description,
331 R.string.dumping_keys_quickstart_link
332 ).show(supportFragmentManager, MessageDialogFragment.TAG)
333 }
334 } 339 }
335 } 340 }
341 return false
342 }
336 343
337 val getFirmware = 344 val getFirmware =
338 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> 345 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt
new file mode 100644
index 000000000..f9a3e4126
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt
@@ -0,0 +1,35 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.view.View
7
8object ViewUtils {
9 fun showView(view: View, length: Long = 300) {
10 view.apply {
11 alpha = 0f
12 visibility = View.VISIBLE
13 isClickable = true
14 }.animate().apply {
15 duration = length
16 alpha(1f)
17 }.start()
18 }
19
20 fun hideView(view: View, length: Long = 300) {
21 if (view.visibility == View.INVISIBLE) {
22 return
23 }
24
25 view.apply {
26 alpha = 1f
27 isClickable = false
28 }.animate().apply {
29 duration = length
30 alpha(0f)
31 }.withEndAction {
32 view.visibility = View.INVISIBLE
33 }.start()
34 }
35}
diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml
index cbe631d88..406df9eab 100644
--- a/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml
+++ b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml
@@ -1,5 +1,5 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout 2<RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 android:id="@+id/setup_root" 5 android:id="@+id/setup_root"
@@ -8,33 +8,39 @@
8 8
9 <androidx.viewpager2.widget.ViewPager2 9 <androidx.viewpager2.widget.ViewPager2
10 android:id="@+id/viewPager2" 10 android:id="@+id/viewPager2"
11 android:layout_width="0dp" 11 android:layout_width="match_parent"
12 android:layout_height="0dp" 12 android:layout_height="match_parent"
13 app:layout_constraintBottom_toBottomOf="parent" 13 android:layout_alignParentTop="true"
14 app:layout_constraintEnd_toEndOf="parent" 14 android:layout_alignParentBottom="true"
15 app:layout_constraintStart_toStartOf="parent" 15 android:clipToPadding="false" />
16 app:layout_constraintTop_toTopOf="parent" />
17 16
18 <com.google.android.material.button.MaterialButton 17 <androidx.constraintlayout.widget.ConstraintLayout
19 style="@style/Widget.Material3.Button.TextButton" 18 android:id="@+id/constraint_buttons"
20 android:id="@+id/button_next" 19 android:layout_width="match_parent"
21 android:layout_width="wrap_content"
22 android:layout_height="wrap_content" 20 android:layout_height="wrap_content"
23 android:layout_margin="16dp" 21 android:layout_alignParentBottom="true"
24 android:text="@string/next" 22 android:layout_margin="8dp">
25 android:visibility="invisible"
26 app:layout_constraintBottom_toBottomOf="parent"
27 app:layout_constraintEnd_toEndOf="parent" />
28 23
29 <com.google.android.material.button.MaterialButton 24 <com.google.android.material.button.MaterialButton
30 android:id="@+id/button_back" 25 android:id="@+id/button_next"
31 style="@style/Widget.Material3.Button.TextButton" 26 style="@style/Widget.Material3.Button.TextButton"
32 android:layout_width="wrap_content" 27 android:layout_width="wrap_content"
33 android:layout_height="wrap_content" 28 android:layout_height="wrap_content"
34 android:layout_margin="16dp" 29 android:text="@string/next"
35 android:text="@string/back" 30 android:visibility="invisible"
36 android:visibility="invisible" 31 app:layout_constraintBottom_toBottomOf="parent"
37 app:layout_constraintBottom_toBottomOf="parent" 32 app:layout_constraintEnd_toEndOf="parent" />
38 app:layout_constraintStart_toStartOf="parent" /> 33
34 <com.google.android.material.button.MaterialButton
35 android:id="@+id/button_back"
36 style="@style/Widget.Material3.Button.TextButton"
37 android:layout_width="wrap_content"
38 android:layout_height="wrap_content"
39 android:text="@string/back"
40 android:visibility="invisible"
41 app:layout_constraintBottom_toBottomOf="parent"
42 app:layout_constraintStart_toStartOf="parent" />
43
44 </androidx.constraintlayout.widget.ConstraintLayout>
39 45
40</androidx.constraintlayout.widget.ConstraintLayout> 46</RelativeLayout>
diff --git a/src/android/app/src/main/res/layout-w600dp/page_setup.xml b/src/android/app/src/main/res/layout-w600dp/page_setup.xml
index e1c26b2f8..9e0ab8ecb 100644
--- a/src/android/app/src/main/res/layout-w600dp/page_setup.xml
+++ b/src/android/app/src/main/res/layout-w600dp/page_setup.xml
@@ -21,45 +21,76 @@
21 21
22 </LinearLayout> 22 </LinearLayout>
23 23
24 <LinearLayout 24 <androidx.constraintlayout.widget.ConstraintLayout
25 android:layout_width="match_parent" 25 android:layout_width="match_parent"
26 android:layout_height="match_parent" 26 android:layout_height="match_parent"
27 android:layout_weight="1" 27 android:layout_weight="1">
28 android:orientation="vertical"
29 android:gravity="center">
30 28
31 <com.google.android.material.textview.MaterialTextView 29 <com.google.android.material.textview.MaterialTextView
32 style="@style/TextAppearance.Material3.DisplaySmall"
33 android:id="@+id/text_title" 30 android:id="@+id/text_title"
34 android:layout_width="match_parent" 31 style="@style/TextAppearance.Material3.DisplaySmall"
35 android:layout_height="wrap_content" 32 android:layout_width="0dp"
36 android:textAlignment="center" 33 android:layout_height="0dp"
34 android:gravity="center"
37 android:textColor="?attr/colorOnSurface" 35 android:textColor="?attr/colorOnSurface"
38 android:textStyle="bold" 36 android:textStyle="bold"
37 app:layout_constraintBottom_toTopOf="@+id/text_description"
38 app:layout_constraintEnd_toEndOf="parent"
39 app:layout_constraintStart_toStartOf="parent"
40 app:layout_constraintTop_toTopOf="parent"
41 app:layout_constraintVertical_weight="2"
39 tools:text="@string/welcome" /> 42 tools:text="@string/welcome" />
40 43
41 <com.google.android.material.textview.MaterialTextView 44 <com.google.android.material.textview.MaterialTextView
42 style="@style/TextAppearance.Material3.TitleLarge"
43 android:id="@+id/text_description" 45 android:id="@+id/text_description"
44 android:layout_width="match_parent" 46 style="@style/TextAppearance.Material3.TitleLarge"
45 android:layout_height="wrap_content" 47 android:layout_width="0dp"
46 android:layout_marginTop="16dp" 48 android:layout_height="0dp"
47 android:paddingHorizontal="32dp" 49 android:gravity="center"
48 android:textAlignment="center" 50 android:textSize="20sp"
49 android:textSize="26sp" 51 android:paddingHorizontal="16dp"
50 app:lineHeight="40sp" 52 app:layout_constraintBottom_toTopOf="@+id/button_action"
53 app:layout_constraintEnd_toEndOf="parent"
54 app:layout_constraintStart_toStartOf="parent"
55 app:layout_constraintTop_toBottomOf="@+id/text_title"
56 app:layout_constraintVertical_weight="2"
57 app:lineHeight="30sp"
51 tools:text="@string/welcome_description" /> 58 tools:text="@string/welcome_description" />
52 59
60 <com.google.android.material.textview.MaterialTextView
61 android:id="@+id/text_confirmation"
62 style="@style/TextAppearance.Material3.TitleLarge"
63 android:layout_width="0dp"
64 android:layout_height="0dp"
65 android:paddingHorizontal="16dp"
66 android:paddingBottom="20dp"
67 android:gravity="center"
68 android:textSize="30sp"
69 android:visibility="invisible"
70 android:text="@string/step_complete"
71 android:textStyle="bold"
72 app:layout_constraintBottom_toBottomOf="parent"
73 app:layout_constraintEnd_toEndOf="parent"
74 app:layout_constraintStart_toStartOf="parent"
75 app:layout_constraintTop_toBottomOf="@+id/text_description"
76 app:layout_constraintVertical_weight="1"
77 app:lineHeight="30sp" />
78
53 <com.google.android.material.button.MaterialButton 79 <com.google.android.material.button.MaterialButton
54 android:id="@+id/button_action" 80 android:id="@+id/button_action"
55 android:layout_width="wrap_content" 81 android:layout_width="wrap_content"
56 android:layout_height="56dp" 82 android:layout_height="56dp"
57 android:layout_marginTop="32dp" 83 android:layout_marginTop="16dp"
84 android:layout_marginBottom="48dp"
58 android:textSize="20sp" 85 android:textSize="20sp"
59 app:iconSize="24sp"
60 app:iconGravity="end" 86 app:iconGravity="end"
87 app:iconSize="24sp"
88 app:layout_constraintBottom_toBottomOf="parent"
89 app:layout_constraintEnd_toEndOf="parent"
90 app:layout_constraintStart_toStartOf="parent"
91 app:layout_constraintTop_toBottomOf="@+id/text_description"
61 tools:text="Get started" /> 92 tools:text="Get started" />
62 93
63 </LinearLayout> 94 </androidx.constraintlayout.widget.ConstraintLayout>
64 95
65</LinearLayout> 96</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/dialog_slider.xml b/src/android/app/src/main/res/layout/dialog_slider.xml
index 8c84cb606..d1cb31739 100644
--- a/src/android/app/src/main/res/layout/dialog_slider.xml
+++ b/src/android/app/src/main/res/layout/dialog_slider.xml
@@ -5,23 +5,16 @@
5 android:layout_height="wrap_content" 5 android:layout_height="wrap_content"
6 android:orientation="vertical"> 6 android:orientation="vertical">
7 7
8 <TextView 8 <com.google.android.material.textview.MaterialTextView
9 android:id="@+id/text_value" 9 android:id="@+id/text_value"
10 style="@style/TextAppearance.Material3.LabelMedium"
10 android:layout_width="wrap_content" 11 android:layout_width="wrap_content"
11 android:layout_height="wrap_content" 12 android:layout_height="wrap_content"
12 android:layout_alignParentTop="true" 13 android:layout_alignParentTop="true"
13 android:layout_centerHorizontal="true" 14 android:layout_centerHorizontal="true"
14 android:layout_marginBottom="@dimen/spacing_medlarge" 15 android:layout_marginBottom="@dimen/spacing_medlarge"
15 android:layout_marginTop="@dimen/spacing_medlarge" 16 android:layout_marginTop="@dimen/spacing_medlarge"
16 tools:text="75" /> 17 tools:text="75%" />
17
18 <TextView
19 android:id="@+id/text_units"
20 android:layout_width="wrap_content"
21 android:layout_height="wrap_content"
22 android:layout_alignTop="@+id/text_value"
23 android:layout_toEndOf="@+id/text_value"
24 tools:text="%" />
25 18
26 <com.google.android.material.slider.Slider 19 <com.google.android.material.slider.Slider
27 android:id="@+id/slider" 20 android:id="@+id/slider"
diff --git a/src/android/app/src/main/res/layout/fragment_setup.xml b/src/android/app/src/main/res/layout/fragment_setup.xml
index d7bafaea2..9499f6463 100644
--- a/src/android/app/src/main/res/layout/fragment_setup.xml
+++ b/src/android/app/src/main/res/layout/fragment_setup.xml
@@ -1,5 +1,5 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout 2<RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 android:id="@+id/setup_root" 5 android:id="@+id/setup_root"
@@ -8,35 +8,39 @@
8 8
9 <androidx.viewpager2.widget.ViewPager2 9 <androidx.viewpager2.widget.ViewPager2
10 android:id="@+id/viewPager2" 10 android:id="@+id/viewPager2"
11 android:layout_width="0dp" 11 android:layout_width="match_parent"
12 android:layout_height="0dp"
13 android:clipToPadding="false"
14 android:layout_marginBottom="16dp"
15 app:layout_constraintBottom_toTopOf="@+id/button_next"
16 app:layout_constraintEnd_toEndOf="parent"
17 app:layout_constraintStart_toStartOf="parent"
18 app:layout_constraintTop_toTopOf="parent" />
19
20 <com.google.android.material.button.MaterialButton
21 style="@style/Widget.Material3.Button.TextButton"
22 android:id="@+id/button_next"
23 android:layout_width="wrap_content"
24 android:layout_height="wrap_content" 12 android:layout_height="wrap_content"
25 android:layout_margin="12dp" 13 android:layout_above="@+id/constraint_buttons"
26 android:text="@string/next" 14 android:layout_alignParentTop="true"
27 android:visibility="invisible" 15 android:clipToPadding="false" />
28 app:layout_constraintBottom_toBottomOf="parent"
29 app:layout_constraintEnd_toEndOf="parent" />
30 16
31 <com.google.android.material.button.MaterialButton 17 <androidx.constraintlayout.widget.ConstraintLayout
32 style="@style/Widget.Material3.Button.TextButton" 18 android:id="@+id/constraint_buttons"
33 android:id="@+id/button_back" 19 android:layout_width="match_parent"
34 android:layout_width="wrap_content"
35 android:layout_height="wrap_content" 20 android:layout_height="wrap_content"
36 android:layout_margin="12dp" 21 android:layout_margin="8dp"
37 android:text="@string/back" 22 android:layout_alignParentBottom="true">
38 android:visibility="invisible" 23
39 app:layout_constraintBottom_toBottomOf="parent" 24 <com.google.android.material.button.MaterialButton
40 app:layout_constraintStart_toStartOf="parent" /> 25 android:id="@+id/button_next"
26 style="@style/Widget.Material3.Button.TextButton"
27 android:layout_width="wrap_content"
28 android:layout_height="wrap_content"
29 android:text="@string/next"
30 android:visibility="invisible"
31 app:layout_constraintBottom_toBottomOf="parent"
32 app:layout_constraintEnd_toEndOf="parent" />
33
34 <com.google.android.material.button.MaterialButton
35 android:id="@+id/button_back"
36 style="@style/Widget.Material3.Button.TextButton"
37 android:layout_width="wrap_content"
38 android:layout_height="wrap_content"
39 android:text="@string/back"
40 android:visibility="invisible"
41 app:layout_constraintBottom_toBottomOf="parent"
42 app:layout_constraintStart_toStartOf="parent" />
43
44 </androidx.constraintlayout.widget.ConstraintLayout>
41 45
42</androidx.constraintlayout.widget.ConstraintLayout> 46</RelativeLayout>
diff --git a/src/android/app/src/main/res/layout/list_item_setting.xml b/src/android/app/src/main/res/layout/list_item_setting.xml
index ec896342b..f1037a740 100644
--- a/src/android/app/src/main/res/layout/list_item_setting.xml
+++ b/src/android/app/src/main/res/layout/list_item_setting.xml
@@ -1,9 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2<RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
3 xmlns:tools="http://schemas.android.com/tools" 5 xmlns:tools="http://schemas.android.com/tools"
4 android:layout_width="match_parent" 6 android:layout_width="match_parent"
5 android:layout_height="wrap_content" 7 android:layout_height="wrap_content"
6 xmlns:app="http://schemas.android.com/apk/res-auto"
7 android:background="?android:attr/selectableItemBackground" 8 android:background="?android:attr/selectableItemBackground"
8 android:clickable="true" 9 android:clickable="true"
9 android:focusable="true" 10 android:focusable="true"
@@ -11,31 +12,40 @@
11 android:minHeight="72dp" 12 android:minHeight="72dp"
12 android:padding="@dimen/spacing_large"> 13 android:padding="@dimen/spacing_large">
13 14
14 <com.google.android.material.textview.MaterialTextView 15 <LinearLayout
15 style="@style/TextAppearance.Material3.HeadlineMedium" 16 android:layout_width="match_parent"
16 android:id="@+id/text_setting_name"
17 android:layout_width="0dp"
18 android:layout_height="wrap_content" 17 android:layout_height="wrap_content"
19 android:layout_alignParentEnd="true" 18 android:orientation="vertical">
20 android:layout_alignParentStart="true"
21 android:layout_alignParentTop="true"
22 android:textSize="16sp"
23 android:textAlignment="viewStart"
24 app:lineHeight="28dp"
25 tools:text="Setting Name" />
26 19
27 <TextView 20 <com.google.android.material.textview.MaterialTextView
28 style="@style/TextAppearance.Material3.BodySmall" 21 android:id="@+id/text_setting_name"
29 android:id="@+id/text_setting_description" 22 style="@style/TextAppearance.Material3.HeadlineMedium"
30 android:layout_width="wrap_content" 23 android:layout_width="match_parent"
31 android:layout_height="wrap_content" 24 android:layout_height="wrap_content"
32 android:layout_alignParentEnd="true" 25 android:textAlignment="viewStart"
33 android:layout_alignParentStart="true" 26 android:textSize="16sp"
34 android:layout_alignStart="@+id/text_setting_name" 27 app:lineHeight="22dp"
35 android:layout_below="@+id/text_setting_name" 28 tools:text="Setting Name" />
36 android:layout_marginTop="@dimen/spacing_small" 29
37 android:visibility="visible" 30 <com.google.android.material.textview.MaterialTextView
38 android:textAlignment="viewStart" 31 android:id="@+id/text_setting_description"
39 tools:text="@string/app_disclaimer" /> 32 style="@style/TextAppearance.Material3.BodySmall"
33 android:layout_width="match_parent"
34 android:layout_height="wrap_content"
35 android:layout_marginTop="@dimen/spacing_small"
36 android:textAlignment="viewStart"
37 tools:text="@string/app_disclaimer" />
38
39 <com.google.android.material.textview.MaterialTextView
40 android:id="@+id/text_setting_value"
41 style="@style/TextAppearance.Material3.LabelMedium"
42 android:layout_width="match_parent"
43 android:layout_height="wrap_content"
44 android:layout_marginTop="@dimen/spacing_small"
45 android:textAlignment="viewStart"
46 android:textStyle="bold"
47 tools:text="1x" />
48
49 </LinearLayout>
40 50
41</RelativeLayout> 51</RelativeLayout>
diff --git a/src/android/app/src/main/res/layout/page_setup.xml b/src/android/app/src/main/res/layout/page_setup.xml
index 1436ef308..535abcf02 100644
--- a/src/android/app/src/main/res/layout/page_setup.xml
+++ b/src/android/app/src/main/res/layout/page_setup.xml
@@ -21,11 +21,12 @@
21 app:layout_constraintVertical_chainStyle="spread" 21 app:layout_constraintVertical_chainStyle="spread"
22 app:layout_constraintWidth_max="220dp" 22 app:layout_constraintWidth_max="220dp"
23 app:layout_constraintWidth_min="110dp" 23 app:layout_constraintWidth_min="110dp"
24 app:layout_constraintVertical_weight="3" /> 24 app:layout_constraintVertical_weight="3"
25 tools:src="@drawable/ic_notification" />
25 26
26 <com.google.android.material.textview.MaterialTextView 27 <com.google.android.material.textview.MaterialTextView
27 android:id="@+id/text_title" 28 android:id="@+id/text_title"
28 style="@style/TextAppearance.Material3.DisplayMedium" 29 style="@style/TextAppearance.Material3.DisplaySmall"
29 android:layout_width="0dp" 30 android:layout_width="0dp"
30 android:layout_height="0dp" 31 android:layout_height="0dp"
31 android:textAlignment="center" 32 android:textAlignment="center"
@@ -44,23 +45,42 @@
44 android:layout_width="0dp" 45 android:layout_width="0dp"
45 android:layout_height="0dp" 46 android:layout_height="0dp"
46 android:textAlignment="center" 47 android:textAlignment="center"
47 android:textSize="26sp" 48 android:textSize="20sp"
48 android:paddingHorizontal="16dp" 49 android:paddingHorizontal="16dp"
49 app:layout_constraintBottom_toTopOf="@+id/button_action" 50 app:layout_constraintBottom_toTopOf="@+id/button_action"
50 app:layout_constraintEnd_toEndOf="parent" 51 app:layout_constraintEnd_toEndOf="parent"
51 app:layout_constraintStart_toStartOf="parent" 52 app:layout_constraintStart_toStartOf="parent"
52 app:layout_constraintTop_toBottomOf="@+id/text_title" 53 app:layout_constraintTop_toBottomOf="@+id/text_title"
53 app:layout_constraintVertical_weight="2" 54 app:layout_constraintVertical_weight="2"
54 app:lineHeight="40sp" 55 app:lineHeight="30sp"
55 tools:text="@string/welcome_description" /> 56 tools:text="@string/welcome_description" />
56 57
58 <com.google.android.material.textview.MaterialTextView
59 android:id="@+id/text_confirmation"
60 style="@style/TextAppearance.Material3.TitleLarge"
61 android:layout_width="wrap_content"
62 android:layout_height="0dp"
63 android:paddingHorizontal="16dp"
64 android:paddingTop="24dp"
65 android:textAlignment="center"
66 android:textSize="30sp"
67 android:visibility="invisible"
68 android:text="@string/step_complete"
69 android:textStyle="bold"
70 app:layout_constraintBottom_toBottomOf="parent"
71 app:layout_constraintEnd_toEndOf="parent"
72 app:layout_constraintStart_toStartOf="parent"
73 app:layout_constraintTop_toBottomOf="@+id/text_description"
74 app:layout_constraintVertical_weight="1"
75 app:lineHeight="30sp" />
76
57 <com.google.android.material.button.MaterialButton 77 <com.google.android.material.button.MaterialButton
58 android:id="@+id/button_action" 78 android:id="@+id/button_action"
59 android:layout_width="wrap_content" 79 android:layout_width="wrap_content"
60 android:layout_height="56dp" 80 android:layout_height="56dp"
61 android:textSize="20sp"
62 android:layout_marginTop="16dp" 81 android:layout_marginTop="16dp"
63 android:layout_marginBottom="48dp" 82 android:layout_marginBottom="48dp"
83 android:textSize="20sp"
64 app:iconGravity="end" 84 app:iconGravity="end"
65 app:iconSize="24sp" 85 app:iconSize="24sp"
66 app:layout_constraintBottom_toBottomOf="parent" 86 app:layout_constraintBottom_toBottomOf="parent"
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 02e25504d..de1b2909b 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -29,6 +29,7 @@
29 <string name="back">Back</string> 29 <string name="back">Back</string>
30 <string name="add_games">Add Games</string> 30 <string name="add_games">Add Games</string>
31 <string name="add_games_description">Select your games folder</string> 31 <string name="add_games_description">Select your games folder</string>
32 <string name="step_complete">Complete!</string>
32 33
33 <!-- Home strings --> 34 <!-- Home strings -->
34 <string name="home_games">Games</string> 35 <string name="home_games">Games</string>
@@ -149,6 +150,7 @@
149 <string name="frame_limit_slider">Limit speed percent</string> 150 <string name="frame_limit_slider">Limit speed percent</string>
150 <string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string> 151 <string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string>
151 <string name="cpu_accuracy">CPU accuracy</string> 152 <string name="cpu_accuracy">CPU accuracy</string>
153 <string name="value_with_units">%1$s%2$s</string>
152 154
153 <!-- System settings strings --> 155 <!-- System settings strings -->
154 <string name="use_docked_mode">Docked Mode</string> 156 <string name="use_docked_mode">Docked Mode</string>
diff --git a/src/common/settings_common.cpp b/src/common/settings_common.cpp
index dedf5ef90..137b65d5f 100644
--- a/src/common/settings_common.cpp
+++ b/src/common/settings_common.cpp
@@ -1,7 +1,9 @@
1// SPDX-FileCopyrightText: Copyright 2023 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-2.0-or-later
3 3
4#include <functional>
4#include <string> 5#include <string>
6#include <vector>
5#include "common/settings_common.h" 7#include "common/settings_common.h"
6 8
7namespace Settings { 9namespace Settings {
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index a1a29ebf6..e7cb59ea5 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -12,8 +12,8 @@ namespace Settings {
12 12
13template <typename T> 13template <typename T>
14struct EnumMetadata { 14struct EnumMetadata {
15 static constexpr std::vector<std::pair<std::string, T>> Canonicalizations(); 15 static std::vector<std::pair<std::string, T>> Canonicalizations();
16 static constexpr u32 Index(); 16 static u32 Index();
17}; 17};
18 18
19#define PAIR_45(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_46(N, __VA_ARGS__)) 19#define PAIR_45(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_46(N, __VA_ARGS__))
@@ -66,11 +66,11 @@ struct EnumMetadata {
66#define ENUM(NAME, ...) \ 66#define ENUM(NAME, ...) \
67 enum class NAME : u32 { __VA_ARGS__ }; \ 67 enum class NAME : u32 { __VA_ARGS__ }; \
68 template <> \ 68 template <> \
69 constexpr std::vector<std::pair<std::string, NAME>> EnumMetadata<NAME>::Canonicalizations() { \ 69 inline std::vector<std::pair<std::string, NAME>> EnumMetadata<NAME>::Canonicalizations() { \
70 return {PAIR(NAME, __VA_ARGS__)}; \ 70 return {PAIR(NAME, __VA_ARGS__)}; \
71 } \ 71 } \
72 template <> \ 72 template <> \
73 constexpr u32 EnumMetadata<NAME>::Index() { \ 73 inline u32 EnumMetadata<NAME>::Index() { \
74 return __COUNTER__; \ 74 return __COUNTER__; \
75 } 75 }
76 76
@@ -85,7 +85,7 @@ enum class AudioEngine : u32 {
85}; 85};
86 86
87template <> 87template <>
88constexpr std::vector<std::pair<std::string, AudioEngine>> 88inline std::vector<std::pair<std::string, AudioEngine>>
89EnumMetadata<AudioEngine>::Canonicalizations() { 89EnumMetadata<AudioEngine>::Canonicalizations() {
90 return { 90 return {
91 {"auto", AudioEngine::Auto}, 91 {"auto", AudioEngine::Auto},
@@ -96,7 +96,7 @@ EnumMetadata<AudioEngine>::Canonicalizations() {
96} 96}
97 97
98template <> 98template <>
99constexpr u32 EnumMetadata<AudioEngine>::Index() { 99inline u32 EnumMetadata<AudioEngine>::Index() {
100 // This is just a sufficiently large number that is more than the number of other enums declared 100 // This is just a sufficiently large number that is more than the number of other enums declared
101 // here 101 // here
102 return 100; 102 return 100;
@@ -147,7 +147,7 @@ ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum);
147ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch); 147ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
148 148
149template <typename Type> 149template <typename Type>
150constexpr std::string CanonicalizeEnum(Type id) { 150inline std::string CanonicalizeEnum(Type id) {
151 const auto group = EnumMetadata<Type>::Canonicalizations(); 151 const auto group = EnumMetadata<Type>::Canonicalizations();
152 for (auto& [name, value] : group) { 152 for (auto& [name, value] : group) {
153 if (value == id) { 153 if (value == id) {
@@ -158,7 +158,7 @@ constexpr std::string CanonicalizeEnum(Type id) {
158} 158}
159 159
160template <typename Type> 160template <typename Type>
161constexpr Type ToEnum(const std::string& canonicalization) { 161inline Type ToEnum(const std::string& canonicalization) {
162 const auto group = EnumMetadata<Type>::Canonicalizations(); 162 const auto group = EnumMetadata<Type>::Canonicalizations();
163 for (auto& [name, value] : group) { 163 for (auto& [name, value] : group) {
164 if (name == canonicalization) { 164 if (name == canonicalization) {
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h
index a8beb06e9..e10843c73 100644
--- a/src/common/settings_setting.h
+++ b/src/common/settings_setting.h
@@ -190,7 +190,7 @@ public:
190 } 190 }
191 } 191 }
192 192
193 [[nodiscard]] std::string constexpr Canonicalize() const override final { 193 [[nodiscard]] std::string Canonicalize() const override final {
194 if constexpr (std::is_enum_v<Type>) { 194 if constexpr (std::is_enum_v<Type>) {
195 return CanonicalizeEnum(this->GetValue()); 195 return CanonicalizeEnum(this->GetValue());
196 } else { 196 } else {
@@ -256,11 +256,11 @@ public:
256 * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded 256 * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
257 * @param other_setting_ A second Setting to associate to this one in metadata 257 * @param other_setting_ A second Setting to associate to this one in metadata
258 */ 258 */
259 template <typename T = BasicSetting>
259 explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name, 260 explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name,
260 Category category_, u32 specialization_ = Specialization::Default, 261 Category category_, u32 specialization_ = Specialization::Default,
261 bool save_ = true, bool runtime_modifiable_ = false, 262 bool save_ = true, bool runtime_modifiable_ = false,
262 BasicSetting* other_setting_ = nullptr) 263 typename std::enable_if<!ranged, T*>::type other_setting_ = nullptr)
263 requires(!ranged)
264 : Setting<Type, false>{ 264 : Setting<Type, false>{
265 linkage, default_val, name, category_, specialization_, 265 linkage, default_val, name, category_, specialization_,
266 save_, runtime_modifiable_, other_setting_} { 266 save_, runtime_modifiable_, other_setting_} {
@@ -282,12 +282,12 @@ public:
282 * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded 282 * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
283 * @param other_setting_ A second Setting to associate to this one in metadata 283 * @param other_setting_ A second Setting to associate to this one in metadata
284 */ 284 */
285 template <typename T = BasicSetting>
285 explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const Type& min_val, 286 explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const Type& min_val,
286 const Type& max_val, const std::string& name, Category category_, 287 const Type& max_val, const std::string& name, Category category_,
287 u32 specialization_ = Specialization::Default, bool save_ = true, 288 u32 specialization_ = Specialization::Default, bool save_ = true,
288 bool runtime_modifiable_ = false, 289 bool runtime_modifiable_ = false,
289 BasicSetting* other_setting_ = nullptr) 290 typename std::enable_if<ranged, T*>::type other_setting_ = nullptr)
290 requires(ranged)
291 : Setting<Type, true>{linkage, default_val, min_val, 291 : Setting<Type, true>{linkage, default_val, min_val,
292 max_val, name, category_, 292 max_val, name, category_,
293 specialization_, save_, runtime_modifiable_, 293 specialization_, save_, runtime_modifiable_,
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h
index 6b35f448c..00beb40dd 100644
--- a/src/core/hid/hid_types.h
+++ b/src/core/hid/hid_types.h
@@ -289,6 +289,19 @@ enum class GyroscopeZeroDriftMode : u32 {
289 Tight = 2, 289 Tight = 2,
290}; 290};
291 291
292// This is nn::settings::system::TouchScreenMode
293enum class TouchScreenMode : u32 {
294 Stylus = 0,
295 Standard = 1,
296};
297
298// This is nn::hid::TouchScreenModeForNx
299enum class TouchScreenModeForNx : u8 {
300 UseSystemSetting,
301 Finger,
302 Heat2,
303};
304
292// This is nn::hid::NpadStyleTag 305// This is nn::hid::NpadStyleTag
293struct NpadStyleTag { 306struct NpadStyleTag {
294 union { 307 union {
@@ -334,6 +347,14 @@ struct TouchState {
334}; 347};
335static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); 348static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
336 349
350// This is nn::hid::TouchScreenConfigurationForNx
351struct TouchScreenConfigurationForNx {
352 TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting};
353 INSERT_PADDING_BYTES(0xF);
354};
355static_assert(sizeof(TouchScreenConfigurationForNx) == 0x10,
356 "TouchScreenConfigurationForNx is an invalid size");
357
337struct NpadColor { 358struct NpadColor {
338 u8 r{}; 359 u8 r{};
339 u8 g{}; 360 u8 g{};
@@ -662,6 +683,11 @@ struct MouseState {
662}; 683};
663static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size"); 684static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size");
664 685
686struct UniquePadId {
687 u64 id;
688};
689static_assert(sizeof(UniquePadId) == 0x8, "UniquePadId is an invalid size");
690
665/// Converts a NpadIdType to an array index. 691/// Converts a NpadIdType to an array index.
666constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) { 692constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) {
667 switch (npad_id_type) { 693 switch (npad_id_type) {
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index e57a3a80e..dd00921fd 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -16,22 +16,6 @@ class EmulatedConsole;
16namespace Service::HID { 16namespace Service::HID {
17class Controller_Touchscreen final : public ControllerBase { 17class Controller_Touchscreen final : public ControllerBase {
18public: 18public:
19 // This is nn::hid::TouchScreenModeForNx
20 enum class TouchScreenModeForNx : u8 {
21 UseSystemSetting,
22 Finger,
23 Heat2,
24 };
25
26 // This is nn::hid::TouchScreenConfigurationForNx
27 struct TouchScreenConfigurationForNx {
28 TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting};
29 INSERT_PADDING_BYTES_NOINIT(0x7);
30 INSERT_PADDING_BYTES_NOINIT(0xF); // Reserved
31 };
32 static_assert(sizeof(TouchScreenConfigurationForNx) == 0x17,
33 "TouchScreenConfigurationForNx is an invalid size");
34
35 explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); 19 explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
36 ~Controller_Touchscreen() override; 20 ~Controller_Touchscreen() override;
37 21
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 2bf1d8a27..fd466db7b 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -2368,7 +2368,7 @@ void Hid::GetNpadCommunicationMode(HLERequestContext& ctx) {
2368 2368
2369void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) { 2369void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) {
2370 IPC::RequestParser rp{ctx}; 2370 IPC::RequestParser rp{ctx};
2371 const auto touchscreen_mode{rp.PopRaw<Controller_Touchscreen::TouchScreenConfigurationForNx>()}; 2371 const auto touchscreen_mode{rp.PopRaw<Core::HID::TouchScreenConfigurationForNx>()};
2372 const auto applet_resource_user_id{rp.Pop<u64>()}; 2372 const auto applet_resource_user_id{rp.Pop<u64>()};
2373 2373
2374 LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}", 2374 LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}",
@@ -2543,7 +2543,8 @@ public:
2543 2543
2544class HidSys final : public ServiceFramework<HidSys> { 2544class HidSys final : public ServiceFramework<HidSys> {
2545public: 2545public:
2546 explicit HidSys(Core::System& system_) : ServiceFramework{system_, "hid:sys"} { 2546 explicit HidSys(Core::System& system_)
2547 : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"} {
2547 // clang-format off 2548 // clang-format off
2548 static const FunctionInfo functions[] = { 2549 static const FunctionInfo functions[] = {
2549 {31, nullptr, "SendKeyboardLockKeyEvent"}, 2550 {31, nullptr, "SendKeyboardLockKeyEvent"},
@@ -2568,7 +2569,7 @@ public:
2568 {303, &HidSys::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"}, 2569 {303, &HidSys::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"},
2569 {304, nullptr, "EnableAssigningSingleOnSlSrPress"}, 2570 {304, nullptr, "EnableAssigningSingleOnSlSrPress"},
2570 {305, nullptr, "DisableAssigningSingleOnSlSrPress"}, 2571 {305, nullptr, "DisableAssigningSingleOnSlSrPress"},
2571 {306, nullptr, "GetLastActiveNpad"}, 2572 {306, &HidSys::GetLastActiveNpad, "GetLastActiveNpad"},
2572 {307, nullptr, "GetNpadSystemExtStyle"}, 2573 {307, nullptr, "GetNpadSystemExtStyle"},
2573 {308, nullptr, "ApplyNpadSystemCommonPolicyFull"}, 2574 {308, nullptr, "ApplyNpadSystemCommonPolicyFull"},
2574 {309, nullptr, "GetNpadFullKeyGripColor"}, 2575 {309, nullptr, "GetNpadFullKeyGripColor"},
@@ -2624,7 +2625,7 @@ public:
2624 {700, nullptr, "ActivateUniquePad"}, 2625 {700, nullptr, "ActivateUniquePad"},
2625 {702, nullptr, "AcquireUniquePadConnectionEventHandle"}, 2626 {702, nullptr, "AcquireUniquePadConnectionEventHandle"},
2626 {703, nullptr, "GetUniquePadIds"}, 2627 {703, nullptr, "GetUniquePadIds"},
2627 {751, nullptr, "AcquireJoyDetachOnBluetoothOffEventHandle"}, 2628 {751, &HidSys::AcquireJoyDetachOnBluetoothOffEventHandle, "AcquireJoyDetachOnBluetoothOffEventHandle"},
2628 {800, nullptr, "ListSixAxisSensorHandles"}, 2629 {800, nullptr, "ListSixAxisSensorHandles"},
2629 {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"}, 2630 {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"},
2630 {802, nullptr, "ResetSixAxisSensorCalibrationValues"}, 2631 {802, nullptr, "ResetSixAxisSensorCalibrationValues"},
@@ -2650,7 +2651,7 @@ public:
2650 {830, nullptr, "SetNotificationLedPattern"}, 2651 {830, nullptr, "SetNotificationLedPattern"},
2651 {831, nullptr, "SetNotificationLedPatternWithTimeout"}, 2652 {831, nullptr, "SetNotificationLedPatternWithTimeout"},
2652 {832, nullptr, "PrepareHidsForNotificationWake"}, 2653 {832, nullptr, "PrepareHidsForNotificationWake"},
2653 {850, nullptr, "IsUsbFullKeyControllerEnabled"}, 2654 {850, &HidSys::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"},
2654 {851, nullptr, "EnableUsbFullKeyController"}, 2655 {851, nullptr, "EnableUsbFullKeyController"},
2655 {852, nullptr, "IsUsbConnected"}, 2656 {852, nullptr, "IsUsbConnected"},
2656 {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"}, 2657 {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"},
@@ -2682,7 +2683,7 @@ public:
2682 {1150, nullptr, "SetTouchScreenMagnification"}, 2683 {1150, nullptr, "SetTouchScreenMagnification"},
2683 {1151, nullptr, "GetTouchScreenFirmwareVersion"}, 2684 {1151, nullptr, "GetTouchScreenFirmwareVersion"},
2684 {1152, nullptr, "SetTouchScreenDefaultConfiguration"}, 2685 {1152, nullptr, "SetTouchScreenDefaultConfiguration"},
2685 {1153, nullptr, "GetTouchScreenDefaultConfiguration"}, 2686 {1153, &HidSys::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"},
2686 {1154, nullptr, "IsFirmwareAvailableForNotification"}, 2687 {1154, nullptr, "IsFirmwareAvailableForNotification"},
2687 {1155, nullptr, "SetForceHandheldStyleVibration"}, 2688 {1155, nullptr, "SetForceHandheldStyleVibration"},
2688 {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"}, 2689 {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"},
@@ -2749,6 +2750,8 @@ public:
2749 // clang-format on 2750 // clang-format on
2750 2751
2751 RegisterHandlers(functions); 2752 RegisterHandlers(functions);
2753
2754 joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent");
2752 } 2755 }
2753 2756
2754private: 2757private:
@@ -2760,17 +2763,66 @@ private:
2760 rb.Push(ResultSuccess); 2763 rb.Push(ResultSuccess);
2761 } 2764 }
2762 2765
2766 void GetLastActiveNpad(HLERequestContext& ctx) {
2767 LOG_DEBUG(Service_HID, "(STUBBED) called");
2768
2769 IPC::ResponseBuilder rb{ctx, 3};
2770 rb.Push(ResultSuccess);
2771 rb.PushEnum(Core::HID::NpadIdType::Handheld);
2772 }
2773
2763 void GetUniquePadsFromNpad(HLERequestContext& ctx) { 2774 void GetUniquePadsFromNpad(HLERequestContext& ctx) {
2764 IPC::RequestParser rp{ctx}; 2775 IPC::RequestParser rp{ctx};
2765 const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()}; 2776 const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
2766 2777
2767 const s64 total_entries = 0;
2768 LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type); 2778 LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type);
2769 2779
2780 const std::vector<Core::HID::UniquePadId> unique_pads{};
2781
2782 ctx.WriteBuffer(unique_pads);
2783
2770 IPC::ResponseBuilder rb{ctx, 3}; 2784 IPC::ResponseBuilder rb{ctx, 3};
2771 rb.Push(ResultSuccess); 2785 rb.Push(ResultSuccess);
2772 rb.Push(total_entries); 2786 rb.Push(static_cast<u32>(unique_pads.size()));
2773 } 2787 }
2788
2789 void AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx) {
2790 LOG_INFO(Service_AM, "called");
2791
2792 IPC::ResponseBuilder rb{ctx, 2, 1};
2793 rb.Push(ResultSuccess);
2794 rb.PushCopyObjects(joy_detach_event->GetReadableEvent());
2795 }
2796
2797 void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) {
2798 const bool is_enabled = false;
2799
2800 LOG_WARNING(Service_HID, "(STUBBED) called, is_enabled={}", is_enabled);
2801
2802 IPC::ResponseBuilder rb{ctx, 3};
2803 rb.Push(ResultSuccess);
2804 rb.Push(is_enabled);
2805 }
2806
2807 void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) {
2808 LOG_WARNING(Service_HID, "(STUBBED) called");
2809
2810 Core::HID::TouchScreenConfigurationForNx touchscreen_config{
2811 .mode = Core::HID::TouchScreenModeForNx::Finger,
2812 };
2813
2814 if (touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Heat2 &&
2815 touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Finger) {
2816 touchscreen_config.mode = Core::HID::TouchScreenModeForNx::UseSystemSetting;
2817 }
2818
2819 IPC::ResponseBuilder rb{ctx, 6};
2820 rb.Push(ResultSuccess);
2821 rb.PushRaw(touchscreen_config);
2822 }
2823
2824 Kernel::KEvent* joy_detach_event;
2825 KernelHelpers::ServiceContext service_context;
2774}; 2826};
2775 2827
2776void LoopProcess(Core::System& system) { 2828void LoopProcess(Core::System& system) {
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
index b16f9933f..dc6917d5d 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
@@ -449,6 +449,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
449 case NativeWindowScalingMode::ScaleToWindow: 449 case NativeWindowScalingMode::ScaleToWindow:
450 case NativeWindowScalingMode::ScaleCrop: 450 case NativeWindowScalingMode::ScaleCrop:
451 case NativeWindowScalingMode::NoScaleCrop: 451 case NativeWindowScalingMode::NoScaleCrop:
452 case NativeWindowScalingMode::PreserveAspectRatio:
452 break; 453 break;
453 default: 454 default:
454 LOG_ERROR(Service_Nvnflinger, "unknown scaling mode {}", scaling_mode); 455 LOG_ERROR(Service_Nvnflinger, "unknown scaling mode {}", scaling_mode);
diff --git a/src/core/hle/service/nvnflinger/window.h b/src/core/hle/service/nvnflinger/window.h
index 61cca5b01..36d6cde3d 100644
--- a/src/core/hle/service/nvnflinger/window.h
+++ b/src/core/hle/service/nvnflinger/window.h
@@ -41,6 +41,7 @@ enum class NativeWindowScalingMode : s32 {
41 ScaleToWindow = 1, 41 ScaleToWindow = 1,
42 ScaleCrop = 2, 42 ScaleCrop = 2,
43 NoScaleCrop = 3, 43 NoScaleCrop = 3,
44 PreserveAspectRatio = 4,
44}; 45};
45 46
46/// Transform parameter for QueueBuffer 47/// Transform parameter for QueueBuffer
diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
index 370678f48..c48914f64 100644
--- a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
+++ b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
@@ -100,7 +100,7 @@ public:
100 100
101 Result DoHandshake() override { 101 Result DoHandshake() override {
102 OSStatus status = SSLHandshake(context); 102 OSStatus status = SSLHandshake(context);
103 return HandleReturn("SSLHandshake", 0, status).Code(); 103 return HandleReturn("SSLHandshake", 0, status);
104 } 104 }
105 105
106 Result Read(size_t* out_size, std::span<u8> data) override { 106 Result Read(size_t* out_size, std::span<u8> data) override {
diff --git a/src/core/memory.h b/src/core/memory.h
index 2eb61ffd3..13047a545 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -509,9 +509,9 @@ class GuestMemory {
509 509
510public: 510public:
511 GuestMemory() = delete; 511 GuestMemory() = delete;
512 explicit GuestMemory(M& memory_, u64 addr_, std::size_t size_, 512 explicit GuestMemory(M& memory, u64 addr, std::size_t size,
513 Common::ScratchBuffer<T>* backup = nullptr) 513 Common::ScratchBuffer<T>* backup = nullptr)
514 : memory{memory_}, addr{addr_}, size{size_} { 514 : m_memory{memory}, m_addr{addr}, m_size{size} {
515 static_assert(FLAGS & GuestMemoryFlags::Read || FLAGS & GuestMemoryFlags::Write); 515 static_assert(FLAGS & GuestMemoryFlags::Read || FLAGS & GuestMemoryFlags::Write);
516 if constexpr (FLAGS & GuestMemoryFlags::Read) { 516 if constexpr (FLAGS & GuestMemoryFlags::Read) {
517 Read(addr, size, backup); 517 Read(addr, size, backup);
@@ -521,89 +521,97 @@ public:
521 ~GuestMemory() = default; 521 ~GuestMemory() = default;
522 522
523 T* data() noexcept { 523 T* data() noexcept {
524 return data_span.data(); 524 return m_data_span.data();
525 } 525 }
526 526
527 const T* data() const noexcept { 527 const T* data() const noexcept {
528 return data_span.data(); 528 return m_data_span.data();
529 }
530
531 size_t size() const noexcept {
532 return m_size;
533 }
534
535 size_t size_bytes() const noexcept {
536 return this->size() * sizeof(T);
529 } 537 }
530 538
531 [[nodiscard]] T* begin() noexcept { 539 [[nodiscard]] T* begin() noexcept {
532 return data(); 540 return this->data();
533 } 541 }
534 542
535 [[nodiscard]] const T* begin() const noexcept { 543 [[nodiscard]] const T* begin() const noexcept {
536 return data(); 544 return this->data();
537 } 545 }
538 546
539 [[nodiscard]] T* end() noexcept { 547 [[nodiscard]] T* end() noexcept {
540 return data() + size; 548 return this->data() + this->size();
541 } 549 }
542 550
543 [[nodiscard]] const T* end() const noexcept { 551 [[nodiscard]] const T* end() const noexcept {
544 return data() + size; 552 return this->data() + this->size();
545 } 553 }
546 554
547 T& operator[](size_t index) noexcept { 555 T& operator[](size_t index) noexcept {
548 return data_span[index]; 556 return m_data_span[index];
549 } 557 }
550 558
551 const T& operator[](size_t index) const noexcept { 559 const T& operator[](size_t index) const noexcept {
552 return data_span[index]; 560 return m_data_span[index];
553 } 561 }
554 562
555 void SetAddressAndSize(u64 addr_, std::size_t size_) noexcept { 563 void SetAddressAndSize(u64 addr, std::size_t size) noexcept {
556 addr = addr_; 564 m_addr = addr;
557 size = size_; 565 m_size = size;
558 addr_changed = true; 566 m_addr_changed = true;
559 } 567 }
560 568
561 std::span<T> Read(u64 addr_, std::size_t size_, 569 std::span<T> Read(u64 addr, std::size_t size,
562 Common::ScratchBuffer<T>* backup = nullptr) noexcept { 570 Common::ScratchBuffer<T>* backup = nullptr) noexcept {
563 addr = addr_; 571 m_addr = addr;
564 size = size_; 572 m_size = size;
565 if (size == 0) { 573 if (m_size == 0) {
566 is_data_copy = true; 574 m_is_data_copy = true;
567 return {}; 575 return {};
568 } 576 }
569 577
570 if (TrySetSpan()) { 578 if (this->TrySetSpan()) {
571 if constexpr (FLAGS & GuestMemoryFlags::Safe) { 579 if constexpr (FLAGS & GuestMemoryFlags::Safe) {
572 memory.FlushRegion(addr, size * sizeof(T)); 580 m_memory.FlushRegion(m_addr, this->size_bytes());
573 } 581 }
574 } else { 582 } else {
575 if (backup) { 583 if (backup) {
576 backup->resize_destructive(size); 584 backup->resize_destructive(this->size());
577 data_span = *backup; 585 m_data_span = *backup;
578 } else { 586 } else {
579 data_copy.resize(size); 587 m_data_copy.resize(this->size());
580 data_span = std::span(data_copy); 588 m_data_span = std::span(m_data_copy);
581 } 589 }
582 is_data_copy = true; 590 m_is_data_copy = true;
583 span_valid = true; 591 m_span_valid = true;
584 if constexpr (FLAGS & GuestMemoryFlags::Safe) { 592 if constexpr (FLAGS & GuestMemoryFlags::Safe) {
585 memory.ReadBlock(addr, data_span.data(), size * sizeof(T)); 593 m_memory.ReadBlock(m_addr, this->data(), this->size_bytes());
586 } else { 594 } else {
587 memory.ReadBlockUnsafe(addr, data_span.data(), size * sizeof(T)); 595 m_memory.ReadBlockUnsafe(m_addr, this->data(), this->size_bytes());
588 } 596 }
589 } 597 }
590 return data_span; 598 return m_data_span;
591 } 599 }
592 600
593 void Write(std::span<T> write_data) noexcept { 601 void Write(std::span<T> write_data) noexcept {
594 if constexpr (FLAGS & GuestMemoryFlags::Cached) { 602 if constexpr (FLAGS & GuestMemoryFlags::Cached) {
595 memory.WriteBlockCached(addr, write_data.data(), size * sizeof(T)); 603 m_memory.WriteBlockCached(m_addr, write_data.data(), this->size_bytes());
596 } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { 604 } else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
597 memory.WriteBlock(addr, write_data.data(), size * sizeof(T)); 605 m_memory.WriteBlock(m_addr, write_data.data(), this->size_bytes());
598 } else { 606 } else {
599 memory.WriteBlockUnsafe(addr, write_data.data(), size * sizeof(T)); 607 m_memory.WriteBlockUnsafe(m_addr, write_data.data(), this->size_bytes());
600 } 608 }
601 } 609 }
602 610
603 bool TrySetSpan() noexcept { 611 bool TrySetSpan() noexcept {
604 if (u8* ptr = memory.GetSpan(addr, size * sizeof(T)); ptr) { 612 if (u8* ptr = m_memory.GetSpan(m_addr, this->size_bytes()); ptr) {
605 data_span = {reinterpret_cast<T*>(ptr), size}; 613 m_data_span = {reinterpret_cast<T*>(ptr), this->size()};
606 span_valid = true; 614 m_span_valid = true;
607 return true; 615 return true;
608 } 616 }
609 return false; 617 return false;
@@ -611,36 +619,36 @@ public:
611 619
612protected: 620protected:
613 bool IsDataCopy() const noexcept { 621 bool IsDataCopy() const noexcept {
614 return is_data_copy; 622 return m_is_data_copy;
615 } 623 }
616 624
617 bool AddressChanged() const noexcept { 625 bool AddressChanged() const noexcept {
618 return addr_changed; 626 return m_addr_changed;
619 } 627 }
620 628
621 M& memory; 629 M& m_memory;
622 u64 addr; 630 u64 m_addr{};
623 size_t size; 631 size_t m_size{};
624 std::span<T> data_span{}; 632 std::span<T> m_data_span{};
625 std::vector<T> data_copy; 633 std::vector<T> m_data_copy{};
626 bool span_valid{false}; 634 bool m_span_valid{false};
627 bool is_data_copy{false}; 635 bool m_is_data_copy{false};
628 bool addr_changed{false}; 636 bool m_addr_changed{false};
629}; 637};
630 638
631template <typename M, typename T, GuestMemoryFlags FLAGS> 639template <typename M, typename T, GuestMemoryFlags FLAGS>
632class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> { 640class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> {
633public: 641public:
634 GuestMemoryScoped() = delete; 642 GuestMemoryScoped() = delete;
635 explicit GuestMemoryScoped(M& memory_, u64 addr_, std::size_t size_, 643 explicit GuestMemoryScoped(M& memory, u64 addr, std::size_t size,
636 Common::ScratchBuffer<T>* backup = nullptr) 644 Common::ScratchBuffer<T>* backup = nullptr)
637 : GuestMemory<M, T, FLAGS>(memory_, addr_, size_, backup) { 645 : GuestMemory<M, T, FLAGS>(memory, addr, size, backup) {
638 if constexpr (!(FLAGS & GuestMemoryFlags::Read)) { 646 if constexpr (!(FLAGS & GuestMemoryFlags::Read)) {
639 if (!this->TrySetSpan()) { 647 if (!this->TrySetSpan()) {
640 if (backup) { 648 if (backup) {
641 this->data_span = *backup; 649 this->m_data_span = *backup;
642 this->span_valid = true; 650 this->m_span_valid = true;
643 this->is_data_copy = true; 651 this->m_is_data_copy = true;
644 } 652 }
645 } 653 }
646 } 654 }
@@ -648,24 +656,21 @@ public:
648 656
649 ~GuestMemoryScoped() { 657 ~GuestMemoryScoped() {
650 if constexpr (FLAGS & GuestMemoryFlags::Write) { 658 if constexpr (FLAGS & GuestMemoryFlags::Write) {
651 if (this->size == 0) [[unlikely]] { 659 if (this->size() == 0) [[unlikely]] {
652 return; 660 return;
653 } 661 }
654 662
655 if (this->AddressChanged() || this->IsDataCopy()) { 663 if (this->AddressChanged() || this->IsDataCopy()) {
656 ASSERT(this->span_valid); 664 ASSERT(this->m_span_valid);
657 if constexpr (FLAGS & GuestMemoryFlags::Cached) { 665 if constexpr (FLAGS & GuestMemoryFlags::Cached) {
658 this->memory.WriteBlockCached(this->addr, this->data_span.data(), 666 this->m_memory.WriteBlockCached(this->m_addr, this->data(), this->size_bytes());
659 this->size * sizeof(T));
660 } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { 667 } else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
661 this->memory.WriteBlock(this->addr, this->data_span.data(), 668 this->m_memory.WriteBlock(this->m_addr, this->data(), this->size_bytes());
662 this->size * sizeof(T));
663 } else { 669 } else {
664 this->memory.WriteBlockUnsafe(this->addr, this->data_span.data(), 670 this->m_memory.WriteBlockUnsafe(this->m_addr, this->data(), this->size_bytes());
665 this->size * sizeof(T));
666 } 671 }
667 } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { 672 } else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
668 this->memory.InvalidateRegion(this->addr, this->size * sizeof(T)); 673 this->m_memory.InvalidateRegion(this->m_addr, this->size_bytes());
669 } 674 }
670 } 675 }
671 } 676 }
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index cd8e24b0b..da8eab7ee 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -5,6 +5,7 @@
5#include "common/assert.h" 5#include "common/assert.h"
6#include "common/logging/log.h" 6#include "common/logging/log.h"
7#include "common/microprofile.h" 7#include "common/microprofile.h"
8#include "common/polyfill_ranges.h"
8#include "common/settings.h" 9#include "common/settings.h"
9#include "core/core.h" 10#include "core/core.h"
10#include "core/memory.h" 11#include "core/memory.h"
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
index f822fa856..44a771d65 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
@@ -220,7 +220,8 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c
220 ASSERT(num_textures <= MAX_TEXTURES); 220 ASSERT(num_textures <= MAX_TEXTURES);
221 ASSERT(num_images <= MAX_IMAGES); 221 ASSERT(num_images <= MAX_IMAGES);
222 222
223 const bool assembly_shaders{assembly_programs[0].handle != 0}; 223 const auto backend = device.GetShaderBackend();
224 const bool assembly_shaders{backend == Settings::ShaderBackend::Glasm};
224 use_storage_buffers = 225 use_storage_buffers =
225 !assembly_shaders || num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks(); 226 !assembly_shaders || num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks();
226 writes_global_memory &= !use_storage_buffers; 227 writes_global_memory &= !use_storage_buffers;
@@ -230,7 +231,6 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c
230 GenerateTransformFeedbackState(); 231 GenerateTransformFeedbackState();
231 } 232 }
232 const bool in_parallel = thread_worker != nullptr; 233 const bool in_parallel = thread_worker != nullptr;
233 const auto backend = device.GetShaderBackend();
234 auto func{[this, sources_ = std::move(sources), sources_spirv_ = std::move(sources_spirv), 234 auto func{[this, sources_ = std::move(sources), sources_spirv_ = std::move(sources_spirv),
235 shader_notify, backend, in_parallel, 235 shader_notify, backend, in_parallel,
236 force_context_flush](ShaderContext::Context*) mutable { 236 force_context_flush](ShaderContext::Context*) mutable {
@@ -559,15 +559,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
559} 559}
560 560
561void GraphicsPipeline::ConfigureTransformFeedbackImpl() const { 561void GraphicsPipeline::ConfigureTransformFeedbackImpl() const {
562 glTransformFeedbackStreamAttribsNV(num_xfb_attribs, xfb_attribs.data(), num_xfb_strides, 562 glTransformFeedbackAttribsNV(num_xfb_attribs, xfb_attribs.data(), GL_SEPARATE_ATTRIBS);
563 xfb_streams.data(), GL_INTERLEAVED_ATTRIBS);
564} 563}
565 564
566void GraphicsPipeline::GenerateTransformFeedbackState() { 565void GraphicsPipeline::GenerateTransformFeedbackState() {
567 // TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal 566 // TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal
568 // when this is required. 567 // when this is required.
569 GLint* cursor{xfb_attribs.data()}; 568 GLint* cursor{xfb_attribs.data()};
570 GLint* current_stream{xfb_streams.data()};
571 569
572 for (size_t feedback = 0; feedback < Maxwell::NumTransformFeedbackBuffers; ++feedback) { 570 for (size_t feedback = 0; feedback < Maxwell::NumTransformFeedbackBuffers; ++feedback) {
573 const auto& layout = key.xfb_state.layouts[feedback]; 571 const auto& layout = key.xfb_state.layouts[feedback];
@@ -575,15 +573,6 @@ void GraphicsPipeline::GenerateTransformFeedbackState() {
575 if (layout.varying_count == 0) { 573 if (layout.varying_count == 0) {
576 continue; 574 continue;
577 } 575 }
578 *current_stream = static_cast<GLint>(feedback);
579 if (current_stream != xfb_streams.data()) {
580 // When stepping one stream, push the expected token
581 cursor[0] = GL_NEXT_BUFFER_NV;
582 cursor[1] = 0;
583 cursor[2] = 0;
584 cursor += XFB_ENTRY_STRIDE;
585 }
586 ++current_stream;
587 576
588 const auto& locations = key.xfb_state.varyings[feedback]; 577 const auto& locations = key.xfb_state.varyings[feedback];
589 std::optional<u32> current_index; 578 std::optional<u32> current_index;
@@ -619,7 +608,6 @@ void GraphicsPipeline::GenerateTransformFeedbackState() {
619 } 608 }
620 } 609 }
621 num_xfb_attribs = static_cast<GLsizei>((cursor - xfb_attribs.data()) / XFB_ENTRY_STRIDE); 610 num_xfb_attribs = static_cast<GLsizei>((cursor - xfb_attribs.data()) / XFB_ENTRY_STRIDE);
622 num_xfb_strides = static_cast<GLsizei>(current_stream - xfb_streams.data());
623} 611}
624 612
625void GraphicsPipeline::WaitForBuild() { 613void GraphicsPipeline::WaitForBuild() {
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.h b/src/video_core/renderer_opengl/gl_graphics_pipeline.h
index 7b3d7eae8..74fc9cc3d 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.h
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.h
@@ -154,9 +154,7 @@ private:
154 154
155 static constexpr std::size_t XFB_ENTRY_STRIDE = 3; 155 static constexpr std::size_t XFB_ENTRY_STRIDE = 3;
156 GLsizei num_xfb_attribs{}; 156 GLsizei num_xfb_attribs{};
157 GLsizei num_xfb_strides{};
158 std::array<GLint, 128 * XFB_ENTRY_STRIDE * Maxwell::NumTransformFeedbackBuffers> xfb_attribs{}; 157 std::array<GLint, 128 * XFB_ENTRY_STRIDE * Maxwell::NumTransformFeedbackBuffers> xfb_attribs{};
159 std::array<GLint, Maxwell::NumTransformFeedbackBuffers> xfb_streams{};
160 158
161 std::mutex built_mutex; 159 std::mutex built_mutex;
162 std::condition_variable built_condvar; 160 std::condition_variable built_condvar;
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 710929ac5..adde96aa5 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -326,6 +326,43 @@ std::vector<const char*> ExtensionListForVulkan(
326 326
327} // Anonymous namespace 327} // Anonymous namespace
328 328
329void Device::RemoveExtension(bool& extension, const std::string& extension_name) {
330 extension = false;
331 loaded_extensions.erase(extension_name);
332}
333
334void Device::RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name) {
335 if (loaded_extensions.contains(extension_name) && !is_suitable) {
336 LOG_WARNING(Render_Vulkan, "Removing unsuitable extension {}", extension_name);
337 this->RemoveExtension(is_suitable, extension_name);
338 }
339}
340
341template <typename Feature>
342void Device::RemoveExtensionFeature(bool& extension, Feature& feature,
343 const std::string& extension_name) {
344 // Unload extension.
345 this->RemoveExtension(extension, extension_name);
346
347 // Save sType and pNext for chain.
348 VkStructureType sType = feature.sType;
349 void* pNext = feature.pNext;
350
351 // Clear feature struct and restore chain.
352 feature = {};
353 feature.sType = sType;
354 feature.pNext = pNext;
355}
356
357template <typename Feature>
358void Device::RemoveExtensionFeatureIfUnsuitable(bool is_suitable, Feature& feature,
359 const std::string& extension_name) {
360 if (loaded_extensions.contains(extension_name) && !is_suitable) {
361 LOG_WARNING(Render_Vulkan, "Removing features for unsuitable extension {}", extension_name);
362 this->RemoveExtensionFeature(is_suitable, feature, extension_name);
363 }
364}
365
329Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR surface, 366Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR surface,
330 const vk::InstanceDispatch& dld_) 367 const vk::InstanceDispatch& dld_)
331 : instance{instance_}, dld{dld_}, physical{physical_}, 368 : instance{instance_}, dld{dld_}, physical{physical_},
@@ -397,21 +434,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
397 if (is_qualcomm || is_turnip) { 434 if (is_qualcomm || is_turnip) {
398 LOG_WARNING(Render_Vulkan, 435 LOG_WARNING(Render_Vulkan,
399 "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color"); 436 "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color");
400 extensions.custom_border_color = false; 437 RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color,
401 loaded_extensions.erase(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); 438 VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
402 } 439 }
403 440
404 if (is_qualcomm) { 441 if (is_qualcomm) {
405 must_emulate_scaled_formats = true; 442 must_emulate_scaled_formats = true;
406 443
407 LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken VK_EXT_extended_dynamic_state"); 444 LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken VK_EXT_extended_dynamic_state");
408 extensions.extended_dynamic_state = false; 445 RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state,
409 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); 446 VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
410 447
411 LOG_WARNING(Render_Vulkan, 448 LOG_WARNING(Render_Vulkan,
412 "Qualcomm drivers have a slow VK_KHR_push_descriptor implementation"); 449 "Qualcomm drivers have a slow VK_KHR_push_descriptor implementation");
413 extensions.push_descriptor = false; 450 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
414 loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
415 451
416#if defined(ANDROID) && defined(ARCHITECTURE_arm64) 452#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
417 // Patch the driver to enable BCn textures. 453 // Patch the driver to enable BCn textures.
@@ -440,15 +476,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
440 must_emulate_scaled_formats = true; 476 must_emulate_scaled_formats = true;
441 477
442 LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state"); 478 LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state");
443 extensions.extended_dynamic_state = false; 479 RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state,
444 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); 480 VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
445 481
446 LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state2"); 482 LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state2");
447 features.extended_dynamic_state2.extendedDynamicState2 = false; 483 RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
448 features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; 484 VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
449 features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false;
450 extensions.extended_dynamic_state2 = false;
451 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
452 } 485 }
453 486
454 if (is_nvidia) { 487 if (is_nvidia) {
@@ -464,8 +497,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
464 case NvidiaArchitecture::VoltaOrOlder: 497 case NvidiaArchitecture::VoltaOrOlder:
465 if (nv_major_version < 527) { 498 if (nv_major_version < 527) {
466 LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor"); 499 LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
467 extensions.push_descriptor = false; 500 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
468 loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
469 } 501 }
470 break; 502 break;
471 } 503 }
@@ -480,8 +512,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
480 if (version < VK_MAKE_API_VERSION(0, 21, 2, 0)) { 512 if (version < VK_MAKE_API_VERSION(0, 21, 2, 0)) {
481 LOG_WARNING(Render_Vulkan, 513 LOG_WARNING(Render_Vulkan,
482 "RADV versions older than 21.2 have broken VK_EXT_extended_dynamic_state"); 514 "RADV versions older than 21.2 have broken VK_EXT_extended_dynamic_state");
483 extensions.extended_dynamic_state = false; 515 RemoveExtensionFeature(extensions.extended_dynamic_state,
484 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); 516 features.extended_dynamic_state,
517 VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
485 } 518 }
486 } 519 }
487 if (extensions.extended_dynamic_state2 && is_radv) { 520 if (extensions.extended_dynamic_state2 && is_radv) {
@@ -490,11 +523,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
490 LOG_WARNING( 523 LOG_WARNING(
491 Render_Vulkan, 524 Render_Vulkan,
492 "RADV versions older than 22.3.1 have broken VK_EXT_extended_dynamic_state2"); 525 "RADV versions older than 22.3.1 have broken VK_EXT_extended_dynamic_state2");
493 features.extended_dynamic_state2.extendedDynamicState2 = false; 526 RemoveExtensionFeature(extensions.extended_dynamic_state2,
494 features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; 527 features.extended_dynamic_state2,
495 features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false; 528 VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
496 extensions.extended_dynamic_state2 = false;
497 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
498 } 529 }
499 } 530 }
500 if (extensions.extended_dynamic_state2 && is_qualcomm) { 531 if (extensions.extended_dynamic_state2 && is_qualcomm) {
@@ -504,11 +535,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
504 // Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2. 535 // Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2.
505 LOG_WARNING(Render_Vulkan, 536 LOG_WARNING(Render_Vulkan,
506 "Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2"); 537 "Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2");
507 features.extended_dynamic_state2.extendedDynamicState2 = false; 538 RemoveExtensionFeature(extensions.extended_dynamic_state2,
508 features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; 539 features.extended_dynamic_state2,
509 features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false; 540 VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
510 extensions.extended_dynamic_state2 = false;
511 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
512 } 541 }
513 } 542 }
514 if (extensions.extended_dynamic_state3 && is_radv) { 543 if (extensions.extended_dynamic_state3 && is_radv) {
@@ -540,9 +569,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
540 if (is_rdna2) { 569 if (is_rdna2) {
541 LOG_WARNING(Render_Vulkan, 570 LOG_WARNING(Render_Vulkan,
542 "RADV has broken VK_EXT_vertex_input_dynamic_state on RDNA2 hardware"); 571 "RADV has broken VK_EXT_vertex_input_dynamic_state on RDNA2 hardware");
543 features.vertex_input_dynamic_state.vertexInputDynamicState = false; 572 RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
544 extensions.vertex_input_dynamic_state = false; 573 features.vertex_input_dynamic_state,
545 loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); 574 VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
546 } 575 }
547 } 576 }
548 if (extensions.vertex_input_dynamic_state && is_qualcomm) { 577 if (extensions.vertex_input_dynamic_state && is_qualcomm) {
@@ -553,9 +582,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
553 LOG_WARNING( 582 LOG_WARNING(
554 Render_Vulkan, 583 Render_Vulkan,
555 "Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state"); 584 "Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state");
556 features.vertex_input_dynamic_state.vertexInputDynamicState = false; 585 RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
557 extensions.vertex_input_dynamic_state = false; 586 features.vertex_input_dynamic_state,
558 loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); 587 VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
559 } 588 }
560 } 589 }
561 590
@@ -575,8 +604,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
575 if (!features.shader_float16_int8.shaderFloat16) { 604 if (!features.shader_float16_int8.shaderFloat16) {
576 LOG_WARNING(Render_Vulkan, 605 LOG_WARNING(Render_Vulkan,
577 "AMD GCN4 and earlier have broken VK_EXT_sampler_filter_minmax"); 606 "AMD GCN4 and earlier have broken VK_EXT_sampler_filter_minmax");
578 extensions.sampler_filter_minmax = false; 607 RemoveExtension(extensions.sampler_filter_minmax,
579 loaded_extensions.erase(VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME); 608 VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME);
580 } 609 }
581 } 610 }
582 611
@@ -584,8 +613,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
584 const u32 version = (properties.properties.driverVersion << 3) >> 3; 613 const u32 version = (properties.properties.driverVersion << 3) >> 3;
585 if (version < VK_MAKE_API_VERSION(27, 20, 100, 0)) { 614 if (version < VK_MAKE_API_VERSION(27, 20, 100, 0)) {
586 LOG_WARNING(Render_Vulkan, "Intel has broken VK_EXT_vertex_input_dynamic_state"); 615 LOG_WARNING(Render_Vulkan, "Intel has broken VK_EXT_vertex_input_dynamic_state");
587 extensions.vertex_input_dynamic_state = false; 616 RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
588 loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); 617 features.vertex_input_dynamic_state,
618 VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
589 } 619 }
590 } 620 }
591 if (features.shader_float16_int8.shaderFloat16 && is_intel_windows) { 621 if (features.shader_float16_int8.shaderFloat16 && is_intel_windows) {
@@ -612,8 +642,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
612 // mesa/mesa/-/commit/ff91c5ca42bc80aa411cb3fd8f550aa6fdd16bdc 642 // mesa/mesa/-/commit/ff91c5ca42bc80aa411cb3fd8f550aa6fdd16bdc
613 LOG_WARNING(Render_Vulkan, 643 LOG_WARNING(Render_Vulkan,
614 "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor"); 644 "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
615 extensions.push_descriptor = false; 645 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
616 loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
617 } 646 }
618 } 647 }
619 if (is_mvk) { 648 if (is_mvk) {
@@ -1007,34 +1036,29 @@ bool Device::GetSuitability(bool requires_swapchain) {
1007 return suitable; 1036 return suitable;
1008} 1037}
1009 1038
1010void Device::RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name) {
1011 if (loaded_extensions.contains(extension_name) && !is_suitable) {
1012 LOG_WARNING(Render_Vulkan, "Removing unsuitable extension {}", extension_name);
1013 loaded_extensions.erase(extension_name);
1014 }
1015}
1016
1017void Device::RemoveUnsuitableExtensions() { 1039void Device::RemoveUnsuitableExtensions() {
1018 // VK_EXT_custom_border_color 1040 // VK_EXT_custom_border_color
1019 extensions.custom_border_color = features.custom_border_color.customBorderColors && 1041 extensions.custom_border_color = features.custom_border_color.customBorderColors &&
1020 features.custom_border_color.customBorderColorWithoutFormat; 1042 features.custom_border_color.customBorderColorWithoutFormat;
1021 RemoveExtensionIfUnsuitable(extensions.custom_border_color, 1043 RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color,
1022 VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); 1044 VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
1023 1045
1024 // VK_EXT_depth_clip_control 1046 // VK_EXT_depth_clip_control
1025 extensions.depth_clip_control = features.depth_clip_control.depthClipControl; 1047 extensions.depth_clip_control = features.depth_clip_control.depthClipControl;
1026 RemoveExtensionIfUnsuitable(extensions.depth_clip_control, 1048 RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control,
1027 VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); 1049 VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME);
1028 1050
1029 // VK_EXT_extended_dynamic_state 1051 // VK_EXT_extended_dynamic_state
1030 extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState; 1052 extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState;
1031 RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state, 1053 RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state,
1032 VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); 1054 features.extended_dynamic_state,
1055 VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
1033 1056
1034 // VK_EXT_extended_dynamic_state2 1057 // VK_EXT_extended_dynamic_state2
1035 extensions.extended_dynamic_state2 = features.extended_dynamic_state2.extendedDynamicState2; 1058 extensions.extended_dynamic_state2 = features.extended_dynamic_state2.extendedDynamicState2;
1036 RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state2, 1059 RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state2,
1037 VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); 1060 features.extended_dynamic_state2,
1061 VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
1038 1062
1039 // VK_EXT_extended_dynamic_state3 1063 // VK_EXT_extended_dynamic_state3
1040 dynamic_state3_blending = 1064 dynamic_state3_blending =
@@ -1048,35 +1072,38 @@ void Device::RemoveUnsuitableExtensions() {
1048 extensions.extended_dynamic_state3 = dynamic_state3_blending || dynamic_state3_enables; 1072 extensions.extended_dynamic_state3 = dynamic_state3_blending || dynamic_state3_enables;
1049 dynamic_state3_blending = dynamic_state3_blending && extensions.extended_dynamic_state3; 1073 dynamic_state3_blending = dynamic_state3_blending && extensions.extended_dynamic_state3;
1050 dynamic_state3_enables = dynamic_state3_enables && extensions.extended_dynamic_state3; 1074 dynamic_state3_enables = dynamic_state3_enables && extensions.extended_dynamic_state3;
1051 RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state3, 1075 RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state3,
1052 VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); 1076 features.extended_dynamic_state3,
1077 VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
1053 1078
1054 // VK_EXT_provoking_vertex 1079 // VK_EXT_provoking_vertex
1055 extensions.provoking_vertex = 1080 extensions.provoking_vertex =
1056 features.provoking_vertex.provokingVertexLast && 1081 features.provoking_vertex.provokingVertexLast &&
1057 features.provoking_vertex.transformFeedbackPreservesProvokingVertex; 1082 features.provoking_vertex.transformFeedbackPreservesProvokingVertex;
1058 RemoveExtensionIfUnsuitable(extensions.provoking_vertex, 1083 RemoveExtensionFeatureIfUnsuitable(extensions.provoking_vertex, features.provoking_vertex,
1059 VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); 1084 VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME);
1060 1085
1061 // VK_KHR_shader_atomic_int64 1086 // VK_KHR_shader_atomic_int64
1062 extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics && 1087 extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics &&
1063 features.shader_atomic_int64.shaderSharedInt64Atomics; 1088 features.shader_atomic_int64.shaderSharedInt64Atomics;
1064 RemoveExtensionIfUnsuitable(extensions.shader_atomic_int64, 1089 RemoveExtensionFeatureIfUnsuitable(extensions.shader_atomic_int64, features.shader_atomic_int64,
1065 VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); 1090 VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME);
1066 1091
1067 // VK_EXT_shader_demote_to_helper_invocation 1092 // VK_EXT_shader_demote_to_helper_invocation
1068 extensions.shader_demote_to_helper_invocation = 1093 extensions.shader_demote_to_helper_invocation =
1069 features.shader_demote_to_helper_invocation.shaderDemoteToHelperInvocation; 1094 features.shader_demote_to_helper_invocation.shaderDemoteToHelperInvocation;
1070 RemoveExtensionIfUnsuitable(extensions.shader_demote_to_helper_invocation, 1095 RemoveExtensionFeatureIfUnsuitable(extensions.shader_demote_to_helper_invocation,
1071 VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); 1096 features.shader_demote_to_helper_invocation,
1097 VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME);
1072 1098
1073 // VK_EXT_subgroup_size_control 1099 // VK_EXT_subgroup_size_control
1074 extensions.subgroup_size_control = 1100 extensions.subgroup_size_control =
1075 features.subgroup_size_control.subgroupSizeControl && 1101 features.subgroup_size_control.subgroupSizeControl &&
1076 properties.subgroup_size_control.minSubgroupSize <= GuestWarpSize && 1102 properties.subgroup_size_control.minSubgroupSize <= GuestWarpSize &&
1077 properties.subgroup_size_control.maxSubgroupSize >= GuestWarpSize; 1103 properties.subgroup_size_control.maxSubgroupSize >= GuestWarpSize;
1078 RemoveExtensionIfUnsuitable(extensions.subgroup_size_control, 1104 RemoveExtensionFeatureIfUnsuitable(extensions.subgroup_size_control,
1079 VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME); 1105 features.subgroup_size_control,
1106 VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME);
1080 1107
1081 // VK_EXT_transform_feedback 1108 // VK_EXT_transform_feedback
1082 extensions.transform_feedback = 1109 extensions.transform_feedback =
@@ -1086,24 +1113,27 @@ void Device::RemoveUnsuitableExtensions() {
1086 properties.transform_feedback.maxTransformFeedbackBuffers > 0 && 1113 properties.transform_feedback.maxTransformFeedbackBuffers > 0 &&
1087 properties.transform_feedback.transformFeedbackQueries && 1114 properties.transform_feedback.transformFeedbackQueries &&
1088 properties.transform_feedback.transformFeedbackDraw; 1115 properties.transform_feedback.transformFeedbackDraw;
1089 RemoveExtensionIfUnsuitable(extensions.transform_feedback, 1116 RemoveExtensionFeatureIfUnsuitable(extensions.transform_feedback, features.transform_feedback,
1090 VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME); 1117 VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
1091 1118
1092 // VK_EXT_vertex_input_dynamic_state 1119 // VK_EXT_vertex_input_dynamic_state
1093 extensions.vertex_input_dynamic_state = 1120 extensions.vertex_input_dynamic_state =
1094 features.vertex_input_dynamic_state.vertexInputDynamicState; 1121 features.vertex_input_dynamic_state.vertexInputDynamicState;
1095 RemoveExtensionIfUnsuitable(extensions.vertex_input_dynamic_state, 1122 RemoveExtensionFeatureIfUnsuitable(extensions.vertex_input_dynamic_state,
1096 VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); 1123 features.vertex_input_dynamic_state,
1124 VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
1097 1125
1098 // VK_KHR_pipeline_executable_properties 1126 // VK_KHR_pipeline_executable_properties
1099 if (Settings::values.renderer_shader_feedback.GetValue()) { 1127 if (Settings::values.renderer_shader_feedback.GetValue()) {
1100 extensions.pipeline_executable_properties = 1128 extensions.pipeline_executable_properties =
1101 features.pipeline_executable_properties.pipelineExecutableInfo; 1129 features.pipeline_executable_properties.pipelineExecutableInfo;
1102 RemoveExtensionIfUnsuitable(extensions.pipeline_executable_properties, 1130 RemoveExtensionFeatureIfUnsuitable(extensions.pipeline_executable_properties,
1103 VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); 1131 features.pipeline_executable_properties,
1132 VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME);
1104 } else { 1133 } else {
1105 extensions.pipeline_executable_properties = false; 1134 RemoveExtensionFeature(extensions.pipeline_executable_properties,
1106 loaded_extensions.erase(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); 1135 features.pipeline_executable_properties,
1136 VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME);
1107 } 1137 }
1108 1138
1109 // VK_KHR_workgroup_memory_explicit_layout 1139 // VK_KHR_workgroup_memory_explicit_layout
@@ -1113,8 +1143,9 @@ void Device::RemoveUnsuitableExtensions() {
1113 features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess && 1143 features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess &&
1114 features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess && 1144 features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess &&
1115 features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout; 1145 features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout;
1116 RemoveExtensionIfUnsuitable(extensions.workgroup_memory_explicit_layout, 1146 RemoveExtensionFeatureIfUnsuitable(extensions.workgroup_memory_explicit_layout,
1117 VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); 1147 features.workgroup_memory_explicit_layout,
1148 VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME);
1118} 1149}
1119 1150
1120void Device::SetupFamilies(VkSurfaceKHR surface) { 1151void Device::SetupFamilies(VkSurfaceKHR surface) {
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index d8dd41e51..488fdd313 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -639,8 +639,17 @@ private:
639 639
640 // Remove extensions which have incomplete feature support. 640 // Remove extensions which have incomplete feature support.
641 void RemoveUnsuitableExtensions(); 641 void RemoveUnsuitableExtensions();
642
643 void RemoveExtension(bool& extension, const std::string& extension_name);
642 void RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name); 644 void RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name);
643 645
646 template <typename Feature>
647 void RemoveExtensionFeature(bool& extension, Feature& feature,
648 const std::string& extension_name);
649 template <typename Feature>
650 void RemoveExtensionFeatureIfUnsuitable(bool is_suitable, Feature& feature,
651 const std::string& extension_name);
652
644 /// Sets up queue families. 653 /// Sets up queue families.
645 void SetupFamilies(VkSurfaceKHR surface); 654 void SetupFamilies(VkSurfaceKHR surface);
646 655
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 465084fea..b5a02700d 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -214,13 +214,17 @@ void GameList::OnTextChanged(const QString& new_text) {
214 const int children_count = folder->rowCount(); 214 const int children_count = folder->rowCount();
215 for (int j = 0; j < children_count; ++j) { 215 for (int j = 0; j < children_count; ++j) {
216 ++children_total; 216 ++children_total;
217
217 const QStandardItem* child = folder->child(j, 0); 218 const QStandardItem* child = folder->child(j, 0);
219
220 const auto program_id = child->data(GameListItemPath::ProgramIdRole).toULongLong();
221
218 const QString file_path = 222 const QString file_path =
219 child->data(GameListItemPath::FullPathRole).toString().toLower(); 223 child->data(GameListItemPath::FullPathRole).toString().toLower();
220 const QString file_title = 224 const QString file_title =
221 child->data(GameListItemPath::TitleRole).toString().toLower(); 225 child->data(GameListItemPath::TitleRole).toString().toLower();
222 const QString file_program_id = 226 const QString file_program_id =
223 child->data(GameListItemPath::ProgramIdRole).toString().toLower(); 227 QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'});
224 228
225 // Only items which filename in combination with its title contains all words 229 // Only items which filename in combination with its title contains all words
226 // that are in the searchfield will be visible in the gamelist 230 // that are in the searchfield will be visible in the gamelist
@@ -231,7 +235,7 @@ void GameList::OnTextChanged(const QString& new_text) {
231 file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + 235 file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} +
232 file_title; 236 file_title;
233 if (ContainsAllWords(file_name, edit_filter_text) || 237 if (ContainsAllWords(file_name, edit_filter_text) ||
234 (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) { 238 (file_program_id.count() == 16 && file_program_id.contains(edit_filter_text))) {
235 tree_view->setRowHidden(j, folder_index, false); 239 tree_view->setRowHidden(j, folder_index, false);
236 ++result_count; 240 ++result_count;
237 } else { 241 } else {