diff options
121 files changed, 5408 insertions, 982 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 431f899b3..84a3308b7 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts | |||
| @@ -214,7 +214,7 @@ dependencies { | |||
| 214 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") | 214 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") |
| 215 | implementation("io.coil-kt:coil:2.2.2") | 215 | implementation("io.coil-kt:coil:2.2.2") |
| 216 | implementation("androidx.core:core-splashscreen:1.0.1") | 216 | implementation("androidx.core:core-splashscreen:1.0.1") |
| 217 | implementation("androidx.window:window:1.1.0") | 217 | implementation("androidx.window:window:1.2.0-beta03") |
| 218 | implementation("org.ini4j:ini4j:0.5.4") | 218 | implementation("org.ini4j:ini4j:0.5.4") |
| 219 | implementation("androidx.constraintlayout:constraintlayout:2.1.4") | 219 | implementation("androidx.constraintlayout:constraintlayout:2.1.4") |
| 220 | implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") | 220 | implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 21f67f32a..6e39e542b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt | |||
| @@ -247,7 +247,12 @@ object NativeLibrary { | |||
| 247 | 247 | ||
| 248 | external fun setAppDirectory(directory: String) | 248 | external fun setAppDirectory(directory: String) |
| 249 | 249 | ||
| 250 | external fun installFileToNand(filename: String): Int | 250 | /** |
| 251 | * Installs a nsp or xci file to nand | ||
| 252 | * @param filename String representation of file uri | ||
| 253 | * @param extension Lowercase string representation of file extension without "." | ||
| 254 | */ | ||
| 255 | external fun installFileToNand(filename: String, extension: String): Int | ||
| 251 | 256 | ||
| 252 | external fun initializeGpuDriver( | 257 | external fun initializeGpuDriver( |
| 253 | hookLibDir: String?, | 258 | hookLibDir: String?, |
| @@ -512,6 +517,11 @@ object NativeLibrary { | |||
| 512 | external fun submitInlineKeyboardInput(key_code: Int) | 517 | external fun submitInlineKeyboardInput(key_code: Int) |
| 513 | 518 | ||
| 514 | /** | 519 | /** |
| 520 | * Creates a generic user directory if it doesn't exist already | ||
| 521 | */ | ||
| 522 | external fun initializeEmptyUserDirectory() | ||
| 523 | |||
| 524 | /** | ||
| 515 | * Button type for use in onTouchEvent | 525 | * Button type for use in onTouchEvent |
| 516 | */ | 526 | */ |
| 517 | object ButtonType { | 527 | object ButtonType { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt index 1675627a1..58ce343f4 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt | |||
| @@ -49,6 +49,7 @@ class HomeSettingAdapter( | |||
| 49 | holder.option.onClick.invoke() | 49 | holder.option.onClick.invoke() |
| 50 | } else { | 50 | } else { |
| 51 | MessageDialogFragment.newInstance( | 51 | MessageDialogFragment.newInstance( |
| 52 | activity, | ||
| 52 | titleId = holder.option.disabledTitleId, | 53 | titleId = holder.option.disabledTitleId, |
| 53 | descriptionId = holder.option.disabledMessageId | 54 | descriptionId = holder.option.disabledMessageId |
| 54 | ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) | 55 | ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt new file mode 100644 index 000000000..e960fbaab --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.adapters | ||
| 5 | |||
| 6 | import android.view.LayoutInflater | ||
| 7 | import android.view.View | ||
| 8 | import android.view.ViewGroup | ||
| 9 | import androidx.recyclerview.widget.RecyclerView | ||
| 10 | import org.yuzu.yuzu_emu.databinding.CardInstallableBinding | ||
| 11 | import org.yuzu.yuzu_emu.model.Installable | ||
| 12 | |||
| 13 | class InstallableAdapter(private val installables: List<Installable>) : | ||
| 14 | RecyclerView.Adapter<InstallableAdapter.InstallableViewHolder>() { | ||
| 15 | override fun onCreateViewHolder( | ||
| 16 | parent: ViewGroup, | ||
| 17 | viewType: Int | ||
| 18 | ): InstallableAdapter.InstallableViewHolder { | ||
| 19 | val binding = | ||
| 20 | CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false) | ||
| 21 | return InstallableViewHolder(binding) | ||
| 22 | } | ||
| 23 | |||
| 24 | override fun getItemCount(): Int = installables.size | ||
| 25 | |||
| 26 | override fun onBindViewHolder(holder: InstallableAdapter.InstallableViewHolder, position: Int) = | ||
| 27 | holder.bind(installables[position]) | ||
| 28 | |||
| 29 | inner class InstallableViewHolder(val binding: CardInstallableBinding) : | ||
| 30 | RecyclerView.ViewHolder(binding.root) { | ||
| 31 | lateinit var installable: Installable | ||
| 32 | |||
| 33 | fun bind(installable: Installable) { | ||
| 34 | this.installable = installable | ||
| 35 | |||
| 36 | binding.title.setText(installable.titleId) | ||
| 37 | binding.description.setText(installable.descriptionId) | ||
| 38 | |||
| 39 | if (installable.install != null) { | ||
| 40 | binding.buttonInstall.visibility = View.VISIBLE | ||
| 41 | binding.buttonInstall.setOnClickListener { installable.install.invoke() } | ||
| 42 | } | ||
| 43 | if (installable.export != null) { | ||
| 44 | binding.buttonExport.visibility = View.VISIBLE | ||
| 45 | binding.buttonExport.setOnClickListener { installable.export.invoke() } | ||
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt index 4d2f2f604..c73edd50e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt | |||
| @@ -21,6 +21,7 @@ import androidx.navigation.navArgs | |||
| 21 | import com.google.android.material.color.MaterialColors | 21 | import com.google.android.material.color.MaterialColors |
| 22 | import kotlinx.coroutines.flow.collectLatest | 22 | import kotlinx.coroutines.flow.collectLatest |
| 23 | import kotlinx.coroutines.launch | 23 | import kotlinx.coroutines.launch |
| 24 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 24 | import java.io.IOException | 25 | import java.io.IOException |
| 25 | import org.yuzu.yuzu_emu.R | 26 | import org.yuzu.yuzu_emu.R |
| 26 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding | 27 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding |
| @@ -168,7 +169,7 @@ class SettingsActivity : AppCompatActivity() { | |||
| 168 | if (!settingsFile.delete()) { | 169 | if (!settingsFile.delete()) { |
| 169 | throw IOException("Failed to delete $settingsFile") | 170 | throw IOException("Failed to delete $settingsFile") |
| 170 | } | 171 | } |
| 171 | Settings.settingsList.forEach { it.reset() } | 172 | NativeLibrary.reloadSettings() |
| 172 | 173 | ||
| 173 | Toast.makeText( | 174 | Toast.makeText( |
| 174 | applicationContext, | 175 | applicationContext, |
| @@ -181,12 +182,14 @@ class SettingsActivity : AppCompatActivity() { | |||
| 181 | private fun setInsets() { | 182 | private fun setInsets() { |
| 182 | ViewCompat.setOnApplyWindowInsetsListener( | 183 | ViewCompat.setOnApplyWindowInsetsListener( |
| 183 | binding.navigationBarShade | 184 | binding.navigationBarShade |
| 184 | ) { view: View, windowInsets: WindowInsetsCompat -> | 185 | ) { _: View, windowInsets: WindowInsetsCompat -> |
| 185 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 186 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 186 | 187 | ||
| 187 | val mlpShade = view.layoutParams as MarginLayoutParams | 188 | // The only situation where we care to have a nav bar shade is when it's at the bottom |
| 188 | mlpShade.height = barInsets.bottom | 189 | // of the screen where scrolling list elements can go behind it. |
| 189 | view.layoutParams = mlpShade | 190 | val mlpNavShade = binding.navigationBarShade.layoutParams as MarginLayoutParams |
| 191 | mlpNavShade.height = barInsets.bottom | ||
| 192 | binding.navigationBarShade.layoutParams = mlpNavShade | ||
| 190 | 193 | ||
| 191 | windowInsets | 194 | windowInsets |
| 192 | } | 195 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 750638bc9..e6ad2aa77 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt | |||
| @@ -17,6 +17,7 @@ import android.os.Handler | |||
| 17 | import android.os.Looper | 17 | import android.os.Looper |
| 18 | import android.view.* | 18 | import android.view.* |
| 19 | import android.widget.TextView | 19 | import android.widget.TextView |
| 20 | import android.widget.Toast | ||
| 20 | import androidx.activity.OnBackPressedCallback | 21 | import androidx.activity.OnBackPressedCallback |
| 21 | import androidx.appcompat.widget.PopupMenu | 22 | import androidx.appcompat.widget.PopupMenu |
| 22 | import androidx.core.content.res.ResourcesCompat | 23 | import androidx.core.content.res.ResourcesCompat |
| @@ -53,6 +54,7 @@ import org.yuzu.yuzu_emu.model.Game | |||
| 53 | import org.yuzu.yuzu_emu.model.EmulationViewModel | 54 | import org.yuzu.yuzu_emu.model.EmulationViewModel |
| 54 | import org.yuzu.yuzu_emu.overlay.InputOverlay | 55 | import org.yuzu.yuzu_emu.overlay.InputOverlay |
| 55 | import org.yuzu.yuzu_emu.utils.* | 56 | import org.yuzu.yuzu_emu.utils.* |
| 57 | import java.lang.NullPointerException | ||
| 56 | 58 | ||
| 57 | class EmulationFragment : Fragment(), SurfaceHolder.Callback { | 59 | class EmulationFragment : Fragment(), SurfaceHolder.Callback { |
| 58 | private lateinit var preferences: SharedPreferences | 60 | private lateinit var preferences: SharedPreferences |
| @@ -104,10 +106,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 104 | null | 106 | null |
| 105 | } | 107 | } |
| 106 | } | 108 | } |
| 107 | game = if (args.game != null) { | 109 | |
| 108 | args.game!! | 110 | try { |
| 109 | } else { | 111 | game = if (args.game != null) { |
| 110 | intentGame ?: error("[EmulationFragment] No bootable game present!") | 112 | args.game!! |
| 113 | } else { | ||
| 114 | intentGame!! | ||
| 115 | } | ||
| 116 | } catch (e: NullPointerException) { | ||
| 117 | Toast.makeText( | ||
| 118 | requireContext(), | ||
| 119 | R.string.no_game_present, | ||
| 120 | Toast.LENGTH_SHORT | ||
| 121 | ).show() | ||
| 122 | requireActivity().finish() | ||
| 123 | return | ||
| 111 | } | 124 | } |
| 112 | 125 | ||
| 113 | // So this fragment doesn't restart on configuration changes; i.e. rotation. | 126 | // So this fragment doesn't restart on configuration changes; i.e. rotation. |
| @@ -131,6 +144,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 131 | // This is using the correct scope, lint is just acting up | 144 | // This is using the correct scope, lint is just acting up |
| 132 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | 145 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") |
| 133 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 146 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| 147 | super.onViewCreated(view, savedInstanceState) | ||
| 148 | if (requireActivity().isFinishing) { | ||
| 149 | return | ||
| 150 | } | ||
| 151 | |||
| 134 | binding.surfaceEmulation.holder.addCallback(this) | 152 | binding.surfaceEmulation.holder.addCallback(this) |
| 135 | binding.showFpsText.setTextColor(Color.YELLOW) | 153 | binding.showFpsText.setTextColor(Color.YELLOW) |
| 136 | binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } | 154 | binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } |
| @@ -286,25 +304,23 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 286 | 304 | ||
| 287 | override fun onConfigurationChanged(newConfig: Configuration) { | 305 | override fun onConfigurationChanged(newConfig: Configuration) { |
| 288 | super.onConfigurationChanged(newConfig) | 306 | super.onConfigurationChanged(newConfig) |
| 307 | if (_binding == null) { | ||
| 308 | return | ||
| 309 | } | ||
| 310 | |||
| 289 | updateScreenLayout() | 311 | updateScreenLayout() |
| 290 | if (emulationActivity?.isInPictureInPictureMode == true) { | 312 | if (emulationActivity?.isInPictureInPictureMode == true) { |
| 291 | if (binding.drawerLayout.isOpen) { | 313 | if (binding.drawerLayout.isOpen) { |
| 292 | binding.drawerLayout.close() | 314 | binding.drawerLayout.close() |
| 293 | } | 315 | } |
| 294 | if (EmulationMenuSettings.showOverlay) { | 316 | if (EmulationMenuSettings.showOverlay) { |
| 295 | binding.surfaceInputOverlay.post { | 317 | binding.surfaceInputOverlay.visibility = View.INVISIBLE |
| 296 | binding.surfaceInputOverlay.visibility = View.INVISIBLE | ||
| 297 | } | ||
| 298 | } | 318 | } |
| 299 | } else { | 319 | } else { |
| 300 | if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) { | 320 | if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) { |
| 301 | binding.surfaceInputOverlay.post { | 321 | binding.surfaceInputOverlay.visibility = View.VISIBLE |
| 302 | binding.surfaceInputOverlay.visibility = View.VISIBLE | ||
| 303 | } | ||
| 304 | } else { | 322 | } else { |
| 305 | binding.surfaceInputOverlay.post { | 323 | binding.surfaceInputOverlay.visibility = View.INVISIBLE |
| 306 | binding.surfaceInputOverlay.visibility = View.INVISIBLE | ||
| 307 | } | ||
| 308 | } | 324 | } |
| 309 | if (!isInFoldableLayout) { | 325 | if (!isInFoldableLayout) { |
| 310 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { | 326 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index c119e69c9..8923c0ea2 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt | |||
| @@ -118,18 +118,13 @@ class HomeSettingsFragment : Fragment() { | |||
| 118 | ) | 118 | ) |
| 119 | add( | 119 | add( |
| 120 | HomeSetting( | 120 | HomeSetting( |
| 121 | R.string.install_amiibo_keys, | 121 | R.string.manage_yuzu_data, |
| 122 | R.string.install_amiibo_keys_description, | 122 | R.string.manage_yuzu_data_description, |
| 123 | R.drawable.ic_nfc, | 123 | R.drawable.ic_install, |
| 124 | { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) } | 124 | { |
| 125 | ) | 125 | binding.root.findNavController() |
| 126 | ) | 126 | .navigate(R.id.action_homeSettingsFragment_to_installableFragment) |
| 127 | add( | 127 | } |
| 128 | HomeSetting( | ||
| 129 | R.string.install_game_content, | ||
| 130 | R.string.install_game_content_description, | ||
| 131 | R.drawable.ic_system_update_alt, | ||
| 132 | { mainActivity.installGameUpdate.launch(arrayOf("*/*")) } | ||
| 133 | ) | 128 | ) |
| 134 | ) | 129 | ) |
| 135 | add( | 130 | add( |
| @@ -150,35 +145,6 @@ class HomeSettingsFragment : Fragment() { | |||
| 150 | ) | 145 | ) |
| 151 | add( | 146 | add( |
| 152 | HomeSetting( | 147 | HomeSetting( |
| 153 | R.string.manage_save_data, | ||
| 154 | R.string.import_export_saves_description, | ||
| 155 | R.drawable.ic_save, | ||
| 156 | { | ||
| 157 | ImportExportSavesFragment().show( | ||
| 158 | parentFragmentManager, | ||
| 159 | ImportExportSavesFragment.TAG | ||
| 160 | ) | ||
| 161 | } | ||
| 162 | ) | ||
| 163 | ) | ||
| 164 | add( | ||
| 165 | HomeSetting( | ||
| 166 | R.string.install_prod_keys, | ||
| 167 | R.string.install_prod_keys_description, | ||
| 168 | R.drawable.ic_unlock, | ||
| 169 | { mainActivity.getProdKey.launch(arrayOf("*/*")) } | ||
| 170 | ) | ||
| 171 | ) | ||
| 172 | add( | ||
| 173 | HomeSetting( | ||
| 174 | R.string.install_firmware, | ||
| 175 | R.string.install_firmware_description, | ||
| 176 | R.drawable.ic_firmware, | ||
| 177 | { mainActivity.getFirmware.launch(arrayOf("application/zip")) } | ||
| 178 | ) | ||
| 179 | ) | ||
| 180 | add( | ||
| 181 | HomeSetting( | ||
| 182 | R.string.share_log, | 148 | R.string.share_log, |
| 183 | R.string.share_log_description, | 149 | R.string.share_log_description, |
| 184 | R.drawable.ic_log, | 150 | R.drawable.ic_log, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt deleted file mode 100644 index f38aeea53..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt +++ /dev/null | |||
| @@ -1,213 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.app.Dialog | ||
| 7 | import android.content.Intent | ||
| 8 | import android.net.Uri | ||
| 9 | import android.os.Bundle | ||
| 10 | import android.provider.DocumentsContract | ||
| 11 | import android.widget.Toast | ||
| 12 | import androidx.activity.result.ActivityResultLauncher | ||
| 13 | import androidx.activity.result.contract.ActivityResultContracts | ||
| 14 | import androidx.appcompat.app.AppCompatActivity | ||
| 15 | import androidx.documentfile.provider.DocumentFile | ||
| 16 | import androidx.fragment.app.DialogFragment | ||
| 17 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 18 | import java.io.BufferedOutputStream | ||
| 19 | import java.io.File | ||
| 20 | import java.io.FileOutputStream | ||
| 21 | import java.io.FilenameFilter | ||
| 22 | import java.time.LocalDateTime | ||
| 23 | import java.time.format.DateTimeFormatter | ||
| 24 | import java.util.zip.ZipEntry | ||
| 25 | import java.util.zip.ZipOutputStream | ||
| 26 | import kotlinx.coroutines.CoroutineScope | ||
| 27 | import kotlinx.coroutines.Dispatchers | ||
| 28 | import kotlinx.coroutines.launch | ||
| 29 | import kotlinx.coroutines.withContext | ||
| 30 | import org.yuzu.yuzu_emu.R | ||
| 31 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 32 | import org.yuzu.yuzu_emu.features.DocumentProvider | ||
| 33 | import org.yuzu.yuzu_emu.getPublicFilesDir | ||
| 34 | import org.yuzu.yuzu_emu.utils.FileUtil | ||
| 35 | |||
| 36 | class ImportExportSavesFragment : DialogFragment() { | ||
| 37 | private val context = YuzuApplication.appContext | ||
| 38 | private val savesFolder = | ||
| 39 | "${context.getPublicFilesDir().canonicalPath}/nand/user/save/0000000000000000" | ||
| 40 | |||
| 41 | // Get first subfolder in saves folder (should be the user folder) | ||
| 42 | private val savesFolderRoot = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: "" | ||
| 43 | private var lastZipCreated: File? = null | ||
| 44 | |||
| 45 | private lateinit var startForResultExportSave: ActivityResultLauncher<Intent> | ||
| 46 | private lateinit var documentPicker: ActivityResultLauncher<Array<String>> | ||
| 47 | |||
| 48 | override fun onCreate(savedInstanceState: Bundle?) { | ||
| 49 | super.onCreate(savedInstanceState) | ||
| 50 | val activity = requireActivity() as AppCompatActivity | ||
| 51 | |||
| 52 | val activityResultRegistry = requireActivity().activityResultRegistry | ||
| 53 | startForResultExportSave = activityResultRegistry.register( | ||
| 54 | "startForResultExportSaveKey", | ||
| 55 | ActivityResultContracts.StartActivityForResult() | ||
| 56 | ) { | ||
| 57 | File(context.getPublicFilesDir().canonicalPath, "temp").deleteRecursively() | ||
| 58 | } | ||
| 59 | documentPicker = activityResultRegistry.register( | ||
| 60 | "documentPickerKey", | ||
| 61 | ActivityResultContracts.OpenDocument() | ||
| 62 | ) { | ||
| 63 | it?.let { uri -> importSave(uri, activity) } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
| 68 | return if (savesFolderRoot == "") { | ||
| 69 | MaterialAlertDialogBuilder(requireContext()) | ||
| 70 | .setTitle(R.string.manage_save_data) | ||
| 71 | .setMessage(R.string.import_export_saves_no_profile) | ||
| 72 | .setPositiveButton(android.R.string.ok, null) | ||
| 73 | .show() | ||
| 74 | } else { | ||
| 75 | MaterialAlertDialogBuilder(requireContext()) | ||
| 76 | .setTitle(R.string.manage_save_data) | ||
| 77 | .setMessage(R.string.manage_save_data_description) | ||
| 78 | .setNegativeButton(R.string.export_saves) { _, _ -> | ||
| 79 | exportSave() | ||
| 80 | } | ||
| 81 | .setPositiveButton(R.string.import_saves) { _, _ -> | ||
| 82 | documentPicker.launch(arrayOf("application/zip")) | ||
| 83 | } | ||
| 84 | .setNeutralButton(android.R.string.cancel, null) | ||
| 85 | .show() | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | /** | ||
| 90 | * Zips the save files located in the given folder path and creates a new zip file with the current date and time. | ||
| 91 | * @return true if the zip file is successfully created, false otherwise. | ||
| 92 | */ | ||
| 93 | private fun zipSave(): Boolean { | ||
| 94 | try { | ||
| 95 | val tempFolder = File(requireContext().getPublicFilesDir().canonicalPath, "temp") | ||
| 96 | tempFolder.mkdirs() | ||
| 97 | val saveFolder = File(savesFolderRoot) | ||
| 98 | val outputZipFile = File( | ||
| 99 | tempFolder, | ||
| 100 | "yuzu saves - ${ | ||
| 101 | LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) | ||
| 102 | }.zip" | ||
| 103 | ) | ||
| 104 | outputZipFile.createNewFile() | ||
| 105 | ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos -> | ||
| 106 | saveFolder.walkTopDown().forEach { file -> | ||
| 107 | val zipFileName = | ||
| 108 | file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/") | ||
| 109 | if (zipFileName == "") { | ||
| 110 | return@forEach | ||
| 111 | } | ||
| 112 | val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}") | ||
| 113 | zos.putNextEntry(entry) | ||
| 114 | if (file.isFile) { | ||
| 115 | file.inputStream().use { fis -> fis.copyTo(zos) } | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | lastZipCreated = outputZipFile | ||
| 120 | } catch (e: Exception) { | ||
| 121 | return false | ||
| 122 | } | ||
| 123 | return true | ||
| 124 | } | ||
| 125 | |||
| 126 | /** | ||
| 127 | * Exports the save file located in the given folder path by creating a zip file and sharing it via intent. | ||
| 128 | */ | ||
| 129 | private fun exportSave() { | ||
| 130 | CoroutineScope(Dispatchers.IO).launch { | ||
| 131 | val wasZipCreated = zipSave() | ||
| 132 | val lastZipFile = lastZipCreated | ||
| 133 | if (!wasZipCreated || lastZipFile == null) { | ||
| 134 | withContext(Dispatchers.Main) { | ||
| 135 | Toast.makeText(context, "Failed to export save", Toast.LENGTH_LONG).show() | ||
| 136 | } | ||
| 137 | return@launch | ||
| 138 | } | ||
| 139 | |||
| 140 | withContext(Dispatchers.Main) { | ||
| 141 | val file = DocumentFile.fromSingleUri( | ||
| 142 | context, | ||
| 143 | DocumentsContract.buildDocumentUri( | ||
| 144 | DocumentProvider.AUTHORITY, | ||
| 145 | "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}" | ||
| 146 | ) | ||
| 147 | )!! | ||
| 148 | val intent = Intent(Intent.ACTION_SEND) | ||
| 149 | .setDataAndType(file.uri, "application/zip") | ||
| 150 | .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) | ||
| 151 | .putExtra(Intent.EXTRA_STREAM, file.uri) | ||
| 152 | startForResultExportSave.launch(Intent.createChooser(intent, "Share save file")) | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | /** | ||
| 158 | * Imports the save files contained in the zip file, and replaces any existing ones with the new save file. | ||
| 159 | * @param zipUri The Uri of the zip file containing the save file(s) to import. | ||
| 160 | */ | ||
| 161 | private fun importSave(zipUri: Uri, activity: AppCompatActivity) { | ||
| 162 | val inputZip = context.contentResolver.openInputStream(zipUri) | ||
| 163 | // A zip needs to have at least one subfolder named after a TitleId in order to be considered valid. | ||
| 164 | var validZip = false | ||
| 165 | val savesFolder = File(savesFolderRoot) | ||
| 166 | val cacheSaveDir = File("${context.cacheDir.path}/saves/") | ||
| 167 | cacheSaveDir.mkdir() | ||
| 168 | |||
| 169 | if (inputZip == null) { | ||
| 170 | Toast.makeText(context, context.getString(R.string.fatal_error), Toast.LENGTH_LONG) | ||
| 171 | .show() | ||
| 172 | return | ||
| 173 | } | ||
| 174 | |||
| 175 | val filterTitleId = | ||
| 176 | FilenameFilter { _, dirName -> dirName.matches(Regex("^0100[\\dA-Fa-f]{12}$")) } | ||
| 177 | |||
| 178 | try { | ||
| 179 | CoroutineScope(Dispatchers.IO).launch { | ||
| 180 | FileUtil.unzip(inputZip, cacheSaveDir) | ||
| 181 | cacheSaveDir.list(filterTitleId)?.forEach { savePath -> | ||
| 182 | File(savesFolder, savePath).deleteRecursively() | ||
| 183 | File(cacheSaveDir, savePath).copyRecursively(File(savesFolder, savePath), true) | ||
| 184 | validZip = true | ||
| 185 | } | ||
| 186 | |||
| 187 | withContext(Dispatchers.Main) { | ||
| 188 | if (!validZip) { | ||
| 189 | MessageDialogFragment.newInstance( | ||
| 190 | titleId = R.string.save_file_invalid_zip_structure, | ||
| 191 | descriptionId = R.string.save_file_invalid_zip_structure_description | ||
| 192 | ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) | ||
| 193 | return@withContext | ||
| 194 | } | ||
| 195 | Toast.makeText( | ||
| 196 | context, | ||
| 197 | context.getString(R.string.save_file_imported_success), | ||
| 198 | Toast.LENGTH_LONG | ||
| 199 | ).show() | ||
| 200 | } | ||
| 201 | |||
| 202 | cacheSaveDir.deleteRecursively() | ||
| 203 | } | ||
| 204 | } catch (e: Exception) { | ||
| 205 | Toast.makeText(context, context.getString(R.string.fatal_error), Toast.LENGTH_LONG) | ||
| 206 | .show() | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | companion object { | ||
| 211 | const val TAG = "ImportExportSavesFragment" | ||
| 212 | } | ||
| 213 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt index 18bc34b9f..f128deda8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt | |||
| @@ -9,6 +9,7 @@ import android.view.LayoutInflater | |||
| 9 | import android.view.View | 9 | import android.view.View |
| 10 | import android.view.ViewGroup | 10 | import android.view.ViewGroup |
| 11 | import android.widget.Toast | 11 | import android.widget.Toast |
| 12 | import androidx.appcompat.app.AlertDialog | ||
| 12 | import androidx.appcompat.app.AppCompatActivity | 13 | import androidx.appcompat.app.AppCompatActivity |
| 13 | import androidx.fragment.app.DialogFragment | 14 | import androidx.fragment.app.DialogFragment |
| 14 | import androidx.fragment.app.activityViewModels | 15 | import androidx.fragment.app.activityViewModels |
| @@ -18,6 +19,7 @@ import androidx.lifecycle.lifecycleScope | |||
| 18 | import androidx.lifecycle.repeatOnLifecycle | 19 | import androidx.lifecycle.repeatOnLifecycle |
| 19 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 20 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 20 | import kotlinx.coroutines.launch | 21 | import kotlinx.coroutines.launch |
| 22 | import org.yuzu.yuzu_emu.R | ||
| 21 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | 23 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding |
| 22 | import org.yuzu.yuzu_emu.model.TaskViewModel | 24 | import org.yuzu.yuzu_emu.model.TaskViewModel |
| 23 | 25 | ||
| @@ -28,19 +30,25 @@ class IndeterminateProgressDialogFragment : DialogFragment() { | |||
| 28 | 30 | ||
| 29 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | 31 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { |
| 30 | val titleId = requireArguments().getInt(TITLE) | 32 | val titleId = requireArguments().getInt(TITLE) |
| 33 | val cancellable = requireArguments().getBoolean(CANCELLABLE) | ||
| 31 | 34 | ||
| 32 | binding = DialogProgressBarBinding.inflate(layoutInflater) | 35 | binding = DialogProgressBarBinding.inflate(layoutInflater) |
| 33 | binding.progressBar.isIndeterminate = true | 36 | binding.progressBar.isIndeterminate = true |
| 34 | val dialog = MaterialAlertDialogBuilder(requireContext()) | 37 | val dialog = MaterialAlertDialogBuilder(requireContext()) |
| 35 | .setTitle(titleId) | 38 | .setTitle(titleId) |
| 36 | .setView(binding.root) | 39 | .setView(binding.root) |
| 37 | .create() | 40 | |
| 38 | dialog.setCanceledOnTouchOutside(false) | 41 | if (cancellable) { |
| 42 | dialog.setNegativeButton(android.R.string.cancel, null) | ||
| 43 | } | ||
| 44 | |||
| 45 | val alertDialog = dialog.create() | ||
| 46 | alertDialog.setCanceledOnTouchOutside(false) | ||
| 39 | 47 | ||
| 40 | if (!taskViewModel.isRunning.value) { | 48 | if (!taskViewModel.isRunning.value) { |
| 41 | taskViewModel.runTask() | 49 | taskViewModel.runTask() |
| 42 | } | 50 | } |
| 43 | return dialog | 51 | return alertDialog |
| 44 | } | 52 | } |
| 45 | 53 | ||
| 46 | override fun onCreateView( | 54 | override fun onCreateView( |
| @@ -53,24 +61,50 @@ class IndeterminateProgressDialogFragment : DialogFragment() { | |||
| 53 | 61 | ||
| 54 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 62 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| 55 | super.onViewCreated(view, savedInstanceState) | 63 | super.onViewCreated(view, savedInstanceState) |
| 56 | viewLifecycleOwner.lifecycleScope.launch { | 64 | viewLifecycleOwner.lifecycleScope.apply { |
| 57 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 65 | launch { |
| 58 | taskViewModel.isComplete.collect { | 66 | repeatOnLifecycle(Lifecycle.State.CREATED) { |
| 59 | if (it) { | 67 | taskViewModel.isComplete.collect { |
| 60 | dismiss() | 68 | if (it) { |
| 61 | when (val result = taskViewModel.result.value) { | 69 | dismiss() |
| 62 | is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG) | 70 | when (val result = taskViewModel.result.value) { |
| 63 | .show() | 71 | is String -> Toast.makeText( |
| 64 | 72 | requireContext(), | |
| 65 | is MessageDialogFragment -> result.show( | 73 | result, |
| 66 | requireActivity().supportFragmentManager, | 74 | Toast.LENGTH_LONG |
| 67 | MessageDialogFragment.TAG | 75 | ).show() |
| 68 | ) | 76 | |
| 77 | is MessageDialogFragment -> result.show( | ||
| 78 | requireActivity().supportFragmentManager, | ||
| 79 | MessageDialogFragment.TAG | ||
| 80 | ) | ||
| 81 | } | ||
| 82 | taskViewModel.clear() | ||
| 69 | } | 83 | } |
| 70 | taskViewModel.clear() | ||
| 71 | } | 84 | } |
| 72 | } | 85 | } |
| 73 | } | 86 | } |
| 87 | launch { | ||
| 88 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 89 | taskViewModel.cancelled.collect { | ||
| 90 | if (it) { | ||
| 91 | dialog?.setTitle(R.string.cancelling) | ||
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | // By default, the ProgressDialog will immediately dismiss itself upon a button being pressed. | ||
| 100 | // Setting the OnClickListener again after the dialog is shown overrides this behavior. | ||
| 101 | override fun onResume() { | ||
| 102 | super.onResume() | ||
| 103 | val alertDialog = dialog as AlertDialog | ||
| 104 | val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE) | ||
| 105 | negativeButton.setOnClickListener { | ||
| 106 | alertDialog.setTitle(getString(R.string.cancelling)) | ||
| 107 | taskViewModel.setCancelled(true) | ||
| 74 | } | 108 | } |
| 75 | } | 109 | } |
| 76 | 110 | ||
| @@ -78,16 +112,19 @@ class IndeterminateProgressDialogFragment : DialogFragment() { | |||
| 78 | const val TAG = "IndeterminateProgressDialogFragment" | 112 | const val TAG = "IndeterminateProgressDialogFragment" |
| 79 | 113 | ||
| 80 | private const val TITLE = "Title" | 114 | private const val TITLE = "Title" |
| 115 | private const val CANCELLABLE = "Cancellable" | ||
| 81 | 116 | ||
| 82 | fun newInstance( | 117 | fun newInstance( |
| 83 | activity: AppCompatActivity, | 118 | activity: AppCompatActivity, |
| 84 | titleId: Int, | 119 | titleId: Int, |
| 120 | cancellable: Boolean = false, | ||
| 85 | task: () -> Any | 121 | task: () -> Any |
| 86 | ): IndeterminateProgressDialogFragment { | 122 | ): IndeterminateProgressDialogFragment { |
| 87 | val dialog = IndeterminateProgressDialogFragment() | 123 | val dialog = IndeterminateProgressDialogFragment() |
| 88 | val args = Bundle() | 124 | val args = Bundle() |
| 89 | ViewModelProvider(activity)[TaskViewModel::class.java].task = task | 125 | ViewModelProvider(activity)[TaskViewModel::class.java].task = task |
| 90 | args.putInt(TITLE, titleId) | 126 | args.putInt(TITLE, titleId) |
| 127 | args.putBoolean(CANCELLABLE, cancellable) | ||
| 91 | dialog.arguments = args | 128 | dialog.arguments = args |
| 92 | return dialog | 129 | return dialog |
| 93 | } | 130 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt new file mode 100644 index 000000000..ec116ab62 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt | |||
| @@ -0,0 +1,138 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.os.Bundle | ||
| 7 | import android.view.LayoutInflater | ||
| 8 | import android.view.View | ||
| 9 | import android.view.ViewGroup | ||
| 10 | import androidx.core.view.ViewCompat | ||
| 11 | import androidx.core.view.WindowInsetsCompat | ||
| 12 | import androidx.core.view.updatePadding | ||
| 13 | import androidx.fragment.app.Fragment | ||
| 14 | import androidx.fragment.app.activityViewModels | ||
| 15 | import androidx.navigation.findNavController | ||
| 16 | import androidx.recyclerview.widget.GridLayoutManager | ||
| 17 | import com.google.android.material.transition.MaterialSharedAxis | ||
| 18 | import org.yuzu.yuzu_emu.R | ||
| 19 | import org.yuzu.yuzu_emu.adapters.InstallableAdapter | ||
| 20 | import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding | ||
| 21 | import org.yuzu.yuzu_emu.model.HomeViewModel | ||
| 22 | import org.yuzu.yuzu_emu.model.Installable | ||
| 23 | import org.yuzu.yuzu_emu.ui.main.MainActivity | ||
| 24 | |||
| 25 | class InstallableFragment : Fragment() { | ||
| 26 | private var _binding: FragmentInstallablesBinding? = null | ||
| 27 | private val binding get() = _binding!! | ||
| 28 | |||
| 29 | private val homeViewModel: HomeViewModel by activityViewModels() | ||
| 30 | |||
| 31 | override fun onCreate(savedInstanceState: Bundle?) { | ||
| 32 | super.onCreate(savedInstanceState) | ||
| 33 | enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | ||
| 34 | returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) | ||
| 35 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) | ||
| 36 | } | ||
| 37 | |||
| 38 | override fun onCreateView( | ||
| 39 | inflater: LayoutInflater, | ||
| 40 | container: ViewGroup?, | ||
| 41 | savedInstanceState: Bundle? | ||
| 42 | ): View { | ||
| 43 | _binding = FragmentInstallablesBinding.inflate(layoutInflater) | ||
| 44 | return binding.root | ||
| 45 | } | ||
| 46 | |||
| 47 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| 48 | super.onViewCreated(view, savedInstanceState) | ||
| 49 | |||
| 50 | val mainActivity = requireActivity() as MainActivity | ||
| 51 | |||
| 52 | homeViewModel.setNavigationVisibility(visible = false, animated = true) | ||
| 53 | homeViewModel.setStatusBarShadeVisibility(visible = false) | ||
| 54 | |||
| 55 | binding.toolbarInstallables.setNavigationOnClickListener { | ||
| 56 | binding.root.findNavController().popBackStack() | ||
| 57 | } | ||
| 58 | |||
| 59 | val installables = listOf( | ||
| 60 | Installable( | ||
| 61 | R.string.user_data, | ||
| 62 | R.string.user_data_description, | ||
| 63 | install = { mainActivity.importUserData.launch(arrayOf("application/zip")) }, | ||
| 64 | export = { mainActivity.exportUserData.launch("export.zip") } | ||
| 65 | ), | ||
| 66 | Installable( | ||
| 67 | R.string.install_game_content, | ||
| 68 | R.string.install_game_content_description, | ||
| 69 | install = { mainActivity.installGameUpdate.launch(arrayOf("*/*")) } | ||
| 70 | ), | ||
| 71 | Installable( | ||
| 72 | R.string.install_firmware, | ||
| 73 | R.string.install_firmware_description, | ||
| 74 | install = { mainActivity.getFirmware.launch(arrayOf("application/zip")) } | ||
| 75 | ), | ||
| 76 | if (mainActivity.savesFolderRoot != "") { | ||
| 77 | Installable( | ||
| 78 | R.string.manage_save_data, | ||
| 79 | R.string.import_export_saves_description, | ||
| 80 | install = { mainActivity.importSaves.launch(arrayOf("application/zip")) }, | ||
| 81 | export = { mainActivity.exportSave() } | ||
| 82 | ) | ||
| 83 | } else { | ||
| 84 | Installable( | ||
| 85 | R.string.manage_save_data, | ||
| 86 | R.string.import_export_saves_description, | ||
| 87 | install = { mainActivity.importSaves.launch(arrayOf("application/zip")) } | ||
| 88 | ) | ||
| 89 | }, | ||
| 90 | Installable( | ||
| 91 | R.string.install_prod_keys, | ||
| 92 | R.string.install_prod_keys_description, | ||
| 93 | install = { mainActivity.getProdKey.launch(arrayOf("*/*")) } | ||
| 94 | ), | ||
| 95 | Installable( | ||
| 96 | R.string.install_amiibo_keys, | ||
| 97 | R.string.install_amiibo_keys_description, | ||
| 98 | install = { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) } | ||
| 99 | ) | ||
| 100 | ) | ||
| 101 | |||
| 102 | binding.listInstallables.apply { | ||
| 103 | layoutManager = GridLayoutManager( | ||
| 104 | requireContext(), | ||
| 105 | resources.getInteger(R.integer.grid_columns) | ||
| 106 | ) | ||
| 107 | adapter = InstallableAdapter(installables) | ||
| 108 | } | ||
| 109 | |||
| 110 | setInsets() | ||
| 111 | } | ||
| 112 | |||
| 113 | private fun setInsets() = | ||
| 114 | ViewCompat.setOnApplyWindowInsetsListener( | ||
| 115 | binding.root | ||
| 116 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 117 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | ||
| 118 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | ||
| 119 | |||
| 120 | val leftInsets = barInsets.left + cutoutInsets.left | ||
| 121 | val rightInsets = barInsets.right + cutoutInsets.right | ||
| 122 | |||
| 123 | val mlpAppBar = binding.toolbarInstallables.layoutParams as ViewGroup.MarginLayoutParams | ||
| 124 | mlpAppBar.leftMargin = leftInsets | ||
| 125 | mlpAppBar.rightMargin = rightInsets | ||
| 126 | binding.toolbarInstallables.layoutParams = mlpAppBar | ||
| 127 | |||
| 128 | val mlpScrollAbout = | ||
| 129 | binding.listInstallables.layoutParams as ViewGroup.MarginLayoutParams | ||
| 130 | mlpScrollAbout.leftMargin = leftInsets | ||
| 131 | mlpScrollAbout.rightMargin = rightInsets | ||
| 132 | binding.listInstallables.layoutParams = mlpScrollAbout | ||
| 133 | |||
| 134 | binding.listInstallables.updatePadding(bottom = barInsets.bottom) | ||
| 135 | |||
| 136 | windowInsets | ||
| 137 | } | ||
| 138 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt index 7d1c2c8dd..541b22f47 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt | |||
| @@ -4,14 +4,21 @@ | |||
| 4 | package org.yuzu.yuzu_emu.fragments | 4 | package org.yuzu.yuzu_emu.fragments |
| 5 | 5 | ||
| 6 | import android.app.Dialog | 6 | import android.app.Dialog |
| 7 | import android.content.DialogInterface | ||
| 7 | import android.content.Intent | 8 | import android.content.Intent |
| 8 | import android.net.Uri | 9 | import android.net.Uri |
| 9 | import android.os.Bundle | 10 | import android.os.Bundle |
| 10 | import androidx.fragment.app.DialogFragment | 11 | import androidx.fragment.app.DialogFragment |
| 12 | import androidx.fragment.app.FragmentActivity | ||
| 13 | import androidx.fragment.app.activityViewModels | ||
| 14 | import androidx.lifecycle.ViewModelProvider | ||
| 11 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 15 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 12 | import org.yuzu.yuzu_emu.R | 16 | import org.yuzu.yuzu_emu.R |
| 17 | import org.yuzu.yuzu_emu.model.MessageDialogViewModel | ||
| 13 | 18 | ||
| 14 | class MessageDialogFragment : DialogFragment() { | 19 | class MessageDialogFragment : DialogFragment() { |
| 20 | private val messageDialogViewModel: MessageDialogViewModel by activityViewModels() | ||
| 21 | |||
| 15 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | 22 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { |
| 16 | val titleId = requireArguments().getInt(TITLE_ID) | 23 | val titleId = requireArguments().getInt(TITLE_ID) |
| 17 | val titleString = requireArguments().getString(TITLE_STRING)!! | 24 | val titleString = requireArguments().getString(TITLE_STRING)!! |
| @@ -37,6 +44,12 @@ class MessageDialogFragment : DialogFragment() { | |||
| 37 | return dialog.show() | 44 | return dialog.show() |
| 38 | } | 45 | } |
| 39 | 46 | ||
| 47 | override fun onDismiss(dialog: DialogInterface) { | ||
| 48 | super.onDismiss(dialog) | ||
| 49 | messageDialogViewModel.dismissAction.invoke() | ||
| 50 | messageDialogViewModel.clear() | ||
| 51 | } | ||
| 52 | |||
| 40 | private fun openLink(link: String) { | 53 | private fun openLink(link: String) { |
| 41 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link)) | 54 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link)) |
| 42 | startActivity(intent) | 55 | startActivity(intent) |
| @@ -52,11 +65,13 @@ class MessageDialogFragment : DialogFragment() { | |||
| 52 | private const val HELP_LINK = "Link" | 65 | private const val HELP_LINK = "Link" |
| 53 | 66 | ||
| 54 | fun newInstance( | 67 | fun newInstance( |
| 68 | activity: FragmentActivity, | ||
| 55 | titleId: Int = 0, | 69 | titleId: Int = 0, |
| 56 | titleString: String = "", | 70 | titleString: String = "", |
| 57 | descriptionId: Int = 0, | 71 | descriptionId: Int = 0, |
| 58 | descriptionString: String = "", | 72 | descriptionString: String = "", |
| 59 | helpLinkId: Int = 0 | 73 | helpLinkId: Int = 0, |
| 74 | dismissAction: () -> Unit = {} | ||
| 60 | ): MessageDialogFragment { | 75 | ): MessageDialogFragment { |
| 61 | val dialog = MessageDialogFragment() | 76 | val dialog = MessageDialogFragment() |
| 62 | val bundle = Bundle() | 77 | val bundle = Bundle() |
| @@ -67,6 +82,8 @@ class MessageDialogFragment : DialogFragment() { | |||
| 67 | putString(DESCRIPTION_STRING, descriptionString) | 82 | putString(DESCRIPTION_STRING, descriptionString) |
| 68 | putInt(HELP_LINK, helpLinkId) | 83 | putInt(HELP_LINK, helpLinkId) |
| 69 | } | 84 | } |
| 85 | ViewModelProvider(activity)[MessageDialogViewModel::class.java].dismissAction = | ||
| 86 | dismissAction | ||
| 70 | dialog.arguments = bundle | 87 | dialog.arguments = bundle |
| 71 | return dialog | 88 | return dialog |
| 72 | } | 89 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt index fbb2f6e18..c66bb635a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt | |||
| @@ -295,8 +295,10 @@ class SetupFragment : Fragment() { | |||
| 295 | 295 | ||
| 296 | override fun onSaveInstanceState(outState: Bundle) { | 296 | override fun onSaveInstanceState(outState: Bundle) { |
| 297 | super.onSaveInstanceState(outState) | 297 | super.onSaveInstanceState(outState) |
| 298 | outState.putBoolean(KEY_NEXT_VISIBILITY, binding.buttonNext.isVisible) | 298 | if (_binding != null) { |
| 299 | outState.putBoolean(KEY_BACK_VISIBILITY, binding.buttonBack.isVisible) | 299 | outState.putBoolean(KEY_NEXT_VISIBILITY, binding.buttonNext.isVisible) |
| 300 | outState.putBoolean(KEY_BACK_VISIBILITY, binding.buttonBack.isVisible) | ||
| 301 | } | ||
| 300 | outState.putBooleanArray(KEY_HAS_BEEN_WARNED, hasBeenWarned) | 302 | outState.putBooleanArray(KEY_HAS_BEEN_WARNED, hasBeenWarned) |
| 301 | } | 303 | } |
| 302 | 304 | ||
| @@ -353,11 +355,15 @@ class SetupFragment : Fragment() { | |||
| 353 | } | 355 | } |
| 354 | 356 | ||
| 355 | fun pageForward() { | 357 | fun pageForward() { |
| 356 | binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 | 358 | if (_binding != null) { |
| 359 | binding.viewPager2.currentItem += 1 | ||
| 360 | } | ||
| 357 | } | 361 | } |
| 358 | 362 | ||
| 359 | fun pageBackward() { | 363 | fun pageBackward() { |
| 360 | binding.viewPager2.currentItem = binding.viewPager2.currentItem - 1 | 364 | if (_binding != null) { |
| 365 | binding.viewPager2.currentItem -= 1 | ||
| 366 | } | ||
| 361 | } | 367 | } |
| 362 | 368 | ||
| 363 | fun setPageWarned(page: Int) { | 369 | fun setPageWarned(page: Int) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt new file mode 100644 index 000000000..36a7c97b8 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | import androidx.annotation.StringRes | ||
| 7 | |||
| 8 | data class Installable( | ||
| 9 | @StringRes val titleId: Int, | ||
| 10 | @StringRes val descriptionId: Int, | ||
| 11 | val install: (() -> Unit)? = null, | ||
| 12 | val export: (() -> Unit)? = null | ||
| 13 | ) | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt new file mode 100644 index 000000000..36ffd08d2 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | import androidx.lifecycle.ViewModel | ||
| 7 | |||
| 8 | class MessageDialogViewModel : ViewModel() { | ||
| 9 | var dismissAction: () -> Unit = {} | ||
| 10 | |||
| 11 | fun clear() { | ||
| 12 | dismissAction = {} | ||
| 13 | } | ||
| 14 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt index 531c2aaf0..16a794dee 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt | |||
| @@ -20,12 +20,20 @@ class TaskViewModel : ViewModel() { | |||
| 20 | val isRunning: StateFlow<Boolean> get() = _isRunning | 20 | val isRunning: StateFlow<Boolean> get() = _isRunning |
| 21 | private val _isRunning = MutableStateFlow(false) | 21 | private val _isRunning = MutableStateFlow(false) |
| 22 | 22 | ||
| 23 | val cancelled: StateFlow<Boolean> get() = _cancelled | ||
| 24 | private val _cancelled = MutableStateFlow(false) | ||
| 25 | |||
| 23 | lateinit var task: () -> Any | 26 | lateinit var task: () -> Any |
| 24 | 27 | ||
| 25 | fun clear() { | 28 | fun clear() { |
| 26 | _result.value = Any() | 29 | _result.value = Any() |
| 27 | _isComplete.value = false | 30 | _isComplete.value = false |
| 28 | _isRunning.value = false | 31 | _isRunning.value = false |
| 32 | _cancelled.value = false | ||
| 33 | } | ||
| 34 | |||
| 35 | fun setCancelled(value: Boolean) { | ||
| 36 | _cancelled.value = value | ||
| 29 | } | 37 | } |
| 30 | 38 | ||
| 31 | fun runTask() { | 39 | fun runTask() { |
| @@ -42,3 +50,9 @@ class TaskViewModel : ViewModel() { | |||
| 42 | } | 50 | } |
| 43 | } | 51 | } |
| 44 | } | 52 | } |
| 53 | |||
| 54 | enum class TaskState { | ||
| 55 | Completed, | ||
| 56 | Failed, | ||
| 57 | Cancelled | ||
| 58 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt index c055c2e35..a13faf3c7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt | |||
| @@ -352,7 +352,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
| 352 | } | 352 | } |
| 353 | 353 | ||
| 354 | private fun addOverlayControls(layout: String) { | 354 | private fun addOverlayControls(layout: String) { |
| 355 | val windowSize = getSafeScreenSize(context) | 355 | val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight)) |
| 356 | if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) { | 356 | if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) { |
| 357 | overlayButtons.add( | 357 | overlayButtons.add( |
| 358 | initializeOverlayButton( | 358 | initializeOverlayButton( |
| @@ -593,7 +593,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
| 593 | } | 593 | } |
| 594 | 594 | ||
| 595 | private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) { | 595 | private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) { |
| 596 | val windowSize = getSafeScreenSize(context) | 596 | val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight)) |
| 597 | val min = windowSize.first | 597 | val min = windowSize.first |
| 598 | val max = windowSize.second | 598 | val max = windowSize.second |
| 599 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() | 599 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() |
| @@ -968,14 +968,17 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
| 968 | * @return A pair of points, the first being the top left corner of the safe area, | 968 | * @return A pair of points, the first being the top left corner of the safe area, |
| 969 | * the second being the bottom right corner of the safe area | 969 | * the second being the bottom right corner of the safe area |
| 970 | */ | 970 | */ |
| 971 | private fun getSafeScreenSize(context: Context): Pair<Point, Point> { | 971 | private fun getSafeScreenSize( |
| 972 | context: Context, | ||
| 973 | screenSize: Pair<Int, Int> | ||
| 974 | ): Pair<Point, Point> { | ||
| 972 | // Get screen size | 975 | // Get screen size |
| 973 | val windowMetrics = WindowMetricsCalculator.getOrCreate() | 976 | val windowMetrics = WindowMetricsCalculator.getOrCreate() |
| 974 | .computeCurrentWindowMetrics(context as Activity) | 977 | .computeCurrentWindowMetrics(context as Activity) |
| 975 | var maxY = windowMetrics.bounds.height().toFloat() | 978 | var maxX = screenSize.first.toFloat() |
| 976 | var maxX = windowMetrics.bounds.width().toFloat() | 979 | var maxY = screenSize.second.toFloat() |
| 977 | var minY = 0 | ||
| 978 | var minX = 0 | 980 | var minX = 0 |
| 981 | var minY = 0 | ||
| 979 | 982 | ||
| 980 | // If we have API access, calculate the safe area to draw the overlay | 983 | // If we have API access, calculate the safe area to draw the overlay |
| 981 | var cutoutLeft = 0 | 984 | var cutoutLeft = 0 |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index b6b6c6c17..0fa5df5e5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt | |||
| @@ -6,6 +6,7 @@ package org.yuzu.yuzu_emu.ui.main | |||
| 6 | import android.content.Intent | 6 | import android.content.Intent |
| 7 | import android.net.Uri | 7 | import android.net.Uri |
| 8 | import android.os.Bundle | 8 | import android.os.Bundle |
| 9 | import android.provider.DocumentsContract | ||
| 9 | import android.view.View | 10 | import android.view.View |
| 10 | import android.view.ViewGroup.MarginLayoutParams | 11 | import android.view.ViewGroup.MarginLayoutParams |
| 11 | import android.view.WindowManager | 12 | import android.view.WindowManager |
| @@ -19,6 +20,7 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen | |||
| 19 | import androidx.core.view.ViewCompat | 20 | import androidx.core.view.ViewCompat |
| 20 | import androidx.core.view.WindowCompat | 21 | import androidx.core.view.WindowCompat |
| 21 | import androidx.core.view.WindowInsetsCompat | 22 | import androidx.core.view.WindowInsetsCompat |
| 23 | import androidx.documentfile.provider.DocumentFile | ||
| 22 | import androidx.lifecycle.Lifecycle | 24 | import androidx.lifecycle.Lifecycle |
| 23 | import androidx.lifecycle.lifecycleScope | 25 | import androidx.lifecycle.lifecycleScope |
| 24 | import androidx.lifecycle.repeatOnLifecycle | 26 | import androidx.lifecycle.repeatOnLifecycle |
| @@ -29,6 +31,7 @@ import androidx.preference.PreferenceManager | |||
| 29 | import com.google.android.material.color.MaterialColors | 31 | import com.google.android.material.color.MaterialColors |
| 30 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 32 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 31 | import com.google.android.material.navigation.NavigationBarView | 33 | import com.google.android.material.navigation.NavigationBarView |
| 34 | import kotlinx.coroutines.CoroutineScope | ||
| 32 | import java.io.File | 35 | import java.io.File |
| 33 | import java.io.FilenameFilter | 36 | import java.io.FilenameFilter |
| 34 | import java.io.IOException | 37 | import java.io.IOException |
| @@ -41,21 +44,40 @@ import org.yuzu.yuzu_emu.R | |||
| 41 | import org.yuzu.yuzu_emu.activities.EmulationActivity | 44 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
| 42 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding | 45 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding |
| 43 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | 46 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding |
| 47 | import org.yuzu.yuzu_emu.features.DocumentProvider | ||
| 44 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 48 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 45 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment | 49 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment |
| 46 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | 50 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
| 51 | import org.yuzu.yuzu_emu.getPublicFilesDir | ||
| 47 | import org.yuzu.yuzu_emu.model.GamesViewModel | 52 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 48 | import org.yuzu.yuzu_emu.model.HomeViewModel | 53 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 54 | import org.yuzu.yuzu_emu.model.TaskState | ||
| 55 | import org.yuzu.yuzu_emu.model.TaskViewModel | ||
| 49 | import org.yuzu.yuzu_emu.utils.* | 56 | import org.yuzu.yuzu_emu.utils.* |
| 57 | import java.io.BufferedInputStream | ||
| 58 | import java.io.BufferedOutputStream | ||
| 59 | import java.io.FileOutputStream | ||
| 60 | import java.time.LocalDateTime | ||
| 61 | import java.time.format.DateTimeFormatter | ||
| 62 | import java.util.zip.ZipEntry | ||
| 63 | import java.util.zip.ZipInputStream | ||
| 50 | 64 | ||
| 51 | class MainActivity : AppCompatActivity(), ThemeProvider { | 65 | class MainActivity : AppCompatActivity(), ThemeProvider { |
| 52 | private lateinit var binding: ActivityMainBinding | 66 | private lateinit var binding: ActivityMainBinding |
| 53 | 67 | ||
| 54 | private val homeViewModel: HomeViewModel by viewModels() | 68 | private val homeViewModel: HomeViewModel by viewModels() |
| 55 | private val gamesViewModel: GamesViewModel by viewModels() | 69 | private val gamesViewModel: GamesViewModel by viewModels() |
| 70 | private val taskViewModel: TaskViewModel by viewModels() | ||
| 56 | 71 | ||
| 57 | override var themeId: Int = 0 | 72 | override var themeId: Int = 0 |
| 58 | 73 | ||
| 74 | private val savesFolder | ||
| 75 | get() = "${getPublicFilesDir().canonicalPath}/nand/user/save/0000000000000000" | ||
| 76 | |||
| 77 | // Get first subfolder in saves folder (should be the user folder) | ||
| 78 | val savesFolderRoot get() = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: "" | ||
| 79 | private var lastZipCreated: File? = null | ||
| 80 | |||
| 59 | override fun onCreate(savedInstanceState: Bundle?) { | 81 | override fun onCreate(savedInstanceState: Bundle?) { |
| 60 | val splashScreen = installSplashScreen() | 82 | val splashScreen = installSplashScreen() |
| 61 | splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } | 83 | splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } |
| @@ -307,6 +329,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 307 | fun processKey(result: Uri): Boolean { | 329 | fun processKey(result: Uri): Boolean { |
| 308 | if (FileUtil.getExtension(result) != "keys") { | 330 | if (FileUtil.getExtension(result) != "keys") { |
| 309 | MessageDialogFragment.newInstance( | 331 | MessageDialogFragment.newInstance( |
| 332 | this, | ||
| 310 | titleId = R.string.reading_keys_failure, | 333 | titleId = R.string.reading_keys_failure, |
| 311 | descriptionId = R.string.install_prod_keys_failure_extension_description | 334 | descriptionId = R.string.install_prod_keys_failure_extension_description |
| 312 | ).show(supportFragmentManager, MessageDialogFragment.TAG) | 335 | ).show(supportFragmentManager, MessageDialogFragment.TAG) |
| @@ -336,6 +359,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 336 | return true | 359 | return true |
| 337 | } else { | 360 | } else { |
| 338 | MessageDialogFragment.newInstance( | 361 | MessageDialogFragment.newInstance( |
| 362 | this, | ||
| 339 | titleId = R.string.invalid_keys_error, | 363 | titleId = R.string.invalid_keys_error, |
| 340 | descriptionId = R.string.install_keys_failure_description, | 364 | descriptionId = R.string.install_keys_failure_description, |
| 341 | helpLinkId = R.string.dumping_keys_quickstart_link | 365 | helpLinkId = R.string.dumping_keys_quickstart_link |
| @@ -371,11 +395,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 371 | val task: () -> Any = { | 395 | val task: () -> Any = { |
| 372 | var messageToShow: Any | 396 | var messageToShow: Any |
| 373 | try { | 397 | try { |
| 374 | FileUtil.unzip(inputZip, cacheFirmwareDir) | 398 | FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheFirmwareDir) |
| 375 | val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 | 399 | val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 |
| 376 | val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 | 400 | val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 |
| 377 | messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { | 401 | messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { |
| 378 | MessageDialogFragment.newInstance( | 402 | MessageDialogFragment.newInstance( |
| 403 | this, | ||
| 379 | titleId = R.string.firmware_installed_failure, | 404 | titleId = R.string.firmware_installed_failure, |
| 380 | descriptionId = R.string.firmware_installed_failure_description | 405 | descriptionId = R.string.firmware_installed_failure_description |
| 381 | ) | 406 | ) |
| @@ -395,7 +420,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 395 | IndeterminateProgressDialogFragment.newInstance( | 420 | IndeterminateProgressDialogFragment.newInstance( |
| 396 | this, | 421 | this, |
| 397 | R.string.firmware_installing, | 422 | R.string.firmware_installing, |
| 398 | task | 423 | task = task |
| 399 | ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | 424 | ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) |
| 400 | } | 425 | } |
| 401 | 426 | ||
| @@ -407,6 +432,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 407 | 432 | ||
| 408 | if (FileUtil.getExtension(result) != "bin") { | 433 | if (FileUtil.getExtension(result) != "bin") { |
| 409 | MessageDialogFragment.newInstance( | 434 | MessageDialogFragment.newInstance( |
| 435 | this, | ||
| 410 | titleId = R.string.reading_keys_failure, | 436 | titleId = R.string.reading_keys_failure, |
| 411 | descriptionId = R.string.install_amiibo_keys_failure_extension_description | 437 | descriptionId = R.string.install_amiibo_keys_failure_extension_description |
| 412 | ).show(supportFragmentManager, MessageDialogFragment.TAG) | 438 | ).show(supportFragmentManager, MessageDialogFragment.TAG) |
| @@ -434,6 +460,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 434 | ).show() | 460 | ).show() |
| 435 | } else { | 461 | } else { |
| 436 | MessageDialogFragment.newInstance( | 462 | MessageDialogFragment.newInstance( |
| 463 | this, | ||
| 437 | titleId = R.string.invalid_keys_error, | 464 | titleId = R.string.invalid_keys_error, |
| 438 | descriptionId = R.string.install_keys_failure_description, | 465 | descriptionId = R.string.install_keys_failure_description, |
| 439 | helpLinkId = R.string.dumping_keys_quickstart_link | 466 | helpLinkId = R.string.dumping_keys_quickstart_link |
| @@ -501,7 +528,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 501 | if (documents.isNotEmpty()) { | 528 | if (documents.isNotEmpty()) { |
| 502 | IndeterminateProgressDialogFragment.newInstance( | 529 | IndeterminateProgressDialogFragment.newInstance( |
| 503 | this@MainActivity, | 530 | this@MainActivity, |
| 504 | R.string.install_game_content | 531 | R.string.installing_game_content |
| 505 | ) { | 532 | ) { |
| 506 | var installSuccess = 0 | 533 | var installSuccess = 0 |
| 507 | var installOverwrite = 0 | 534 | var installOverwrite = 0 |
| @@ -509,7 +536,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 509 | var errorExtension = 0 | 536 | var errorExtension = 0 |
| 510 | var errorOther = 0 | 537 | var errorOther = 0 |
| 511 | documents.forEach { | 538 | documents.forEach { |
| 512 | when (NativeLibrary.installFileToNand(it.toString())) { | 539 | when ( |
| 540 | NativeLibrary.installFileToNand( | ||
| 541 | it.toString(), | ||
| 542 | FileUtil.getExtension(it) | ||
| 543 | ) | ||
| 544 | ) { | ||
| 513 | NativeLibrary.InstallFileToNandResult.Success -> { | 545 | NativeLibrary.InstallFileToNandResult.Success -> { |
| 514 | installSuccess += 1 | 546 | installSuccess += 1 |
| 515 | } | 547 | } |
| @@ -583,12 +615,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 583 | installResult.append(separator) | 615 | installResult.append(separator) |
| 584 | } | 616 | } |
| 585 | return@newInstance MessageDialogFragment.newInstance( | 617 | return@newInstance MessageDialogFragment.newInstance( |
| 618 | this, | ||
| 586 | titleId = R.string.install_game_content_failure, | 619 | titleId = R.string.install_game_content_failure, |
| 587 | descriptionString = installResult.toString().trim(), | 620 | descriptionString = installResult.toString().trim(), |
| 588 | helpLinkId = R.string.install_game_content_help_link | 621 | helpLinkId = R.string.install_game_content_help_link |
| 589 | ) | 622 | ) |
| 590 | } else { | 623 | } else { |
| 591 | return@newInstance MessageDialogFragment.newInstance( | 624 | return@newInstance MessageDialogFragment.newInstance( |
| 625 | this, | ||
| 592 | titleId = R.string.install_game_content_success, | 626 | titleId = R.string.install_game_content_success, |
| 593 | descriptionString = installResult.toString().trim() | 627 | descriptionString = installResult.toString().trim() |
| 594 | ) | 628 | ) |
| @@ -596,4 +630,228 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 596 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | 630 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) |
| 597 | } | 631 | } |
| 598 | } | 632 | } |
| 633 | |||
| 634 | val exportUserData = registerForActivityResult( | ||
| 635 | ActivityResultContracts.CreateDocument("application/zip") | ||
| 636 | ) { result -> | ||
| 637 | if (result == null) { | ||
| 638 | return@registerForActivityResult | ||
| 639 | } | ||
| 640 | |||
| 641 | IndeterminateProgressDialogFragment.newInstance( | ||
| 642 | this, | ||
| 643 | R.string.exporting_user_data, | ||
| 644 | true | ||
| 645 | ) { | ||
| 646 | val zipResult = FileUtil.zipFromInternalStorage( | ||
| 647 | File(DirectoryInitialization.userDirectory!!), | ||
| 648 | DirectoryInitialization.userDirectory!!, | ||
| 649 | BufferedOutputStream(contentResolver.openOutputStream(result)), | ||
| 650 | taskViewModel.cancelled | ||
| 651 | ) | ||
| 652 | return@newInstance when (zipResult) { | ||
| 653 | TaskState.Completed -> getString(R.string.user_data_export_success) | ||
| 654 | TaskState.Failed -> R.string.export_failed | ||
| 655 | TaskState.Cancelled -> R.string.user_data_export_cancelled | ||
| 656 | } | ||
| 657 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | ||
| 658 | } | ||
| 659 | |||
| 660 | val importUserData = | ||
| 661 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | ||
| 662 | if (result == null) { | ||
| 663 | return@registerForActivityResult | ||
| 664 | } | ||
| 665 | |||
| 666 | IndeterminateProgressDialogFragment.newInstance( | ||
| 667 | this, | ||
| 668 | R.string.importing_user_data | ||
| 669 | ) { | ||
| 670 | val checkStream = | ||
| 671 | ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result))) | ||
| 672 | var isYuzuBackup = false | ||
| 673 | checkStream.use { stream -> | ||
| 674 | var ze: ZipEntry? = null | ||
| 675 | while (stream.nextEntry?.also { ze = it } != null) { | ||
| 676 | val itemName = ze!!.name.trim() | ||
| 677 | if (itemName == "/config/config.ini" || itemName == "config/config.ini") { | ||
| 678 | isYuzuBackup = true | ||
| 679 | return@use | ||
| 680 | } | ||
| 681 | } | ||
| 682 | } | ||
| 683 | if (!isYuzuBackup) { | ||
| 684 | return@newInstance MessageDialogFragment.newInstance( | ||
| 685 | this, | ||
| 686 | titleId = R.string.invalid_yuzu_backup, | ||
| 687 | descriptionId = R.string.user_data_import_failed_description | ||
| 688 | ) | ||
| 689 | } | ||
| 690 | |||
| 691 | // Clear existing user data | ||
| 692 | File(DirectoryInitialization.userDirectory!!).deleteRecursively() | ||
| 693 | |||
| 694 | // Copy archive to internal storage | ||
| 695 | try { | ||
| 696 | FileUtil.unzipToInternalStorage( | ||
| 697 | BufferedInputStream(contentResolver.openInputStream(result)), | ||
| 698 | File(DirectoryInitialization.userDirectory!!) | ||
| 699 | ) | ||
| 700 | } catch (e: Exception) { | ||
| 701 | return@newInstance MessageDialogFragment.newInstance( | ||
| 702 | this, | ||
| 703 | titleId = R.string.import_failed, | ||
| 704 | descriptionId = R.string.user_data_import_failed_description | ||
| 705 | ) | ||
| 706 | } | ||
| 707 | |||
| 708 | // Reinitialize relevant data | ||
| 709 | NativeLibrary.initializeEmulation() | ||
| 710 | gamesViewModel.reloadGames(false) | ||
| 711 | |||
| 712 | return@newInstance getString(R.string.user_data_import_success) | ||
| 713 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | ||
| 714 | } | ||
| 715 | |||
| 716 | /** | ||
| 717 | * Zips the save files located in the given folder path and creates a new zip file with the current date and time. | ||
| 718 | * @return true if the zip file is successfully created, false otherwise. | ||
| 719 | */ | ||
| 720 | private fun zipSave(): Boolean { | ||
| 721 | try { | ||
| 722 | val tempFolder = File(getPublicFilesDir().canonicalPath, "temp") | ||
| 723 | tempFolder.mkdirs() | ||
| 724 | val saveFolder = File(savesFolderRoot) | ||
| 725 | val outputZipFile = File( | ||
| 726 | tempFolder, | ||
| 727 | "yuzu saves - ${ | ||
| 728 | LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) | ||
| 729 | }.zip" | ||
| 730 | ) | ||
| 731 | outputZipFile.createNewFile() | ||
| 732 | val result = FileUtil.zipFromInternalStorage( | ||
| 733 | saveFolder, | ||
| 734 | savesFolderRoot, | ||
| 735 | BufferedOutputStream(FileOutputStream(outputZipFile)) | ||
| 736 | ) | ||
| 737 | if (result == TaskState.Failed) { | ||
| 738 | return false | ||
| 739 | } | ||
| 740 | lastZipCreated = outputZipFile | ||
| 741 | } catch (e: Exception) { | ||
| 742 | return false | ||
| 743 | } | ||
| 744 | return true | ||
| 745 | } | ||
| 746 | |||
| 747 | /** | ||
| 748 | * Exports the save file located in the given folder path by creating a zip file and sharing it via intent. | ||
| 749 | */ | ||
| 750 | fun exportSave() { | ||
| 751 | CoroutineScope(Dispatchers.IO).launch { | ||
| 752 | val wasZipCreated = zipSave() | ||
| 753 | val lastZipFile = lastZipCreated | ||
| 754 | if (!wasZipCreated || lastZipFile == null) { | ||
| 755 | withContext(Dispatchers.Main) { | ||
| 756 | Toast.makeText( | ||
| 757 | this@MainActivity, | ||
| 758 | getString(R.string.export_save_failed), | ||
| 759 | Toast.LENGTH_LONG | ||
| 760 | ).show() | ||
| 761 | } | ||
| 762 | return@launch | ||
| 763 | } | ||
| 764 | |||
| 765 | withContext(Dispatchers.Main) { | ||
| 766 | val file = DocumentFile.fromSingleUri( | ||
| 767 | this@MainActivity, | ||
| 768 | DocumentsContract.buildDocumentUri( | ||
| 769 | DocumentProvider.AUTHORITY, | ||
| 770 | "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}" | ||
| 771 | ) | ||
| 772 | )!! | ||
| 773 | val intent = Intent(Intent.ACTION_SEND) | ||
| 774 | .setDataAndType(file.uri, "application/zip") | ||
| 775 | .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) | ||
| 776 | .putExtra(Intent.EXTRA_STREAM, file.uri) | ||
| 777 | startForResultExportSave.launch( | ||
| 778 | Intent.createChooser( | ||
| 779 | intent, | ||
| 780 | getString(R.string.share_save_file) | ||
| 781 | ) | ||
| 782 | ) | ||
| 783 | } | ||
| 784 | } | ||
| 785 | } | ||
| 786 | |||
| 787 | private val startForResultExportSave = | ||
| 788 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ -> | ||
| 789 | File(getPublicFilesDir().canonicalPath, "temp").deleteRecursively() | ||
| 790 | } | ||
| 791 | |||
| 792 | val importSaves = | ||
| 793 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | ||
| 794 | if (result == null) { | ||
| 795 | return@registerForActivityResult | ||
| 796 | } | ||
| 797 | |||
| 798 | NativeLibrary.initializeEmptyUserDirectory() | ||
| 799 | |||
| 800 | val inputZip = contentResolver.openInputStream(result) | ||
| 801 | // A zip needs to have at least one subfolder named after a TitleId in order to be considered valid. | ||
| 802 | var validZip = false | ||
| 803 | val savesFolder = File(savesFolderRoot) | ||
| 804 | val cacheSaveDir = File("${applicationContext.cacheDir.path}/saves/") | ||
| 805 | cacheSaveDir.mkdir() | ||
| 806 | |||
| 807 | if (inputZip == null) { | ||
| 808 | Toast.makeText( | ||
| 809 | applicationContext, | ||
| 810 | getString(R.string.fatal_error), | ||
| 811 | Toast.LENGTH_LONG | ||
| 812 | ).show() | ||
| 813 | return@registerForActivityResult | ||
| 814 | } | ||
| 815 | |||
| 816 | val filterTitleId = | ||
| 817 | FilenameFilter { _, dirName -> dirName.matches(Regex("^0100[\\dA-Fa-f]{12}$")) } | ||
| 818 | |||
| 819 | try { | ||
| 820 | CoroutineScope(Dispatchers.IO).launch { | ||
| 821 | FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir) | ||
| 822 | cacheSaveDir.list(filterTitleId)?.forEach { savePath -> | ||
| 823 | File(savesFolder, savePath).deleteRecursively() | ||
| 824 | File(cacheSaveDir, savePath).copyRecursively( | ||
| 825 | File(savesFolder, savePath), | ||
| 826 | true | ||
| 827 | ) | ||
| 828 | validZip = true | ||
| 829 | } | ||
| 830 | |||
| 831 | withContext(Dispatchers.Main) { | ||
| 832 | if (!validZip) { | ||
| 833 | MessageDialogFragment.newInstance( | ||
| 834 | this@MainActivity, | ||
| 835 | titleId = R.string.save_file_invalid_zip_structure, | ||
| 836 | descriptionId = R.string.save_file_invalid_zip_structure_description | ||
| 837 | ).show(supportFragmentManager, MessageDialogFragment.TAG) | ||
| 838 | return@withContext | ||
| 839 | } | ||
| 840 | Toast.makeText( | ||
| 841 | applicationContext, | ||
| 842 | getString(R.string.save_file_imported_success), | ||
| 843 | Toast.LENGTH_LONG | ||
| 844 | ).show() | ||
| 845 | } | ||
| 846 | |||
| 847 | cacheSaveDir.deleteRecursively() | ||
| 848 | } | ||
| 849 | } catch (e: Exception) { | ||
| 850 | Toast.makeText( | ||
| 851 | applicationContext, | ||
| 852 | getString(R.string.fatal_error), | ||
| 853 | Toast.LENGTH_LONG | ||
| 854 | ).show() | ||
| 855 | } | ||
| 856 | } | ||
| 599 | } | 857 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt index 142af5f26..c3f53f1c5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt | |||
| @@ -8,6 +8,7 @@ import android.database.Cursor | |||
| 8 | import android.net.Uri | 8 | import android.net.Uri |
| 9 | import android.provider.DocumentsContract | 9 | import android.provider.DocumentsContract |
| 10 | import androidx.documentfile.provider.DocumentFile | 10 | import androidx.documentfile.provider.DocumentFile |
| 11 | import kotlinx.coroutines.flow.StateFlow | ||
| 11 | import java.io.BufferedInputStream | 12 | import java.io.BufferedInputStream |
| 12 | import java.io.File | 13 | import java.io.File |
| 13 | import java.io.FileOutputStream | 14 | import java.io.FileOutputStream |
| @@ -18,6 +19,9 @@ import java.util.zip.ZipEntry | |||
| 18 | import java.util.zip.ZipInputStream | 19 | import java.util.zip.ZipInputStream |
| 19 | import org.yuzu.yuzu_emu.YuzuApplication | 20 | import org.yuzu.yuzu_emu.YuzuApplication |
| 20 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | 21 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile |
| 22 | import org.yuzu.yuzu_emu.model.TaskState | ||
| 23 | import java.io.BufferedOutputStream | ||
| 24 | import java.util.zip.ZipOutputStream | ||
| 21 | 25 | ||
| 22 | object FileUtil { | 26 | object FileUtil { |
| 23 | const val PATH_TREE = "tree" | 27 | const val PATH_TREE = "tree" |
| @@ -282,30 +286,65 @@ object FileUtil { | |||
| 282 | 286 | ||
| 283 | /** | 287 | /** |
| 284 | * Extracts the given zip file into the given directory. | 288 | * Extracts the given zip file into the given directory. |
| 285 | * @exception IOException if the file was being created outside of the target directory | ||
| 286 | */ | 289 | */ |
| 287 | @Throws(SecurityException::class) | 290 | @Throws(SecurityException::class) |
| 288 | fun unzip(zipStream: InputStream, destDir: File): Boolean { | 291 | fun unzipToInternalStorage(zipStream: BufferedInputStream, destDir: File) { |
| 289 | ZipInputStream(BufferedInputStream(zipStream)).use { zis -> | 292 | ZipInputStream(zipStream).use { zis -> |
| 290 | var entry: ZipEntry? = zis.nextEntry | 293 | var entry: ZipEntry? = zis.nextEntry |
| 291 | while (entry != null) { | 294 | while (entry != null) { |
| 292 | val entryName = entry.name | 295 | val newFile = File(destDir, entry.name) |
| 293 | val entryFile = File(destDir, entryName) | 296 | val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile |
| 294 | if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) { | 297 | |
| 295 | throw SecurityException("Entry is outside of the target dir: " + entryFile.name) | 298 | if (!newFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) { |
| 299 | throw SecurityException("Zip file attempted path traversal! ${entry.name}") | ||
| 300 | } | ||
| 301 | |||
| 302 | if (!destinationDirectory.isDirectory && !destinationDirectory.mkdirs()) { | ||
| 303 | throw IOException("Failed to create directory $destinationDirectory") | ||
| 296 | } | 304 | } |
| 297 | if (entry.isDirectory) { | 305 | |
| 298 | entryFile.mkdirs() | 306 | if (!entry.isDirectory) { |
| 299 | } else { | 307 | newFile.outputStream().use { fos -> zis.copyTo(fos) } |
| 300 | entryFile.parentFile?.mkdirs() | ||
| 301 | entryFile.createNewFile() | ||
| 302 | entryFile.outputStream().use { fos -> zis.copyTo(fos) } | ||
| 303 | } | 308 | } |
| 304 | entry = zis.nextEntry | 309 | entry = zis.nextEntry |
| 305 | } | 310 | } |
| 306 | } | 311 | } |
| 312 | } | ||
| 307 | 313 | ||
| 308 | return true | 314 | /** |
| 315 | * Creates a zip file from a directory within internal storage | ||
| 316 | * @param inputFile File representation of the item that will be zipped | ||
| 317 | * @param rootDir Directory containing the inputFile | ||
| 318 | * @param outputStream Stream where the zip file will be output | ||
| 319 | */ | ||
| 320 | fun zipFromInternalStorage( | ||
| 321 | inputFile: File, | ||
| 322 | rootDir: String, | ||
| 323 | outputStream: BufferedOutputStream, | ||
| 324 | cancelled: StateFlow<Boolean>? = null | ||
| 325 | ): TaskState { | ||
| 326 | try { | ||
| 327 | ZipOutputStream(outputStream).use { zos -> | ||
| 328 | inputFile.walkTopDown().forEach { file -> | ||
| 329 | if (cancelled?.value == true) { | ||
| 330 | return TaskState.Cancelled | ||
| 331 | } | ||
| 332 | |||
| 333 | if (!file.isDirectory) { | ||
| 334 | val entryName = | ||
| 335 | file.absolutePath.removePrefix(rootDir).removePrefix("/") | ||
| 336 | val entry = ZipEntry(entryName) | ||
| 337 | zos.putNextEntry(entry) | ||
| 338 | if (file.isFile) { | ||
| 339 | file.inputStream().use { fis -> fis.copyTo(zos) } | ||
| 340 | } | ||
| 341 | } | ||
| 342 | } | ||
| 343 | } | ||
| 344 | } catch (e: Exception) { | ||
| 345 | return TaskState.Failed | ||
| 346 | } | ||
| 347 | return TaskState.Completed | ||
| 309 | } | 348 | } |
| 310 | 349 | ||
| 311 | fun isRootTreeUri(uri: Uri): Boolean { | 350 | fun isRootTreeUri(uri: Uri): Boolean { |
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index f31fe054b..9cf71680c 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp | |||
| @@ -13,6 +13,8 @@ | |||
| 13 | 13 | ||
| 14 | #include <android/api-level.h> | 14 | #include <android/api-level.h> |
| 15 | #include <android/native_window_jni.h> | 15 | #include <android/native_window_jni.h> |
| 16 | #include <common/fs/fs.h> | ||
| 17 | #include <core/file_sys/savedata_factory.h> | ||
| 16 | #include <core/loader/nro.h> | 18 | #include <core/loader/nro.h> |
| 17 | #include <jni.h> | 19 | #include <jni.h> |
| 18 | 20 | ||
| @@ -102,7 +104,7 @@ public: | |||
| 102 | m_native_window = native_window; | 104 | m_native_window = native_window; |
| 103 | } | 105 | } |
| 104 | 106 | ||
| 105 | int InstallFileToNand(std::string filename) { | 107 | int InstallFileToNand(std::string filename, std::string file_extension) { |
| 106 | jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, | 108 | jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, |
| 107 | std::size_t block_size) { | 109 | std::size_t block_size) { |
| 108 | if (src == nullptr || dest == nullptr) { | 110 | if (src == nullptr || dest == nullptr) { |
| @@ -134,15 +136,11 @@ public: | |||
| 134 | m_system.GetFileSystemController().CreateFactories(*m_vfs); | 136 | m_system.GetFileSystemController().CreateFactories(*m_vfs); |
| 135 | 137 | ||
| 136 | [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp; | 138 | [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp; |
| 137 | if (filename.ends_with("nsp")) { | 139 | if (file_extension == "nsp") { |
| 138 | nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); | 140 | nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); |
| 139 | if (nsp->IsExtractedType()) { | 141 | if (nsp->IsExtractedType()) { |
| 140 | return InstallError; | 142 | return InstallError; |
| 141 | } | 143 | } |
| 142 | } else if (filename.ends_with("xci")) { | ||
| 143 | jconst xci = | ||
| 144 | std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); | ||
| 145 | nsp = xci->GetSecurePartitionNSP(); | ||
| 146 | } else { | 144 | } else { |
| 147 | return ErrorFilenameExtension; | 145 | return ErrorFilenameExtension; |
| 148 | } | 146 | } |
| @@ -607,8 +605,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject | |||
| 607 | } | 605 | } |
| 608 | 606 | ||
| 609 | int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance, | 607 | int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance, |
| 610 | [[maybe_unused]] jstring j_file) { | 608 | jstring j_file, |
| 611 | return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file)); | 609 | jstring j_file_extension) { |
| 610 | return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file), | ||
| 611 | GetJString(env, j_file_extension)); | ||
| 612 | } | 612 | } |
| 613 | 613 | ||
| 614 | void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz, | 614 | void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz, |
| @@ -879,4 +879,24 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardInput(JNIEnv* env | |||
| 879 | EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code); | 879 | EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code); |
| 880 | } | 880 | } |
| 881 | 881 | ||
| 882 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv* env, | ||
| 883 | jobject instance) { | ||
| 884 | const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir); | ||
| 885 | auto vfs_nand_dir = EmulationSession::GetInstance().System().GetFilesystem()->OpenDirectory( | ||
| 886 | Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read); | ||
| 887 | |||
| 888 | Service::Account::ProfileManager manager; | ||
| 889 | const auto user_id = manager.GetUser(static_cast<std::size_t>(0)); | ||
| 890 | ASSERT(user_id); | ||
| 891 | |||
| 892 | const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath( | ||
| 893 | EmulationSession::GetInstance().System(), vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, | ||
| 894 | FileSys::SaveDataType::SaveData, 1, user_id->AsU128(), 0); | ||
| 895 | |||
| 896 | const auto full_path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path); | ||
| 897 | if (!Common::FS::CreateParentDirs(full_path)) { | ||
| 898 | LOG_WARNING(Frontend, "Failed to create full path of the default user's save directory"); | ||
| 899 | } | ||
| 900 | } | ||
| 901 | |||
| 882 | } // extern "C" | 902 | } // extern "C" |
diff --git a/src/android/app/src/main/res/drawable/ic_export.xml b/src/android/app/src/main/res/drawable/ic_export.xml new file mode 100644 index 000000000..463d2f41c --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_export.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportWidth="24" | ||
| 5 | android:viewportHeight="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="?attr/colorControlNormal" | ||
| 8 | android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z" /> | ||
| 9 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_import.xml b/src/android/app/src/main/res/drawable/ic_import.xml new file mode 100644 index 000000000..3a99dd5e6 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_import.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportWidth="24" | ||
| 5 | android:viewportHeight="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="?attr/colorControlNormal" | ||
| 8 | android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" /> | ||
| 9 | </vector> | ||
diff --git a/src/android/app/src/main/res/layout/activity_settings.xml b/src/android/app/src/main/res/layout/activity_settings.xml index 8a026a30a..a187665f2 100644 --- a/src/android/app/src/main/res/layout/activity_settings.xml +++ b/src/android/app/src/main/res/layout/activity_settings.xml | |||
| @@ -22,7 +22,7 @@ | |||
| 22 | 22 | ||
| 23 | <View | 23 | <View |
| 24 | android:id="@+id/navigation_bar_shade" | 24 | android:id="@+id/navigation_bar_shade" |
| 25 | android:layout_width="match_parent" | 25 | android:layout_width="0dp" |
| 26 | android:layout_height="1px" | 26 | android:layout_height="1px" |
| 27 | android:background="@android:color/transparent" | 27 | android:background="@android:color/transparent" |
| 28 | android:clickable="false" | 28 | android:clickable="false" |
diff --git a/src/android/app/src/main/res/layout/card_installable.xml b/src/android/app/src/main/res/layout/card_installable.xml new file mode 100644 index 000000000..f5b0e3741 --- /dev/null +++ b/src/android/app/src/main/res/layout/card_installable.xml | |||
| @@ -0,0 +1,71 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | xmlns:tools="http://schemas.android.com/tools" | ||
| 5 | style="?attr/materialCardViewOutlinedStyle" | ||
| 6 | android:layout_width="match_parent" | ||
| 7 | android:layout_height="wrap_content" | ||
| 8 | android:layout_marginHorizontal="16dp" | ||
| 9 | android:layout_marginVertical="12dp"> | ||
| 10 | |||
| 11 | <LinearLayout | ||
| 12 | android:layout_width="match_parent" | ||
| 13 | android:layout_height="wrap_content" | ||
| 14 | android:layout_margin="16dp" | ||
| 15 | android:orientation="horizontal" | ||
| 16 | android:layout_gravity="center"> | ||
| 17 | |||
| 18 | <LinearLayout | ||
| 19 | android:layout_width="0dp" | ||
| 20 | android:layout_height="wrap_content" | ||
| 21 | android:layout_marginEnd="16dp" | ||
| 22 | android:layout_weight="1" | ||
| 23 | android:orientation="vertical"> | ||
| 24 | |||
| 25 | <com.google.android.material.textview.MaterialTextView | ||
| 26 | android:id="@+id/title" | ||
| 27 | style="@style/TextAppearance.Material3.TitleMedium" | ||
| 28 | android:layout_width="match_parent" | ||
| 29 | android:layout_height="wrap_content" | ||
| 30 | android:text="@string/user_data" | ||
| 31 | android:textAlignment="viewStart" /> | ||
| 32 | |||
| 33 | <com.google.android.material.textview.MaterialTextView | ||
| 34 | android:id="@+id/description" | ||
| 35 | style="@style/TextAppearance.Material3.BodyMedium" | ||
| 36 | android:layout_width="match_parent" | ||
| 37 | android:layout_height="wrap_content" | ||
| 38 | android:layout_marginTop="6dp" | ||
| 39 | android:text="@string/user_data_description" | ||
| 40 | android:textAlignment="viewStart" /> | ||
| 41 | |||
| 42 | </LinearLayout> | ||
| 43 | |||
| 44 | <Button | ||
| 45 | android:id="@+id/button_export" | ||
| 46 | style="@style/Widget.Material3.Button.IconButton.Filled.Tonal" | ||
| 47 | android:layout_width="wrap_content" | ||
| 48 | android:layout_height="wrap_content" | ||
| 49 | android:layout_gravity="center_vertical" | ||
| 50 | android:contentDescription="@string/export" | ||
| 51 | android:tooltipText="@string/export" | ||
| 52 | android:visibility="gone" | ||
| 53 | app:icon="@drawable/ic_export" | ||
| 54 | tools:visibility="visible" /> | ||
| 55 | |||
| 56 | <Button | ||
| 57 | android:id="@+id/button_install" | ||
| 58 | style="@style/Widget.Material3.Button.IconButton.Filled.Tonal" | ||
| 59 | android:layout_width="wrap_content" | ||
| 60 | android:layout_height="wrap_content" | ||
| 61 | android:layout_gravity="center_vertical" | ||
| 62 | android:layout_marginStart="12dp" | ||
| 63 | android:contentDescription="@string/string_import" | ||
| 64 | android:tooltipText="@string/string_import" | ||
| 65 | android:visibility="gone" | ||
| 66 | app:icon="@drawable/ic_import" | ||
| 67 | tools:visibility="visible" /> | ||
| 68 | |||
| 69 | </LinearLayout> | ||
| 70 | |||
| 71 | </com.google.android.material.card.MaterialCardView> | ||
diff --git a/src/android/app/src/main/res/layout/dialog_progress_bar.xml b/src/android/app/src/main/res/layout/dialog_progress_bar.xml index d17711a65..0209ea082 100644 --- a/src/android/app/src/main/res/layout/dialog_progress_bar.xml +++ b/src/android/app/src/main/res/layout/dialog_progress_bar.xml | |||
| @@ -1,24 +1,8 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | 2 | <com.google.android.material.progressindicator.LinearProgressIndicator xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | android:layout_width="match_parent" | ||
| 4 | android:layout_height="match_parent" | ||
| 5 | xmlns:app="http://schemas.android.com/apk/res-auto" | 3 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 6 | android:orientation="vertical"> | 4 | android:id="@+id/progress_bar" |
| 7 | 5 | android:layout_width="match_parent" | |
| 8 | <com.google.android.material.progressindicator.LinearProgressIndicator | 6 | android:layout_height="wrap_content" |
| 9 | android:id="@+id/progress_bar" | 7 | android:padding="24dp" |
| 10 | android:layout_width="match_parent" | 8 | app:trackCornerRadius="4dp" /> |
| 11 | android:layout_height="wrap_content" | ||
| 12 | android:layout_margin="24dp" | ||
| 13 | app:trackCornerRadius="4dp" /> | ||
| 14 | |||
| 15 | <TextView | ||
| 16 | android:id="@+id/progress_text" | ||
| 17 | android:layout_width="match_parent" | ||
| 18 | android:layout_height="wrap_content" | ||
| 19 | android:layout_marginLeft="24dp" | ||
| 20 | android:layout_marginRight="24dp" | ||
| 21 | android:layout_marginBottom="24dp" | ||
| 22 | android:gravity="end" /> | ||
| 23 | |||
| 24 | </LinearLayout> | ||
diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml index da97d85c1..750ce094a 100644 --- a/src/android/app/src/main/res/layout/fragment_emulation.xml +++ b/src/android/app/src/main/res/layout/fragment_emulation.xml | |||
| @@ -32,7 +32,8 @@ | |||
| 32 | android:layout_width="wrap_content" | 32 | android:layout_width="wrap_content" |
| 33 | android:layout_height="wrap_content" | 33 | android:layout_height="wrap_content" |
| 34 | android:layout_gravity="center" | 34 | android:layout_gravity="center" |
| 35 | android:focusable="false"> | 35 | android:focusable="false" |
| 36 | android:clickable="false"> | ||
| 36 | 37 | ||
| 37 | <androidx.constraintlayout.widget.ConstraintLayout | 38 | <androidx.constraintlayout.widget.ConstraintLayout |
| 38 | android:id="@+id/loading_layout" | 39 | android:id="@+id/loading_layout" |
| @@ -155,7 +156,7 @@ | |||
| 155 | android:id="@+id/in_game_menu" | 156 | android:id="@+id/in_game_menu" |
| 156 | android:layout_width="wrap_content" | 157 | android:layout_width="wrap_content" |
| 157 | android:layout_height="match_parent" | 158 | android:layout_height="match_parent" |
| 158 | android:layout_gravity="start|bottom" | 159 | android:layout_gravity="start" |
| 159 | app:headerLayout="@layout/header_in_game" | 160 | app:headerLayout="@layout/header_in_game" |
| 160 | app:menu="@menu/menu_in_game" | 161 | app:menu="@menu/menu_in_game" |
| 161 | tools:visibility="gone" /> | 162 | tools:visibility="gone" /> |
diff --git a/src/android/app/src/main/res/layout/fragment_installables.xml b/src/android/app/src/main/res/layout/fragment_installables.xml new file mode 100644 index 000000000..3a4df81a6 --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_installables.xml | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | android:id="@+id/coordinator_licenses" | ||
| 5 | android:layout_width="match_parent" | ||
| 6 | android:layout_height="match_parent" | ||
| 7 | android:background="?attr/colorSurface"> | ||
| 8 | |||
| 9 | <com.google.android.material.appbar.AppBarLayout | ||
| 10 | android:id="@+id/appbar_installables" | ||
| 11 | android:layout_width="match_parent" | ||
| 12 | android:layout_height="wrap_content" | ||
| 13 | android:fitsSystemWindows="true"> | ||
| 14 | |||
| 15 | <com.google.android.material.appbar.MaterialToolbar | ||
| 16 | android:id="@+id/toolbar_installables" | ||
| 17 | android:layout_width="match_parent" | ||
| 18 | android:layout_height="?attr/actionBarSize" | ||
| 19 | app:title="@string/manage_yuzu_data" | ||
| 20 | app:navigationIcon="@drawable/ic_back" /> | ||
| 21 | |||
| 22 | </com.google.android.material.appbar.AppBarLayout> | ||
| 23 | |||
| 24 | <androidx.recyclerview.widget.RecyclerView | ||
| 25 | android:id="@+id/list_installables" | ||
| 26 | android:layout_width="match_parent" | ||
| 27 | android:layout_height="match_parent" | ||
| 28 | android:clipToPadding="false" | ||
| 29 | app:layout_behavior="@string/appbar_scrolling_view_behavior" /> | ||
| 30 | |||
| 31 | </androidx.coordinatorlayout.widget.CoordinatorLayout> | ||
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml index 2e0ce7a3d..2356b802b 100644 --- a/src/android/app/src/main/res/navigation/home_navigation.xml +++ b/src/android/app/src/main/res/navigation/home_navigation.xml | |||
| @@ -19,6 +19,9 @@ | |||
| 19 | <action | 19 | <action |
| 20 | android:id="@+id/action_homeSettingsFragment_to_earlyAccessFragment" | 20 | android:id="@+id/action_homeSettingsFragment_to_earlyAccessFragment" |
| 21 | app:destination="@id/earlyAccessFragment" /> | 21 | app:destination="@id/earlyAccessFragment" /> |
| 22 | <action | ||
| 23 | android:id="@+id/action_homeSettingsFragment_to_installableFragment" | ||
| 24 | app:destination="@id/installableFragment" /> | ||
| 22 | </fragment> | 25 | </fragment> |
| 23 | 26 | ||
| 24 | <fragment | 27 | <fragment |
| @@ -88,5 +91,9 @@ | |||
| 88 | <action | 91 | <action |
| 89 | android:id="@+id/action_global_settingsActivity" | 92 | android:id="@+id/action_global_settingsActivity" |
| 90 | app:destination="@id/settingsActivity" /> | 93 | app:destination="@id/settingsActivity" /> |
| 94 | <fragment | ||
| 95 | android:id="@+id/installableFragment" | ||
| 96 | android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment" | ||
| 97 | android:label="InstallableFragment" /> | ||
| 91 | 98 | ||
| 92 | </navigation> | 99 | </navigation> |
diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index daaa7ffde..dd0f36392 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml | |||
| @@ -79,7 +79,6 @@ | |||
| 79 | <string name="manage_save_data">Speicherdaten verwalten</string> | 79 | <string name="manage_save_data">Speicherdaten verwalten</string> |
| 80 | <string name="manage_save_data_description">Speicherdaten gefunden. Bitte wähle unten eine Option aus.</string> | 80 | <string name="manage_save_data_description">Speicherdaten gefunden. Bitte wähle unten eine Option aus.</string> |
| 81 | <string name="import_export_saves_description">Speicherdaten importieren oder exportieren</string> | 81 | <string name="import_export_saves_description">Speicherdaten importieren oder exportieren</string> |
| 82 | <string name="import_export_saves_no_profile">Keine Speicherdaten gefunden. Bitte starte ein Spiel und versuche es erneut.</string> | ||
| 83 | <string name="save_file_imported_success">Erfolgreich importiert</string> | 82 | <string name="save_file_imported_success">Erfolgreich importiert</string> |
| 84 | <string name="save_file_invalid_zip_structure">Ungültige Speicherverzeichnisstruktur</string> | 83 | <string name="save_file_invalid_zip_structure">Ungültige Speicherverzeichnisstruktur</string> |
| 85 | <string name="save_file_invalid_zip_structure_description">Der erste Unterordnername muss die Titel-ID des Spiels sein.</string> | 84 | <string name="save_file_invalid_zip_structure_description">Der erste Unterordnername muss die Titel-ID des Spiels sein.</string> |
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index e9129cb00..d398f862f 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml | |||
| @@ -81,7 +81,6 @@ | |||
| 81 | <string name="manage_save_data">Administrar datos de guardado</string> | 81 | <string name="manage_save_data">Administrar datos de guardado</string> |
| 82 | <string name="manage_save_data_description">Guardar los datos encontrados. Por favor, seleccione una opción de abajo.</string> | 82 | <string name="manage_save_data_description">Guardar los datos encontrados. Por favor, seleccione una opción de abajo.</string> |
| 83 | <string name="import_export_saves_description">Importar o exportar archivos de guardado</string> | 83 | <string name="import_export_saves_description">Importar o exportar archivos de guardado</string> |
| 84 | <string name="import_export_saves_no_profile">No se han encontrado datos de guardado. Por favor, ejecute un juego y vuelva a intentarlo.</string> | ||
| 85 | <string name="save_file_imported_success">Importado correctamente</string> | 84 | <string name="save_file_imported_success">Importado correctamente</string> |
| 86 | <string name="save_file_invalid_zip_structure">Estructura del directorio de guardado no válido</string> | 85 | <string name="save_file_invalid_zip_structure">Estructura del directorio de guardado no válido</string> |
| 87 | <string name="save_file_invalid_zip_structure_description">El nombre de la primera subcarpeta debe ser el Title ID del juego.</string> | 86 | <string name="save_file_invalid_zip_structure_description">El nombre de la primera subcarpeta debe ser el Title ID del juego.</string> |
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml index 2d99d618e..a7abd9077 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml | |||
| @@ -81,7 +81,6 @@ | |||
| 81 | <string name="manage_save_data">Gérer les données de sauvegarde</string> | 81 | <string name="manage_save_data">Gérer les données de sauvegarde</string> |
| 82 | <string name="manage_save_data_description">Données de sauvegarde trouvées. Veuillez sélectionner une option ci-dessous.</string> | 82 | <string name="manage_save_data_description">Données de sauvegarde trouvées. Veuillez sélectionner une option ci-dessous.</string> |
| 83 | <string name="import_export_saves_description">Importer ou exporter des fichiers de sauvegarde</string> | 83 | <string name="import_export_saves_description">Importer ou exporter des fichiers de sauvegarde</string> |
| 84 | <string name="import_export_saves_no_profile">Aucune données de sauvegarde trouvées. Veuillez lancer un jeu et réessayer.</string> | ||
| 85 | <string name="save_file_imported_success">Importé avec succès</string> | 84 | <string name="save_file_imported_success">Importé avec succès</string> |
| 86 | <string name="save_file_invalid_zip_structure">Structure de répertoire de sauvegarde non valide</string> | 85 | <string name="save_file_invalid_zip_structure">Structure de répertoire de sauvegarde non valide</string> |
| 87 | <string name="save_file_invalid_zip_structure_description">Le nom du premier sous-dossier doit être l\'identifiant du titre du jeu.</string> | 86 | <string name="save_file_invalid_zip_structure_description">Le nom du premier sous-dossier doit être l\'identifiant du titre du jeu.</string> |
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml index d9c3de385..b18161801 100644 --- a/src/android/app/src/main/res/values-it/strings.xml +++ b/src/android/app/src/main/res/values-it/strings.xml | |||
| @@ -81,7 +81,6 @@ | |||
| 81 | <string name="manage_save_data">Gestisci i salvataggi</string> | 81 | <string name="manage_save_data">Gestisci i salvataggi</string> |
| 82 | <string name="manage_save_data_description">Salvataggio non trovato. Seleziona un\'opzione di seguito.</string> | 82 | <string name="manage_save_data_description">Salvataggio non trovato. Seleziona un\'opzione di seguito.</string> |
| 83 | <string name="import_export_saves_description">Importa o esporta i salvataggi</string> | 83 | <string name="import_export_saves_description">Importa o esporta i salvataggi</string> |
| 84 | <string name="import_export_saves_no_profile">Nessun salvataggio trovato. Avvia un gioco e riprova.</string> | ||
| 85 | <string name="save_file_imported_success">Importato con successo</string> | 84 | <string name="save_file_imported_success">Importato con successo</string> |
| 86 | <string name="save_file_invalid_zip_structure">La struttura della cartella dei salvataggi è invalida</string> | 85 | <string name="save_file_invalid_zip_structure">La struttura della cartella dei salvataggi è invalida</string> |
| 87 | <string name="save_file_invalid_zip_structure_description">La prima sotto cartella <b>deve</b> chiamarsi come l\'ID del titolo del gioco.</string> | 86 | <string name="save_file_invalid_zip_structure_description">La prima sotto cartella <b>deve</b> chiamarsi come l\'ID del titolo del gioco.</string> |
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml index 7a226cd5c..88fa5a0bb 100644 --- a/src/android/app/src/main/res/values-ja/strings.xml +++ b/src/android/app/src/main/res/values-ja/strings.xml | |||
| @@ -80,7 +80,6 @@ | |||
| 80 | <string name="manage_save_data">セーブデータを管理</string> | 80 | <string name="manage_save_data">セーブデータを管理</string> |
| 81 | <string name="manage_save_data_description">セーブデータが見つかりました。以下のオプションから選択してください。</string> | 81 | <string name="manage_save_data_description">セーブデータが見つかりました。以下のオプションから選択してください。</string> |
| 82 | <string name="import_export_saves_description">セーブファイルをインポート/エクスポート</string> | 82 | <string name="import_export_saves_description">セーブファイルをインポート/エクスポート</string> |
| 83 | <string name="import_export_saves_no_profile">セーブデータがありません。ゲームを起動してから再度お試しください。</string> | ||
| 84 | <string name="save_file_imported_success">インポートが完了しました</string> | 83 | <string name="save_file_imported_success">インポートが完了しました</string> |
| 85 | <string name="save_file_invalid_zip_structure">セーブデータのディレクトリ構造が無効です</string> | 84 | <string name="save_file_invalid_zip_structure">セーブデータのディレクトリ構造が無効です</string> |
| 86 | <string name="save_file_invalid_zip_structure_description">最初のサブフォルダ名は、ゲームのタイトルIDである必要があります。</string> | 85 | <string name="save_file_invalid_zip_structure_description">最初のサブフォルダ名は、ゲームのタイトルIDである必要があります。</string> |
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml index 427b6e5a0..4b658255c 100644 --- a/src/android/app/src/main/res/values-ko/strings.xml +++ b/src/android/app/src/main/res/values-ko/strings.xml | |||
| @@ -81,7 +81,6 @@ | |||
| 81 | <string name="manage_save_data">저장 데이터 관리</string> | 81 | <string name="manage_save_data">저장 데이터 관리</string> |
| 82 | <string name="manage_save_data_description">데이터를 저장했습니다. 아래에서 옵션을 선택하세요.</string> | 82 | <string name="manage_save_data_description">데이터를 저장했습니다. 아래에서 옵션을 선택하세요.</string> |
| 83 | <string name="import_export_saves_description">저장 파일 가져오기 또는 내보내기</string> | 83 | <string name="import_export_saves_description">저장 파일 가져오기 또는 내보내기</string> |
| 84 | <string name="import_export_saves_no_profile">저장 데이터를 찾을 수 없습니다. 게임을 실행한 후 다시 시도하세요.</string> | ||
| 85 | <string name="save_file_imported_success">가져오기 성공</string> | 84 | <string name="save_file_imported_success">가져오기 성공</string> |
| 86 | <string name="save_file_invalid_zip_structure">저장 디렉터리 구조가 잘못됨</string> | 85 | <string name="save_file_invalid_zip_structure">저장 디렉터리 구조가 잘못됨</string> |
| 87 | <string name="save_file_invalid_zip_structure_description">첫 번째 하위 폴더 이름은 게임의 타이틀 ID여야 합니다.</string> | 86 | <string name="save_file_invalid_zip_structure_description">첫 번째 하위 폴더 이름은 게임의 타이틀 ID여야 합니다.</string> |
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml index ce8d7a9e4..dd602a389 100644 --- a/src/android/app/src/main/res/values-nb/strings.xml +++ b/src/android/app/src/main/res/values-nb/strings.xml | |||
| @@ -81,7 +81,6 @@ | |||
| 81 | <string name="manage_save_data">Administrere lagringsdata</string> | 81 | <string name="manage_save_data">Administrere lagringsdata</string> |
| 82 | <string name="manage_save_data_description">Lagringsdata funnet. Velg et alternativ nedenfor.</string> | 82 | <string name="manage_save_data_description">Lagringsdata funnet. Velg et alternativ nedenfor.</string> |
| 83 | <string name="import_export_saves_description">Importer eller eksporter lagringsfiler</string> | 83 | <string name="import_export_saves_description">Importer eller eksporter lagringsfiler</string> |
| 84 | <string name="import_export_saves_no_profile">Ingen lagringsdata funnet. Start et nytt spill og prøv på nytt.</string> | ||
| 85 | <string name="save_file_imported_success">Vellykket import</string> | 84 | <string name="save_file_imported_success">Vellykket import</string> |
| 86 | <string name="save_file_invalid_zip_structure">Ugyldig struktur for lagringskatalog</string> | 85 | <string name="save_file_invalid_zip_structure">Ugyldig struktur for lagringskatalog</string> |
| 87 | <string name="save_file_invalid_zip_structure_description">Det første undermappenavnet må være spillets tittel-ID.</string> | 86 | <string name="save_file_invalid_zip_structure_description">Det første undermappenavnet må være spillets tittel-ID.</string> |
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml index c2c24b48f..2fdd1f952 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml | |||
| @@ -81,7 +81,6 @@ | |||
| 81 | <string name="manage_save_data">Zarządzaj plikami zapisów gier</string> | 81 | <string name="manage_save_data">Zarządzaj plikami zapisów gier</string> |
| 82 | <string name="manage_save_data_description">Znaleziono pliki zapisów gier. Wybierz opcję poniżej.</string> | 82 | <string name="manage_save_data_description">Znaleziono pliki zapisów gier. Wybierz opcję poniżej.</string> |
| 83 | <string name="import_export_saves_description">Importuj lub wyeksportuj pliki zapisów</string> | 83 | <string name="import_export_saves_description">Importuj lub wyeksportuj pliki zapisów</string> |
| 84 | <string name="import_export_saves_no_profile">Nie znaleziono plików zapisów. Uruchom grę i spróbuj ponownie.</string> | ||
| 85 | <string name="save_file_imported_success">Zaimportowano pomyślnie</string> | 84 | <string name="save_file_imported_success">Zaimportowano pomyślnie</string> |
| 86 | <string name="save_file_invalid_zip_structure">Niepoprawna struktura folderów</string> | 85 | <string name="save_file_invalid_zip_structure">Niepoprawna struktura folderów</string> |
| 87 | <string name="save_file_invalid_zip_structure_description">Pierwszy podkatalog musi zawierać w nazwie numer ID tytułu gry.</string> | 86 | <string name="save_file_invalid_zip_structure_description">Pierwszy podkatalog musi zawierać w nazwie numer ID tytułu gry.</string> |
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml index 04f276108..2f26367fe 100644 --- a/src/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml | |||
| @@ -81,7 +81,6 @@ | |||
| 81 | <string name="manage_save_data">Gerir dados guardados</string> | 81 | <string name="manage_save_data">Gerir dados guardados</string> |
| 82 | <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string> | 82 | <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string> |
| 83 | <string name="import_export_saves_description">Importa ou exporta dados guardados</string> | 83 | <string name="import_export_saves_description">Importa ou exporta dados guardados</string> |
| 84 | <string name="import_export_saves_no_profile">Dados não encontrados. Por favor lança o jogo e tenta novamente.</string> | ||
| 85 | <string name="save_file_imported_success">Importado com sucesso</string> | 84 | <string name="save_file_imported_success">Importado com sucesso</string> |
| 86 | <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string> | 85 | <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string> |
| 87 | <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string> | 86 | <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string> |
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml index 66a3a1a2e..4e1eb4cd7 100644 --- a/src/android/app/src/main/res/values-pt-rPT/strings.xml +++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml | |||
| @@ -81,7 +81,6 @@ | |||
| 81 | <string name="manage_save_data">Gerir dados guardados</string> | 81 | <string name="manage_save_data">Gerir dados guardados</string> |
| 82 | <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string> | 82 | <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string> |
| 83 | <string name="import_export_saves_description">Importa ou exporta dados guardados</string> | 83 | <string name="import_export_saves_description">Importa ou exporta dados guardados</string> |
| 84 | <string name="import_export_saves_no_profile">Dados não encontrados. Por favor lança o jogo e tenta novamente.</string> | ||
| 85 | <string name="save_file_imported_success">Importado com sucesso</string> | 84 | <string name="save_file_imported_success">Importado com sucesso</string> |
| 86 | <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string> | 85 | <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string> |
| 87 | <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string> | 86 | <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string> |
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index f770e954f..f5695dc93 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml | |||
| @@ -81,7 +81,6 @@ | |||
| 81 | <string name="manage_save_data">Управление данными сохранений</string> | 81 | <string name="manage_save_data">Управление данными сохранений</string> |
| 82 | <string name="manage_save_data_description">Найдено данные сохранений. Пожалуйста, выберите вариант ниже.</string> | 82 | <string name="manage_save_data_description">Найдено данные сохранений. Пожалуйста, выберите вариант ниже.</string> |
| 83 | <string name="import_export_saves_description">Импорт или экспорт файлов сохранения</string> | 83 | <string name="import_export_saves_description">Импорт или экспорт файлов сохранения</string> |
| 84 | <string name="import_export_saves_no_profile">Данные сохранений не найдены. Пожалуйста, запустите игру и повторите попытку.</string> | ||
| 85 | <string name="save_file_imported_success">Успешно импортировано</string> | 84 | <string name="save_file_imported_success">Успешно импортировано</string> |
| 86 | <string name="save_file_invalid_zip_structure">Недопустимая структура папки сохранения</string> | 85 | <string name="save_file_invalid_zip_structure">Недопустимая структура папки сохранения</string> |
| 87 | <string name="save_file_invalid_zip_structure_description">Название первой вложенной папки должно быть идентификатором игры.</string> | 86 | <string name="save_file_invalid_zip_structure_description">Название первой вложенной папки должно быть идентификатором игры.</string> |
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index ea3ab1b15..061bc6f04 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml | |||
| @@ -81,7 +81,6 @@ | |||
| 81 | <string name="manage_save_data">Керування даними збережень</string> | 81 | <string name="manage_save_data">Керування даними збережень</string> |
| 82 | <string name="manage_save_data_description">Знайдено дані збережень. Будь ласка, виберіть варіант нижче.</string> | 82 | <string name="manage_save_data_description">Знайдено дані збережень. Будь ласка, виберіть варіант нижче.</string> |
| 83 | <string name="import_export_saves_description">Імпорт або експорт файлів збереження</string> | 83 | <string name="import_export_saves_description">Імпорт або експорт файлів збереження</string> |
| 84 | <string name="import_export_saves_no_profile">Дані збережень не знайдено. Будь ласка, запустіть гру та повторіть спробу.</string> | ||
| 85 | <string name="save_file_imported_success">Успішно імпортовано</string> | 84 | <string name="save_file_imported_success">Успішно імпортовано</string> |
| 86 | <string name="save_file_invalid_zip_structure">Неприпустима структура папки збереження</string> | 85 | <string name="save_file_invalid_zip_structure">Неприпустима структура папки збереження</string> |
| 87 | <string name="save_file_invalid_zip_structure_description">Назва першої вкладеної папки має бути ідентифікатором гри.</string> | 86 | <string name="save_file_invalid_zip_structure_description">Назва першої вкладеної папки має бути ідентифікатором гри.</string> |
diff --git a/src/android/app/src/main/res/values-w600dp/integers.xml b/src/android/app/src/main/res/values-w600dp/integers.xml new file mode 100644 index 000000000..9975db801 --- /dev/null +++ b/src/android/app/src/main/res/values-w600dp/integers.xml | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <resources> | ||
| 3 | |||
| 4 | <integer name="grid_columns">2</integer> | ||
| 5 | |||
| 6 | </resources> | ||
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index b45a5a528..fe6dd5eaa 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml | |||
| @@ -81,7 +81,6 @@ | |||
| 81 | <string name="manage_save_data">管理存档数据</string> | 81 | <string name="manage_save_data">管理存档数据</string> |
| 82 | <string name="manage_save_data_description">已找到存档数据,请选择下方的选项。</string> | 82 | <string name="manage_save_data_description">已找到存档数据,请选择下方的选项。</string> |
| 83 | <string name="import_export_saves_description">导入或导出存档</string> | 83 | <string name="import_export_saves_description">导入或导出存档</string> |
| 84 | <string name="import_export_saves_no_profile">找不到存档数据,请启动游戏并重试。</string> | ||
| 85 | <string name="save_file_imported_success">已成功导入存档</string> | 84 | <string name="save_file_imported_success">已成功导入存档</string> |
| 86 | <string name="save_file_invalid_zip_structure">无效的存档目录</string> | 85 | <string name="save_file_invalid_zip_structure">无效的存档目录</string> |
| 87 | <string name="save_file_invalid_zip_structure_description">第一个子文件夹名称必须为当前游戏的 ID。</string> | 86 | <string name="save_file_invalid_zip_structure_description">第一个子文件夹名称必须为当前游戏的 ID。</string> |
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml index 3aab889e4..9b3e54224 100644 --- a/src/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml | |||
| @@ -81,7 +81,6 @@ | |||
| 81 | <string name="manage_save_data">管理儲存資料</string> | 81 | <string name="manage_save_data">管理儲存資料</string> |
| 82 | <string name="manage_save_data_description">已找到儲存資料,請選取下方的選項。</string> | 82 | <string name="manage_save_data_description">已找到儲存資料,請選取下方的選項。</string> |
| 83 | <string name="import_export_saves_description">匯入或匯出儲存檔案</string> | 83 | <string name="import_export_saves_description">匯入或匯出儲存檔案</string> |
| 84 | <string name="import_export_saves_no_profile">找不到儲存資料,請啟動遊戲並重試。</string> | ||
| 85 | <string name="save_file_imported_success">已成功匯入</string> | 84 | <string name="save_file_imported_success">已成功匯入</string> |
| 86 | <string name="save_file_invalid_zip_structure">無效的儲存目錄結構</string> | 85 | <string name="save_file_invalid_zip_structure">無效的儲存目錄結構</string> |
| 87 | <string name="save_file_invalid_zip_structure_description">首個子資料夾名稱必須為遊戲標題 ID。</string> | 86 | <string name="save_file_invalid_zip_structure_description">首個子資料夾名稱必須為遊戲標題 ID。</string> |
diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml index 5e39bc7d9..dc527965c 100644 --- a/src/android/app/src/main/res/values/integers.xml +++ b/src/android/app/src/main/res/values/integers.xml | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <resources> | 2 | <resources> |
| 3 | <integer name="game_title_lines">2</integer> | 3 | <integer name="grid_columns">1</integer> |
| 4 | 4 | ||
| 5 | <!-- Default SWITCH landscape layout --> | 5 | <!-- Default SWITCH landscape layout --> |
| 6 | <integer name="SWITCH_BUTTON_A_X">760</integer> | 6 | <integer name="SWITCH_BUTTON_A_X">760</integer> |
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index b163e6fc1..e51edf872 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml | |||
| @@ -90,7 +90,6 @@ | |||
| 90 | <string name="manage_save_data">Manage save data</string> | 90 | <string name="manage_save_data">Manage save data</string> |
| 91 | <string name="manage_save_data_description">Save data found. Please select an option below.</string> | 91 | <string name="manage_save_data_description">Save data found. Please select an option below.</string> |
| 92 | <string name="import_export_saves_description">Import or export save files</string> | 92 | <string name="import_export_saves_description">Import or export save files</string> |
| 93 | <string name="import_export_saves_no_profile">No save data found. Please launch a game and retry.</string> | ||
| 94 | <string name="save_file_imported_success">Imported successfully</string> | 93 | <string name="save_file_imported_success">Imported successfully</string> |
| 95 | <string name="save_file_invalid_zip_structure">Invalid save directory structure</string> | 94 | <string name="save_file_invalid_zip_structure">Invalid save directory structure</string> |
| 96 | <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string> | 95 | <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string> |
| @@ -101,12 +100,13 @@ | |||
| 101 | <string name="firmware_installing">Installing firmware</string> | 100 | <string name="firmware_installing">Installing firmware</string> |
| 102 | <string name="firmware_installed_success">Firmware installed successfully</string> | 101 | <string name="firmware_installed_success">Firmware installed successfully</string> |
| 103 | <string name="firmware_installed_failure">Firmware installation failed</string> | 102 | <string name="firmware_installed_failure">Firmware installation failed</string> |
| 104 | <string name="firmware_installed_failure_description">Verify that the ZIP contains valid firmware and try again.</string> | 103 | <string name="firmware_installed_failure_description">Make sure the firmware nca files are at the root of the zip and try again.</string> |
| 105 | <string name="share_log">Share debug logs</string> | 104 | <string name="share_log">Share debug logs</string> |
| 106 | <string name="share_log_description">Share yuzu\'s log file to debug issues</string> | 105 | <string name="share_log_description">Share yuzu\'s log file to debug issues</string> |
| 107 | <string name="share_log_missing">No log file found</string> | 106 | <string name="share_log_missing">No log file found</string> |
| 108 | <string name="install_game_content">Install game content</string> | 107 | <string name="install_game_content">Install game content</string> |
| 109 | <string name="install_game_content_description">Install game updates or DLC</string> | 108 | <string name="install_game_content_description">Install game updates or DLC</string> |
| 109 | <string name="installing_game_content">Installing content…</string> | ||
| 110 | <string name="install_game_content_failure">Error installing file(s) to NAND</string> | 110 | <string name="install_game_content_failure">Error installing file(s) to NAND</string> |
| 111 | <string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string> | 111 | <string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string> |
| 112 | <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string> | 112 | <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string> |
| @@ -118,6 +118,10 @@ | |||
| 118 | <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string> | 118 | <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string> |
| 119 | <string name="custom_driver_not_supported">Custom drivers not supported</string> | 119 | <string name="custom_driver_not_supported">Custom drivers not supported</string> |
| 120 | <string name="custom_driver_not_supported_description">Custom driver loading isn\'t currently supported for this device.\nCheck this option again in the future to see if support was added!</string> | 120 | <string name="custom_driver_not_supported_description">Custom driver loading isn\'t currently supported for this device.\nCheck this option again in the future to see if support was added!</string> |
| 121 | <string name="manage_yuzu_data">Manage yuzu data</string> | ||
| 122 | <string name="manage_yuzu_data_description">Import/export firmware, keys, user data, and more!</string> | ||
| 123 | <string name="share_save_file">Share save file</string> | ||
| 124 | <string name="export_save_failed">Failed to export save</string> | ||
| 121 | 125 | ||
| 122 | <!-- About screen strings --> | 126 | <!-- About screen strings --> |
| 123 | <string name="gaia_is_not_real">Gaia isn\'t real</string> | 127 | <string name="gaia_is_not_real">Gaia isn\'t real</string> |
| @@ -128,6 +132,16 @@ | |||
| 128 | <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string> | 132 | <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string> |
| 129 | <string name="licenses_description">Projects that make yuzu for Android possible</string> | 133 | <string name="licenses_description">Projects that make yuzu for Android possible</string> |
| 130 | <string name="build">Build</string> | 134 | <string name="build">Build</string> |
| 135 | <string name="user_data">User data</string> | ||
| 136 | <string name="user_data_description">Import/export all app data.\n\nWhen importing user data, all existing user data will be deleted!</string> | ||
| 137 | <string name="exporting_user_data">Exporting user data…</string> | ||
| 138 | <string name="importing_user_data">Importing user data…</string> | ||
| 139 | <string name="import_user_data">Import user data</string> | ||
| 140 | <string name="invalid_yuzu_backup">Invalid yuzu backup</string> | ||
| 141 | <string name="user_data_export_success">User data exported successfully</string> | ||
| 142 | <string name="user_data_import_success">User data imported successfully</string> | ||
| 143 | <string name="user_data_export_cancelled">Export cancelled</string> | ||
| 144 | <string name="user_data_import_failed_description">Make sure the user data folders are at the root of the zip folder and contain a config file at config/config.ini and try again.</string> | ||
| 131 | <string name="support_link">https://discord.gg/u77vRWY</string> | 145 | <string name="support_link">https://discord.gg/u77vRWY</string> |
| 132 | <string name="website_link">https://yuzu-emu.org/</string> | 146 | <string name="website_link">https://yuzu-emu.org/</string> |
| 133 | <string name="github_link">https://github.com/yuzu-emu</string> | 147 | <string name="github_link">https://github.com/yuzu-emu</string> |
| @@ -215,6 +229,11 @@ | |||
| 215 | <string name="auto">Auto</string> | 229 | <string name="auto">Auto</string> |
| 216 | <string name="submit">Submit</string> | 230 | <string name="submit">Submit</string> |
| 217 | <string name="string_null">Null</string> | 231 | <string name="string_null">Null</string> |
| 232 | <string name="string_import">Import</string> | ||
| 233 | <string name="export">Export</string> | ||
| 234 | <string name="export_failed">Export failed</string> | ||
| 235 | <string name="import_failed">Import failed</string> | ||
| 236 | <string name="cancelling">Cancelling</string> | ||
| 218 | 237 | ||
| 219 | <!-- GPU driver installation --> | 238 | <!-- GPU driver installation --> |
| 220 | <string name="select_gpu_driver">Select GPU driver</string> | 239 | <string name="select_gpu_driver">Select GPU driver</string> |
| @@ -281,6 +300,7 @@ | |||
| 281 | <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string> | 300 | <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string> |
| 282 | <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string> | 301 | <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string> |
| 283 | <string name="memory_formatted">%1$s %2$s</string> | 302 | <string name="memory_formatted">%1$s %2$s</string> |
| 303 | <string name="no_game_present">No bootable game present!</string> | ||
| 284 | 304 | ||
| 285 | <!-- Region Names --> | 305 | <!-- Region Names --> |
| 286 | <string name="region_japan">Japan</string> | 306 | <string name="region_japan">Japan</string> |
diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 4ecaf550b..3fde3cae6 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp | |||
| @@ -130,13 +130,17 @@ void LogSettings() { | |||
| 130 | log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir)); | 130 | log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir)); |
| 131 | } | 131 | } |
| 132 | 132 | ||
| 133 | void UpdateGPUAccuracy() { | ||
| 134 | values.current_gpu_accuracy = values.gpu_accuracy.GetValue(); | ||
| 135 | } | ||
| 136 | |||
| 133 | bool IsGPULevelExtreme() { | 137 | bool IsGPULevelExtreme() { |
| 134 | return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme; | 138 | return values.current_gpu_accuracy == GpuAccuracy::Extreme; |
| 135 | } | 139 | } |
| 136 | 140 | ||
| 137 | bool IsGPULevelHigh() { | 141 | bool IsGPULevelHigh() { |
| 138 | return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme || | 142 | return values.current_gpu_accuracy == GpuAccuracy::Extreme || |
| 139 | values.gpu_accuracy.GetValue() == GpuAccuracy::High; | 143 | values.current_gpu_accuracy == GpuAccuracy::High; |
| 140 | } | 144 | } |
| 141 | 145 | ||
| 142 | bool IsFastmemEnabled() { | 146 | bool IsFastmemEnabled() { |
diff --git a/src/common/settings.h b/src/common/settings.h index 82ec9077e..98ab0ec2e 100644 --- a/src/common/settings.h +++ b/src/common/settings.h | |||
| @@ -307,6 +307,7 @@ struct Values { | |||
| 307 | Specialization::Default, | 307 | Specialization::Default, |
| 308 | true, | 308 | true, |
| 309 | true}; | 309 | true}; |
| 310 | GpuAccuracy current_gpu_accuracy{GpuAccuracy::High}; | ||
| 310 | SwitchableSetting<AnisotropyMode, true> max_anisotropy{ | 311 | SwitchableSetting<AnisotropyMode, true> max_anisotropy{ |
| 311 | linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16, | 312 | linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16, |
| 312 | "max_anisotropy", Category::RendererAdvanced}; | 313 | "max_anisotropy", Category::RendererAdvanced}; |
| @@ -350,6 +351,8 @@ struct Values { | |||
| 350 | linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; | 351 | linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; |
| 351 | Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey", | 352 | Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey", |
| 352 | Category::RendererDebug}; | 353 | Category::RendererDebug}; |
| 354 | // TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control | ||
| 355 | bool renderer_amdvlk_depth_bias_workaround{}; | ||
| 353 | 356 | ||
| 354 | // System | 357 | // System |
| 355 | SwitchableSetting<Language, true> language_index{linkage, | 358 | SwitchableSetting<Language, true> language_index{linkage, |
| @@ -522,6 +525,7 @@ struct Values { | |||
| 522 | 525 | ||
| 523 | extern Values values; | 526 | extern Values values; |
| 524 | 527 | ||
| 528 | void UpdateGPUAccuracy(); | ||
| 525 | bool IsGPULevelExtreme(); | 529 | bool IsGPULevelExtreme(); |
| 526 | bool IsGPULevelHigh(); | 530 | bool IsGPULevelHigh(); |
| 527 | 531 | ||
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h index 7be6f26f7..3175ab07d 100644 --- a/src/common/settings_setting.h +++ b/src/common/settings_setting.h | |||
| @@ -187,6 +187,8 @@ public: | |||
| 187 | this->SetValue(input == "true"); | 187 | this->SetValue(input == "true"); |
| 188 | } else if constexpr (std::is_same_v<Type, float>) { | 188 | } else if constexpr (std::is_same_v<Type, float>) { |
| 189 | this->SetValue(std::stof(input)); | 189 | this->SetValue(std::stof(input)); |
| 190 | } else if constexpr (std::is_same_v<Type, AudioEngine>) { | ||
| 191 | this->SetValue(ToEnum<AudioEngine>(input)); | ||
| 190 | } else { | 192 | } else { |
| 191 | this->SetValue(static_cast<Type>(std::stoll(input))); | 193 | this->SetValue(static_cast<Type>(std::stoll(input))); |
| 192 | } | 194 | } |
diff --git a/src/core/core.cpp b/src/core/core.cpp index e8300cd05..08cbb8978 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -381,6 +381,10 @@ struct System::Impl { | |||
| 381 | room_member->SendGameInfo(game_info); | 381 | room_member->SendGameInfo(game_info); |
| 382 | } | 382 | } |
| 383 | 383 | ||
| 384 | // Workarounds: | ||
| 385 | // Activate this in Super Smash Brothers Ultimate, it only affects AMD cards using AMDVLK | ||
| 386 | Settings::values.renderer_amdvlk_depth_bias_workaround = program_id == 0x1006A800016E000ULL; | ||
| 387 | |||
| 384 | status = SystemResultStatus::Success; | 388 | status = SystemResultStatus::Success; |
| 385 | return status; | 389 | return status; |
| 386 | } | 390 | } |
| @@ -440,6 +444,9 @@ struct System::Impl { | |||
| 440 | room_member->SendGameInfo(game_info); | 444 | room_member->SendGameInfo(game_info); |
| 441 | } | 445 | } |
| 442 | 446 | ||
| 447 | // Workarounds | ||
| 448 | Settings::values.renderer_amdvlk_depth_bias_workaround = false; | ||
| 449 | |||
| 443 | LOG_DEBUG(Core, "Shutdown OK"); | 450 | LOG_DEBUG(Core, "Shutdown OK"); |
| 444 | } | 451 | } |
| 445 | 452 | ||
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp index 2527ae606..2422cb51b 100644 --- a/src/core/file_sys/partition_filesystem.cpp +++ b/src/core/file_sys/partition_filesystem.cpp | |||
| @@ -47,6 +47,7 @@ PartitionFilesystem::PartitionFilesystem(VirtualFile file) { | |||
| 47 | // Actually read in now... | 47 | // Actually read in now... |
| 48 | std::vector<u8> file_data = file->ReadBytes(metadata_size); | 48 | std::vector<u8> file_data = file->ReadBytes(metadata_size); |
| 49 | const std::size_t total_size = file_data.size(); | 49 | const std::size_t total_size = file_data.size(); |
| 50 | file_data.push_back(0); | ||
| 50 | 51 | ||
| 51 | if (total_size != metadata_size) { | 52 | if (total_size != metadata_size) { |
| 52 | status = Loader::ResultStatus::ErrorIncorrectPFSFileSize; | 53 | status = Loader::ResultStatus::ErrorIncorrectPFSFileSize; |
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 8ffdd19e7..a83622f7c 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp | |||
| @@ -19,6 +19,7 @@ | |||
| 19 | #include "core/hle/service/am/am.h" | 19 | #include "core/hle/service/am/am.h" |
| 20 | #include "core/hle/service/am/applet_ae.h" | 20 | #include "core/hle/service/am/applet_ae.h" |
| 21 | #include "core/hle/service/am/applet_oe.h" | 21 | #include "core/hle/service/am/applet_oe.h" |
| 22 | #include "core/hle/service/am/applets/applet_mii_edit_types.h" | ||
| 22 | #include "core/hle/service/am/applets/applet_profile_select.h" | 23 | #include "core/hle/service/am/applets/applet_profile_select.h" |
| 23 | #include "core/hle/service/am/applets/applet_web_browser.h" | 24 | #include "core/hle/service/am/applets/applet_web_browser.h" |
| 24 | #include "core/hle/service/am/applets/applets.h" | 25 | #include "core/hle/service/am/applets/applets.h" |
| @@ -190,7 +191,7 @@ IDisplayController::IDisplayController(Core::System& system_) | |||
| 190 | {5, nullptr, "GetLastForegroundCaptureImageEx"}, | 191 | {5, nullptr, "GetLastForegroundCaptureImageEx"}, |
| 191 | {6, nullptr, "GetLastApplicationCaptureImageEx"}, | 192 | {6, nullptr, "GetLastApplicationCaptureImageEx"}, |
| 192 | {7, nullptr, "GetCallerAppletCaptureImageEx"}, | 193 | {7, nullptr, "GetCallerAppletCaptureImageEx"}, |
| 193 | {8, nullptr, "TakeScreenShotOfOwnLayer"}, | 194 | {8, &IDisplayController::TakeScreenShotOfOwnLayer, "TakeScreenShotOfOwnLayer"}, |
| 194 | {9, nullptr, "CopyBetweenCaptureBuffers"}, | 195 | {9, nullptr, "CopyBetweenCaptureBuffers"}, |
| 195 | {10, nullptr, "AcquireLastApplicationCaptureBuffer"}, | 196 | {10, nullptr, "AcquireLastApplicationCaptureBuffer"}, |
| 196 | {11, nullptr, "ReleaseLastApplicationCaptureBuffer"}, | 197 | {11, nullptr, "ReleaseLastApplicationCaptureBuffer"}, |
| @@ -218,6 +219,13 @@ IDisplayController::IDisplayController(Core::System& system_) | |||
| 218 | 219 | ||
| 219 | IDisplayController::~IDisplayController() = default; | 220 | IDisplayController::~IDisplayController() = default; |
| 220 | 221 | ||
| 222 | void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) { | ||
| 223 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 224 | |||
| 225 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 226 | rb.Push(ResultSuccess); | ||
| 227 | } | ||
| 228 | |||
| 221 | IDebugFunctions::IDebugFunctions(Core::System& system_) | 229 | IDebugFunctions::IDebugFunctions(Core::System& system_) |
| 222 | : ServiceFramework{system_, "IDebugFunctions"} { | 230 | : ServiceFramework{system_, "IDebugFunctions"} { |
| 223 | // clang-format off | 231 | // clang-format off |
| @@ -724,7 +732,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, | |||
| 724 | {110, nullptr, "OpenMyGpuErrorHandler"}, | 732 | {110, nullptr, "OpenMyGpuErrorHandler"}, |
| 725 | {120, nullptr, "GetAppletLaunchedHistory"}, | 733 | {120, nullptr, "GetAppletLaunchedHistory"}, |
| 726 | {200, nullptr, "GetOperationModeSystemInfo"}, | 734 | {200, nullptr, "GetOperationModeSystemInfo"}, |
| 727 | {300, nullptr, "GetSettingsPlatformRegion"}, | 735 | {300, &ICommonStateGetter::GetSettingsPlatformRegion, "GetSettingsPlatformRegion"}, |
| 728 | {400, nullptr, "ActivateMigrationService"}, | 736 | {400, nullptr, "ActivateMigrationService"}, |
| 729 | {401, nullptr, "DeactivateMigrationService"}, | 737 | {401, nullptr, "DeactivateMigrationService"}, |
| 730 | {500, nullptr, "DisableSleepTillShutdown"}, | 738 | {500, nullptr, "DisableSleepTillShutdown"}, |
| @@ -736,6 +744,10 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, | |||
| 736 | // clang-format on | 744 | // clang-format on |
| 737 | 745 | ||
| 738 | RegisterHandlers(functions); | 746 | RegisterHandlers(functions); |
| 747 | |||
| 748 | // Configure applets to be in foreground state | ||
| 749 | msg_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); | ||
| 750 | msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground); | ||
| 739 | } | 751 | } |
| 740 | 752 | ||
| 741 | ICommonStateGetter::~ICommonStateGetter() = default; | 753 | ICommonStateGetter::~ICommonStateGetter() = default; |
| @@ -867,6 +879,14 @@ void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& | |||
| 867 | rb.Push(ResultSuccess); | 879 | rb.Push(ResultSuccess); |
| 868 | } | 880 | } |
| 869 | 881 | ||
| 882 | void ICommonStateGetter::GetSettingsPlatformRegion(HLERequestContext& ctx) { | ||
| 883 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 884 | |||
| 885 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 886 | rb.Push(ResultSuccess); | ||
| 887 | rb.PushEnum(SysPlatformRegion::Global); | ||
| 888 | } | ||
| 889 | |||
| 870 | void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled( | 890 | void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled( |
| 871 | HLERequestContext& ctx) { | 891 | HLERequestContext& ctx) { |
| 872 | LOG_WARNING(Service_AM, "(STUBBED) called"); | 892 | LOG_WARNING(Service_AM, "(STUBBED) called"); |
| @@ -1324,18 +1344,19 @@ void ILibraryAppletCreator::CreateHandleStorage(HLERequestContext& ctx) { | |||
| 1324 | 1344 | ||
| 1325 | ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_) | 1345 | ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_) |
| 1326 | : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} { | 1346 | : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} { |
| 1347 | // clang-format off | ||
| 1327 | static const FunctionInfo functions[] = { | 1348 | static const FunctionInfo functions[] = { |
| 1328 | {0, nullptr, "PopInData"}, | 1349 | {0, &ILibraryAppletSelfAccessor::PopInData, "PopInData"}, |
| 1329 | {1, nullptr, "PushOutData"}, | 1350 | {1, &ILibraryAppletSelfAccessor::PushOutData, "PushOutData"}, |
| 1330 | {2, nullptr, "PopInteractiveInData"}, | 1351 | {2, nullptr, "PopInteractiveInData"}, |
| 1331 | {3, nullptr, "PushInteractiveOutData"}, | 1352 | {3, nullptr, "PushInteractiveOutData"}, |
| 1332 | {5, nullptr, "GetPopInDataEvent"}, | 1353 | {5, nullptr, "GetPopInDataEvent"}, |
| 1333 | {6, nullptr, "GetPopInteractiveInDataEvent"}, | 1354 | {6, nullptr, "GetPopInteractiveInDataEvent"}, |
| 1334 | {10, nullptr, "ExitProcessAndReturn"}, | 1355 | {10, &ILibraryAppletSelfAccessor::ExitProcessAndReturn, "ExitProcessAndReturn"}, |
| 1335 | {11, nullptr, "GetLibraryAppletInfo"}, | 1356 | {11, &ILibraryAppletSelfAccessor::GetLibraryAppletInfo, "GetLibraryAppletInfo"}, |
| 1336 | {12, nullptr, "GetMainAppletIdentityInfo"}, | 1357 | {12, nullptr, "GetMainAppletIdentityInfo"}, |
| 1337 | {13, nullptr, "CanUseApplicationCore"}, | 1358 | {13, nullptr, "CanUseApplicationCore"}, |
| 1338 | {14, nullptr, "GetCallerAppletIdentityInfo"}, | 1359 | {14, &ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo, "GetCallerAppletIdentityInfo"}, |
| 1339 | {15, nullptr, "GetMainAppletApplicationControlProperty"}, | 1360 | {15, nullptr, "GetMainAppletApplicationControlProperty"}, |
| 1340 | {16, nullptr, "GetMainAppletStorageId"}, | 1361 | {16, nullptr, "GetMainAppletStorageId"}, |
| 1341 | {17, nullptr, "GetCallerAppletIdentityInfoStack"}, | 1362 | {17, nullptr, "GetCallerAppletIdentityInfoStack"}, |
| @@ -1361,10 +1382,142 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_) | |||
| 1361 | {140, nullptr, "SetApplicationMemoryReservation"}, | 1382 | {140, nullptr, "SetApplicationMemoryReservation"}, |
| 1362 | {150, nullptr, "ShouldSetGpuTimeSliceManually"}, | 1383 | {150, nullptr, "ShouldSetGpuTimeSliceManually"}, |
| 1363 | }; | 1384 | }; |
| 1385 | // clang-format on | ||
| 1364 | RegisterHandlers(functions); | 1386 | RegisterHandlers(functions); |
| 1387 | |||
| 1388 | PushInShowMiiEditData(); | ||
| 1365 | } | 1389 | } |
| 1366 | 1390 | ||
| 1367 | ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default; | 1391 | ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default; |
| 1392 | void ILibraryAppletSelfAccessor::PopInData(HLERequestContext& ctx) { | ||
| 1393 | LOG_INFO(Service_AM, "called"); | ||
| 1394 | |||
| 1395 | if (queue_data.empty()) { | ||
| 1396 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1397 | rb.Push(ResultNoDataInChannel); | ||
| 1398 | return; | ||
| 1399 | } | ||
| 1400 | |||
| 1401 | auto data = queue_data.front(); | ||
| 1402 | queue_data.pop_front(); | ||
| 1403 | |||
| 1404 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 1405 | rb.Push(ResultSuccess); | ||
| 1406 | rb.PushIpcInterface<IStorage>(system, std::move(data)); | ||
| 1407 | } | ||
| 1408 | |||
| 1409 | void ILibraryAppletSelfAccessor::PushOutData(HLERequestContext& ctx) { | ||
| 1410 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 1411 | |||
| 1412 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1413 | rb.Push(ResultSuccess); | ||
| 1414 | } | ||
| 1415 | |||
| 1416 | void ILibraryAppletSelfAccessor::ExitProcessAndReturn(HLERequestContext& ctx) { | ||
| 1417 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 1418 | |||
| 1419 | system.Exit(); | ||
| 1420 | |||
| 1421 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1422 | rb.Push(ResultSuccess); | ||
| 1423 | } | ||
| 1424 | |||
| 1425 | void ILibraryAppletSelfAccessor::GetLibraryAppletInfo(HLERequestContext& ctx) { | ||
| 1426 | struct LibraryAppletInfo { | ||
| 1427 | Applets::AppletId applet_id; | ||
| 1428 | Applets::LibraryAppletMode library_applet_mode; | ||
| 1429 | }; | ||
| 1430 | |||
| 1431 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 1432 | |||
| 1433 | const LibraryAppletInfo applet_info{ | ||
| 1434 | .applet_id = Applets::AppletId::MiiEdit, | ||
| 1435 | .library_applet_mode = Applets::LibraryAppletMode::AllForeground, | ||
| 1436 | }; | ||
| 1437 | |||
| 1438 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 1439 | rb.Push(ResultSuccess); | ||
| 1440 | rb.PushRaw(applet_info); | ||
| 1441 | } | ||
| 1442 | |||
| 1443 | void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext& ctx) { | ||
| 1444 | struct AppletIdentityInfo { | ||
| 1445 | Applets::AppletId applet_id; | ||
| 1446 | INSERT_PADDING_BYTES(0x4); | ||
| 1447 | u64 application_id; | ||
| 1448 | }; | ||
| 1449 | |||
| 1450 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 1451 | |||
| 1452 | const AppletIdentityInfo applet_info{ | ||
| 1453 | .applet_id = Applets::AppletId::QLaunch, | ||
| 1454 | .application_id = 0x0100000000001000ull, | ||
| 1455 | }; | ||
| 1456 | |||
| 1457 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 1458 | rb.Push(ResultSuccess); | ||
| 1459 | rb.PushRaw(applet_info); | ||
| 1460 | } | ||
| 1461 | |||
| 1462 | void ILibraryAppletSelfAccessor::PushInShowMiiEditData() { | ||
| 1463 | struct MiiEditV3 { | ||
| 1464 | Applets::MiiEditAppletInputCommon common; | ||
| 1465 | Applets::MiiEditAppletInputV3 input; | ||
| 1466 | }; | ||
| 1467 | static_assert(sizeof(MiiEditV3) == 0x100, "MiiEditV3 has incorrect size."); | ||
| 1468 | |||
| 1469 | MiiEditV3 mii_arguments{ | ||
| 1470 | .common = | ||
| 1471 | { | ||
| 1472 | .version = Applets::MiiEditAppletVersion::Version3, | ||
| 1473 | .applet_mode = Applets::MiiEditAppletMode::ShowMiiEdit, | ||
| 1474 | }, | ||
| 1475 | .input{}, | ||
| 1476 | }; | ||
| 1477 | |||
| 1478 | std::vector<u8> argument_data(sizeof(mii_arguments)); | ||
| 1479 | std::memcpy(argument_data.data(), &mii_arguments, sizeof(mii_arguments)); | ||
| 1480 | |||
| 1481 | queue_data.emplace_back(std::move(argument_data)); | ||
| 1482 | } | ||
| 1483 | |||
| 1484 | IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_) | ||
| 1485 | : ServiceFramework{system_, "IAppletCommonFunctions"} { | ||
| 1486 | // clang-format off | ||
| 1487 | static const FunctionInfo functions[] = { | ||
| 1488 | {0, nullptr, "SetTerminateResult"}, | ||
| 1489 | {10, nullptr, "ReadThemeStorage"}, | ||
| 1490 | {11, nullptr, "WriteThemeStorage"}, | ||
| 1491 | {20, nullptr, "PushToAppletBoundChannel"}, | ||
| 1492 | {21, nullptr, "TryPopFromAppletBoundChannel"}, | ||
| 1493 | {40, nullptr, "GetDisplayLogicalResolution"}, | ||
| 1494 | {42, nullptr, "SetDisplayMagnification"}, | ||
| 1495 | {50, nullptr, "SetHomeButtonDoubleClickEnabled"}, | ||
| 1496 | {51, nullptr, "GetHomeButtonDoubleClickEnabled"}, | ||
| 1497 | {52, nullptr, "IsHomeButtonShortPressedBlocked"}, | ||
| 1498 | {60, nullptr, "IsVrModeCurtainRequired"}, | ||
| 1499 | {61, nullptr, "IsSleepRequiredByHighTemperature"}, | ||
| 1500 | {62, nullptr, "IsSleepRequiredByLowBattery"}, | ||
| 1501 | {70, &IAppletCommonFunctions::SetCpuBoostRequestPriority, "SetCpuBoostRequestPriority"}, | ||
| 1502 | {80, nullptr, "SetHandlingCaptureButtonShortPressedMessageEnabledForApplet"}, | ||
| 1503 | {81, nullptr, "SetHandlingCaptureButtonLongPressedMessageEnabledForApplet"}, | ||
| 1504 | {90, nullptr, "OpenNamedChannelAsParent"}, | ||
| 1505 | {91, nullptr, "OpenNamedChannelAsChild"}, | ||
| 1506 | {100, nullptr, "SetApplicationCoreUsageMode"}, | ||
| 1507 | }; | ||
| 1508 | // clang-format on | ||
| 1509 | |||
| 1510 | RegisterHandlers(functions); | ||
| 1511 | } | ||
| 1512 | |||
| 1513 | IAppletCommonFunctions::~IAppletCommonFunctions() = default; | ||
| 1514 | |||
| 1515 | void IAppletCommonFunctions::SetCpuBoostRequestPriority(HLERequestContext& ctx) { | ||
| 1516 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 1517 | |||
| 1518 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1519 | rb.Push(ResultSuccess); | ||
| 1520 | } | ||
| 1368 | 1521 | ||
| 1369 | IApplicationFunctions::IApplicationFunctions(Core::System& system_) | 1522 | IApplicationFunctions::IApplicationFunctions(Core::System& system_) |
| 1370 | : ServiceFramework{system_, "IApplicationFunctions"}, service_context{system, | 1523 | : ServiceFramework{system_, "IApplicationFunctions"}, service_context{system, |
| @@ -1941,9 +2094,6 @@ void IApplicationFunctions::PrepareForJit(HLERequestContext& ctx) { | |||
| 1941 | 2094 | ||
| 1942 | void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) { | 2095 | void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) { |
| 1943 | auto message_queue = std::make_shared<AppletMessageQueue>(system); | 2096 | auto message_queue = std::make_shared<AppletMessageQueue>(system); |
| 1944 | // Needed on game boot | ||
| 1945 | message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); | ||
| 1946 | |||
| 1947 | auto server_manager = std::make_unique<ServerManager>(system); | 2097 | auto server_manager = std::make_unique<ServerManager>(system); |
| 1948 | 2098 | ||
| 1949 | server_manager->RegisterNamedService( | 2099 | server_manager->RegisterNamedService( |
| @@ -2049,8 +2199,8 @@ IProcessWindingController::IProcessWindingController(Core::System& system_) | |||
| 2049 | : ServiceFramework{system_, "IProcessWindingController"} { | 2199 | : ServiceFramework{system_, "IProcessWindingController"} { |
| 2050 | // clang-format off | 2200 | // clang-format off |
| 2051 | static const FunctionInfo functions[] = { | 2201 | static const FunctionInfo functions[] = { |
| 2052 | {0, nullptr, "GetLaunchReason"}, | 2202 | {0, &IProcessWindingController::GetLaunchReason, "GetLaunchReason"}, |
| 2053 | {11, nullptr, "OpenCallingLibraryApplet"}, | 2203 | {11, &IProcessWindingController::OpenCallingLibraryApplet, "OpenCallingLibraryApplet"}, |
| 2054 | {21, nullptr, "PushContext"}, | 2204 | {21, nullptr, "PushContext"}, |
| 2055 | {22, nullptr, "PopContext"}, | 2205 | {22, nullptr, "PopContext"}, |
| 2056 | {23, nullptr, "CancelWindingReservation"}, | 2206 | {23, nullptr, "CancelWindingReservation"}, |
| @@ -2064,4 +2214,46 @@ IProcessWindingController::IProcessWindingController(Core::System& system_) | |||
| 2064 | } | 2214 | } |
| 2065 | 2215 | ||
| 2066 | IProcessWindingController::~IProcessWindingController() = default; | 2216 | IProcessWindingController::~IProcessWindingController() = default; |
| 2217 | |||
| 2218 | void IProcessWindingController::GetLaunchReason(HLERequestContext& ctx) { | ||
| 2219 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 2220 | |||
| 2221 | struct AppletProcessLaunchReason { | ||
| 2222 | u8 flag; | ||
| 2223 | INSERT_PADDING_BYTES(3); | ||
| 2224 | }; | ||
| 2225 | static_assert(sizeof(AppletProcessLaunchReason) == 0x4, | ||
| 2226 | "AppletProcessLaunchReason is an invalid size"); | ||
| 2227 | |||
| 2228 | AppletProcessLaunchReason reason{ | ||
| 2229 | .flag = 0, | ||
| 2230 | }; | ||
| 2231 | |||
| 2232 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 2233 | rb.Push(ResultSuccess); | ||
| 2234 | rb.PushRaw(reason); | ||
| 2235 | } | ||
| 2236 | |||
| 2237 | void IProcessWindingController::OpenCallingLibraryApplet(HLERequestContext& ctx) { | ||
| 2238 | const auto applet_id = Applets::AppletId::MiiEdit; | ||
| 2239 | const auto applet_mode = Applets::LibraryAppletMode::AllForeground; | ||
| 2240 | |||
| 2241 | LOG_WARNING(Service_AM, "(STUBBED) called with applet_id={:08X}, applet_mode={:08X}", applet_id, | ||
| 2242 | applet_mode); | ||
| 2243 | |||
| 2244 | const auto& applet_manager{system.GetAppletManager()}; | ||
| 2245 | const auto applet = applet_manager.GetApplet(applet_id, applet_mode); | ||
| 2246 | |||
| 2247 | if (applet == nullptr) { | ||
| 2248 | LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id); | ||
| 2249 | |||
| 2250 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2251 | rb.Push(ResultUnknown); | ||
| 2252 | return; | ||
| 2253 | } | ||
| 2254 | |||
| 2255 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 2256 | rb.Push(ResultSuccess); | ||
| 2257 | rb.PushIpcInterface<ILibraryAppletAccessor>(system, applet); | ||
| 2258 | } | ||
| 2067 | } // namespace Service::AM | 2259 | } // namespace Service::AM |
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index f86841c60..5b97eb5e3 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h | |||
| @@ -120,6 +120,9 @@ class IDisplayController final : public ServiceFramework<IDisplayController> { | |||
| 120 | public: | 120 | public: |
| 121 | explicit IDisplayController(Core::System& system_); | 121 | explicit IDisplayController(Core::System& system_); |
| 122 | ~IDisplayController() override; | 122 | ~IDisplayController() override; |
| 123 | |||
| 124 | private: | ||
| 125 | void TakeScreenShotOfOwnLayer(HLERequestContext& ctx); | ||
| 123 | }; | 126 | }; |
| 124 | 127 | ||
| 125 | class IDebugFunctions final : public ServiceFramework<IDebugFunctions> { | 128 | class IDebugFunctions final : public ServiceFramework<IDebugFunctions> { |
| @@ -212,6 +215,11 @@ private: | |||
| 212 | CaptureButtonLongPressing, | 215 | CaptureButtonLongPressing, |
| 213 | }; | 216 | }; |
| 214 | 217 | ||
| 218 | enum class SysPlatformRegion : s32 { | ||
| 219 | Global = 1, | ||
| 220 | Terra = 2, | ||
| 221 | }; | ||
| 222 | |||
| 215 | void GetEventHandle(HLERequestContext& ctx); | 223 | void GetEventHandle(HLERequestContext& ctx); |
| 216 | void ReceiveMessage(HLERequestContext& ctx); | 224 | void ReceiveMessage(HLERequestContext& ctx); |
| 217 | void GetCurrentFocusState(HLERequestContext& ctx); | 225 | void GetCurrentFocusState(HLERequestContext& ctx); |
| @@ -227,6 +235,7 @@ private: | |||
| 227 | void GetDefaultDisplayResolution(HLERequestContext& ctx); | 235 | void GetDefaultDisplayResolution(HLERequestContext& ctx); |
| 228 | void SetCpuBoostMode(HLERequestContext& ctx); | 236 | void SetCpuBoostMode(HLERequestContext& ctx); |
| 229 | void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx); | 237 | void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx); |
| 238 | void GetSettingsPlatformRegion(HLERequestContext& ctx); | ||
| 230 | void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx); | 239 | void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx); |
| 231 | 240 | ||
| 232 | std::shared_ptr<AppletMessageQueue> msg_queue; | 241 | std::shared_ptr<AppletMessageQueue> msg_queue; |
| @@ -294,6 +303,26 @@ class ILibraryAppletSelfAccessor final : public ServiceFramework<ILibraryAppletS | |||
| 294 | public: | 303 | public: |
| 295 | explicit ILibraryAppletSelfAccessor(Core::System& system_); | 304 | explicit ILibraryAppletSelfAccessor(Core::System& system_); |
| 296 | ~ILibraryAppletSelfAccessor() override; | 305 | ~ILibraryAppletSelfAccessor() override; |
| 306 | |||
| 307 | private: | ||
| 308 | void PopInData(HLERequestContext& ctx); | ||
| 309 | void PushOutData(HLERequestContext& ctx); | ||
| 310 | void GetLibraryAppletInfo(HLERequestContext& ctx); | ||
| 311 | void ExitProcessAndReturn(HLERequestContext& ctx); | ||
| 312 | void GetCallerAppletIdentityInfo(HLERequestContext& ctx); | ||
| 313 | |||
| 314 | void PushInShowMiiEditData(); | ||
| 315 | |||
| 316 | std::deque<std::vector<u8>> queue_data; | ||
| 317 | }; | ||
| 318 | |||
| 319 | class IAppletCommonFunctions final : public ServiceFramework<IAppletCommonFunctions> { | ||
| 320 | public: | ||
| 321 | explicit IAppletCommonFunctions(Core::System& system_); | ||
| 322 | ~IAppletCommonFunctions() override; | ||
| 323 | |||
| 324 | private: | ||
| 325 | void SetCpuBoostRequestPriority(HLERequestContext& ctx); | ||
| 297 | }; | 326 | }; |
| 298 | 327 | ||
| 299 | class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { | 328 | class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { |
| @@ -378,6 +407,10 @@ class IProcessWindingController final : public ServiceFramework<IProcessWindingC | |||
| 378 | public: | 407 | public: |
| 379 | explicit IProcessWindingController(Core::System& system_); | 408 | explicit IProcessWindingController(Core::System& system_); |
| 380 | ~IProcessWindingController() override; | 409 | ~IProcessWindingController() override; |
| 410 | |||
| 411 | private: | ||
| 412 | void GetLaunchReason(HLERequestContext& ctx); | ||
| 413 | void OpenCallingLibraryApplet(HLERequestContext& ctx); | ||
| 381 | }; | 414 | }; |
| 382 | 415 | ||
| 383 | void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system); | 416 | void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system); |
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp index ee9d99a54..eb12312cc 100644 --- a/src/core/hle/service/am/applet_ae.cpp +++ b/src/core/hle/service/am/applet_ae.cpp | |||
| @@ -27,7 +27,7 @@ public: | |||
| 27 | {10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"}, | 27 | {10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"}, |
| 28 | {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"}, | 28 | {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"}, |
| 29 | {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"}, | 29 | {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"}, |
| 30 | {21, nullptr, "GetAppletCommonFunctions"}, | 30 | {21, &ILibraryAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"}, |
| 31 | {22, nullptr, "GetHomeMenuFunctions"}, | 31 | {22, nullptr, "GetHomeMenuFunctions"}, |
| 32 | {23, nullptr, "GetGlobalStateController"}, | 32 | {23, nullptr, "GetGlobalStateController"}, |
| 33 | {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, | 33 | {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, |
| @@ -86,28 +86,36 @@ private: | |||
| 86 | rb.PushIpcInterface<IProcessWindingController>(system); | 86 | rb.PushIpcInterface<IProcessWindingController>(system); |
| 87 | } | 87 | } |
| 88 | 88 | ||
| 89 | void GetDebugFunctions(HLERequestContext& ctx) { | 89 | void GetLibraryAppletCreator(HLERequestContext& ctx) { |
| 90 | LOG_DEBUG(Service_AM, "called"); | 90 | LOG_DEBUG(Service_AM, "called"); |
| 91 | 91 | ||
| 92 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 92 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 93 | rb.Push(ResultSuccess); | 93 | rb.Push(ResultSuccess); |
| 94 | rb.PushIpcInterface<IDebugFunctions>(system); | 94 | rb.PushIpcInterface<ILibraryAppletCreator>(system); |
| 95 | } | 95 | } |
| 96 | 96 | ||
| 97 | void GetLibraryAppletCreator(HLERequestContext& ctx) { | 97 | void OpenLibraryAppletSelfAccessor(HLERequestContext& ctx) { |
| 98 | LOG_DEBUG(Service_AM, "called"); | 98 | LOG_DEBUG(Service_AM, "called"); |
| 99 | 99 | ||
| 100 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 100 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 101 | rb.Push(ResultSuccess); | 101 | rb.Push(ResultSuccess); |
| 102 | rb.PushIpcInterface<ILibraryAppletCreator>(system); | 102 | rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system); |
| 103 | } | 103 | } |
| 104 | 104 | ||
| 105 | void OpenLibraryAppletSelfAccessor(HLERequestContext& ctx) { | 105 | void GetAppletCommonFunctions(HLERequestContext& ctx) { |
| 106 | LOG_DEBUG(Service_AM, "called"); | 106 | LOG_DEBUG(Service_AM, "called"); |
| 107 | 107 | ||
| 108 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 108 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 109 | rb.Push(ResultSuccess); | 109 | rb.Push(ResultSuccess); |
| 110 | rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system); | 110 | rb.PushIpcInterface<IAppletCommonFunctions>(system); |
| 111 | } | ||
| 112 | |||
| 113 | void GetDebugFunctions(HLERequestContext& ctx) { | ||
| 114 | LOG_DEBUG(Service_AM, "called"); | ||
| 115 | |||
| 116 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 117 | rb.Push(ResultSuccess); | ||
| 118 | rb.PushIpcInterface<IDebugFunctions>(system); | ||
| 111 | } | 119 | } |
| 112 | 120 | ||
| 113 | Nvnflinger::Nvnflinger& nvnflinger; | 121 | Nvnflinger::Nvnflinger& nvnflinger; |
| @@ -133,7 +141,7 @@ public: | |||
| 133 | {20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"}, | 141 | {20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"}, |
| 134 | {21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"}, | 142 | {21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"}, |
| 135 | {22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"}, | 143 | {22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"}, |
| 136 | {23, nullptr, "GetAppletCommonFunctions"}, | 144 | {23, &ISystemAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"}, |
| 137 | {1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, | 145 | {1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, |
| 138 | }; | 146 | }; |
| 139 | // clang-format on | 147 | // clang-format on |
| @@ -182,14 +190,6 @@ private: | |||
| 182 | rb.PushIpcInterface<IDisplayController>(system); | 190 | rb.PushIpcInterface<IDisplayController>(system); |
| 183 | } | 191 | } |
| 184 | 192 | ||
| 185 | void GetDebugFunctions(HLERequestContext& ctx) { | ||
| 186 | LOG_DEBUG(Service_AM, "called"); | ||
| 187 | |||
| 188 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 189 | rb.Push(ResultSuccess); | ||
| 190 | rb.PushIpcInterface<IDebugFunctions>(system); | ||
| 191 | } | ||
| 192 | |||
| 193 | void GetLibraryAppletCreator(HLERequestContext& ctx) { | 193 | void GetLibraryAppletCreator(HLERequestContext& ctx) { |
| 194 | LOG_DEBUG(Service_AM, "called"); | 194 | LOG_DEBUG(Service_AM, "called"); |
| 195 | 195 | ||
| @@ -222,6 +222,22 @@ private: | |||
| 222 | rb.PushIpcInterface<IApplicationCreator>(system); | 222 | rb.PushIpcInterface<IApplicationCreator>(system); |
| 223 | } | 223 | } |
| 224 | 224 | ||
| 225 | void GetAppletCommonFunctions(HLERequestContext& ctx) { | ||
| 226 | LOG_DEBUG(Service_AM, "called"); | ||
| 227 | |||
| 228 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 229 | rb.Push(ResultSuccess); | ||
| 230 | rb.PushIpcInterface<IAppletCommonFunctions>(system); | ||
| 231 | } | ||
| 232 | |||
| 233 | void GetDebugFunctions(HLERequestContext& ctx) { | ||
| 234 | LOG_DEBUG(Service_AM, "called"); | ||
| 235 | |||
| 236 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 237 | rb.Push(ResultSuccess); | ||
| 238 | rb.PushIpcInterface<IDebugFunctions>(system); | ||
| 239 | } | ||
| 240 | |||
| 225 | Nvnflinger::Nvnflinger& nvnflinger; | 241 | Nvnflinger::Nvnflinger& nvnflinger; |
| 226 | std::shared_ptr<AppletMessageQueue> msg_queue; | 242 | std::shared_ptr<AppletMessageQueue> msg_queue; |
| 227 | }; | 243 | }; |
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.cpp b/src/core/hle/service/am/applets/applet_mii_edit.cpp index 350a90818..50adc7c02 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit.cpp +++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp | |||
| @@ -7,7 +7,9 @@ | |||
| 7 | #include "core/frontend/applets/mii_edit.h" | 7 | #include "core/frontend/applets/mii_edit.h" |
| 8 | #include "core/hle/service/am/am.h" | 8 | #include "core/hle/service/am/am.h" |
| 9 | #include "core/hle/service/am/applets/applet_mii_edit.h" | 9 | #include "core/hle/service/am/applets/applet_mii_edit.h" |
| 10 | #include "core/hle/service/mii/mii.h" | ||
| 10 | #include "core/hle/service/mii/mii_manager.h" | 11 | #include "core/hle/service/mii/mii_manager.h" |
| 12 | #include "core/hle/service/sm/sm.h" | ||
| 11 | 13 | ||
| 12 | namespace Service::AM::Applets { | 14 | namespace Service::AM::Applets { |
| 13 | 15 | ||
| @@ -56,6 +58,12 @@ void MiiEdit::Initialize() { | |||
| 56 | sizeof(MiiEditAppletInputV4)); | 58 | sizeof(MiiEditAppletInputV4)); |
| 57 | break; | 59 | break; |
| 58 | } | 60 | } |
| 61 | |||
| 62 | manager = system.ServiceManager().GetService<Mii::MiiDBModule>("mii:e")->GetMiiManager(); | ||
| 63 | if (manager == nullptr) { | ||
| 64 | manager = std::make_shared<Mii::MiiManager>(); | ||
| 65 | } | ||
| 66 | manager->Initialize(metadata); | ||
| 59 | } | 67 | } |
| 60 | 68 | ||
| 61 | bool MiiEdit::TransactionComplete() const { | 69 | bool MiiEdit::TransactionComplete() const { |
| @@ -78,22 +86,46 @@ void MiiEdit::Execute() { | |||
| 78 | // This is a default stub for each of the MiiEdit applet modes. | 86 | // This is a default stub for each of the MiiEdit applet modes. |
| 79 | switch (applet_input_common.applet_mode) { | 87 | switch (applet_input_common.applet_mode) { |
| 80 | case MiiEditAppletMode::ShowMiiEdit: | 88 | case MiiEditAppletMode::ShowMiiEdit: |
| 81 | case MiiEditAppletMode::AppendMii: | ||
| 82 | case MiiEditAppletMode::AppendMiiImage: | 89 | case MiiEditAppletMode::AppendMiiImage: |
| 83 | case MiiEditAppletMode::UpdateMiiImage: | 90 | case MiiEditAppletMode::UpdateMiiImage: |
| 84 | MiiEditOutput(MiiEditResult::Success, 0); | 91 | MiiEditOutput(MiiEditResult::Success, 0); |
| 85 | break; | 92 | break; |
| 86 | case MiiEditAppletMode::CreateMii: | 93 | case MiiEditAppletMode::AppendMii: { |
| 87 | case MiiEditAppletMode::EditMii: { | ||
| 88 | Mii::CharInfo char_info{}; | ||
| 89 | Mii::StoreData store_data{}; | 94 | Mii::StoreData store_data{}; |
| 90 | store_data.BuildBase(Mii::Gender::Male); | 95 | store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All); |
| 91 | char_info.SetFromStoreData(store_data); | 96 | store_data.SetNickname({u'y', u'u', u'z', u'u'}); |
| 97 | store_data.SetChecksum(); | ||
| 98 | const auto result = manager->AddOrReplace(metadata, store_data); | ||
| 99 | |||
| 100 | if (result.IsError()) { | ||
| 101 | MiiEditOutput(MiiEditResult::Cancel, 0); | ||
| 102 | break; | ||
| 103 | } | ||
| 104 | |||
| 105 | s32 index = manager->FindIndex(store_data.GetCreateId(), false); | ||
| 106 | |||
| 107 | if (index == -1) { | ||
| 108 | MiiEditOutput(MiiEditResult::Cancel, 0); | ||
| 109 | break; | ||
| 110 | } | ||
| 111 | |||
| 112 | MiiEditOutput(MiiEditResult::Success, index); | ||
| 113 | break; | ||
| 114 | } | ||
| 115 | case MiiEditAppletMode::CreateMii: { | ||
| 116 | Mii::CharInfo char_info{}; | ||
| 117 | manager->BuildRandom(char_info, Mii::Age::All, Mii::Gender::All, Mii::Race::All); | ||
| 92 | 118 | ||
| 93 | const MiiEditCharInfo edit_char_info{ | 119 | const MiiEditCharInfo edit_char_info{ |
| 94 | .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii | 120 | .mii_info{char_info}, |
| 95 | ? applet_input_v4.char_info.mii_info | 121 | }; |
| 96 | : char_info}, | 122 | |
| 123 | MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info); | ||
| 124 | break; | ||
| 125 | } | ||
| 126 | case MiiEditAppletMode::EditMii: { | ||
| 127 | const MiiEditCharInfo edit_char_info{ | ||
| 128 | .mii_info{applet_input_v4.char_info.mii_info}, | ||
| 97 | }; | 129 | }; |
| 98 | 130 | ||
| 99 | MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info); | 131 | MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info); |
| @@ -113,6 +145,8 @@ void MiiEdit::MiiEditOutput(MiiEditResult result, s32 index) { | |||
| 113 | .index{index}, | 145 | .index{index}, |
| 114 | }; | 146 | }; |
| 115 | 147 | ||
| 148 | LOG_INFO(Input, "called, result={}, index={}", result, index); | ||
| 149 | |||
| 116 | std::vector<u8> out_data(sizeof(MiiEditAppletOutput)); | 150 | std::vector<u8> out_data(sizeof(MiiEditAppletOutput)); |
| 117 | std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutput)); | 151 | std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutput)); |
| 118 | 152 | ||
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.h b/src/core/hle/service/am/applets/applet_mii_edit.h index 3f46fae1b..7ff34af49 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit.h +++ b/src/core/hle/service/am/applets/applet_mii_edit.h | |||
| @@ -11,6 +11,11 @@ namespace Core { | |||
| 11 | class System; | 11 | class System; |
| 12 | } // namespace Core | 12 | } // namespace Core |
| 13 | 13 | ||
| 14 | namespace Service::Mii { | ||
| 15 | struct DatabaseSessionMetadata; | ||
| 16 | class MiiManager; | ||
| 17 | } // namespace Service::Mii | ||
| 18 | |||
| 14 | namespace Service::AM::Applets { | 19 | namespace Service::AM::Applets { |
| 15 | 20 | ||
| 16 | class MiiEdit final : public Applet { | 21 | class MiiEdit final : public Applet { |
| @@ -40,6 +45,8 @@ private: | |||
| 40 | MiiEditAppletInputV4 applet_input_v4{}; | 45 | MiiEditAppletInputV4 applet_input_v4{}; |
| 41 | 46 | ||
| 42 | bool is_complete{false}; | 47 | bool is_complete{false}; |
| 48 | std::shared_ptr<Mii::MiiManager> manager = nullptr; | ||
| 49 | Mii::DatabaseSessionMetadata metadata{}; | ||
| 43 | }; | 50 | }; |
| 44 | 51 | ||
| 45 | } // namespace Service::AM::Applets | 52 | } // namespace Service::AM::Applets |
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 6e4d26b1e..c2054e8a0 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp | |||
| @@ -329,6 +329,7 @@ public: | |||
| 329 | {13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"}, | 329 | {13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"}, |
| 330 | {14, &IFileSystem::GetFileTimeStampRaw, "GetFileTimeStampRaw"}, | 330 | {14, &IFileSystem::GetFileTimeStampRaw, "GetFileTimeStampRaw"}, |
| 331 | {15, nullptr, "QueryEntry"}, | 331 | {15, nullptr, "QueryEntry"}, |
| 332 | {16, &IFileSystem::GetFileSystemAttribute, "GetFileSystemAttribute"}, | ||
| 332 | }; | 333 | }; |
| 333 | RegisterHandlers(functions); | 334 | RegisterHandlers(functions); |
| 334 | } | 335 | } |
| @@ -521,6 +522,46 @@ public: | |||
| 521 | rb.PushRaw(vfs_timestamp); | 522 | rb.PushRaw(vfs_timestamp); |
| 522 | } | 523 | } |
| 523 | 524 | ||
| 525 | void GetFileSystemAttribute(HLERequestContext& ctx) { | ||
| 526 | LOG_WARNING(Service_FS, "(STUBBED) called"); | ||
| 527 | |||
| 528 | struct FileSystemAttribute { | ||
| 529 | u8 dir_entry_name_length_max_defined; | ||
| 530 | u8 file_entry_name_length_max_defined; | ||
| 531 | u8 dir_path_name_length_max_defined; | ||
| 532 | u8 file_path_name_length_max_defined; | ||
| 533 | INSERT_PADDING_BYTES_NOINIT(0x5); | ||
| 534 | u8 utf16_dir_entry_name_length_max_defined; | ||
| 535 | u8 utf16_file_entry_name_length_max_defined; | ||
| 536 | u8 utf16_dir_path_name_length_max_defined; | ||
| 537 | u8 utf16_file_path_name_length_max_defined; | ||
| 538 | INSERT_PADDING_BYTES_NOINIT(0x18); | ||
| 539 | s32 dir_entry_name_length_max; | ||
| 540 | s32 file_entry_name_length_max; | ||
| 541 | s32 dir_path_name_length_max; | ||
| 542 | s32 file_path_name_length_max; | ||
| 543 | INSERT_PADDING_WORDS_NOINIT(0x5); | ||
| 544 | s32 utf16_dir_entry_name_length_max; | ||
| 545 | s32 utf16_file_entry_name_length_max; | ||
| 546 | s32 utf16_dir_path_name_length_max; | ||
| 547 | s32 utf16_file_path_name_length_max; | ||
| 548 | INSERT_PADDING_WORDS_NOINIT(0x18); | ||
| 549 | INSERT_PADDING_WORDS_NOINIT(0x1); | ||
| 550 | }; | ||
| 551 | static_assert(sizeof(FileSystemAttribute) == 0xc0, | ||
| 552 | "FileSystemAttribute has incorrect size"); | ||
| 553 | |||
| 554 | FileSystemAttribute savedata_attribute{}; | ||
| 555 | savedata_attribute.dir_entry_name_length_max_defined = true; | ||
| 556 | savedata_attribute.file_entry_name_length_max_defined = true; | ||
| 557 | savedata_attribute.dir_entry_name_length_max = 0x40; | ||
| 558 | savedata_attribute.file_entry_name_length_max = 0x40; | ||
| 559 | |||
| 560 | IPC::ResponseBuilder rb{ctx, 50}; | ||
| 561 | rb.Push(ResultSuccess); | ||
| 562 | rb.PushRaw(savedata_attribute); | ||
| 563 | } | ||
| 564 | |||
| 524 | private: | 565 | private: |
| 525 | VfsDirectoryServiceWrapper backend; | 566 | VfsDirectoryServiceWrapper backend; |
| 526 | SizeGetter size; | 567 | SizeGetter size; |
| @@ -698,7 +739,7 @@ FSP_SRV::FSP_SRV(Core::System& system_) | |||
| 698 | {19, nullptr, "FormatSdCardFileSystem"}, | 739 | {19, nullptr, "FormatSdCardFileSystem"}, |
| 699 | {21, nullptr, "DeleteSaveDataFileSystem"}, | 740 | {21, nullptr, "DeleteSaveDataFileSystem"}, |
| 700 | {22, &FSP_SRV::CreateSaveDataFileSystem, "CreateSaveDataFileSystem"}, | 741 | {22, &FSP_SRV::CreateSaveDataFileSystem, "CreateSaveDataFileSystem"}, |
| 701 | {23, nullptr, "CreateSaveDataFileSystemBySystemSaveDataId"}, | 742 | {23, &FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId, "CreateSaveDataFileSystemBySystemSaveDataId"}, |
| 702 | {24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"}, | 743 | {24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"}, |
| 703 | {25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"}, | 744 | {25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"}, |
| 704 | {26, nullptr, "FormatSdCardDryRun"}, | 745 | {26, nullptr, "FormatSdCardDryRun"}, |
| @@ -712,7 +753,7 @@ FSP_SRV::FSP_SRV(Core::System& system_) | |||
| 712 | {35, nullptr, "CreateSaveDataFileSystemByHashSalt"}, | 753 | {35, nullptr, "CreateSaveDataFileSystemByHashSalt"}, |
| 713 | {36, nullptr, "OpenHostFileSystemWithOption"}, | 754 | {36, nullptr, "OpenHostFileSystemWithOption"}, |
| 714 | {51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"}, | 755 | {51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"}, |
| 715 | {52, nullptr, "OpenSaveDataFileSystemBySystemSaveDataId"}, | 756 | {52, &FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId, "OpenSaveDataFileSystemBySystemSaveDataId"}, |
| 716 | {53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"}, | 757 | {53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"}, |
| 717 | {57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"}, | 758 | {57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"}, |
| 718 | {58, nullptr, "ReadSaveDataFileSystemExtraData"}, | 759 | {58, nullptr, "ReadSaveDataFileSystemExtraData"}, |
| @@ -870,6 +911,21 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) { | |||
| 870 | rb.Push(ResultSuccess); | 911 | rb.Push(ResultSuccess); |
| 871 | } | 912 | } |
| 872 | 913 | ||
| 914 | void FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) { | ||
| 915 | IPC::RequestParser rp{ctx}; | ||
| 916 | |||
| 917 | auto save_struct = rp.PopRaw<FileSys::SaveDataAttribute>(); | ||
| 918 | [[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>(); | ||
| 919 | |||
| 920 | LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo()); | ||
| 921 | |||
| 922 | FileSys::VirtualDir save_data_dir{}; | ||
| 923 | fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandSystem, save_struct); | ||
| 924 | |||
| 925 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 926 | rb.Push(ResultSuccess); | ||
| 927 | } | ||
| 928 | |||
| 873 | void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) { | 929 | void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) { |
| 874 | IPC::RequestParser rp{ctx}; | 930 | IPC::RequestParser rp{ctx}; |
| 875 | 931 | ||
| @@ -916,6 +972,11 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) { | |||
| 916 | rb.PushIpcInterface<IFileSystem>(std::move(filesystem)); | 972 | rb.PushIpcInterface<IFileSystem>(std::move(filesystem)); |
| 917 | } | 973 | } |
| 918 | 974 | ||
| 975 | void FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) { | ||
| 976 | LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem"); | ||
| 977 | OpenSaveDataFileSystem(ctx); | ||
| 978 | } | ||
| 979 | |||
| 919 | void FSP_SRV::OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx) { | 980 | void FSP_SRV::OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx) { |
| 920 | LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem"); | 981 | LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem"); |
| 921 | OpenSaveDataFileSystem(ctx); | 982 | OpenSaveDataFileSystem(ctx); |
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index 4f3c2f6de..280bc9867 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h | |||
| @@ -39,7 +39,9 @@ private: | |||
| 39 | void OpenFileSystemWithPatch(HLERequestContext& ctx); | 39 | void OpenFileSystemWithPatch(HLERequestContext& ctx); |
| 40 | void OpenSdCardFileSystem(HLERequestContext& ctx); | 40 | void OpenSdCardFileSystem(HLERequestContext& ctx); |
| 41 | void CreateSaveDataFileSystem(HLERequestContext& ctx); | 41 | void CreateSaveDataFileSystem(HLERequestContext& ctx); |
| 42 | void CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx); | ||
| 42 | void OpenSaveDataFileSystem(HLERequestContext& ctx); | 43 | void OpenSaveDataFileSystem(HLERequestContext& ctx); |
| 44 | void OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx); | ||
| 43 | void OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx); | 45 | void OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx); |
| 44 | void OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx); | 46 | void OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx); |
| 45 | void OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx); | 47 | void OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx); |
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 146bb486d..bc822f19e 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp | |||
| @@ -346,6 +346,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 346 | } | 346 | } |
| 347 | SignalStyleSetChangedEvent(npad_id); | 347 | SignalStyleSetChangedEvent(npad_id); |
| 348 | WriteEmptyEntry(controller.shared_memory); | 348 | WriteEmptyEntry(controller.shared_memory); |
| 349 | hid_core.SetLastActiveController(npad_id); | ||
| 349 | } | 350 | } |
| 350 | 351 | ||
| 351 | void Controller_NPad::OnInit() { | 352 | void Controller_NPad::OnInit() { |
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index 8de806cfb..c28eed926 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp | |||
| @@ -18,8 +18,10 @@ namespace Service::Mii { | |||
| 18 | 18 | ||
| 19 | class IDatabaseService final : public ServiceFramework<IDatabaseService> { | 19 | class IDatabaseService final : public ServiceFramework<IDatabaseService> { |
| 20 | public: | 20 | public: |
| 21 | explicit IDatabaseService(Core::System& system_, bool is_system_) | 21 | explicit IDatabaseService(Core::System& system_, std::shared_ptr<MiiManager> mii_manager, |
| 22 | : ServiceFramework{system_, "IDatabaseService"}, is_system{is_system_} { | 22 | bool is_system_) |
| 23 | : ServiceFramework{system_, "IDatabaseService"}, manager{mii_manager}, is_system{ | ||
| 24 | is_system_} { | ||
| 23 | // clang-format off | 25 | // clang-format off |
| 24 | static const FunctionInfo functions[] = { | 26 | static const FunctionInfo functions[] = { |
| 25 | {0, &IDatabaseService::IsUpdated, "IsUpdated"}, | 27 | {0, &IDatabaseService::IsUpdated, "IsUpdated"}, |
| @@ -54,7 +56,7 @@ public: | |||
| 54 | 56 | ||
| 55 | RegisterHandlers(functions); | 57 | RegisterHandlers(functions); |
| 56 | 58 | ||
| 57 | manager.Initialize(metadata); | 59 | manager->Initialize(metadata); |
| 58 | } | 60 | } |
| 59 | 61 | ||
| 60 | private: | 62 | private: |
| @@ -64,7 +66,7 @@ private: | |||
| 64 | 66 | ||
| 65 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 67 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); |
| 66 | 68 | ||
| 67 | const bool is_updated = manager.IsUpdated(metadata, source_flag); | 69 | const bool is_updated = manager->IsUpdated(metadata, source_flag); |
| 68 | 70 | ||
| 69 | IPC::ResponseBuilder rb{ctx, 3}; | 71 | IPC::ResponseBuilder rb{ctx, 3}; |
| 70 | rb.Push(ResultSuccess); | 72 | rb.Push(ResultSuccess); |
| @@ -74,7 +76,7 @@ private: | |||
| 74 | void IsFullDatabase(HLERequestContext& ctx) { | 76 | void IsFullDatabase(HLERequestContext& ctx) { |
| 75 | LOG_DEBUG(Service_Mii, "called"); | 77 | LOG_DEBUG(Service_Mii, "called"); |
| 76 | 78 | ||
| 77 | const bool is_full_database = manager.IsFullDatabase(); | 79 | const bool is_full_database = manager->IsFullDatabase(); |
| 78 | 80 | ||
| 79 | IPC::ResponseBuilder rb{ctx, 3}; | 81 | IPC::ResponseBuilder rb{ctx, 3}; |
| 80 | rb.Push(ResultSuccess); | 82 | rb.Push(ResultSuccess); |
| @@ -85,7 +87,7 @@ private: | |||
| 85 | IPC::RequestParser rp{ctx}; | 87 | IPC::RequestParser rp{ctx}; |
| 86 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 88 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 87 | 89 | ||
| 88 | const u32 mii_count = manager.GetCount(metadata, source_flag); | 90 | const u32 mii_count = manager->GetCount(metadata, source_flag); |
| 89 | 91 | ||
| 90 | LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count); | 92 | LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count); |
| 91 | 93 | ||
| @@ -101,7 +103,7 @@ private: | |||
| 101 | 103 | ||
| 102 | u32 mii_count{}; | 104 | u32 mii_count{}; |
| 103 | std::vector<CharInfoElement> char_info_elements(output_size); | 105 | std::vector<CharInfoElement> char_info_elements(output_size); |
| 104 | const auto result = manager.Get(metadata, char_info_elements, mii_count, source_flag); | 106 | const auto result = manager->Get(metadata, char_info_elements, mii_count, source_flag); |
| 105 | 107 | ||
| 106 | if (mii_count != 0) { | 108 | if (mii_count != 0) { |
| 107 | ctx.WriteBuffer(char_info_elements); | 109 | ctx.WriteBuffer(char_info_elements); |
| @@ -122,7 +124,7 @@ private: | |||
| 122 | 124 | ||
| 123 | u32 mii_count{}; | 125 | u32 mii_count{}; |
| 124 | std::vector<CharInfo> char_info(output_size); | 126 | std::vector<CharInfo> char_info(output_size); |
| 125 | const auto result = manager.Get(metadata, char_info, mii_count, source_flag); | 127 | const auto result = manager->Get(metadata, char_info, mii_count, source_flag); |
| 126 | 128 | ||
| 127 | if (mii_count != 0) { | 129 | if (mii_count != 0) { |
| 128 | ctx.WriteBuffer(char_info); | 130 | ctx.WriteBuffer(char_info); |
| @@ -144,7 +146,7 @@ private: | |||
| 144 | LOG_INFO(Service_Mii, "called with source_flag={}", source_flag); | 146 | LOG_INFO(Service_Mii, "called with source_flag={}", source_flag); |
| 145 | 147 | ||
| 146 | CharInfo new_char_info{}; | 148 | CharInfo new_char_info{}; |
| 147 | const auto result = manager.UpdateLatest(metadata, new_char_info, char_info, source_flag); | 149 | const auto result = manager->UpdateLatest(metadata, new_char_info, char_info, source_flag); |
| 148 | if (result.IsFailure()) { | 150 | if (result.IsFailure()) { |
| 149 | IPC::ResponseBuilder rb{ctx, 2}; | 151 | IPC::ResponseBuilder rb{ctx, 2}; |
| 150 | rb.Push(result); | 152 | rb.Push(result); |
| @@ -183,7 +185,7 @@ private: | |||
| 183 | } | 185 | } |
| 184 | 186 | ||
| 185 | CharInfo char_info{}; | 187 | CharInfo char_info{}; |
| 186 | manager.BuildRandom(char_info, age, gender, race); | 188 | manager->BuildRandom(char_info, age, gender, race); |
| 187 | 189 | ||
| 188 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 190 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 189 | rb.Push(ResultSuccess); | 191 | rb.Push(ResultSuccess); |
| @@ -203,7 +205,7 @@ private: | |||
| 203 | } | 205 | } |
| 204 | 206 | ||
| 205 | CharInfo char_info{}; | 207 | CharInfo char_info{}; |
| 206 | manager.BuildDefault(char_info, index); | 208 | manager->BuildDefault(char_info, index); |
| 207 | 209 | ||
| 208 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 210 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 209 | rb.Push(ResultSuccess); | 211 | rb.Push(ResultSuccess); |
| @@ -217,7 +219,7 @@ private: | |||
| 217 | 219 | ||
| 218 | u32 mii_count{}; | 220 | u32 mii_count{}; |
| 219 | std::vector<StoreDataElement> store_data_elements(output_size); | 221 | std::vector<StoreDataElement> store_data_elements(output_size); |
| 220 | const auto result = manager.Get(metadata, store_data_elements, mii_count, source_flag); | 222 | const auto result = manager->Get(metadata, store_data_elements, mii_count, source_flag); |
| 221 | 223 | ||
| 222 | if (mii_count != 0) { | 224 | if (mii_count != 0) { |
| 223 | ctx.WriteBuffer(store_data_elements); | 225 | ctx.WriteBuffer(store_data_elements); |
| @@ -238,7 +240,7 @@ private: | |||
| 238 | 240 | ||
| 239 | u32 mii_count{}; | 241 | u32 mii_count{}; |
| 240 | std::vector<StoreData> store_data(output_size); | 242 | std::vector<StoreData> store_data(output_size); |
| 241 | const auto result = manager.Get(metadata, store_data, mii_count, source_flag); | 243 | const auto result = manager->Get(metadata, store_data, mii_count, source_flag); |
| 242 | 244 | ||
| 243 | if (mii_count != 0) { | 245 | if (mii_count != 0) { |
| 244 | ctx.WriteBuffer(store_data); | 246 | ctx.WriteBuffer(store_data); |
| @@ -266,7 +268,7 @@ private: | |||
| 266 | 268 | ||
| 267 | StoreData new_store_data{}; | 269 | StoreData new_store_data{}; |
| 268 | if (result.IsSuccess()) { | 270 | if (result.IsSuccess()) { |
| 269 | result = manager.UpdateLatest(metadata, new_store_data, store_data, source_flag); | 271 | result = manager->UpdateLatest(metadata, new_store_data, store_data, source_flag); |
| 270 | } | 272 | } |
| 271 | 273 | ||
| 272 | if (result.IsFailure()) { | 274 | if (result.IsFailure()) { |
| @@ -288,7 +290,7 @@ private: | |||
| 288 | LOG_INFO(Service_Mii, "called with create_id={}, is_special={}", | 290 | LOG_INFO(Service_Mii, "called with create_id={}, is_special={}", |
| 289 | create_id.FormattedString(), is_special); | 291 | create_id.FormattedString(), is_special); |
| 290 | 292 | ||
| 291 | const s32 index = manager.FindIndex(create_id, is_special); | 293 | const s32 index = manager->FindIndex(create_id, is_special); |
| 292 | 294 | ||
| 293 | IPC::ResponseBuilder rb{ctx, 3}; | 295 | IPC::ResponseBuilder rb{ctx, 3}; |
| 294 | rb.Push(ResultSuccess); | 296 | rb.Push(ResultSuccess); |
| @@ -309,14 +311,14 @@ private: | |||
| 309 | } | 311 | } |
| 310 | 312 | ||
| 311 | if (result.IsSuccess()) { | 313 | if (result.IsSuccess()) { |
| 312 | const u32 count = manager.GetCount(metadata, SourceFlag::Database); | 314 | const u32 count = manager->GetCount(metadata, SourceFlag::Database); |
| 313 | if (new_index < 0 || new_index >= static_cast<s32>(count)) { | 315 | if (new_index < 0 || new_index >= static_cast<s32>(count)) { |
| 314 | result = ResultInvalidArgument; | 316 | result = ResultInvalidArgument; |
| 315 | } | 317 | } |
| 316 | } | 318 | } |
| 317 | 319 | ||
| 318 | if (result.IsSuccess()) { | 320 | if (result.IsSuccess()) { |
| 319 | result = manager.Move(metadata, new_index, create_id); | 321 | result = manager->Move(metadata, new_index, create_id); |
| 320 | } | 322 | } |
| 321 | 323 | ||
| 322 | IPC::ResponseBuilder rb{ctx, 2}; | 324 | IPC::ResponseBuilder rb{ctx, 2}; |
| @@ -336,7 +338,7 @@ private: | |||
| 336 | } | 338 | } |
| 337 | 339 | ||
| 338 | if (result.IsSuccess()) { | 340 | if (result.IsSuccess()) { |
| 339 | result = manager.AddOrReplace(metadata, store_data); | 341 | result = manager->AddOrReplace(metadata, store_data); |
| 340 | } | 342 | } |
| 341 | 343 | ||
| 342 | IPC::ResponseBuilder rb{ctx, 2}; | 344 | IPC::ResponseBuilder rb{ctx, 2}; |
| @@ -356,7 +358,7 @@ private: | |||
| 356 | } | 358 | } |
| 357 | 359 | ||
| 358 | if (result.IsSuccess()) { | 360 | if (result.IsSuccess()) { |
| 359 | result = manager.Delete(metadata, create_id); | 361 | result = manager->Delete(metadata, create_id); |
| 360 | } | 362 | } |
| 361 | 363 | ||
| 362 | IPC::ResponseBuilder rb{ctx, 2}; | 364 | IPC::ResponseBuilder rb{ctx, 2}; |
| @@ -376,7 +378,7 @@ private: | |||
| 376 | } | 378 | } |
| 377 | 379 | ||
| 378 | if (result.IsSuccess()) { | 380 | if (result.IsSuccess()) { |
| 379 | result = manager.DestroyFile(metadata); | 381 | result = manager->DestroyFile(metadata); |
| 380 | } | 382 | } |
| 381 | 383 | ||
| 382 | IPC::ResponseBuilder rb{ctx, 2}; | 384 | IPC::ResponseBuilder rb{ctx, 2}; |
| @@ -396,7 +398,7 @@ private: | |||
| 396 | } | 398 | } |
| 397 | 399 | ||
| 398 | if (result.IsSuccess()) { | 400 | if (result.IsSuccess()) { |
| 399 | result = manager.DeleteFile(); | 401 | result = manager->DeleteFile(); |
| 400 | } | 402 | } |
| 401 | 403 | ||
| 402 | IPC::ResponseBuilder rb{ctx, 2}; | 404 | IPC::ResponseBuilder rb{ctx, 2}; |
| @@ -416,7 +418,7 @@ private: | |||
| 416 | } | 418 | } |
| 417 | 419 | ||
| 418 | if (result.IsSuccess()) { | 420 | if (result.IsSuccess()) { |
| 419 | result = manager.Format(metadata); | 421 | result = manager->Format(metadata); |
| 420 | } | 422 | } |
| 421 | 423 | ||
| 422 | IPC::ResponseBuilder rb{ctx, 2}; | 424 | IPC::ResponseBuilder rb{ctx, 2}; |
| @@ -434,7 +436,7 @@ private: | |||
| 434 | } | 436 | } |
| 435 | 437 | ||
| 436 | if (result.IsSuccess()) { | 438 | if (result.IsSuccess()) { |
| 437 | is_broken_with_clear_flag = manager.IsBrokenWithClearFlag(metadata); | 439 | is_broken_with_clear_flag = manager->IsBrokenWithClearFlag(metadata); |
| 438 | } | 440 | } |
| 439 | 441 | ||
| 440 | IPC::ResponseBuilder rb{ctx, 3}; | 442 | IPC::ResponseBuilder rb{ctx, 3}; |
| @@ -449,7 +451,7 @@ private: | |||
| 449 | LOG_DEBUG(Service_Mii, "called"); | 451 | LOG_DEBUG(Service_Mii, "called"); |
| 450 | 452 | ||
| 451 | s32 index{}; | 453 | s32 index{}; |
| 452 | const auto result = manager.GetIndex(metadata, info, index); | 454 | const auto result = manager->GetIndex(metadata, info, index); |
| 453 | 455 | ||
| 454 | IPC::ResponseBuilder rb{ctx, 3}; | 456 | IPC::ResponseBuilder rb{ctx, 3}; |
| 455 | rb.Push(result); | 457 | rb.Push(result); |
| @@ -462,7 +464,7 @@ private: | |||
| 462 | 464 | ||
| 463 | LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version); | 465 | LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version); |
| 464 | 466 | ||
| 465 | manager.SetInterfaceVersion(metadata, interface_version); | 467 | manager->SetInterfaceVersion(metadata, interface_version); |
| 466 | 468 | ||
| 467 | IPC::ResponseBuilder rb{ctx, 2}; | 469 | IPC::ResponseBuilder rb{ctx, 2}; |
| 468 | rb.Push(ResultSuccess); | 470 | rb.Push(ResultSuccess); |
| @@ -475,7 +477,7 @@ private: | |||
| 475 | LOG_INFO(Service_Mii, "called"); | 477 | LOG_INFO(Service_Mii, "called"); |
| 476 | 478 | ||
| 477 | CharInfo char_info{}; | 479 | CharInfo char_info{}; |
| 478 | const auto result = manager.ConvertV3ToCharInfo(char_info, mii_v3); | 480 | const auto result = manager->ConvertV3ToCharInfo(char_info, mii_v3); |
| 479 | 481 | ||
| 480 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 482 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 481 | rb.Push(result); | 483 | rb.Push(result); |
| @@ -489,7 +491,7 @@ private: | |||
| 489 | LOG_INFO(Service_Mii, "called"); | 491 | LOG_INFO(Service_Mii, "called"); |
| 490 | 492 | ||
| 491 | CharInfo char_info{}; | 493 | CharInfo char_info{}; |
| 492 | const auto result = manager.ConvertCoreDataToCharInfo(char_info, core_data); | 494 | const auto result = manager->ConvertCoreDataToCharInfo(char_info, core_data); |
| 493 | 495 | ||
| 494 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 496 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 495 | rb.Push(result); | 497 | rb.Push(result); |
| @@ -503,7 +505,7 @@ private: | |||
| 503 | LOG_INFO(Service_Mii, "called"); | 505 | LOG_INFO(Service_Mii, "called"); |
| 504 | 506 | ||
| 505 | CoreData core_data{}; | 507 | CoreData core_data{}; |
| 506 | const auto result = manager.ConvertCharInfoToCoreData(core_data, char_info); | 508 | const auto result = manager->ConvertCharInfoToCoreData(core_data, char_info); |
| 507 | 509 | ||
| 508 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)}; | 510 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)}; |
| 509 | rb.Push(result); | 511 | rb.Push(result); |
| @@ -516,41 +518,46 @@ private: | |||
| 516 | 518 | ||
| 517 | LOG_INFO(Service_Mii, "called"); | 519 | LOG_INFO(Service_Mii, "called"); |
| 518 | 520 | ||
| 519 | const auto result = manager.Append(metadata, char_info); | 521 | const auto result = manager->Append(metadata, char_info); |
| 520 | 522 | ||
| 521 | IPC::ResponseBuilder rb{ctx, 2}; | 523 | IPC::ResponseBuilder rb{ctx, 2}; |
| 522 | rb.Push(result); | 524 | rb.Push(result); |
| 523 | } | 525 | } |
| 524 | 526 | ||
| 525 | MiiManager manager{}; | 527 | std::shared_ptr<MiiManager> manager = nullptr; |
| 526 | DatabaseSessionMetadata metadata{}; | 528 | DatabaseSessionMetadata metadata{}; |
| 527 | bool is_system{}; | 529 | bool is_system{}; |
| 528 | }; | 530 | }; |
| 529 | 531 | ||
| 530 | class MiiDBModule final : public ServiceFramework<MiiDBModule> { | 532 | MiiDBModule::MiiDBModule(Core::System& system_, const char* name_, |
| 531 | public: | 533 | std::shared_ptr<MiiManager> mii_manager, bool is_system_) |
| 532 | explicit MiiDBModule(Core::System& system_, const char* name_, bool is_system_) | 534 | : ServiceFramework{system_, name_}, manager{mii_manager}, is_system{is_system_} { |
| 533 | : ServiceFramework{system_, name_}, is_system{is_system_} { | 535 | // clang-format off |
| 534 | // clang-format off | 536 | static const FunctionInfo functions[] = { |
| 535 | static const FunctionInfo functions[] = { | 537 | {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, |
| 536 | {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, | 538 | }; |
| 537 | }; | 539 | // clang-format on |
| 538 | // clang-format on | ||
| 539 | 540 | ||
| 540 | RegisterHandlers(functions); | 541 | RegisterHandlers(functions); |
| 542 | |||
| 543 | if (manager == nullptr) { | ||
| 544 | manager = std::make_shared<MiiManager>(); | ||
| 541 | } | 545 | } |
| 546 | } | ||
| 542 | 547 | ||
| 543 | private: | 548 | MiiDBModule::~MiiDBModule() = default; |
| 544 | void GetDatabaseService(HLERequestContext& ctx) { | ||
| 545 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 546 | rb.Push(ResultSuccess); | ||
| 547 | rb.PushIpcInterface<IDatabaseService>(system, is_system); | ||
| 548 | 549 | ||
| 549 | LOG_DEBUG(Service_Mii, "called"); | 550 | void MiiDBModule::GetDatabaseService(HLERequestContext& ctx) { |
| 550 | } | 551 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 552 | rb.Push(ResultSuccess); | ||
| 553 | rb.PushIpcInterface<IDatabaseService>(system, manager, is_system); | ||
| 551 | 554 | ||
| 552 | bool is_system{}; | 555 | LOG_DEBUG(Service_Mii, "called"); |
| 553 | }; | 556 | } |
| 557 | |||
| 558 | std::shared_ptr<MiiManager> MiiDBModule::GetMiiManager() { | ||
| 559 | return manager; | ||
| 560 | } | ||
| 554 | 561 | ||
| 555 | class MiiImg final : public ServiceFramework<MiiImg> { | 562 | class MiiImg final : public ServiceFramework<MiiImg> { |
| 556 | public: | 563 | public: |
| @@ -596,11 +603,12 @@ private: | |||
| 596 | 603 | ||
| 597 | void LoopProcess(Core::System& system) { | 604 | void LoopProcess(Core::System& system) { |
| 598 | auto server_manager = std::make_unique<ServerManager>(system); | 605 | auto server_manager = std::make_unique<ServerManager>(system); |
| 606 | std::shared_ptr<MiiManager> manager = nullptr; | ||
| 599 | 607 | ||
| 600 | server_manager->RegisterNamedService("mii:e", | 608 | server_manager->RegisterNamedService( |
| 601 | std::make_shared<MiiDBModule>(system, "mii:e", true)); | 609 | "mii:e", std::make_shared<MiiDBModule>(system, "mii:e", manager, true)); |
| 602 | server_manager->RegisterNamedService("mii:u", | 610 | server_manager->RegisterNamedService( |
| 603 | std::make_shared<MiiDBModule>(system, "mii:u", false)); | 611 | "mii:u", std::make_shared<MiiDBModule>(system, "mii:u", manager, false)); |
| 604 | server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); | 612 | server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); |
| 605 | ServerManager::RunServer(std::move(server_manager)); | 613 | ServerManager::RunServer(std::move(server_manager)); |
| 606 | } | 614 | } |
diff --git a/src/core/hle/service/mii/mii.h b/src/core/hle/service/mii/mii.h index ed4e3f62b..9aa4426f6 100644 --- a/src/core/hle/service/mii/mii.h +++ b/src/core/hle/service/mii/mii.h | |||
| @@ -3,11 +3,29 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include "core/hle/service/service.h" | ||
| 7 | |||
| 6 | namespace Core { | 8 | namespace Core { |
| 7 | class System; | 9 | class System; |
| 8 | } | 10 | } |
| 9 | 11 | ||
| 10 | namespace Service::Mii { | 12 | namespace Service::Mii { |
| 13 | class MiiManager; | ||
| 14 | |||
| 15 | class MiiDBModule final : public ServiceFramework<MiiDBModule> { | ||
| 16 | public: | ||
| 17 | explicit MiiDBModule(Core::System& system_, const char* name_, | ||
| 18 | std::shared_ptr<MiiManager> mii_manager, bool is_system_); | ||
| 19 | ~MiiDBModule() override; | ||
| 20 | |||
| 21 | std::shared_ptr<MiiManager> GetMiiManager(); | ||
| 22 | |||
| 23 | private: | ||
| 24 | void GetDatabaseService(HLERequestContext& ctx); | ||
| 25 | |||
| 26 | std::shared_ptr<MiiManager> manager = nullptr; | ||
| 27 | bool is_system{}; | ||
| 28 | }; | ||
| 11 | 29 | ||
| 12 | void LoopProcess(Core::System& system); | 30 | void LoopProcess(Core::System& system); |
| 13 | 31 | ||
diff --git a/src/core/hle/service/mii/mii_database_manager.cpp b/src/core/hle/service/mii/mii_database_manager.cpp index c39898594..0080b6705 100644 --- a/src/core/hle/service/mii/mii_database_manager.cpp +++ b/src/core/hle/service/mii/mii_database_manager.cpp | |||
| @@ -168,7 +168,7 @@ Result DatabaseManager::FindIndex(s32& out_index, const Common::UUID& create_id, | |||
| 168 | return ResultSuccess; | 168 | return ResultSuccess; |
| 169 | } | 169 | } |
| 170 | 170 | ||
| 171 | for (std::size_t i = 0; i <= index; ++i) { | 171 | for (std::size_t i = 0; i < index; ++i) { |
| 172 | if (database.Get(i).IsSpecial()) { | 172 | if (database.Get(i).IsSpecial()) { |
| 173 | continue; | 173 | continue; |
| 174 | } | 174 | } |
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index a5a2a9232..dcfd6b2e2 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp | |||
| @@ -130,11 +130,11 @@ Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharI | |||
| 130 | } | 130 | } |
| 131 | 131 | ||
| 132 | s32 index{}; | 132 | s32 index{}; |
| 133 | Result result = {}; | 133 | const bool is_special = metadata.magic == MiiMagic; |
| 134 | // FindIndex(index); | 134 | const auto result = database_manager.FindIndex(index, char_info.GetCreateId(), is_special); |
| 135 | 135 | ||
| 136 | if (result.IsError()) { | 136 | if (result.IsError()) { |
| 137 | return ResultNotFound; | 137 | index = -1; |
| 138 | } | 138 | } |
| 139 | 139 | ||
| 140 | if (index == -1) { | 140 | if (index == -1) { |
diff --git a/src/core/hle/service/mii/mii_types.h b/src/core/hle/service/mii/mii_types.h index f43efd83c..08c6029df 100644 --- a/src/core/hle/service/mii/mii_types.h +++ b/src/core/hle/service/mii/mii_types.h | |||
| @@ -614,7 +614,7 @@ struct Nickname { | |||
| 614 | } | 614 | } |
| 615 | 615 | ||
| 616 | std::size_t index = 1; | 616 | std::size_t index = 1; |
| 617 | while (data[index] != 0) { | 617 | while (index < MaxNameSize && data[index] != 0) { |
| 618 | index++; | 618 | index++; |
| 619 | } | 619 | } |
| 620 | while (index < MaxNameSize && data[index] == 0) { | 620 | while (index < MaxNameSize && data[index] == 0) { |
diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp index 465c6293a..970c748ca 100644 --- a/src/core/hle/service/mii/types/core_data.cpp +++ b/src/core/hle/service/mii/types/core_data.cpp | |||
| @@ -113,7 +113,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) { | |||
| 113 | .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); | 113 | .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); |
| 114 | 114 | ||
| 115 | const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; | 115 | const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; |
| 116 | const auto eyebrow_y{race == Race::Asian ? 9 : 10}; | 116 | const auto eyebrow_y{race == Race::Asian ? 6 : 7}; |
| 117 | const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6}; | 117 | const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6}; |
| 118 | const auto eyebrow_rotate{ | 118 | const auto eyebrow_rotate{ |
| 119 | 32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]}; | 119 | 32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]}; |
| @@ -171,7 +171,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) { | |||
| 171 | u8 glasses_type{}; | 171 | u8 glasses_type{}; |
| 172 | while (glasses_type_start < glasses_type_info.values[glasses_type]) { | 172 | while (glasses_type_start < glasses_type_info.values[glasses_type]) { |
| 173 | if (++glasses_type >= glasses_type_info.values_count) { | 173 | if (++glasses_type >= glasses_type_info.values_count) { |
| 174 | ASSERT(false); | 174 | glasses_type = 0; |
| 175 | break; | 175 | break; |
| 176 | } | 176 | } |
| 177 | } | 177 | } |
| @@ -179,6 +179,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) { | |||
| 179 | SetGlassType(static_cast<GlassType>(glasses_type)); | 179 | SetGlassType(static_cast<GlassType>(glasses_type)); |
| 180 | SetGlassColor(RawData::GetGlassColorFromVer3(0)); | 180 | SetGlassColor(RawData::GetGlassColorFromVer3(0)); |
| 181 | SetGlassScale(4); | 181 | SetGlassScale(4); |
| 182 | SetGlassY(static_cast<u8>(axis_y + 10)); | ||
| 182 | 183 | ||
| 183 | SetMoleType(MoleType::None); | 184 | SetMoleType(MoleType::None); |
| 184 | SetMoleScale(4); | 185 | SetMoleScale(4); |
diff --git a/src/core/hle/service/mii/types/raw_data.cpp b/src/core/hle/service/mii/types/raw_data.cpp index 5143abcc8..0e1a07fd7 100644 --- a/src/core/hle/service/mii/types/raw_data.cpp +++ b/src/core/hle/service/mii/types/raw_data.cpp | |||
| @@ -1716,18 +1716,18 @@ const std::array<RandomMiiData4, 18> RandomMiiMouthType{ | |||
| 1716 | const std::array<RandomMiiData2, 3> RandomMiiGlassType{ | 1716 | const std::array<RandomMiiData2, 3> RandomMiiGlassType{ |
| 1717 | RandomMiiData2{ | 1717 | RandomMiiData2{ |
| 1718 | .arg_1 = 0, | 1718 | .arg_1 = 0, |
| 1719 | .values_count = 9, | 1719 | .values_count = 4, |
| 1720 | .values = {90, 94, 96, 100, 0, 0, 0, 0, 0}, | 1720 | .values = {90, 94, 96, 100}, |
| 1721 | }, | 1721 | }, |
| 1722 | RandomMiiData2{ | 1722 | RandomMiiData2{ |
| 1723 | .arg_1 = 1, | 1723 | .arg_1 = 1, |
| 1724 | .values_count = 9, | 1724 | .values_count = 8, |
| 1725 | .values = {83, 86, 90, 93, 94, 96, 98, 100, 0}, | 1725 | .values = {83, 86, 90, 93, 94, 96, 98, 100}, |
| 1726 | }, | 1726 | }, |
| 1727 | RandomMiiData2{ | 1727 | RandomMiiData2{ |
| 1728 | .arg_1 = 2, | 1728 | .arg_1 = 2, |
| 1729 | .values_count = 9, | 1729 | .values_count = 8, |
| 1730 | .values = {78, 83, 0, 93, 0, 0, 98, 100, 0}, | 1730 | .values = {78, 83, 0, 93, 0, 0, 98, 100}, |
| 1731 | }, | 1731 | }, |
| 1732 | }; | 1732 | }; |
| 1733 | 1733 | ||
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index 05951d8cb..68c407f81 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp | |||
| @@ -1374,7 +1374,7 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co | |||
| 1374 | 1374 | ||
| 1375 | // Convert from utf16 to utf8 | 1375 | // Convert from utf16 to utf8 |
| 1376 | const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data()); | 1376 | const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data()); |
| 1377 | memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size() - 1); | 1377 | memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size()); |
| 1378 | 1378 | ||
| 1379 | return amiibo_name; | 1379 | return amiibo_name; |
| 1380 | } | 1380 | } |
diff --git a/src/core/hle/service/ns/iplatform_service_manager.cpp b/src/core/hle/service/ns/iplatform_service_manager.cpp index 6c2f5e70b..46268be95 100644 --- a/src/core/hle/service/ns/iplatform_service_manager.cpp +++ b/src/core/hle/service/ns/iplatform_service_manager.cpp | |||
| @@ -144,7 +144,7 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch | |||
| 144 | {3, &IPlatformServiceManager::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"}, | 144 | {3, &IPlatformServiceManager::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"}, |
| 145 | {4, &IPlatformServiceManager::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}, | 145 | {4, &IPlatformServiceManager::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}, |
| 146 | {5, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"}, | 146 | {5, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"}, |
| 147 | {6, nullptr, "GetSharedFontInOrderOfPriorityForSystem"}, | 147 | {6, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriorityForSystem"}, |
| 148 | {100, nullptr, "RequestApplicationFunctionAuthorization"}, | 148 | {100, nullptr, "RequestApplicationFunctionAuthorization"}, |
| 149 | {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"}, | 149 | {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"}, |
| 150 | {102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"}, | 150 | {102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"}, |
| @@ -262,8 +262,17 @@ void IPlatformServiceManager::GetSharedMemoryNativeHandle(HLERequestContext& ctx | |||
| 262 | } | 262 | } |
| 263 | 263 | ||
| 264 | void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext& ctx) { | 264 | void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext& ctx) { |
| 265 | // The maximum number of elements that can be returned is 6. Regardless of the available fonts | ||
| 266 | // or buffer size. | ||
| 267 | constexpr std::size_t MaxElementCount = 6; | ||
| 265 | IPC::RequestParser rp{ctx}; | 268 | IPC::RequestParser rp{ctx}; |
| 266 | const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for | 269 | const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for |
| 270 | const std::size_t font_codes_count = | ||
| 271 | std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(0)); | ||
| 272 | const std::size_t font_offsets_count = | ||
| 273 | std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(1)); | ||
| 274 | const std::size_t font_sizes_count = | ||
| 275 | std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(2)); | ||
| 267 | LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code); | 276 | LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code); |
| 268 | 277 | ||
| 269 | IPC::ResponseBuilder rb{ctx, 4}; | 278 | IPC::ResponseBuilder rb{ctx, 4}; |
| @@ -280,9 +289,9 @@ void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext& | |||
| 280 | } | 289 | } |
| 281 | 290 | ||
| 282 | // Resize buffers if game requests smaller size output | 291 | // Resize buffers if game requests smaller size output |
| 283 | font_codes.resize(std::min(font_codes.size(), ctx.GetWriteBufferNumElements<u32>(0))); | 292 | font_codes.resize(std::min(font_codes.size(), font_codes_count)); |
| 284 | font_offsets.resize(std::min(font_offsets.size(), ctx.GetWriteBufferNumElements<u32>(1))); | 293 | font_offsets.resize(std::min(font_offsets.size(), font_offsets_count)); |
| 285 | font_sizes.resize(std::min(font_sizes.size(), ctx.GetWriteBufferNumElements<u32>(2))); | 294 | font_sizes.resize(std::min(font_sizes.size(), font_sizes_count)); |
| 286 | 295 | ||
| 287 | ctx.WriteBuffer(font_codes, 0); | 296 | ctx.WriteBuffer(font_codes, 0); |
| 288 | ctx.WriteBuffer(font_offsets, 1); | 297 | ctx.WriteBuffer(font_offsets, 1); |
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp index 2a12feddc..dde0f6e9c 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp | |||
| @@ -7,15 +7,12 @@ | |||
| 7 | 7 | ||
| 8 | namespace Shader::Backend::SPIRV { | 8 | namespace Shader::Backend::SPIRV { |
| 9 | namespace { | 9 | namespace { |
| 10 | Id Image(EmitContext& ctx, const IR::Value& index, IR::TextureInstInfo info) { | 10 | Id Image(EmitContext& ctx, IR::TextureInstInfo info) { |
| 11 | if (!index.IsImmediate()) { | ||
| 12 | throw NotImplementedException("Indirect image indexing"); | ||
| 13 | } | ||
| 14 | if (info.type == TextureType::Buffer) { | 11 | if (info.type == TextureType::Buffer) { |
| 15 | const ImageBufferDefinition def{ctx.image_buffers.at(index.U32())}; | 12 | const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)}; |
| 16 | return def.id; | 13 | return def.id; |
| 17 | } else { | 14 | } else { |
| 18 | const ImageDefinition def{ctx.images.at(index.U32())}; | 15 | const ImageDefinition def{ctx.images.at(info.descriptor_index)}; |
| 19 | return def.id; | 16 | return def.id; |
| 20 | } | 17 | } |
| 21 | } | 18 | } |
| @@ -28,8 +25,12 @@ std::pair<Id, Id> AtomicArgs(EmitContext& ctx) { | |||
| 28 | 25 | ||
| 29 | Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id value, | 26 | Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id value, |
| 30 | Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { | 27 | Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { |
| 28 | if (!index.IsImmediate() || index.U32() != 0) { | ||
| 29 | // TODO: handle layers | ||
| 30 | throw NotImplementedException("Image indexing"); | ||
| 31 | } | ||
| 31 | const auto info{inst->Flags<IR::TextureInstInfo>()}; | 32 | const auto info{inst->Flags<IR::TextureInstInfo>()}; |
| 32 | const Id image{Image(ctx, index, info)}; | 33 | const Id image{Image(ctx, info)}; |
| 33 | const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, image, coords, ctx.Const(0U))}; | 34 | const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, image, coords, ctx.Const(0U))}; |
| 34 | const auto [scope, semantics]{AtomicArgs(ctx)}; | 35 | const auto [scope, semantics]{AtomicArgs(ctx)}; |
| 35 | return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value); | 36 | return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value); |
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 72f69b7aa..57df6fc34 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp | |||
| @@ -74,11 +74,6 @@ spv::ImageFormat GetImageFormat(ImageFormat format) { | |||
| 74 | throw InvalidArgument("Invalid image format {}", format); | 74 | throw InvalidArgument("Invalid image format {}", format); |
| 75 | } | 75 | } |
| 76 | 76 | ||
| 77 | spv::ImageFormat GetImageFormatForBuffer(ImageFormat format) { | ||
| 78 | const auto spv_format = GetImageFormat(format); | ||
| 79 | return spv_format == spv::ImageFormat::Unknown ? spv::ImageFormat::R32ui : spv_format; | ||
| 80 | } | ||
| 81 | |||
| 82 | Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) { | 77 | Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) { |
| 83 | const spv::ImageFormat format{GetImageFormat(desc.format)}; | 78 | const spv::ImageFormat format{GetImageFormat(desc.format)}; |
| 84 | const Id type{ctx.U32[1]}; | 79 | const Id type{ctx.U32[1]}; |
| @@ -1275,7 +1270,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) { | |||
| 1275 | if (desc.count != 1) { | 1270 | if (desc.count != 1) { |
| 1276 | throw NotImplementedException("Array of image buffers"); | 1271 | throw NotImplementedException("Array of image buffers"); |
| 1277 | } | 1272 | } |
| 1278 | const spv::ImageFormat format{GetImageFormatForBuffer(desc.format)}; | 1273 | const spv::ImageFormat format{GetImageFormat(desc.format)}; |
| 1279 | const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)}; | 1274 | const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)}; |
| 1280 | const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; | 1275 | const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; |
| 1281 | const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; | 1276 | const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; |
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 9b13ccbab..cf9266d54 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt | |||
| @@ -95,6 +95,12 @@ add_library(video_core STATIC | |||
| 95 | memory_manager.h | 95 | memory_manager.h |
| 96 | precompiled_headers.h | 96 | precompiled_headers.h |
| 97 | pte_kind.h | 97 | pte_kind.h |
| 98 | query_cache/bank_base.h | ||
| 99 | query_cache/query_base.h | ||
| 100 | query_cache/query_cache_base.h | ||
| 101 | query_cache/query_cache.h | ||
| 102 | query_cache/query_stream.h | ||
| 103 | query_cache/types.h | ||
| 98 | query_cache.h | 104 | query_cache.h |
| 99 | rasterizer_accelerated.cpp | 105 | rasterizer_accelerated.cpp |
| 100 | rasterizer_accelerated.h | 106 | rasterizer_accelerated.h |
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 8be7bd594..9e90c587c 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h | |||
| @@ -272,13 +272,19 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad | |||
| 272 | if (!cpu_addr) { | 272 | if (!cpu_addr) { |
| 273 | return {&slot_buffers[NULL_BUFFER_ID], 0}; | 273 | return {&slot_buffers[NULL_BUFFER_ID], 0}; |
| 274 | } | 274 | } |
| 275 | const BufferId buffer_id = FindBuffer(*cpu_addr, size); | 275 | return ObtainCPUBuffer(*cpu_addr, size, sync_info, post_op); |
| 276 | } | ||
| 277 | |||
| 278 | template <class P> | ||
| 279 | std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainCPUBuffer( | ||
| 280 | VAddr cpu_addr, u32 size, ObtainBufferSynchronize sync_info, ObtainBufferOperation post_op) { | ||
| 281 | const BufferId buffer_id = FindBuffer(cpu_addr, size); | ||
| 276 | Buffer& buffer = slot_buffers[buffer_id]; | 282 | Buffer& buffer = slot_buffers[buffer_id]; |
| 277 | 283 | ||
| 278 | // synchronize op | 284 | // synchronize op |
| 279 | switch (sync_info) { | 285 | switch (sync_info) { |
| 280 | case ObtainBufferSynchronize::FullSynchronize: | 286 | case ObtainBufferSynchronize::FullSynchronize: |
| 281 | SynchronizeBuffer(buffer, *cpu_addr, size); | 287 | SynchronizeBuffer(buffer, cpu_addr, size); |
| 282 | break; | 288 | break; |
| 283 | default: | 289 | default: |
| 284 | break; | 290 | break; |
| @@ -286,11 +292,11 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad | |||
| 286 | 292 | ||
| 287 | switch (post_op) { | 293 | switch (post_op) { |
| 288 | case ObtainBufferOperation::MarkAsWritten: | 294 | case ObtainBufferOperation::MarkAsWritten: |
| 289 | MarkWrittenBuffer(buffer_id, *cpu_addr, size); | 295 | MarkWrittenBuffer(buffer_id, cpu_addr, size); |
| 290 | break; | 296 | break; |
| 291 | case ObtainBufferOperation::DiscardWrite: { | 297 | case ObtainBufferOperation::DiscardWrite: { |
| 292 | VAddr cpu_addr_start = Common::AlignDown(*cpu_addr, 64); | 298 | VAddr cpu_addr_start = Common::AlignDown(cpu_addr, 64); |
| 293 | VAddr cpu_addr_end = Common::AlignUp(*cpu_addr + size, 64); | 299 | VAddr cpu_addr_end = Common::AlignUp(cpu_addr + size, 64); |
| 294 | IntervalType interval{cpu_addr_start, cpu_addr_end}; | 300 | IntervalType interval{cpu_addr_start, cpu_addr_end}; |
| 295 | ClearDownload(interval); | 301 | ClearDownload(interval); |
| 296 | common_ranges.subtract(interval); | 302 | common_ranges.subtract(interval); |
| @@ -300,7 +306,7 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad | |||
| 300 | break; | 306 | break; |
| 301 | } | 307 | } |
| 302 | 308 | ||
| 303 | return {&buffer, buffer.Offset(*cpu_addr)}; | 309 | return {&buffer, buffer.Offset(cpu_addr)}; |
| 304 | } | 310 | } |
| 305 | 311 | ||
| 306 | template <class P> | 312 | template <class P> |
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 0b7135d49..c4f6e8d12 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h | |||
| @@ -295,6 +295,10 @@ public: | |||
| 295 | [[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(GPUVAddr gpu_addr, u32 size, | 295 | [[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(GPUVAddr gpu_addr, u32 size, |
| 296 | ObtainBufferSynchronize sync_info, | 296 | ObtainBufferSynchronize sync_info, |
| 297 | ObtainBufferOperation post_op); | 297 | ObtainBufferOperation post_op); |
| 298 | |||
| 299 | [[nodiscard]] std::pair<Buffer*, u32> ObtainCPUBuffer(VAddr gpu_addr, u32 size, | ||
| 300 | ObtainBufferSynchronize sync_info, | ||
| 301 | ObtainBufferOperation post_op); | ||
| 298 | void FlushCachedWrites(); | 302 | void FlushCachedWrites(); |
| 299 | 303 | ||
| 300 | /// Return true when there are uncommitted buffers to be downloaded | 304 | /// Return true when there are uncommitted buffers to be downloaded |
| @@ -335,6 +339,14 @@ public: | |||
| 335 | 339 | ||
| 336 | [[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer(); | 340 | [[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer(); |
| 337 | 341 | ||
| 342 | template <typename Func> | ||
| 343 | void BufferOperations(Func&& func) { | ||
| 344 | do { | ||
| 345 | channel_state->has_deleted_buffers = false; | ||
| 346 | func(); | ||
| 347 | } while (channel_state->has_deleted_buffers); | ||
| 348 | } | ||
| 349 | |||
| 338 | std::recursive_mutex mutex; | 350 | std::recursive_mutex mutex; |
| 339 | Runtime& runtime; | 351 | Runtime& runtime; |
| 340 | 352 | ||
diff --git a/src/video_core/control/channel_state_cache.h b/src/video_core/control/channel_state_cache.h index 46bc9e322..5574e1fba 100644 --- a/src/video_core/control/channel_state_cache.h +++ b/src/video_core/control/channel_state_cache.h | |||
| @@ -51,7 +51,7 @@ public: | |||
| 51 | virtual void CreateChannel(Tegra::Control::ChannelState& channel); | 51 | virtual void CreateChannel(Tegra::Control::ChannelState& channel); |
| 52 | 52 | ||
| 53 | /// Bind a channel for execution. | 53 | /// Bind a channel for execution. |
| 54 | void BindToChannel(s32 id); | 54 | virtual void BindToChannel(s32 id); |
| 55 | 55 | ||
| 56 | /// Erase channel's state. | 56 | /// Erase channel's state. |
| 57 | void EraseChannel(s32 id); | 57 | void EraseChannel(s32 id); |
diff --git a/src/video_core/engines/draw_manager.h b/src/video_core/engines/draw_manager.h index 7c22c49f1..18d959143 100644 --- a/src/video_core/engines/draw_manager.h +++ b/src/video_core/engines/draw_manager.h | |||
| @@ -46,6 +46,7 @@ public: | |||
| 46 | }; | 46 | }; |
| 47 | 47 | ||
| 48 | struct IndirectParams { | 48 | struct IndirectParams { |
| 49 | bool is_byte_count; | ||
| 49 | bool is_indexed; | 50 | bool is_indexed; |
| 50 | bool include_count; | 51 | bool include_count; |
| 51 | GPUVAddr count_start_address; | 52 | GPUVAddr count_start_address; |
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 06e349e43..32d767d85 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp | |||
| @@ -20,8 +20,6 @@ | |||
| 20 | 20 | ||
| 21 | namespace Tegra::Engines { | 21 | namespace Tegra::Engines { |
| 22 | 22 | ||
| 23 | using VideoCore::QueryType; | ||
| 24 | |||
| 25 | /// First register id that is actually a Macro call. | 23 | /// First register id that is actually a Macro call. |
| 26 | constexpr u32 MacroRegistersStart = 0xE00; | 24 | constexpr u32 MacroRegistersStart = 0xE00; |
| 27 | 25 | ||
| @@ -500,27 +498,21 @@ void Maxwell3D::StampQueryResult(u64 payload, bool long_query) { | |||
| 500 | } | 498 | } |
| 501 | 499 | ||
| 502 | void Maxwell3D::ProcessQueryGet() { | 500 | void Maxwell3D::ProcessQueryGet() { |
| 501 | VideoCommon::QueryPropertiesFlags flags{}; | ||
| 502 | if (regs.report_semaphore.query.short_query == 0) { | ||
| 503 | flags |= VideoCommon::QueryPropertiesFlags::HasTimeout; | ||
| 504 | } | ||
| 505 | const GPUVAddr sequence_address{regs.report_semaphore.Address()}; | ||
| 506 | const VideoCommon::QueryType query_type = | ||
| 507 | static_cast<VideoCommon::QueryType>(regs.report_semaphore.query.report.Value()); | ||
| 508 | const u32 payload = regs.report_semaphore.payload; | ||
| 509 | const u32 subreport = regs.report_semaphore.query.sub_report; | ||
| 503 | switch (regs.report_semaphore.query.operation) { | 510 | switch (regs.report_semaphore.query.operation) { |
| 504 | case Regs::ReportSemaphore::Operation::Release: | 511 | case Regs::ReportSemaphore::Operation::Release: |
| 505 | if (regs.report_semaphore.query.short_query != 0) { | 512 | if (regs.report_semaphore.query.short_query != 0) { |
| 506 | const GPUVAddr sequence_address{regs.report_semaphore.Address()}; | 513 | flags |= VideoCommon::QueryPropertiesFlags::IsAFence; |
| 507 | const u32 payload = regs.report_semaphore.payload; | ||
| 508 | std::function<void()> operation([this, sequence_address, payload] { | ||
| 509 | memory_manager.Write<u32>(sequence_address, payload); | ||
| 510 | }); | ||
| 511 | rasterizer->SignalFence(std::move(operation)); | ||
| 512 | } else { | ||
| 513 | struct LongQueryResult { | ||
| 514 | u64_le value; | ||
| 515 | u64_le timestamp; | ||
| 516 | }; | ||
| 517 | const GPUVAddr sequence_address{regs.report_semaphore.Address()}; | ||
| 518 | const u32 payload = regs.report_semaphore.payload; | ||
| 519 | [this, sequence_address, payload] { | ||
| 520 | memory_manager.Write<u64>(sequence_address + sizeof(u64), system.GPU().GetTicks()); | ||
| 521 | memory_manager.Write<u64>(sequence_address, payload); | ||
| 522 | }(); | ||
| 523 | } | 514 | } |
| 515 | rasterizer->Query(sequence_address, query_type, flags, payload, subreport); | ||
| 524 | break; | 516 | break; |
| 525 | case Regs::ReportSemaphore::Operation::Acquire: | 517 | case Regs::ReportSemaphore::Operation::Acquire: |
| 526 | // TODO(Blinkhawk): Under this operation, the GPU waits for the CPU to write a value that | 518 | // TODO(Blinkhawk): Under this operation, the GPU waits for the CPU to write a value that |
| @@ -528,11 +520,7 @@ void Maxwell3D::ProcessQueryGet() { | |||
| 528 | UNIMPLEMENTED_MSG("Unimplemented query operation ACQUIRE"); | 520 | UNIMPLEMENTED_MSG("Unimplemented query operation ACQUIRE"); |
| 529 | break; | 521 | break; |
| 530 | case Regs::ReportSemaphore::Operation::ReportOnly: | 522 | case Regs::ReportSemaphore::Operation::ReportOnly: |
| 531 | if (const std::optional<u64> result = GetQueryResult()) { | 523 | rasterizer->Query(sequence_address, query_type, flags, payload, subreport); |
| 532 | // If the query returns an empty optional it means it's cached and deferred. | ||
| 533 | // In this case we have a non-empty result, so we stamp it immediately. | ||
| 534 | StampQueryResult(*result, regs.report_semaphore.query.short_query == 0); | ||
| 535 | } | ||
| 536 | break; | 524 | break; |
| 537 | case Regs::ReportSemaphore::Operation::Trap: | 525 | case Regs::ReportSemaphore::Operation::Trap: |
| 538 | UNIMPLEMENTED_MSG("Unimplemented query operation TRAP"); | 526 | UNIMPLEMENTED_MSG("Unimplemented query operation TRAP"); |
| @@ -544,6 +532,10 @@ void Maxwell3D::ProcessQueryGet() { | |||
| 544 | } | 532 | } |
| 545 | 533 | ||
| 546 | void Maxwell3D::ProcessQueryCondition() { | 534 | void Maxwell3D::ProcessQueryCondition() { |
| 535 | if (rasterizer->AccelerateConditionalRendering()) { | ||
| 536 | execute_on = true; | ||
| 537 | return; | ||
| 538 | } | ||
| 547 | const GPUVAddr condition_address{regs.render_enable.Address()}; | 539 | const GPUVAddr condition_address{regs.render_enable.Address()}; |
| 548 | switch (regs.render_enable_override) { | 540 | switch (regs.render_enable_override) { |
| 549 | case Regs::RenderEnable::Override::AlwaysRender: | 541 | case Regs::RenderEnable::Override::AlwaysRender: |
| @@ -553,10 +545,6 @@ void Maxwell3D::ProcessQueryCondition() { | |||
| 553 | execute_on = false; | 545 | execute_on = false; |
| 554 | break; | 546 | break; |
| 555 | case Regs::RenderEnable::Override::UseRenderEnable: { | 547 | case Regs::RenderEnable::Override::UseRenderEnable: { |
| 556 | if (rasterizer->AccelerateConditionalRendering()) { | ||
| 557 | execute_on = true; | ||
| 558 | return; | ||
| 559 | } | ||
| 560 | switch (regs.render_enable.mode) { | 548 | switch (regs.render_enable.mode) { |
| 561 | case Regs::RenderEnable::Mode::True: { | 549 | case Regs::RenderEnable::Mode::True: { |
| 562 | execute_on = true; | 550 | execute_on = true; |
| @@ -598,15 +586,9 @@ void Maxwell3D::ProcessQueryCondition() { | |||
| 598 | } | 586 | } |
| 599 | 587 | ||
| 600 | void Maxwell3D::ProcessCounterReset() { | 588 | void Maxwell3D::ProcessCounterReset() { |
| 601 | #if ANDROID | ||
| 602 | if (!Settings::IsGPULevelHigh()) { | ||
| 603 | // This is problematic on Android, disable on GPU Normal. | ||
| 604 | return; | ||
| 605 | } | ||
| 606 | #endif | ||
| 607 | switch (regs.clear_report_value) { | 589 | switch (regs.clear_report_value) { |
| 608 | case Regs::ClearReport::ZPassPixelCount: | 590 | case Regs::ClearReport::ZPassPixelCount: |
| 609 | rasterizer->ResetCounter(QueryType::SamplesPassed); | 591 | rasterizer->ResetCounter(VideoCommon::QueryType::ZPassPixelCount64); |
| 610 | break; | 592 | break; |
| 611 | default: | 593 | default: |
| 612 | LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}", regs.clear_report_value); | 594 | LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}", regs.clear_report_value); |
| @@ -620,28 +602,6 @@ void Maxwell3D::ProcessSyncPoint() { | |||
| 620 | rasterizer->SignalSyncPoint(sync_point); | 602 | rasterizer->SignalSyncPoint(sync_point); |
| 621 | } | 603 | } |
| 622 | 604 | ||
| 623 | std::optional<u64> Maxwell3D::GetQueryResult() { | ||
| 624 | switch (regs.report_semaphore.query.report) { | ||
| 625 | case Regs::ReportSemaphore::Report::Payload: | ||
| 626 | return regs.report_semaphore.payload; | ||
| 627 | case Regs::ReportSemaphore::Report::ZPassPixelCount64: | ||
| 628 | #if ANDROID | ||
| 629 | if (!Settings::IsGPULevelHigh()) { | ||
| 630 | // This is problematic on Android, disable on GPU Normal. | ||
| 631 | return 120; | ||
| 632 | } | ||
| 633 | #endif | ||
| 634 | // Deferred. | ||
| 635 | rasterizer->Query(regs.report_semaphore.Address(), QueryType::SamplesPassed, | ||
| 636 | system.GPU().GetTicks()); | ||
| 637 | return std::nullopt; | ||
| 638 | default: | ||
| 639 | LOG_DEBUG(HW_GPU, "Unimplemented query report type {}", | ||
| 640 | regs.report_semaphore.query.report.Value()); | ||
| 641 | return 1; | ||
| 642 | } | ||
| 643 | } | ||
| 644 | |||
| 645 | void Maxwell3D::ProcessCBBind(size_t stage_index) { | 605 | void Maxwell3D::ProcessCBBind(size_t stage_index) { |
| 646 | // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader | 606 | // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader |
| 647 | // stage. | 607 | // stage. |
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 6c19354e1..17faacc37 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h | |||
| @@ -3182,9 +3182,6 @@ private: | |||
| 3182 | /// Handles writes to syncing register. | 3182 | /// Handles writes to syncing register. |
| 3183 | void ProcessSyncPoint(); | 3183 | void ProcessSyncPoint(); |
| 3184 | 3184 | ||
| 3185 | /// Returns a query's value or an empty object if the value will be deferred through a cache. | ||
| 3186 | std::optional<u64> GetQueryResult(); | ||
| 3187 | |||
| 3188 | void RefreshParametersImpl(); | 3185 | void RefreshParametersImpl(); |
| 3189 | 3186 | ||
| 3190 | bool IsMethodExecutable(u32 method); | 3187 | bool IsMethodExecutable(u32 method); |
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index da8eab7ee..422d4d859 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp | |||
| @@ -109,10 +109,11 @@ void MaxwellDMA::Launch() { | |||
| 109 | const bool is_const_a_dst = regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A; | 109 | const bool is_const_a_dst = regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A; |
| 110 | if (regs.launch_dma.remap_enable != 0 && is_const_a_dst) { | 110 | if (regs.launch_dma.remap_enable != 0 && is_const_a_dst) { |
| 111 | ASSERT(regs.remap_const.component_size_minus_one == 3); | 111 | ASSERT(regs.remap_const.component_size_minus_one == 3); |
| 112 | accelerate.BufferClear(regs.offset_out, regs.line_length_in, regs.remap_consta_value); | 112 | accelerate.BufferClear(regs.offset_out, regs.line_length_in, |
| 113 | regs.remap_const.remap_consta_value); | ||
| 113 | read_buffer.resize_destructive(regs.line_length_in * sizeof(u32)); | 114 | read_buffer.resize_destructive(regs.line_length_in * sizeof(u32)); |
| 114 | std::span<u32> span(reinterpret_cast<u32*>(read_buffer.data()), regs.line_length_in); | 115 | std::span<u32> span(reinterpret_cast<u32*>(read_buffer.data()), regs.line_length_in); |
| 115 | std::ranges::fill(span, regs.remap_consta_value); | 116 | std::ranges::fill(span, regs.remap_const.remap_consta_value); |
| 116 | memory_manager.WriteBlockUnsafe(regs.offset_out, | 117 | memory_manager.WriteBlockUnsafe(regs.offset_out, |
| 117 | reinterpret_cast<u8*>(read_buffer.data()), | 118 | reinterpret_cast<u8*>(read_buffer.data()), |
| 118 | regs.line_length_in * sizeof(u32)); | 119 | regs.line_length_in * sizeof(u32)); |
| @@ -361,21 +362,17 @@ void MaxwellDMA::ReleaseSemaphore() { | |||
| 361 | const auto type = regs.launch_dma.semaphore_type; | 362 | const auto type = regs.launch_dma.semaphore_type; |
| 362 | const GPUVAddr address = regs.semaphore.address; | 363 | const GPUVAddr address = regs.semaphore.address; |
| 363 | const u32 payload = regs.semaphore.payload; | 364 | const u32 payload = regs.semaphore.payload; |
| 365 | VideoCommon::QueryPropertiesFlags flags{VideoCommon::QueryPropertiesFlags::IsAFence}; | ||
| 364 | switch (type) { | 366 | switch (type) { |
| 365 | case LaunchDMA::SemaphoreType::NONE: | 367 | case LaunchDMA::SemaphoreType::NONE: |
| 366 | break; | 368 | break; |
| 367 | case LaunchDMA::SemaphoreType::RELEASE_ONE_WORD_SEMAPHORE: { | 369 | case LaunchDMA::SemaphoreType::RELEASE_ONE_WORD_SEMAPHORE: { |
| 368 | std::function<void()> operation( | 370 | rasterizer->Query(address, VideoCommon::QueryType::Payload, flags, payload, 0); |
| 369 | [this, address, payload] { memory_manager.Write<u32>(address, payload); }); | ||
| 370 | rasterizer->SignalFence(std::move(operation)); | ||
| 371 | break; | 371 | break; |
| 372 | } | 372 | } |
| 373 | case LaunchDMA::SemaphoreType::RELEASE_FOUR_WORD_SEMAPHORE: { | 373 | case LaunchDMA::SemaphoreType::RELEASE_FOUR_WORD_SEMAPHORE: { |
| 374 | std::function<void()> operation([this, address, payload] { | 374 | rasterizer->Query(address, VideoCommon::QueryType::Payload, |
| 375 | memory_manager.Write<u64>(address + sizeof(u64), system.GPU().GetTicks()); | 375 | flags | VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0); |
| 376 | memory_manager.Write<u64>(address, payload); | ||
| 377 | }); | ||
| 378 | rasterizer->SignalFence(std::move(operation)); | ||
| 379 | break; | 376 | break; |
| 380 | } | 377 | } |
| 381 | default: | 378 | default: |
diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h index 69e26cb32..1a43e24b6 100644 --- a/src/video_core/engines/maxwell_dma.h +++ b/src/video_core/engines/maxwell_dma.h | |||
| @@ -214,14 +214,15 @@ public: | |||
| 214 | NO_WRITE = 6, | 214 | NO_WRITE = 6, |
| 215 | }; | 215 | }; |
| 216 | 216 | ||
| 217 | PackedGPUVAddr address; | 217 | u32 remap_consta_value; |
| 218 | u32 remap_constb_value; | ||
| 218 | 219 | ||
| 219 | union { | 220 | union { |
| 221 | BitField<0, 12, u32> dst_components_raw; | ||
| 220 | BitField<0, 3, Swizzle> dst_x; | 222 | BitField<0, 3, Swizzle> dst_x; |
| 221 | BitField<4, 3, Swizzle> dst_y; | 223 | BitField<4, 3, Swizzle> dst_y; |
| 222 | BitField<8, 3, Swizzle> dst_z; | 224 | BitField<8, 3, Swizzle> dst_z; |
| 223 | BitField<12, 3, Swizzle> dst_w; | 225 | BitField<12, 3, Swizzle> dst_w; |
| 224 | BitField<0, 12, u32> dst_components_raw; | ||
| 225 | BitField<16, 2, u32> component_size_minus_one; | 226 | BitField<16, 2, u32> component_size_minus_one; |
| 226 | BitField<20, 2, u32> num_src_components_minus_one; | 227 | BitField<20, 2, u32> num_src_components_minus_one; |
| 227 | BitField<24, 2, u32> num_dst_components_minus_one; | 228 | BitField<24, 2, u32> num_dst_components_minus_one; |
| @@ -274,55 +275,57 @@ private: | |||
| 274 | struct Regs { | 275 | struct Regs { |
| 275 | union { | 276 | union { |
| 276 | struct { | 277 | struct { |
| 277 | u32 reserved[0x40]; | 278 | INSERT_PADDING_BYTES_NOINIT(0x100); |
| 278 | u32 nop; | 279 | u32 nop; |
| 279 | u32 reserved01[0xf]; | 280 | INSERT_PADDING_BYTES_NOINIT(0x3C); |
| 280 | u32 pm_trigger; | 281 | u32 pm_trigger; |
| 281 | u32 reserved02[0x3f]; | 282 | INSERT_PADDING_BYTES_NOINIT(0xFC); |
| 282 | Semaphore semaphore; | 283 | Semaphore semaphore; |
| 283 | u32 reserved03[0x2]; | 284 | INSERT_PADDING_BYTES_NOINIT(0x8); |
| 284 | RenderEnable render_enable; | 285 | RenderEnable render_enable; |
| 285 | PhysMode src_phys_mode; | 286 | PhysMode src_phys_mode; |
| 286 | PhysMode dst_phys_mode; | 287 | PhysMode dst_phys_mode; |
| 287 | u32 reserved04[0x26]; | 288 | INSERT_PADDING_BYTES_NOINIT(0x98); |
| 288 | LaunchDMA launch_dma; | 289 | LaunchDMA launch_dma; |
| 289 | u32 reserved05[0x3f]; | 290 | INSERT_PADDING_BYTES_NOINIT(0xFC); |
| 290 | PackedGPUVAddr offset_in; | 291 | PackedGPUVAddr offset_in; |
| 291 | PackedGPUVAddr offset_out; | 292 | PackedGPUVAddr offset_out; |
| 292 | s32 pitch_in; | 293 | s32 pitch_in; |
| 293 | s32 pitch_out; | 294 | s32 pitch_out; |
| 294 | u32 line_length_in; | 295 | u32 line_length_in; |
| 295 | u32 line_count; | 296 | u32 line_count; |
| 296 | u32 reserved06[0xb6]; | 297 | INSERT_PADDING_BYTES_NOINIT(0x2E0); |
| 297 | u32 remap_consta_value; | ||
| 298 | u32 remap_constb_value; | ||
| 299 | RemapConst remap_const; | 298 | RemapConst remap_const; |
| 300 | DMA::Parameters dst_params; | 299 | DMA::Parameters dst_params; |
| 301 | u32 reserved07[0x1]; | 300 | INSERT_PADDING_BYTES_NOINIT(0x4); |
| 302 | DMA::Parameters src_params; | 301 | DMA::Parameters src_params; |
| 303 | u32 reserved08[0x275]; | 302 | INSERT_PADDING_BYTES_NOINIT(0x9D4); |
| 304 | u32 pm_trigger_end; | 303 | u32 pm_trigger_end; |
| 305 | u32 reserved09[0x3ba]; | 304 | INSERT_PADDING_BYTES_NOINIT(0xEE8); |
| 306 | }; | 305 | }; |
| 307 | std::array<u32, NUM_REGS> reg_array; | 306 | std::array<u32, NUM_REGS> reg_array; |
| 308 | }; | 307 | }; |
| 309 | } regs{}; | 308 | } regs{}; |
| 309 | static_assert(sizeof(Regs) == NUM_REGS * 4); | ||
| 310 | 310 | ||
| 311 | #define ASSERT_REG_POSITION(field_name, position) \ | 311 | #define ASSERT_REG_POSITION(field_name, position) \ |
| 312 | static_assert(offsetof(MaxwellDMA::Regs, field_name) == position * 4, \ | 312 | static_assert(offsetof(MaxwellDMA::Regs, field_name) == position, \ |
| 313 | "Field " #field_name " has invalid position") | 313 | "Field " #field_name " has invalid position") |
| 314 | 314 | ||
| 315 | ASSERT_REG_POSITION(launch_dma, 0xC0); | 315 | ASSERT_REG_POSITION(semaphore, 0x240); |
| 316 | ASSERT_REG_POSITION(offset_in, 0x100); | 316 | ASSERT_REG_POSITION(render_enable, 0x254); |
| 317 | ASSERT_REG_POSITION(offset_out, 0x102); | 317 | ASSERT_REG_POSITION(src_phys_mode, 0x260); |
| 318 | ASSERT_REG_POSITION(pitch_in, 0x104); | 318 | ASSERT_REG_POSITION(launch_dma, 0x300); |
| 319 | ASSERT_REG_POSITION(pitch_out, 0x105); | 319 | ASSERT_REG_POSITION(offset_in, 0x400); |
| 320 | ASSERT_REG_POSITION(line_length_in, 0x106); | 320 | ASSERT_REG_POSITION(offset_out, 0x408); |
| 321 | ASSERT_REG_POSITION(line_count, 0x107); | 321 | ASSERT_REG_POSITION(pitch_in, 0x410); |
| 322 | ASSERT_REG_POSITION(remap_const, 0x1C0); | 322 | ASSERT_REG_POSITION(pitch_out, 0x414); |
| 323 | ASSERT_REG_POSITION(dst_params, 0x1C3); | 323 | ASSERT_REG_POSITION(line_length_in, 0x418); |
| 324 | ASSERT_REG_POSITION(src_params, 0x1CA); | 324 | ASSERT_REG_POSITION(line_count, 0x41C); |
| 325 | 325 | ASSERT_REG_POSITION(remap_const, 0x700); | |
| 326 | ASSERT_REG_POSITION(dst_params, 0x70C); | ||
| 327 | ASSERT_REG_POSITION(src_params, 0x728); | ||
| 328 | ASSERT_REG_POSITION(pm_trigger_end, 0x1114); | ||
| 326 | #undef ASSERT_REG_POSITION | 329 | #undef ASSERT_REG_POSITION |
| 327 | }; | 330 | }; |
| 328 | 331 | ||
diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp index 6de2543b7..8dd34c04a 100644 --- a/src/video_core/engines/puller.cpp +++ b/src/video_core/engines/puller.cpp | |||
| @@ -82,10 +82,8 @@ void Puller::ProcessSemaphoreTriggerMethod() { | |||
| 82 | if (op == GpuSemaphoreOperation::WriteLong) { | 82 | if (op == GpuSemaphoreOperation::WriteLong) { |
| 83 | const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()}; | 83 | const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()}; |
| 84 | const u32 payload = regs.semaphore_sequence; | 84 | const u32 payload = regs.semaphore_sequence; |
| 85 | [this, sequence_address, payload] { | 85 | rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload, |
| 86 | memory_manager.Write<u64>(sequence_address + sizeof(u64), gpu.GetTicks()); | 86 | VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0); |
| 87 | memory_manager.Write<u64>(sequence_address, payload); | ||
| 88 | }(); | ||
| 89 | } else { | 87 | } else { |
| 90 | do { | 88 | do { |
| 91 | const u32 word{memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress())}; | 89 | const u32 word{memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress())}; |
| @@ -120,10 +118,8 @@ void Puller::ProcessSemaphoreTriggerMethod() { | |||
| 120 | void Puller::ProcessSemaphoreRelease() { | 118 | void Puller::ProcessSemaphoreRelease() { |
| 121 | const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()}; | 119 | const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()}; |
| 122 | const u32 payload = regs.semaphore_release; | 120 | const u32 payload = regs.semaphore_release; |
| 123 | std::function<void()> operation([this, sequence_address, payload] { | 121 | rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload, |
| 124 | memory_manager.Write<u32>(sequence_address, payload); | 122 | VideoCommon::QueryPropertiesFlags::IsAFence, payload, 0); |
| 125 | }); | ||
| 126 | rasterizer->SignalFence(std::move(operation)); | ||
| 127 | } | 123 | } |
| 128 | 124 | ||
| 129 | void Puller::ProcessSemaphoreAcquire() { | 125 | void Puller::ProcessSemaphoreAcquire() { |
| @@ -132,7 +128,6 @@ void Puller::ProcessSemaphoreAcquire() { | |||
| 132 | while (word != value) { | 128 | while (word != value) { |
| 133 | regs.acquire_active = true; | 129 | regs.acquire_active = true; |
| 134 | regs.acquire_value = value; | 130 | regs.acquire_value = value; |
| 135 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); | ||
| 136 | rasterizer->ReleaseFences(); | 131 | rasterizer->ReleaseFences(); |
| 137 | word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress()); | 132 | word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress()); |
| 138 | // TODO(kemathe73) figure out how to do the acquire_timeout | 133 | // TODO(kemathe73) figure out how to do the acquire_timeout |
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h index ab20ff30f..805a89900 100644 --- a/src/video_core/fence_manager.h +++ b/src/video_core/fence_manager.h | |||
| @@ -55,6 +55,9 @@ public: | |||
| 55 | 55 | ||
| 56 | // Unlike other fences, this one doesn't | 56 | // Unlike other fences, this one doesn't |
| 57 | void SignalOrdering() { | 57 | void SignalOrdering() { |
| 58 | if constexpr (!can_async_check) { | ||
| 59 | TryReleasePendingFences<false>(); | ||
| 60 | } | ||
| 58 | std::scoped_lock lock{buffer_cache.mutex}; | 61 | std::scoped_lock lock{buffer_cache.mutex}; |
| 59 | buffer_cache.AccumulateFlushes(); | 62 | buffer_cache.AccumulateFlushes(); |
| 60 | } | 63 | } |
| @@ -104,9 +107,25 @@ public: | |||
| 104 | SignalFence(std::move(func)); | 107 | SignalFence(std::move(func)); |
| 105 | } | 108 | } |
| 106 | 109 | ||
| 107 | void WaitPendingFences() { | 110 | void WaitPendingFences([[maybe_unused]] bool force) { |
| 108 | if constexpr (!can_async_check) { | 111 | if constexpr (!can_async_check) { |
| 109 | TryReleasePendingFences<true>(); | 112 | TryReleasePendingFences<true>(); |
| 113 | } else { | ||
| 114 | if (!force) { | ||
| 115 | return; | ||
| 116 | } | ||
| 117 | std::mutex wait_mutex; | ||
| 118 | std::condition_variable wait_cv; | ||
| 119 | std::atomic<bool> wait_finished{}; | ||
| 120 | std::function<void()> func([&] { | ||
| 121 | std::scoped_lock lk(wait_mutex); | ||
| 122 | wait_finished.store(true, std::memory_order_relaxed); | ||
| 123 | wait_cv.notify_all(); | ||
| 124 | }); | ||
| 125 | SignalFence(std::move(func)); | ||
| 126 | std::unique_lock lk(wait_mutex); | ||
| 127 | wait_cv.wait( | ||
| 128 | lk, [&wait_finished] { return wait_finished.load(std::memory_order_relaxed); }); | ||
| 110 | } | 129 | } |
| 111 | } | 130 | } |
| 112 | 131 | ||
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index c192e33b2..11549d448 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp | |||
| @@ -102,7 +102,8 @@ struct GPU::Impl { | |||
| 102 | 102 | ||
| 103 | /// Signal the ending of command list. | 103 | /// Signal the ending of command list. |
| 104 | void OnCommandListEnd() { | 104 | void OnCommandListEnd() { |
| 105 | rasterizer->ReleaseFences(); | 105 | rasterizer->ReleaseFences(false); |
| 106 | Settings::UpdateGPUAccuracy(); | ||
| 106 | } | 107 | } |
| 107 | 108 | ||
| 108 | /// Request a host GPU memory flush from the CPU. | 109 | /// Request a host GPU memory flush from the CPU. |
| @@ -220,6 +221,7 @@ struct GPU::Impl { | |||
| 220 | /// This can be used to launch any necessary threads and register any necessary | 221 | /// This can be used to launch any necessary threads and register any necessary |
| 221 | /// core timing events. | 222 | /// core timing events. |
| 222 | void Start() { | 223 | void Start() { |
| 224 | Settings::UpdateGPUAccuracy(); | ||
| 223 | gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler); | 225 | gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler); |
| 224 | } | 226 | } |
| 225 | 227 | ||
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index c4d459077..6b912027f 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt | |||
| @@ -41,6 +41,9 @@ set(SHADER_FILES | |||
| 41 | pitch_unswizzle.comp | 41 | pitch_unswizzle.comp |
| 42 | present_bicubic.frag | 42 | present_bicubic.frag |
| 43 | present_gaussian.frag | 43 | present_gaussian.frag |
| 44 | queries_prefix_scan_sum.comp | ||
| 45 | queries_prefix_scan_sum_nosubgroups.comp | ||
| 46 | resolve_conditional_render.comp | ||
| 44 | smaa_edge_detection.vert | 47 | smaa_edge_detection.vert |
| 45 | smaa_edge_detection.frag | 48 | smaa_edge_detection.frag |
| 46 | smaa_blending_weight_calculation.vert | 49 | smaa_blending_weight_calculation.vert |
| @@ -70,6 +73,7 @@ if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND") | |||
| 70 | endif() | 73 | endif() |
| 71 | 74 | ||
| 72 | set(GLSL_FLAGS "") | 75 | set(GLSL_FLAGS "") |
| 76 | set(SPIR_V_VERSION "spirv1.3") | ||
| 73 | set(QUIET_FLAG "--quiet") | 77 | set(QUIET_FLAG "--quiet") |
| 74 | 78 | ||
| 75 | set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include) | 79 | set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include) |
| @@ -123,7 +127,7 @@ foreach(FILENAME IN ITEMS ${SHADER_FILES}) | |||
| 123 | OUTPUT | 127 | OUTPUT |
| 124 | ${SPIRV_HEADER_FILE} | 128 | ${SPIRV_HEADER_FILE} |
| 125 | COMMAND | 129 | COMMAND |
| 126 | ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE} | 130 | ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE} --target-env ${SPIR_V_VERSION} |
| 127 | MAIN_DEPENDENCY | 131 | MAIN_DEPENDENCY |
| 128 | ${SOURCE_FILE} | 132 | ${SOURCE_FILE} |
| 129 | ) | 133 | ) |
diff --git a/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp b/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp index fc3854d18..66f2ad483 100644 --- a/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp +++ b/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp | |||
| @@ -15,11 +15,14 @@ void main() { | |||
| 15 | 15 | ||
| 16 | // TODO: Specialization constants for num_samples? | 16 | // TODO: Specialization constants for num_samples? |
| 17 | const int num_samples = imageSamples(msaa_in); | 17 | const int num_samples = imageSamples(msaa_in); |
| 18 | const ivec3 msaa_size = imageSize(msaa_in); | ||
| 19 | const ivec3 out_size = imageSize(output_img); | ||
| 20 | const ivec3 scale = out_size / msaa_size; | ||
| 18 | for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) { | 21 | for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) { |
| 19 | const vec4 pixel = imageLoad(msaa_in, coords, curr_sample); | 22 | const vec4 pixel = imageLoad(msaa_in, coords, curr_sample); |
| 20 | 23 | ||
| 21 | const int single_sample_x = 2 * coords.x + (curr_sample & 1); | 24 | const int single_sample_x = scale.x * coords.x + (curr_sample & 1); |
| 22 | const int single_sample_y = 2 * coords.y + ((curr_sample / 2) & 1); | 25 | const int single_sample_y = scale.y * coords.y + ((curr_sample / 2) & 1); |
| 23 | const ivec3 dest_coords = ivec3(single_sample_x, single_sample_y, coords.z); | 26 | const ivec3 dest_coords = ivec3(single_sample_x, single_sample_y, coords.z); |
| 24 | 27 | ||
| 25 | if (any(greaterThanEqual(dest_coords, imageSize(output_img)))) { | 28 | if (any(greaterThanEqual(dest_coords, imageSize(output_img)))) { |
diff --git a/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp b/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp index dedd962f1..c7ce38efa 100644 --- a/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp +++ b/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp | |||
| @@ -15,9 +15,12 @@ void main() { | |||
| 15 | 15 | ||
| 16 | // TODO: Specialization constants for num_samples? | 16 | // TODO: Specialization constants for num_samples? |
| 17 | const int num_samples = imageSamples(output_msaa); | 17 | const int num_samples = imageSamples(output_msaa); |
| 18 | const ivec3 msaa_size = imageSize(output_msaa); | ||
| 19 | const ivec3 out_size = imageSize(img_in); | ||
| 20 | const ivec3 scale = out_size / msaa_size; | ||
| 18 | for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) { | 21 | for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) { |
| 19 | const int single_sample_x = 2 * coords.x + (curr_sample & 1); | 22 | const int single_sample_x = scale.x * coords.x + (curr_sample & 1); |
| 20 | const int single_sample_y = 2 * coords.y + ((curr_sample / 2) & 1); | 23 | const int single_sample_y = scale.y * coords.y + ((curr_sample / 2) & 1); |
| 21 | const ivec3 single_coords = ivec3(single_sample_x, single_sample_y, coords.z); | 24 | const ivec3 single_coords = ivec3(single_sample_x, single_sample_y, coords.z); |
| 22 | 25 | ||
| 23 | if (any(greaterThanEqual(single_coords, imageSize(img_in)))) { | 26 | if (any(greaterThanEqual(single_coords, imageSize(img_in)))) { |
diff --git a/src/video_core/host_shaders/queries_prefix_scan_sum.comp b/src/video_core/host_shaders/queries_prefix_scan_sum.comp new file mode 100644 index 000000000..6faa8981f --- /dev/null +++ b/src/video_core/host_shaders/queries_prefix_scan_sum.comp | |||
| @@ -0,0 +1,173 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | #version 460 core | ||
| 5 | |||
| 6 | #extension GL_KHR_shader_subgroup_basic : require | ||
| 7 | #extension GL_KHR_shader_subgroup_shuffle : require | ||
| 8 | #extension GL_KHR_shader_subgroup_shuffle_relative : require | ||
| 9 | #extension GL_KHR_shader_subgroup_arithmetic : require | ||
| 10 | |||
| 11 | #ifdef VULKAN | ||
| 12 | |||
| 13 | #define HAS_EXTENDED_TYPES 1 | ||
| 14 | #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { | ||
| 15 | #define END_PUSH_CONSTANTS }; | ||
| 16 | #define UNIFORM(n) | ||
| 17 | #define BINDING_INPUT_BUFFER 0 | ||
| 18 | #define BINDING_OUTPUT_IMAGE 1 | ||
| 19 | |||
| 20 | #else // ^^^ Vulkan ^^^ // vvv OpenGL vvv | ||
| 21 | |||
| 22 | #extension GL_NV_gpu_shader5 : enable | ||
| 23 | #ifdef GL_NV_gpu_shader5 | ||
| 24 | #define HAS_EXTENDED_TYPES 1 | ||
| 25 | #else | ||
| 26 | #define HAS_EXTENDED_TYPES 0 | ||
| 27 | #endif | ||
| 28 | #define BEGIN_PUSH_CONSTANTS | ||
| 29 | #define END_PUSH_CONSTANTS | ||
| 30 | #define UNIFORM(n) layout(location = n) uniform | ||
| 31 | #define BINDING_INPUT_BUFFER 0 | ||
| 32 | #define BINDING_OUTPUT_IMAGE 0 | ||
| 33 | |||
| 34 | #endif | ||
| 35 | |||
| 36 | BEGIN_PUSH_CONSTANTS | ||
| 37 | UNIFORM(0) uint min_accumulation_base; | ||
| 38 | UNIFORM(1) uint max_accumulation_base; | ||
| 39 | UNIFORM(2) uint accumulation_limit; | ||
| 40 | UNIFORM(3) uint buffer_offset; | ||
| 41 | END_PUSH_CONSTANTS | ||
| 42 | |||
| 43 | #define LOCAL_RESULTS 8 | ||
| 44 | #define QUERIES_PER_INVOC 2048 | ||
| 45 | |||
| 46 | layout(local_size_x = QUERIES_PER_INVOC / LOCAL_RESULTS) in; | ||
| 47 | |||
| 48 | layout(std430, binding = 0) readonly buffer block1 { | ||
| 49 | uvec2 input_data[]; | ||
| 50 | }; | ||
| 51 | |||
| 52 | layout(std430, binding = 1) coherent buffer block2 { | ||
| 53 | uvec2 output_data[]; | ||
| 54 | }; | ||
| 55 | |||
| 56 | layout(std430, binding = 2) coherent buffer block3 { | ||
| 57 | uvec2 accumulated_data; | ||
| 58 | }; | ||
| 59 | |||
| 60 | shared uvec2 shared_data[128]; | ||
| 61 | |||
| 62 | // Simple Uint64 add that uses 2 uint variables for GPUs that don't support uint64 | ||
| 63 | uvec2 AddUint64(uvec2 value_1, uvec2 value_2) { | ||
| 64 | uint carry = 0; | ||
| 65 | uvec2 result; | ||
| 66 | result.x = uaddCarry(value_1.x, value_2.x, carry); | ||
| 67 | result.y = value_1.y + value_2.y + carry; | ||
| 68 | return result; | ||
| 69 | } | ||
| 70 | |||
| 71 | // do subgroup Prefix Sum using Hillis and Steele's algorithm | ||
| 72 | uvec2 subgroupInclusiveAddUint64(uvec2 value) { | ||
| 73 | uvec2 result = value; | ||
| 74 | for (uint i = 1; i < gl_SubgroupSize; i *= 2) { | ||
| 75 | uvec2 other = subgroupShuffleUp(result, i); // get value from subgroup_inv_id - i; | ||
| 76 | if (i <= gl_SubgroupInvocationID) { | ||
| 77 | result = AddUint64(result, other); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | return result; | ||
| 81 | } | ||
| 82 | |||
| 83 | // Writes down the results to the output buffer and to the accumulation buffer | ||
| 84 | void WriteResults(uvec2 results[LOCAL_RESULTS]) { | ||
| 85 | const uint current_id = gl_LocalInvocationID.x; | ||
| 86 | const uvec2 accum = accumulated_data; | ||
| 87 | for (uint i = 0; i < LOCAL_RESULTS; i++) { | ||
| 88 | uvec2 base_data = current_id * LOCAL_RESULTS + i < min_accumulation_base ? accum : uvec2(0, 0); | ||
| 89 | AddUint64(results[i], base_data); | ||
| 90 | } | ||
| 91 | for (uint i = 0; i < LOCAL_RESULTS; i++) { | ||
| 92 | output_data[buffer_offset + current_id * LOCAL_RESULTS + i] = results[i]; | ||
| 93 | } | ||
| 94 | uint index = accumulation_limit % LOCAL_RESULTS; | ||
| 95 | uint base_id = accumulation_limit / LOCAL_RESULTS; | ||
| 96 | if (min_accumulation_base >= accumulation_limit + 1) { | ||
| 97 | if (current_id == base_id) { | ||
| 98 | accumulated_data = results[index]; | ||
| 99 | } | ||
| 100 | return; | ||
| 101 | } | ||
| 102 | // We have that ugly case in which the accumulation data is reset in the middle somewhere. | ||
| 103 | barrier(); | ||
| 104 | groupMemoryBarrier(); | ||
| 105 | |||
| 106 | if (current_id == base_id) { | ||
| 107 | uvec2 reset_value = output_data[max_accumulation_base - 1]; | ||
| 108 | // Calculate two complement / negate manually | ||
| 109 | reset_value = AddUint64(uvec2(1,0), ~reset_value); | ||
| 110 | accumulated_data = AddUint64(results[index], reset_value); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | void main() { | ||
| 115 | const uint subgroup_inv_id = gl_SubgroupInvocationID; | ||
| 116 | const uint subgroup_id = gl_SubgroupID + gl_WorkGroupID.x * gl_NumSubgroups; | ||
| 117 | const uint last_subgroup_id = subgroupMax(subgroup_inv_id); | ||
| 118 | const uint current_id = gl_LocalInvocationID.x; | ||
| 119 | const uint total_work = accumulation_limit; | ||
| 120 | const uint last_result_id = LOCAL_RESULTS - 1; | ||
| 121 | uvec2 data[LOCAL_RESULTS]; | ||
| 122 | for (uint i = 0; i < LOCAL_RESULTS; i++) { | ||
| 123 | data[i] = input_data[buffer_offset + current_id * LOCAL_RESULTS + i]; | ||
| 124 | } | ||
| 125 | uvec2 results[LOCAL_RESULTS]; | ||
| 126 | results[0] = data[0]; | ||
| 127 | for (uint i = 1; i < LOCAL_RESULTS; i++) { | ||
| 128 | results[i] = AddUint64(data[i], results[i - 1]); | ||
| 129 | } | ||
| 130 | // make sure all input data has been loaded | ||
| 131 | subgroupBarrier(); | ||
| 132 | subgroupMemoryBarrier(); | ||
| 133 | |||
| 134 | // on the last local result, do a subgroup inclusive scan sum | ||
| 135 | results[last_result_id] = subgroupInclusiveAddUint64(results[last_result_id]); | ||
| 136 | // get the last local result from the subgroup behind the current | ||
| 137 | uvec2 result_behind = subgroupShuffleUp(results[last_result_id], 1); | ||
| 138 | if (subgroup_inv_id != 0) { | ||
| 139 | for (uint i = 1; i < LOCAL_RESULTS; i++) { | ||
| 140 | results[i - 1] = AddUint64(results[i - 1], result_behind); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | // if we had less queries than our subgroup, just write down the results. | ||
| 145 | if (total_work <= gl_SubgroupSize * LOCAL_RESULTS) { // This condition is constant per dispatch. | ||
| 146 | WriteResults(results); | ||
| 147 | return; | ||
| 148 | } | ||
| 149 | |||
| 150 | // We now have more, so lets write the last result into shared memory. | ||
| 151 | // Only pick the last subgroup. | ||
| 152 | if (subgroup_inv_id == last_subgroup_id) { | ||
| 153 | shared_data[subgroup_id] = results[last_result_id]; | ||
| 154 | } | ||
| 155 | // wait until everyone loaded their stuffs | ||
| 156 | barrier(); | ||
| 157 | memoryBarrierShared(); | ||
| 158 | |||
| 159 | // only if it's not the first subgroup | ||
| 160 | if (subgroup_id != 0) { | ||
| 161 | // get the results from some previous invocation | ||
| 162 | uvec2 tmp = shared_data[subgroup_inv_id]; | ||
| 163 | subgroupBarrier(); | ||
| 164 | subgroupMemoryBarrierShared(); | ||
| 165 | tmp = subgroupInclusiveAddUint64(tmp); | ||
| 166 | // obtain the result that would be equivalent to the previous result | ||
| 167 | uvec2 shuffled_result = subgroupShuffle(tmp, subgroup_id - 1); | ||
| 168 | for (uint i = 0; i < LOCAL_RESULTS; i++) { | ||
| 169 | results[i] = AddUint64(results[i], shuffled_result); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | WriteResults(results); | ||
| 173 | } \ No newline at end of file | ||
diff --git a/src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp b/src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp new file mode 100644 index 000000000..559a213b9 --- /dev/null +++ b/src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp | |||
| @@ -0,0 +1,138 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2015 Graham Sellers, Richard Wright Jr. and Nicholas Haemel | ||
| 2 | // SPDX-License-Identifier: MIT | ||
| 3 | |||
| 4 | // Code obtained from OpenGL SuperBible, Seventh Edition by Graham Sellers, Richard Wright Jr. and | ||
| 5 | // Nicholas Haemel. Modified to suit needs. | ||
| 6 | |||
| 7 | #version 460 core | ||
| 8 | |||
| 9 | #ifdef VULKAN | ||
| 10 | |||
| 11 | #define HAS_EXTENDED_TYPES 1 | ||
| 12 | #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { | ||
| 13 | #define END_PUSH_CONSTANTS }; | ||
| 14 | #define UNIFORM(n) | ||
| 15 | #define BINDING_INPUT_BUFFER 0 | ||
| 16 | #define BINDING_OUTPUT_IMAGE 1 | ||
| 17 | |||
| 18 | #else // ^^^ Vulkan ^^^ // vvv OpenGL vvv | ||
| 19 | |||
| 20 | #extension GL_NV_gpu_shader5 : enable | ||
| 21 | #ifdef GL_NV_gpu_shader5 | ||
| 22 | #define HAS_EXTENDED_TYPES 1 | ||
| 23 | #else | ||
| 24 | #define HAS_EXTENDED_TYPES 0 | ||
| 25 | #endif | ||
| 26 | #define BEGIN_PUSH_CONSTANTS | ||
| 27 | #define END_PUSH_CONSTANTS | ||
| 28 | #define UNIFORM(n) layout(location = n) uniform | ||
| 29 | #define BINDING_INPUT_BUFFER 0 | ||
| 30 | #define BINDING_OUTPUT_IMAGE 0 | ||
| 31 | |||
| 32 | #endif | ||
| 33 | |||
| 34 | BEGIN_PUSH_CONSTANTS | ||
| 35 | UNIFORM(0) uint min_accumulation_base; | ||
| 36 | UNIFORM(1) uint max_accumulation_base; | ||
| 37 | UNIFORM(2) uint accumulation_limit; | ||
| 38 | UNIFORM(3) uint buffer_offset; | ||
| 39 | END_PUSH_CONSTANTS | ||
| 40 | |||
| 41 | #define LOCAL_RESULTS 4 | ||
| 42 | #define QUERIES_PER_INVOC 2048 | ||
| 43 | |||
| 44 | layout(local_size_x = QUERIES_PER_INVOC / LOCAL_RESULTS) in; | ||
| 45 | |||
| 46 | layout(std430, binding = 0) readonly buffer block1 { | ||
| 47 | uvec2 input_data[gl_WorkGroupSize.x * LOCAL_RESULTS]; | ||
| 48 | }; | ||
| 49 | |||
| 50 | layout(std430, binding = 1) writeonly coherent buffer block2 { | ||
| 51 | uvec2 output_data[gl_WorkGroupSize.x * LOCAL_RESULTS]; | ||
| 52 | }; | ||
| 53 | |||
| 54 | layout(std430, binding = 2) coherent buffer block3 { | ||
| 55 | uvec2 accumulated_data; | ||
| 56 | }; | ||
| 57 | |||
| 58 | shared uvec2 shared_data[gl_WorkGroupSize.x * LOCAL_RESULTS]; | ||
| 59 | |||
| 60 | uvec2 AddUint64(uvec2 value_1, uvec2 value_2) { | ||
| 61 | uint carry = 0; | ||
| 62 | uvec2 result; | ||
| 63 | result.x = uaddCarry(value_1.x, value_2.x, carry); | ||
| 64 | result.y = value_1.y + value_2.y + carry; | ||
| 65 | return result; | ||
| 66 | } | ||
| 67 | |||
| 68 | void main(void) { | ||
| 69 | uint id = gl_LocalInvocationID.x; | ||
| 70 | uvec2 base_value[LOCAL_RESULTS]; | ||
| 71 | const uvec2 accum = accumulated_data; | ||
| 72 | for (uint i = 0; i < LOCAL_RESULTS; i++) { | ||
| 73 | base_value[i] = (buffer_offset + id * LOCAL_RESULTS + i) < min_accumulation_base | ||
| 74 | ? accumulated_data | ||
| 75 | : uvec2(0); | ||
| 76 | } | ||
| 77 | uint work_size = gl_WorkGroupSize.x; | ||
| 78 | uint rd_id; | ||
| 79 | uint wr_id; | ||
| 80 | uint mask; | ||
| 81 | uvec2 inputs[LOCAL_RESULTS]; | ||
| 82 | for (uint i = 0; i < LOCAL_RESULTS; i++) { | ||
| 83 | inputs[i] = input_data[buffer_offset + id * LOCAL_RESULTS + i]; | ||
| 84 | } | ||
| 85 | // The number of steps is the log base 2 of the | ||
| 86 | // work group size, which should be a power of 2 | ||
| 87 | const uint steps = uint(log2(work_size)) + uint(log2(LOCAL_RESULTS)); | ||
| 88 | uint step = 0; | ||
| 89 | |||
| 90 | // Each invocation is responsible for the content of | ||
| 91 | // two elements of the output array | ||
| 92 | for (uint i = 0; i < LOCAL_RESULTS; i++) { | ||
| 93 | shared_data[id * LOCAL_RESULTS + i] = inputs[i]; | ||
| 94 | } | ||
| 95 | // Synchronize to make sure that everyone has initialized | ||
| 96 | // their elements of shared_data[] with data loaded from | ||
| 97 | // the input arrays | ||
| 98 | barrier(); | ||
| 99 | memoryBarrierShared(); | ||
| 100 | // For each step... | ||
| 101 | for (step = 0; step < steps; step++) { | ||
| 102 | // Calculate the read and write index in the | ||
| 103 | // shared array | ||
| 104 | mask = (1 << step) - 1; | ||
| 105 | rd_id = ((id >> step) << (step + 1)) + mask; | ||
| 106 | wr_id = rd_id + 1 + (id & mask); | ||
| 107 | // Accumulate the read data into our element | ||
| 108 | |||
| 109 | shared_data[wr_id] = AddUint64(shared_data[rd_id], shared_data[wr_id]); | ||
| 110 | // Synchronize again to make sure that everyone | ||
| 111 | // has caught up with us | ||
| 112 | barrier(); | ||
| 113 | memoryBarrierShared(); | ||
| 114 | } | ||
| 115 | // Add the accumulation | ||
| 116 | for (uint i = 0; i < LOCAL_RESULTS; i++) { | ||
| 117 | shared_data[id * LOCAL_RESULTS + i] = | ||
| 118 | AddUint64(shared_data[id * LOCAL_RESULTS + i], base_value[i]); | ||
| 119 | } | ||
| 120 | barrier(); | ||
| 121 | memoryBarrierShared(); | ||
| 122 | |||
| 123 | // Finally write our data back to the output buffer | ||
| 124 | for (uint i = 0; i < LOCAL_RESULTS; i++) { | ||
| 125 | output_data[buffer_offset + id * LOCAL_RESULTS + i] = shared_data[id * LOCAL_RESULTS + i]; | ||
| 126 | } | ||
| 127 | if (id == 0) { | ||
| 128 | if (min_accumulation_base >= accumulation_limit + 1) { | ||
| 129 | accumulated_data = shared_data[accumulation_limit]; | ||
| 130 | return; | ||
| 131 | } | ||
| 132 | uvec2 reset_value = shared_data[max_accumulation_base - 1]; | ||
| 133 | uvec2 final_value = shared_data[accumulation_limit]; | ||
| 134 | // Two complements | ||
| 135 | reset_value = AddUint64(uvec2(1, 0), ~reset_value); | ||
| 136 | accumulated_data = AddUint64(final_value, reset_value); | ||
| 137 | } | ||
| 138 | } \ No newline at end of file | ||
diff --git a/src/video_core/host_shaders/resolve_conditional_render.comp b/src/video_core/host_shaders/resolve_conditional_render.comp new file mode 100644 index 000000000..307e77d1a --- /dev/null +++ b/src/video_core/host_shaders/resolve_conditional_render.comp | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | #version 450 | ||
| 5 | |||
| 6 | layout(local_size_x = 1) in; | ||
| 7 | |||
| 8 | layout(std430, binding = 0) buffer Query { | ||
| 9 | uvec2 initial; | ||
| 10 | uvec2 unknown; | ||
| 11 | uvec2 current; | ||
| 12 | }; | ||
| 13 | |||
| 14 | layout(std430, binding = 1) buffer Result { | ||
| 15 | uint result; | ||
| 16 | }; | ||
| 17 | |||
| 18 | void main() { | ||
| 19 | result = all(equal(initial, current)) ? 1 : 0; | ||
| 20 | } | ||
diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp index 6272a4652..046c8085e 100644 --- a/src/video_core/macro/macro_hle.cpp +++ b/src/video_core/macro/macro_hle.cpp | |||
| @@ -67,6 +67,7 @@ public: | |||
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | auto& params = maxwell3d.draw_manager->GetIndirectParams(); | 69 | auto& params = maxwell3d.draw_manager->GetIndirectParams(); |
| 70 | params.is_byte_count = false; | ||
| 70 | params.is_indexed = false; | 71 | params.is_indexed = false; |
| 71 | params.include_count = false; | 72 | params.include_count = false; |
| 72 | params.count_start_address = 0; | 73 | params.count_start_address = 0; |
| @@ -161,6 +162,7 @@ public: | |||
| 161 | 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); | 162 | 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); |
| 162 | } | 163 | } |
| 163 | auto& params = maxwell3d.draw_manager->GetIndirectParams(); | 164 | auto& params = maxwell3d.draw_manager->GetIndirectParams(); |
| 165 | params.is_byte_count = false; | ||
| 164 | params.is_indexed = true; | 166 | params.is_indexed = true; |
| 165 | params.include_count = false; | 167 | params.include_count = false; |
| 166 | params.count_start_address = 0; | 168 | params.count_start_address = 0; |
| @@ -256,6 +258,7 @@ public: | |||
| 256 | const u32 estimate = static_cast<u32>(maxwell3d.EstimateIndexBufferSize()); | 258 | const u32 estimate = static_cast<u32>(maxwell3d.EstimateIndexBufferSize()); |
| 257 | maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; | 259 | maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; |
| 258 | auto& params = maxwell3d.draw_manager->GetIndirectParams(); | 260 | auto& params = maxwell3d.draw_manager->GetIndirectParams(); |
| 261 | params.is_byte_count = false; | ||
| 259 | params.is_indexed = true; | 262 | params.is_indexed = true; |
| 260 | params.include_count = true; | 263 | params.include_count = true; |
| 261 | params.count_start_address = maxwell3d.GetMacroAddress(4); | 264 | params.count_start_address = maxwell3d.GetMacroAddress(4); |
| @@ -319,6 +322,47 @@ private: | |||
| 319 | } | 322 | } |
| 320 | }; | 323 | }; |
| 321 | 324 | ||
| 325 | class HLE_DrawIndirectByteCount final : public HLEMacroImpl { | ||
| 326 | public: | ||
| 327 | explicit HLE_DrawIndirectByteCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} | ||
| 328 | |||
| 329 | void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override { | ||
| 330 | auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0] & 0xFFFFU); | ||
| 331 | if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { | ||
| 332 | Fallback(parameters); | ||
| 333 | return; | ||
| 334 | } | ||
| 335 | |||
| 336 | auto& params = maxwell3d.draw_manager->GetIndirectParams(); | ||
| 337 | params.is_byte_count = true; | ||
| 338 | params.is_indexed = false; | ||
| 339 | params.include_count = false; | ||
| 340 | params.count_start_address = 0; | ||
| 341 | params.indirect_start_address = maxwell3d.GetMacroAddress(2); | ||
| 342 | params.buffer_size = 4; | ||
| 343 | params.max_draw_counts = 1; | ||
| 344 | params.stride = parameters[1]; | ||
| 345 | maxwell3d.regs.draw.begin = parameters[0]; | ||
| 346 | maxwell3d.regs.draw_auto_stride = parameters[1]; | ||
| 347 | maxwell3d.regs.draw_auto_byte_count = parameters[2]; | ||
| 348 | |||
| 349 | maxwell3d.draw_manager->DrawArrayIndirect(topology); | ||
| 350 | } | ||
| 351 | |||
| 352 | private: | ||
| 353 | void Fallback(const std::vector<u32>& parameters) { | ||
| 354 | maxwell3d.RefreshParameters(); | ||
| 355 | |||
| 356 | maxwell3d.regs.draw.begin = parameters[0]; | ||
| 357 | maxwell3d.regs.draw_auto_stride = parameters[1]; | ||
| 358 | maxwell3d.regs.draw_auto_byte_count = parameters[2]; | ||
| 359 | |||
| 360 | maxwell3d.draw_manager->DrawArray( | ||
| 361 | maxwell3d.regs.draw.topology, 0, | ||
| 362 | maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1); | ||
| 363 | } | ||
| 364 | }; | ||
| 365 | |||
| 322 | class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl { | 366 | class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl { |
| 323 | public: | 367 | public: |
| 324 | explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} | 368 | explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} |
| @@ -536,6 +580,11 @@ HLEMacro::HLEMacro(Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} { | |||
| 536 | [](Maxwell3D& maxwell3d__) -> std::unique_ptr<CachedMacro> { | 580 | [](Maxwell3D& maxwell3d__) -> std::unique_ptr<CachedMacro> { |
| 537 | return std::make_unique<HLE_TransformFeedbackSetup>(maxwell3d__); | 581 | return std::make_unique<HLE_TransformFeedbackSetup>(maxwell3d__); |
| 538 | })); | 582 | })); |
| 583 | builders.emplace(0xB5F74EDB717278ECULL, | ||
| 584 | std::function<std::unique_ptr<CachedMacro>(Maxwell3D&)>( | ||
| 585 | [](Maxwell3D& maxwell3d__) -> std::unique_ptr<CachedMacro> { | ||
| 586 | return std::make_unique<HLE_DrawIndirectByteCount>(maxwell3d__); | ||
| 587 | })); | ||
| 539 | } | 588 | } |
| 540 | 589 | ||
| 541 | HLEMacro::~HLEMacro() = default; | 590 | HLEMacro::~HLEMacro() = default; |
diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h index 7047e2e63..9fcaeeac7 100644 --- a/src/video_core/query_cache.h +++ b/src/video_core/query_cache.h | |||
| @@ -25,6 +25,13 @@ | |||
| 25 | #include "video_core/rasterizer_interface.h" | 25 | #include "video_core/rasterizer_interface.h" |
| 26 | #include "video_core/texture_cache/slot_vector.h" | 26 | #include "video_core/texture_cache/slot_vector.h" |
| 27 | 27 | ||
| 28 | namespace VideoCore { | ||
| 29 | enum class QueryType { | ||
| 30 | SamplesPassed, | ||
| 31 | }; | ||
| 32 | constexpr std::size_t NumQueryTypes = 1; | ||
| 33 | } // namespace VideoCore | ||
| 34 | |||
| 28 | namespace VideoCommon { | 35 | namespace VideoCommon { |
| 29 | 36 | ||
| 30 | using AsyncJobId = SlotId; | 37 | using AsyncJobId = SlotId; |
| @@ -98,10 +105,10 @@ private: | |||
| 98 | }; | 105 | }; |
| 99 | 106 | ||
| 100 | template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter> | 107 | template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter> |
| 101 | class QueryCacheBase : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> { | 108 | class QueryCacheLegacy : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> { |
| 102 | public: | 109 | public: |
| 103 | explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_, | 110 | explicit QueryCacheLegacy(VideoCore::RasterizerInterface& rasterizer_, |
| 104 | Core::Memory::Memory& cpu_memory_) | 111 | Core::Memory::Memory& cpu_memory_) |
| 105 | : rasterizer{rasterizer_}, | 112 | : rasterizer{rasterizer_}, |
| 106 | // Use reinterpret_cast instead of static_cast as workaround for | 113 | // Use reinterpret_cast instead of static_cast as workaround for |
| 107 | // UBSan bug (https://github.com/llvm/llvm-project/issues/59060) | 114 | // UBSan bug (https://github.com/llvm/llvm-project/issues/59060) |
diff --git a/src/video_core/query_cache/bank_base.h b/src/video_core/query_cache/bank_base.h new file mode 100644 index 000000000..420927091 --- /dev/null +++ b/src/video_core/query_cache/bank_base.h | |||
| @@ -0,0 +1,104 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <atomic> | ||
| 7 | #include <deque> | ||
| 8 | #include <utility> | ||
| 9 | |||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace VideoCommon { | ||
| 13 | |||
| 14 | class BankBase { | ||
| 15 | protected: | ||
| 16 | const size_t base_bank_size{}; | ||
| 17 | size_t bank_size{}; | ||
| 18 | std::atomic<size_t> references{}; | ||
| 19 | size_t current_slot{}; | ||
| 20 | |||
| 21 | public: | ||
| 22 | explicit BankBase(size_t bank_size_) : base_bank_size{bank_size_}, bank_size(bank_size_) {} | ||
| 23 | |||
| 24 | virtual ~BankBase() = default; | ||
| 25 | |||
| 26 | virtual std::pair<bool, size_t> Reserve() { | ||
| 27 | if (IsClosed()) { | ||
| 28 | return {false, bank_size}; | ||
| 29 | } | ||
| 30 | const size_t result = current_slot++; | ||
| 31 | return {true, result}; | ||
| 32 | } | ||
| 33 | |||
| 34 | virtual void Reset() { | ||
| 35 | current_slot = 0; | ||
| 36 | references = 0; | ||
| 37 | bank_size = base_bank_size; | ||
| 38 | } | ||
| 39 | |||
| 40 | size_t Size() const { | ||
| 41 | return bank_size; | ||
| 42 | } | ||
| 43 | |||
| 44 | void AddReference(size_t how_many = 1) { | ||
| 45 | references.fetch_add(how_many, std::memory_order_relaxed); | ||
| 46 | } | ||
| 47 | |||
| 48 | void CloseReference(size_t how_many = 1) { | ||
| 49 | if (how_many > references.load(std::memory_order_relaxed)) { | ||
| 50 | UNREACHABLE(); | ||
| 51 | } | ||
| 52 | references.fetch_sub(how_many, std::memory_order_relaxed); | ||
| 53 | } | ||
| 54 | |||
| 55 | void Close() { | ||
| 56 | bank_size = current_slot; | ||
| 57 | } | ||
| 58 | |||
| 59 | bool IsClosed() const { | ||
| 60 | return current_slot >= bank_size; | ||
| 61 | } | ||
| 62 | |||
| 63 | bool IsDead() const { | ||
| 64 | return IsClosed() && references == 0; | ||
| 65 | } | ||
| 66 | }; | ||
| 67 | |||
| 68 | template <typename BankType> | ||
| 69 | class BankPool { | ||
| 70 | private: | ||
| 71 | std::deque<BankType> bank_pool; | ||
| 72 | std::deque<size_t> bank_indices; | ||
| 73 | |||
| 74 | public: | ||
| 75 | BankPool() = default; | ||
| 76 | ~BankPool() = default; | ||
| 77 | |||
| 78 | // Reserve a bank from the pool and return its index | ||
| 79 | template <typename Func> | ||
| 80 | size_t ReserveBank(Func&& builder) { | ||
| 81 | if (!bank_indices.empty() && bank_pool[bank_indices.front()].IsDead()) { | ||
| 82 | size_t new_index = bank_indices.front(); | ||
| 83 | bank_indices.pop_front(); | ||
| 84 | bank_pool[new_index].Reset(); | ||
| 85 | return new_index; | ||
| 86 | } | ||
| 87 | size_t new_index = bank_pool.size(); | ||
| 88 | builder(bank_pool, new_index); | ||
| 89 | bank_indices.push_back(new_index); | ||
| 90 | return new_index; | ||
| 91 | } | ||
| 92 | |||
| 93 | // Get a reference to a bank using its index | ||
| 94 | BankType& GetBank(size_t index) { | ||
| 95 | return bank_pool[index]; | ||
| 96 | } | ||
| 97 | |||
| 98 | // Get the total number of banks in the pool | ||
| 99 | size_t BankCount() const { | ||
| 100 | return bank_pool.size(); | ||
| 101 | } | ||
| 102 | }; | ||
| 103 | |||
| 104 | } // namespace VideoCommon | ||
diff --git a/src/video_core/query_cache/query_base.h b/src/video_core/query_cache/query_base.h new file mode 100644 index 000000000..1d786b3a7 --- /dev/null +++ b/src/video_core/query_cache/query_base.h | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace VideoCommon { | ||
| 10 | |||
| 11 | enum class QueryFlagBits : u32 { | ||
| 12 | HasTimestamp = 1 << 0, ///< Indicates if this query has a timestamp. | ||
| 13 | IsFinalValueSynced = 1 << 1, ///< Indicates if the query has been synced in the host | ||
| 14 | IsHostSynced = 1 << 2, ///< Indicates if the query has been synced in the host | ||
| 15 | IsGuestSynced = 1 << 3, ///< Indicates if the query has been synced with the guest. | ||
| 16 | IsHostManaged = 1 << 4, ///< Indicates if this query points to a host query | ||
| 17 | IsRewritten = 1 << 5, ///< Indicates if this query was rewritten by another query | ||
| 18 | IsInvalidated = 1 << 6, ///< Indicates the value of th query has been nullified. | ||
| 19 | IsOrphan = 1 << 7, ///< Indicates the query has not been set by a guest query. | ||
| 20 | IsFence = 1 << 8, ///< Indicates the query is a fence. | ||
| 21 | }; | ||
| 22 | DECLARE_ENUM_FLAG_OPERATORS(QueryFlagBits) | ||
| 23 | |||
| 24 | class QueryBase { | ||
| 25 | public: | ||
| 26 | VAddr guest_address{}; | ||
| 27 | QueryFlagBits flags{}; | ||
| 28 | u64 value{}; | ||
| 29 | |||
| 30 | protected: | ||
| 31 | // Default constructor | ||
| 32 | QueryBase() = default; | ||
| 33 | |||
| 34 | // Parameterized constructor | ||
| 35 | QueryBase(VAddr address, QueryFlagBits flags_, u64 value_) | ||
| 36 | : guest_address(address), flags(flags_), value{value_} {} | ||
| 37 | }; | ||
| 38 | |||
| 39 | class GuestQuery : public QueryBase { | ||
| 40 | public: | ||
| 41 | // Parameterized constructor | ||
| 42 | GuestQuery(bool isLong, VAddr address, u64 queryValue) | ||
| 43 | : QueryBase(address, QueryFlagBits::IsFinalValueSynced, queryValue) { | ||
| 44 | if (isLong) { | ||
| 45 | flags |= QueryFlagBits::HasTimestamp; | ||
| 46 | } | ||
| 47 | } | ||
| 48 | }; | ||
| 49 | |||
| 50 | class HostQueryBase : public QueryBase { | ||
| 51 | public: | ||
| 52 | // Default constructor | ||
| 53 | HostQueryBase() : QueryBase(0, QueryFlagBits::IsHostManaged | QueryFlagBits::IsOrphan, 0) {} | ||
| 54 | |||
| 55 | // Parameterized constructor | ||
| 56 | HostQueryBase(bool has_timestamp, VAddr address) | ||
| 57 | : QueryBase(address, QueryFlagBits::IsHostManaged, 0), start_bank_id{}, size_banks{}, | ||
| 58 | start_slot{}, size_slots{} { | ||
| 59 | if (has_timestamp) { | ||
| 60 | flags |= QueryFlagBits::HasTimestamp; | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | u32 start_bank_id{}; | ||
| 65 | u32 size_banks{}; | ||
| 66 | size_t start_slot{}; | ||
| 67 | size_t size_slots{}; | ||
| 68 | }; | ||
| 69 | |||
| 70 | } // namespace VideoCommon \ No newline at end of file | ||
diff --git a/src/video_core/query_cache/query_cache.h b/src/video_core/query_cache/query_cache.h new file mode 100644 index 000000000..78b42b518 --- /dev/null +++ b/src/video_core/query_cache/query_cache.h | |||
| @@ -0,0 +1,580 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <deque> | ||
| 8 | #include <memory> | ||
| 9 | #include <mutex> | ||
| 10 | #include <unordered_map> | ||
| 11 | #include <utility> | ||
| 12 | |||
| 13 | #include "common/assert.h" | ||
| 14 | #include "common/common_types.h" | ||
| 15 | #include "common/logging/log.h" | ||
| 16 | #include "common/scope_exit.h" | ||
| 17 | #include "common/settings.h" | ||
| 18 | #include "core/memory.h" | ||
| 19 | #include "video_core/engines/maxwell_3d.h" | ||
| 20 | #include "video_core/gpu.h" | ||
| 21 | #include "video_core/memory_manager.h" | ||
| 22 | #include "video_core/query_cache/bank_base.h" | ||
| 23 | #include "video_core/query_cache/query_base.h" | ||
| 24 | #include "video_core/query_cache/query_cache_base.h" | ||
| 25 | #include "video_core/query_cache/query_stream.h" | ||
| 26 | #include "video_core/query_cache/types.h" | ||
| 27 | |||
| 28 | namespace VideoCommon { | ||
| 29 | |||
| 30 | using Maxwell = Tegra::Engines::Maxwell3D; | ||
| 31 | |||
| 32 | struct SyncValuesStruct { | ||
| 33 | VAddr address; | ||
| 34 | u64 value; | ||
| 35 | u64 size; | ||
| 36 | |||
| 37 | static constexpr bool GeneratesBaseBuffer = true; | ||
| 38 | }; | ||
| 39 | |||
| 40 | template <typename Traits> | ||
| 41 | class GuestStreamer : public SimpleStreamer<GuestQuery> { | ||
| 42 | public: | ||
| 43 | using RuntimeType = typename Traits::RuntimeType; | ||
| 44 | |||
| 45 | GuestStreamer(size_t id_, RuntimeType& runtime_) | ||
| 46 | : SimpleStreamer<GuestQuery>(id_), runtime{runtime_} {} | ||
| 47 | |||
| 48 | virtual ~GuestStreamer() = default; | ||
| 49 | |||
| 50 | size_t WriteCounter(VAddr address, bool has_timestamp, u32 value, | ||
| 51 | std::optional<u32> subreport = std::nullopt) override { | ||
| 52 | auto new_id = BuildQuery(has_timestamp, address, static_cast<u64>(value)); | ||
| 53 | pending_sync.push_back(new_id); | ||
| 54 | return new_id; | ||
| 55 | } | ||
| 56 | |||
| 57 | bool HasPendingSync() const override { | ||
| 58 | return !pending_sync.empty(); | ||
| 59 | } | ||
| 60 | |||
| 61 | void SyncWrites() override { | ||
| 62 | if (pending_sync.empty()) { | ||
| 63 | return; | ||
| 64 | } | ||
| 65 | std::vector<SyncValuesStruct> sync_values; | ||
| 66 | sync_values.reserve(pending_sync.size()); | ||
| 67 | for (size_t pending_id : pending_sync) { | ||
| 68 | auto& query = slot_queries[pending_id]; | ||
| 69 | if (True(query.flags & QueryFlagBits::IsRewritten) || | ||
| 70 | True(query.flags & QueryFlagBits::IsInvalidated)) { | ||
| 71 | continue; | ||
| 72 | } | ||
| 73 | query.flags |= QueryFlagBits::IsHostSynced; | ||
| 74 | sync_values.emplace_back(SyncValuesStruct{ | ||
| 75 | .address = query.guest_address, | ||
| 76 | .value = query.value, | ||
| 77 | .size = static_cast<u64>(True(query.flags & QueryFlagBits::HasTimestamp) ? 8 : 4)}); | ||
| 78 | } | ||
| 79 | pending_sync.clear(); | ||
| 80 | if (sync_values.size() > 0) { | ||
| 81 | runtime.template SyncValues<SyncValuesStruct>(sync_values); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | private: | ||
| 86 | RuntimeType& runtime; | ||
| 87 | std::deque<size_t> pending_sync; | ||
| 88 | }; | ||
| 89 | |||
| 90 | template <typename Traits> | ||
| 91 | class StubStreamer : public GuestStreamer<Traits> { | ||
| 92 | public: | ||
| 93 | using RuntimeType = typename Traits::RuntimeType; | ||
| 94 | |||
| 95 | StubStreamer(size_t id_, RuntimeType& runtime_, u32 stub_value_) | ||
| 96 | : GuestStreamer<Traits>(id_, runtime_), stub_value{stub_value_} {} | ||
| 97 | |||
| 98 | ~StubStreamer() override = default; | ||
| 99 | |||
| 100 | size_t WriteCounter(VAddr address, bool has_timestamp, [[maybe_unused]] u32 value, | ||
| 101 | std::optional<u32> subreport = std::nullopt) override { | ||
| 102 | size_t new_id = | ||
| 103 | GuestStreamer<Traits>::WriteCounter(address, has_timestamp, stub_value, subreport); | ||
| 104 | return new_id; | ||
| 105 | } | ||
| 106 | |||
| 107 | private: | ||
| 108 | u32 stub_value; | ||
| 109 | }; | ||
| 110 | |||
| 111 | template <typename Traits> | ||
| 112 | struct QueryCacheBase<Traits>::QueryCacheBaseImpl { | ||
| 113 | using RuntimeType = typename Traits::RuntimeType; | ||
| 114 | |||
| 115 | QueryCacheBaseImpl(QueryCacheBase<Traits>* owner_, VideoCore::RasterizerInterface& rasterizer_, | ||
| 116 | Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_, Tegra::GPU& gpu_) | ||
| 117 | : owner{owner_}, rasterizer{rasterizer_}, | ||
| 118 | cpu_memory{cpu_memory_}, runtime{runtime_}, gpu{gpu_} { | ||
| 119 | streamer_mask = 0; | ||
| 120 | for (size_t i = 0; i < static_cast<size_t>(QueryType::MaxQueryTypes); i++) { | ||
| 121 | streamers[i] = runtime.GetStreamerInterface(static_cast<QueryType>(i)); | ||
| 122 | if (streamers[i]) { | ||
| 123 | streamer_mask |= 1ULL << streamers[i]->GetId(); | ||
| 124 | } | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | template <typename Func> | ||
| 129 | void ForEachStreamerIn(u64 mask, Func&& func) { | ||
| 130 | static constexpr bool RETURNS_BOOL = | ||
| 131 | std::is_same_v<std::invoke_result<Func, StreamerInterface*>, bool>; | ||
| 132 | while (mask != 0) { | ||
| 133 | size_t position = std::countr_zero(mask); | ||
| 134 | mask &= ~(1ULL << position); | ||
| 135 | if constexpr (RETURNS_BOOL) { | ||
| 136 | if (func(streamers[position])) { | ||
| 137 | return; | ||
| 138 | } | ||
| 139 | } else { | ||
| 140 | func(streamers[position]); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | template <typename Func> | ||
| 146 | void ForEachStreamer(Func&& func) { | ||
| 147 | ForEachStreamerIn(streamer_mask, func); | ||
| 148 | } | ||
| 149 | |||
| 150 | QueryBase* ObtainQuery(QueryCacheBase<Traits>::QueryLocation location) { | ||
| 151 | size_t which_stream = location.stream_id.Value(); | ||
| 152 | auto* streamer = streamers[which_stream]; | ||
| 153 | if (!streamer) { | ||
| 154 | return nullptr; | ||
| 155 | } | ||
| 156 | return streamer->GetQuery(location.query_id.Value()); | ||
| 157 | } | ||
| 158 | |||
| 159 | QueryCacheBase<Traits>* owner; | ||
| 160 | VideoCore::RasterizerInterface& rasterizer; | ||
| 161 | Core::Memory::Memory& cpu_memory; | ||
| 162 | RuntimeType& runtime; | ||
| 163 | Tegra::GPU& gpu; | ||
| 164 | std::array<StreamerInterface*, static_cast<size_t>(QueryType::MaxQueryTypes)> streamers; | ||
| 165 | u64 streamer_mask; | ||
| 166 | std::mutex flush_guard; | ||
| 167 | std::deque<u64> flushes_pending; | ||
| 168 | std::vector<QueryCacheBase<Traits>::QueryLocation> pending_unregister; | ||
| 169 | }; | ||
| 170 | |||
| 171 | template <typename Traits> | ||
| 172 | QueryCacheBase<Traits>::QueryCacheBase(Tegra::GPU& gpu_, | ||
| 173 | VideoCore::RasterizerInterface& rasterizer_, | ||
| 174 | Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_) | ||
| 175 | : cached_queries{} { | ||
| 176 | impl = std::make_unique<QueryCacheBase<Traits>::QueryCacheBaseImpl>( | ||
| 177 | this, rasterizer_, cpu_memory_, runtime_, gpu_); | ||
| 178 | } | ||
| 179 | |||
| 180 | template <typename Traits> | ||
| 181 | QueryCacheBase<Traits>::~QueryCacheBase() = default; | ||
| 182 | |||
| 183 | template <typename Traits> | ||
| 184 | void QueryCacheBase<Traits>::CounterEnable(QueryType counter_type, bool is_enabled) { | ||
| 185 | size_t index = static_cast<size_t>(counter_type); | ||
| 186 | StreamerInterface* streamer = impl->streamers[index]; | ||
| 187 | if (!streamer) [[unlikely]] { | ||
| 188 | UNREACHABLE(); | ||
| 189 | return; | ||
| 190 | } | ||
| 191 | if (is_enabled) { | ||
| 192 | streamer->StartCounter(); | ||
| 193 | } else { | ||
| 194 | streamer->PauseCounter(); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | template <typename Traits> | ||
| 199 | void QueryCacheBase<Traits>::CounterClose(QueryType counter_type) { | ||
| 200 | size_t index = static_cast<size_t>(counter_type); | ||
| 201 | StreamerInterface* streamer = impl->streamers[index]; | ||
| 202 | if (!streamer) [[unlikely]] { | ||
| 203 | UNREACHABLE(); | ||
| 204 | return; | ||
| 205 | } | ||
| 206 | streamer->CloseCounter(); | ||
| 207 | } | ||
| 208 | |||
| 209 | template <typename Traits> | ||
| 210 | void QueryCacheBase<Traits>::CounterReset(QueryType counter_type) { | ||
| 211 | size_t index = static_cast<size_t>(counter_type); | ||
| 212 | StreamerInterface* streamer = impl->streamers[index]; | ||
| 213 | if (!streamer) [[unlikely]] { | ||
| 214 | UNIMPLEMENTED(); | ||
| 215 | return; | ||
| 216 | } | ||
| 217 | streamer->ResetCounter(); | ||
| 218 | } | ||
| 219 | |||
| 220 | template <typename Traits> | ||
| 221 | void QueryCacheBase<Traits>::BindToChannel(s32 id) { | ||
| 222 | VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo>::BindToChannel(id); | ||
| 223 | impl->runtime.Bind3DEngine(maxwell3d); | ||
| 224 | } | ||
| 225 | |||
| 226 | template <typename Traits> | ||
| 227 | void QueryCacheBase<Traits>::CounterReport(GPUVAddr addr, QueryType counter_type, | ||
| 228 | QueryPropertiesFlags flags, u32 payload, u32 subreport) { | ||
| 229 | const bool has_timestamp = True(flags & QueryPropertiesFlags::HasTimeout); | ||
| 230 | const bool is_fence = True(flags & QueryPropertiesFlags::IsAFence); | ||
| 231 | size_t streamer_id = static_cast<size_t>(counter_type); | ||
| 232 | auto* streamer = impl->streamers[streamer_id]; | ||
| 233 | if (streamer == nullptr) [[unlikely]] { | ||
| 234 | counter_type = QueryType::Payload; | ||
| 235 | payload = 1U; | ||
| 236 | streamer_id = static_cast<size_t>(counter_type); | ||
| 237 | streamer = impl->streamers[streamer_id]; | ||
| 238 | } | ||
| 239 | auto cpu_addr_opt = gpu_memory->GpuToCpuAddress(addr); | ||
| 240 | if (!cpu_addr_opt) [[unlikely]] { | ||
| 241 | return; | ||
| 242 | } | ||
| 243 | VAddr cpu_addr = *cpu_addr_opt; | ||
| 244 | const size_t new_query_id = streamer->WriteCounter(cpu_addr, has_timestamp, payload, subreport); | ||
| 245 | auto* query = streamer->GetQuery(new_query_id); | ||
| 246 | if (is_fence) { | ||
| 247 | query->flags |= QueryFlagBits::IsFence; | ||
| 248 | } | ||
| 249 | QueryLocation query_location{}; | ||
| 250 | query_location.stream_id.Assign(static_cast<u32>(streamer_id)); | ||
| 251 | query_location.query_id.Assign(static_cast<u32>(new_query_id)); | ||
| 252 | const auto gen_caching_indexing = [](VAddr cur_addr) { | ||
| 253 | return std::make_pair<u64, u32>(cur_addr >> Core::Memory::YUZU_PAGEBITS, | ||
| 254 | static_cast<u32>(cur_addr & Core::Memory::YUZU_PAGEMASK)); | ||
| 255 | }; | ||
| 256 | u8* pointer = impl->cpu_memory.GetPointer(cpu_addr); | ||
| 257 | u8* pointer_timestamp = impl->cpu_memory.GetPointer(cpu_addr + 8); | ||
| 258 | bool is_synced = !Settings::IsGPULevelHigh() && is_fence; | ||
| 259 | |||
| 260 | std::function<void()> operation([this, is_synced, streamer, query_base = query, query_location, | ||
| 261 | pointer, pointer_timestamp] { | ||
| 262 | if (True(query_base->flags & QueryFlagBits::IsInvalidated)) { | ||
| 263 | if (!is_synced) [[likely]] { | ||
| 264 | impl->pending_unregister.push_back(query_location); | ||
| 265 | } | ||
| 266 | return; | ||
| 267 | } | ||
| 268 | if (False(query_base->flags & QueryFlagBits::IsFinalValueSynced)) [[unlikely]] { | ||
| 269 | UNREACHABLE(); | ||
| 270 | return; | ||
| 271 | } | ||
| 272 | query_base->value += streamer->GetAmmendValue(); | ||
| 273 | streamer->SetAccumulationValue(query_base->value); | ||
| 274 | if (True(query_base->flags & QueryFlagBits::HasTimestamp)) { | ||
| 275 | u64 timestamp = impl->gpu.GetTicks(); | ||
| 276 | std::memcpy(pointer_timestamp, ×tamp, sizeof(timestamp)); | ||
| 277 | std::memcpy(pointer, &query_base->value, sizeof(query_base->value)); | ||
| 278 | } else { | ||
| 279 | u32 value = static_cast<u32>(query_base->value); | ||
| 280 | std::memcpy(pointer, &value, sizeof(value)); | ||
| 281 | } | ||
| 282 | if (!is_synced) [[likely]] { | ||
| 283 | impl->pending_unregister.push_back(query_location); | ||
| 284 | } | ||
| 285 | }); | ||
| 286 | if (is_fence) { | ||
| 287 | impl->rasterizer.SignalFence(std::move(operation)); | ||
| 288 | } else { | ||
| 289 | if (!Settings::IsGPULevelHigh() && counter_type == QueryType::Payload) { | ||
| 290 | if (has_timestamp) { | ||
| 291 | u64 timestamp = impl->gpu.GetTicks(); | ||
| 292 | u64 value = static_cast<u64>(payload); | ||
| 293 | std::memcpy(pointer_timestamp, ×tamp, sizeof(timestamp)); | ||
| 294 | std::memcpy(pointer, &value, sizeof(value)); | ||
| 295 | } else { | ||
| 296 | std::memcpy(pointer, &payload, sizeof(payload)); | ||
| 297 | } | ||
| 298 | streamer->Free(new_query_id); | ||
| 299 | return; | ||
| 300 | } | ||
| 301 | impl->rasterizer.SyncOperation(std::move(operation)); | ||
| 302 | } | ||
| 303 | if (is_synced) { | ||
| 304 | streamer->Free(new_query_id); | ||
| 305 | return; | ||
| 306 | } | ||
| 307 | auto [cont_addr, base] = gen_caching_indexing(cpu_addr); | ||
| 308 | { | ||
| 309 | std::scoped_lock lock(cache_mutex); | ||
| 310 | auto it1 = cached_queries.try_emplace(cont_addr); | ||
| 311 | auto& sub_container = it1.first->second; | ||
| 312 | auto it_current = sub_container.find(base); | ||
| 313 | if (it_current == sub_container.end()) { | ||
| 314 | sub_container.insert_or_assign(base, query_location); | ||
| 315 | return; | ||
| 316 | } | ||
| 317 | auto* old_query = impl->ObtainQuery(it_current->second); | ||
| 318 | old_query->flags |= QueryFlagBits::IsRewritten; | ||
| 319 | sub_container.insert_or_assign(base, query_location); | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | template <typename Traits> | ||
| 324 | void QueryCacheBase<Traits>::UnregisterPending() { | ||
| 325 | const auto gen_caching_indexing = [](VAddr cur_addr) { | ||
| 326 | return std::make_pair<u64, u32>(cur_addr >> Core::Memory::YUZU_PAGEBITS, | ||
| 327 | static_cast<u32>(cur_addr & Core::Memory::YUZU_PAGEMASK)); | ||
| 328 | }; | ||
| 329 | std::scoped_lock lock(cache_mutex); | ||
| 330 | for (QueryLocation loc : impl->pending_unregister) { | ||
| 331 | const auto [streamer_id, query_id] = loc.unpack(); | ||
| 332 | auto* streamer = impl->streamers[streamer_id]; | ||
| 333 | if (!streamer) [[unlikely]] { | ||
| 334 | continue; | ||
| 335 | } | ||
| 336 | auto* query = streamer->GetQuery(query_id); | ||
| 337 | auto [cont_addr, base] = gen_caching_indexing(query->guest_address); | ||
| 338 | auto it1 = cached_queries.find(cont_addr); | ||
| 339 | if (it1 != cached_queries.end()) { | ||
| 340 | auto it2 = it1->second.find(base); | ||
| 341 | if (it2 != it1->second.end()) { | ||
| 342 | if (it2->second.raw == loc.raw) { | ||
| 343 | it1->second.erase(it2); | ||
| 344 | } | ||
| 345 | } | ||
| 346 | } | ||
| 347 | streamer->Free(query_id); | ||
| 348 | } | ||
| 349 | impl->pending_unregister.clear(); | ||
| 350 | } | ||
| 351 | |||
| 352 | template <typename Traits> | ||
| 353 | void QueryCacheBase<Traits>::NotifyWFI() { | ||
| 354 | bool should_sync = false; | ||
| 355 | impl->ForEachStreamer( | ||
| 356 | [&should_sync](StreamerInterface* streamer) { should_sync |= streamer->HasPendingSync(); }); | ||
| 357 | if (!should_sync) { | ||
| 358 | return; | ||
| 359 | } | ||
| 360 | |||
| 361 | impl->ForEachStreamer([](StreamerInterface* streamer) { streamer->PresyncWrites(); }); | ||
| 362 | impl->runtime.Barriers(true); | ||
| 363 | impl->ForEachStreamer([](StreamerInterface* streamer) { streamer->SyncWrites(); }); | ||
| 364 | impl->runtime.Barriers(false); | ||
| 365 | } | ||
| 366 | |||
| 367 | template <typename Traits> | ||
| 368 | void QueryCacheBase<Traits>::NotifySegment(bool resume) { | ||
| 369 | if (resume) { | ||
| 370 | impl->runtime.ResumeHostConditionalRendering(); | ||
| 371 | } else { | ||
| 372 | CounterClose(VideoCommon::QueryType::ZPassPixelCount64); | ||
| 373 | CounterClose(VideoCommon::QueryType::StreamingByteCount); | ||
| 374 | impl->runtime.PauseHostConditionalRendering(); | ||
| 375 | } | ||
| 376 | } | ||
| 377 | |||
| 378 | template <typename Traits> | ||
| 379 | bool QueryCacheBase<Traits>::AccelerateHostConditionalRendering() { | ||
| 380 | bool qc_dirty = false; | ||
| 381 | const auto gen_lookup = [this, &qc_dirty](GPUVAddr address) -> VideoCommon::LookupData { | ||
| 382 | auto cpu_addr_opt = gpu_memory->GpuToCpuAddress(address); | ||
| 383 | if (!cpu_addr_opt) [[unlikely]] { | ||
| 384 | return VideoCommon::LookupData{ | ||
| 385 | .address = 0, | ||
| 386 | .found_query = nullptr, | ||
| 387 | }; | ||
| 388 | } | ||
| 389 | VAddr cpu_addr = *cpu_addr_opt; | ||
| 390 | std::scoped_lock lock(cache_mutex); | ||
| 391 | auto it1 = cached_queries.find(cpu_addr >> Core::Memory::YUZU_PAGEBITS); | ||
| 392 | if (it1 == cached_queries.end()) { | ||
| 393 | return VideoCommon::LookupData{ | ||
| 394 | .address = cpu_addr, | ||
| 395 | .found_query = nullptr, | ||
| 396 | }; | ||
| 397 | } | ||
| 398 | auto& sub_container = it1->second; | ||
| 399 | auto it_current = sub_container.find(cpu_addr & Core::Memory::YUZU_PAGEMASK); | ||
| 400 | |||
| 401 | if (it_current == sub_container.end()) { | ||
| 402 | auto it_current_2 = sub_container.find((cpu_addr & Core::Memory::YUZU_PAGEMASK) + 4); | ||
| 403 | if (it_current_2 == sub_container.end()) { | ||
| 404 | return VideoCommon::LookupData{ | ||
| 405 | .address = cpu_addr, | ||
| 406 | .found_query = nullptr, | ||
| 407 | }; | ||
| 408 | } | ||
| 409 | } | ||
| 410 | auto* query = impl->ObtainQuery(it_current->second); | ||
| 411 | qc_dirty |= True(query->flags & QueryFlagBits::IsHostManaged) && | ||
| 412 | False(query->flags & QueryFlagBits::IsGuestSynced); | ||
| 413 | return VideoCommon::LookupData{ | ||
| 414 | .address = cpu_addr, | ||
| 415 | .found_query = query, | ||
| 416 | }; | ||
| 417 | }; | ||
| 418 | |||
| 419 | auto& regs = maxwell3d->regs; | ||
| 420 | if (regs.render_enable_override != Maxwell::Regs::RenderEnable::Override::UseRenderEnable) { | ||
| 421 | impl->runtime.EndHostConditionalRendering(); | ||
| 422 | return false; | ||
| 423 | } | ||
| 424 | const ComparisonMode mode = static_cast<ComparisonMode>(regs.render_enable.mode); | ||
| 425 | const GPUVAddr address = regs.render_enable.Address(); | ||
| 426 | switch (mode) { | ||
| 427 | case ComparisonMode::True: | ||
| 428 | impl->runtime.EndHostConditionalRendering(); | ||
| 429 | return false; | ||
| 430 | case ComparisonMode::False: | ||
| 431 | impl->runtime.EndHostConditionalRendering(); | ||
| 432 | return false; | ||
| 433 | case ComparisonMode::Conditional: { | ||
| 434 | VideoCommon::LookupData object_1{gen_lookup(address)}; | ||
| 435 | return impl->runtime.HostConditionalRenderingCompareValue(object_1, qc_dirty); | ||
| 436 | } | ||
| 437 | case ComparisonMode::IfEqual: { | ||
| 438 | VideoCommon::LookupData object_1{gen_lookup(address)}; | ||
| 439 | VideoCommon::LookupData object_2{gen_lookup(address + 16)}; | ||
| 440 | return impl->runtime.HostConditionalRenderingCompareValues(object_1, object_2, qc_dirty, | ||
| 441 | true); | ||
| 442 | } | ||
| 443 | case ComparisonMode::IfNotEqual: { | ||
| 444 | VideoCommon::LookupData object_1{gen_lookup(address)}; | ||
| 445 | VideoCommon::LookupData object_2{gen_lookup(address + 16)}; | ||
| 446 | return impl->runtime.HostConditionalRenderingCompareValues(object_1, object_2, qc_dirty, | ||
| 447 | false); | ||
| 448 | } | ||
| 449 | default: | ||
| 450 | return false; | ||
| 451 | } | ||
| 452 | } | ||
| 453 | |||
| 454 | // Async downloads | ||
| 455 | template <typename Traits> | ||
| 456 | void QueryCacheBase<Traits>::CommitAsyncFlushes() { | ||
| 457 | // Make sure to have the results synced in Host. | ||
| 458 | NotifyWFI(); | ||
| 459 | |||
| 460 | u64 mask{}; | ||
| 461 | { | ||
| 462 | std::scoped_lock lk(impl->flush_guard); | ||
| 463 | impl->ForEachStreamer([&mask](StreamerInterface* streamer) { | ||
| 464 | bool local_result = streamer->HasUnsyncedQueries(); | ||
| 465 | if (local_result) { | ||
| 466 | mask |= 1ULL << streamer->GetId(); | ||
| 467 | } | ||
| 468 | }); | ||
| 469 | impl->flushes_pending.push_back(mask); | ||
| 470 | } | ||
| 471 | std::function<void()> func([this] { UnregisterPending(); }); | ||
| 472 | impl->rasterizer.SyncOperation(std::move(func)); | ||
| 473 | if (mask == 0) { | ||
| 474 | return; | ||
| 475 | } | ||
| 476 | u64 ran_mask = ~mask; | ||
| 477 | while (mask) { | ||
| 478 | impl->ForEachStreamerIn(mask, [&mask, &ran_mask](StreamerInterface* streamer) { | ||
| 479 | u64 dep_mask = streamer->GetDependentMask(); | ||
| 480 | if ((dep_mask & ~ran_mask) != 0) { | ||
| 481 | return; | ||
| 482 | } | ||
| 483 | u64 index = streamer->GetId(); | ||
| 484 | ran_mask |= (1ULL << index); | ||
| 485 | mask &= ~(1ULL << index); | ||
| 486 | streamer->PushUnsyncedQueries(); | ||
| 487 | }); | ||
| 488 | } | ||
| 489 | } | ||
| 490 | |||
| 491 | template <typename Traits> | ||
| 492 | bool QueryCacheBase<Traits>::HasUncommittedFlushes() const { | ||
| 493 | bool result = false; | ||
| 494 | impl->ForEachStreamer([&result](StreamerInterface* streamer) { | ||
| 495 | result |= streamer->HasUnsyncedQueries(); | ||
| 496 | return result; | ||
| 497 | }); | ||
| 498 | return result; | ||
| 499 | } | ||
| 500 | |||
| 501 | template <typename Traits> | ||
| 502 | bool QueryCacheBase<Traits>::ShouldWaitAsyncFlushes() { | ||
| 503 | std::scoped_lock lk(impl->flush_guard); | ||
| 504 | return !impl->flushes_pending.empty() && impl->flushes_pending.front() != 0ULL; | ||
| 505 | } | ||
| 506 | |||
| 507 | template <typename Traits> | ||
| 508 | void QueryCacheBase<Traits>::PopAsyncFlushes() { | ||
| 509 | u64 mask; | ||
| 510 | { | ||
| 511 | std::scoped_lock lk(impl->flush_guard); | ||
| 512 | mask = impl->flushes_pending.front(); | ||
| 513 | impl->flushes_pending.pop_front(); | ||
| 514 | } | ||
| 515 | if (mask == 0) { | ||
| 516 | return; | ||
| 517 | } | ||
| 518 | u64 ran_mask = ~mask; | ||
| 519 | while (mask) { | ||
| 520 | impl->ForEachStreamerIn(mask, [&mask, &ran_mask](StreamerInterface* streamer) { | ||
| 521 | u64 dep_mask = streamer->GetDependenceMask(); | ||
| 522 | if ((dep_mask & ~ran_mask) != 0) { | ||
| 523 | return; | ||
| 524 | } | ||
| 525 | u64 index = streamer->GetId(); | ||
| 526 | ran_mask |= (1ULL << index); | ||
| 527 | mask &= ~(1ULL << index); | ||
| 528 | streamer->PopUnsyncedQueries(); | ||
| 529 | }); | ||
| 530 | } | ||
| 531 | } | ||
| 532 | |||
| 533 | // Invalidation | ||
| 534 | |||
| 535 | template <typename Traits> | ||
| 536 | void QueryCacheBase<Traits>::InvalidateQuery(QueryCacheBase<Traits>::QueryLocation location) { | ||
| 537 | auto* query_base = impl->ObtainQuery(location); | ||
| 538 | if (!query_base) { | ||
| 539 | return; | ||
| 540 | } | ||
| 541 | query_base->flags |= QueryFlagBits::IsInvalidated; | ||
| 542 | } | ||
| 543 | |||
| 544 | template <typename Traits> | ||
| 545 | bool QueryCacheBase<Traits>::IsQueryDirty(QueryCacheBase<Traits>::QueryLocation location) { | ||
| 546 | auto* query_base = impl->ObtainQuery(location); | ||
| 547 | if (!query_base) { | ||
| 548 | return false; | ||
| 549 | } | ||
| 550 | return True(query_base->flags & QueryFlagBits::IsHostManaged) && | ||
| 551 | False(query_base->flags & QueryFlagBits::IsGuestSynced); | ||
| 552 | } | ||
| 553 | |||
| 554 | template <typename Traits> | ||
| 555 | bool QueryCacheBase<Traits>::SemiFlushQueryDirty(QueryCacheBase<Traits>::QueryLocation location) { | ||
| 556 | auto* query_base = impl->ObtainQuery(location); | ||
| 557 | if (!query_base) { | ||
| 558 | return false; | ||
| 559 | } | ||
| 560 | if (True(query_base->flags & QueryFlagBits::IsFinalValueSynced) && | ||
| 561 | False(query_base->flags & QueryFlagBits::IsGuestSynced)) { | ||
| 562 | auto* ptr = impl->cpu_memory.GetPointer(query_base->guest_address); | ||
| 563 | if (True(query_base->flags & QueryFlagBits::HasTimestamp)) { | ||
| 564 | std::memcpy(ptr, &query_base->value, sizeof(query_base->value)); | ||
| 565 | return false; | ||
| 566 | } | ||
| 567 | u32 value_l = static_cast<u32>(query_base->value); | ||
| 568 | std::memcpy(ptr, &value_l, sizeof(value_l)); | ||
| 569 | return false; | ||
| 570 | } | ||
| 571 | return True(query_base->flags & QueryFlagBits::IsHostManaged) && | ||
| 572 | False(query_base->flags & QueryFlagBits::IsGuestSynced); | ||
| 573 | } | ||
| 574 | |||
| 575 | template <typename Traits> | ||
| 576 | void QueryCacheBase<Traits>::RequestGuestHostSync() { | ||
| 577 | impl->rasterizer.ReleaseFences(); | ||
| 578 | } | ||
| 579 | |||
| 580 | } // namespace VideoCommon | ||
diff --git a/src/video_core/query_cache/query_cache_base.h b/src/video_core/query_cache/query_cache_base.h new file mode 100644 index 000000000..07be421c6 --- /dev/null +++ b/src/video_core/query_cache/query_cache_base.h | |||
| @@ -0,0 +1,181 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <functional> | ||
| 7 | #include <mutex> | ||
| 8 | #include <optional> | ||
| 9 | #include <span> | ||
| 10 | #include <unordered_map> | ||
| 11 | #include <utility> | ||
| 12 | |||
| 13 | #include "common/assert.h" | ||
| 14 | #include "common/bit_field.h" | ||
| 15 | #include "common/common_types.h" | ||
| 16 | #include "core/memory.h" | ||
| 17 | #include "video_core/control/channel_state_cache.h" | ||
| 18 | #include "video_core/query_cache/query_base.h" | ||
| 19 | #include "video_core/query_cache/types.h" | ||
| 20 | |||
| 21 | namespace Core::Memory { | ||
| 22 | class Memory; | ||
| 23 | } | ||
| 24 | |||
| 25 | namespace VideoCore { | ||
| 26 | class RasterizerInterface; | ||
| 27 | } | ||
| 28 | |||
| 29 | namespace Tegra { | ||
| 30 | class GPU; | ||
| 31 | } | ||
| 32 | |||
| 33 | namespace VideoCommon { | ||
| 34 | |||
| 35 | struct LookupData { | ||
| 36 | VAddr address; | ||
| 37 | QueryBase* found_query; | ||
| 38 | }; | ||
| 39 | |||
| 40 | template <typename Traits> | ||
| 41 | class QueryCacheBase : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> { | ||
| 42 | using RuntimeType = typename Traits::RuntimeType; | ||
| 43 | |||
| 44 | public: | ||
| 45 | union QueryLocation { | ||
| 46 | BitField<27, 5, u32> stream_id; | ||
| 47 | BitField<0, 27, u32> query_id; | ||
| 48 | u32 raw; | ||
| 49 | |||
| 50 | std::pair<size_t, size_t> unpack() const { | ||
| 51 | return {static_cast<size_t>(stream_id.Value()), static_cast<size_t>(query_id.Value())}; | ||
| 52 | } | ||
| 53 | }; | ||
| 54 | |||
| 55 | explicit QueryCacheBase(Tegra::GPU& gpu, VideoCore::RasterizerInterface& rasterizer_, | ||
| 56 | Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_); | ||
| 57 | |||
| 58 | ~QueryCacheBase(); | ||
| 59 | |||
| 60 | void InvalidateRegion(VAddr addr, std::size_t size) { | ||
| 61 | IterateCache<true>(addr, size, | ||
| 62 | [this](QueryLocation location) { InvalidateQuery(location); }); | ||
| 63 | } | ||
| 64 | |||
| 65 | void FlushRegion(VAddr addr, std::size_t size) { | ||
| 66 | bool result = false; | ||
| 67 | IterateCache<false>(addr, size, [this, &result](QueryLocation location) { | ||
| 68 | result |= SemiFlushQueryDirty(location); | ||
| 69 | return result; | ||
| 70 | }); | ||
| 71 | if (result) { | ||
| 72 | RequestGuestHostSync(); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | static u64 BuildMask(std::span<const QueryType> types) { | ||
| 77 | u64 mask = 0; | ||
| 78 | for (auto query_type : types) { | ||
| 79 | mask |= 1ULL << (static_cast<u64>(query_type)); | ||
| 80 | } | ||
| 81 | return mask; | ||
| 82 | } | ||
| 83 | |||
| 84 | /// Return true when a CPU region is modified from the GPU | ||
| 85 | [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size) { | ||
| 86 | bool result = false; | ||
| 87 | IterateCache<false>(addr, size, [this, &result](QueryLocation location) { | ||
| 88 | result |= IsQueryDirty(location); | ||
| 89 | return result; | ||
| 90 | }); | ||
| 91 | return result; | ||
| 92 | } | ||
| 93 | |||
| 94 | void CounterEnable(QueryType counter_type, bool is_enabled); | ||
| 95 | |||
| 96 | void CounterReset(QueryType counter_type); | ||
| 97 | |||
| 98 | void CounterClose(QueryType counter_type); | ||
| 99 | |||
| 100 | void CounterReport(GPUVAddr addr, QueryType counter_type, QueryPropertiesFlags flags, | ||
| 101 | u32 payload, u32 subreport); | ||
| 102 | |||
| 103 | void NotifyWFI(); | ||
| 104 | |||
| 105 | bool AccelerateHostConditionalRendering(); | ||
| 106 | |||
| 107 | // Async downloads | ||
| 108 | void CommitAsyncFlushes(); | ||
| 109 | |||
| 110 | bool HasUncommittedFlushes() const; | ||
| 111 | |||
| 112 | bool ShouldWaitAsyncFlushes(); | ||
| 113 | |||
| 114 | void PopAsyncFlushes(); | ||
| 115 | |||
| 116 | void NotifySegment(bool resume); | ||
| 117 | |||
| 118 | void BindToChannel(s32 id) override; | ||
| 119 | |||
| 120 | protected: | ||
| 121 | template <bool remove_from_cache, typename Func> | ||
| 122 | void IterateCache(VAddr addr, std::size_t size, Func&& func) { | ||
| 123 | static constexpr bool RETURNS_BOOL = | ||
| 124 | std::is_same_v<std::invoke_result<Func, QueryLocation>, bool>; | ||
| 125 | const u64 addr_begin = addr; | ||
| 126 | const u64 addr_end = addr_begin + size; | ||
| 127 | |||
| 128 | const u64 page_end = addr_end >> Core::Memory::YUZU_PAGEBITS; | ||
| 129 | std::scoped_lock lock(cache_mutex); | ||
| 130 | for (u64 page = addr_begin >> Core::Memory::YUZU_PAGEBITS; page <= page_end; ++page) { | ||
| 131 | const u64 page_start = page << Core::Memory::YUZU_PAGEBITS; | ||
| 132 | const auto in_range = [page_start, addr_begin, addr_end](const u32 query_location) { | ||
| 133 | const u64 cache_begin = page_start + query_location; | ||
| 134 | const u64 cache_end = cache_begin + sizeof(u32); | ||
| 135 | return cache_begin < addr_end && addr_begin < cache_end; | ||
| 136 | }; | ||
| 137 | const auto& it = cached_queries.find(page); | ||
| 138 | if (it == std::end(cached_queries)) { | ||
| 139 | continue; | ||
| 140 | } | ||
| 141 | auto& contents = it->second; | ||
| 142 | for (auto& query : contents) { | ||
| 143 | if (!in_range(query.first)) { | ||
| 144 | continue; | ||
| 145 | } | ||
| 146 | if constexpr (RETURNS_BOOL) { | ||
| 147 | if (func(query.second)) { | ||
| 148 | return; | ||
| 149 | } | ||
| 150 | } else { | ||
| 151 | func(query.second); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | if constexpr (remove_from_cache) { | ||
| 155 | const auto in_range2 = [&](const std::pair<u32, QueryLocation>& pair) { | ||
| 156 | return in_range(pair.first); | ||
| 157 | }; | ||
| 158 | std::erase_if(contents, in_range2); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | using ContentCache = std::unordered_map<u64, std::unordered_map<u32, QueryLocation>>; | ||
| 164 | |||
| 165 | void InvalidateQuery(QueryLocation location); | ||
| 166 | bool IsQueryDirty(QueryLocation location); | ||
| 167 | bool SemiFlushQueryDirty(QueryLocation location); | ||
| 168 | void RequestGuestHostSync(); | ||
| 169 | void UnregisterPending(); | ||
| 170 | |||
| 171 | std::unordered_map<u64, std::unordered_map<u32, QueryLocation>> cached_queries; | ||
| 172 | std::mutex cache_mutex; | ||
| 173 | |||
| 174 | struct QueryCacheBaseImpl; | ||
| 175 | friend struct QueryCacheBaseImpl; | ||
| 176 | friend RuntimeType; | ||
| 177 | |||
| 178 | std::unique_ptr<QueryCacheBaseImpl> impl; | ||
| 179 | }; | ||
| 180 | |||
| 181 | } // namespace VideoCommon \ No newline at end of file | ||
diff --git a/src/video_core/query_cache/query_stream.h b/src/video_core/query_cache/query_stream.h new file mode 100644 index 000000000..39da6ac07 --- /dev/null +++ b/src/video_core/query_cache/query_stream.h | |||
| @@ -0,0 +1,149 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <deque> | ||
| 7 | #include <optional> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "common/assert.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "video_core/query_cache/bank_base.h" | ||
| 13 | #include "video_core/query_cache/query_base.h" | ||
| 14 | |||
| 15 | namespace VideoCommon { | ||
| 16 | |||
| 17 | class StreamerInterface { | ||
| 18 | public: | ||
| 19 | explicit StreamerInterface(size_t id_) : id{id_}, dependence_mask{}, dependent_mask{} {} | ||
| 20 | virtual ~StreamerInterface() = default; | ||
| 21 | |||
| 22 | virtual QueryBase* GetQuery(size_t id) = 0; | ||
| 23 | |||
| 24 | virtual void StartCounter() { | ||
| 25 | /* Do Nothing */ | ||
| 26 | } | ||
| 27 | |||
| 28 | virtual void PauseCounter() { | ||
| 29 | /* Do Nothing */ | ||
| 30 | } | ||
| 31 | |||
| 32 | virtual void ResetCounter() { | ||
| 33 | /* Do Nothing */ | ||
| 34 | } | ||
| 35 | |||
| 36 | virtual void CloseCounter() { | ||
| 37 | /* Do Nothing */ | ||
| 38 | } | ||
| 39 | |||
| 40 | virtual bool HasPendingSync() const { | ||
| 41 | return false; | ||
| 42 | } | ||
| 43 | |||
| 44 | virtual void PresyncWrites() { | ||
| 45 | /* Do Nothing */ | ||
| 46 | } | ||
| 47 | |||
| 48 | virtual void SyncWrites() { | ||
| 49 | /* Do Nothing */ | ||
| 50 | } | ||
| 51 | |||
| 52 | virtual size_t WriteCounter(VAddr address, bool has_timestamp, u32 value, | ||
| 53 | std::optional<u32> subreport = std::nullopt) = 0; | ||
| 54 | |||
| 55 | virtual bool HasUnsyncedQueries() const { | ||
| 56 | return false; | ||
| 57 | } | ||
| 58 | |||
| 59 | virtual void PushUnsyncedQueries() { | ||
| 60 | /* Do Nothing */ | ||
| 61 | } | ||
| 62 | |||
| 63 | virtual void PopUnsyncedQueries() { | ||
| 64 | /* Do Nothing */ | ||
| 65 | } | ||
| 66 | |||
| 67 | virtual void Free(size_t query_id) = 0; | ||
| 68 | |||
| 69 | size_t GetId() const { | ||
| 70 | return id; | ||
| 71 | } | ||
| 72 | |||
| 73 | u64 GetDependenceMask() const { | ||
| 74 | return dependence_mask; | ||
| 75 | } | ||
| 76 | |||
| 77 | u64 GetDependentMask() const { | ||
| 78 | return dependence_mask; | ||
| 79 | } | ||
| 80 | |||
| 81 | u64 GetAmmendValue() const { | ||
| 82 | return ammend_value; | ||
| 83 | } | ||
| 84 | |||
| 85 | void SetAccumulationValue(u64 new_value) { | ||
| 86 | acumulation_value = new_value; | ||
| 87 | } | ||
| 88 | |||
| 89 | protected: | ||
| 90 | void MakeDependent(StreamerInterface* depend_on) { | ||
| 91 | dependence_mask |= 1ULL << depend_on->id; | ||
| 92 | depend_on->dependent_mask |= 1ULL << id; | ||
| 93 | } | ||
| 94 | |||
| 95 | const size_t id; | ||
| 96 | u64 dependence_mask; | ||
| 97 | u64 dependent_mask; | ||
| 98 | u64 ammend_value{}; | ||
| 99 | u64 acumulation_value{}; | ||
| 100 | }; | ||
| 101 | |||
| 102 | template <typename QueryType> | ||
| 103 | class SimpleStreamer : public StreamerInterface { | ||
| 104 | public: | ||
| 105 | explicit SimpleStreamer(size_t id_) : StreamerInterface{id_} {} | ||
| 106 | virtual ~SimpleStreamer() = default; | ||
| 107 | |||
| 108 | protected: | ||
| 109 | virtual QueryType* GetQuery(size_t query_id) override { | ||
| 110 | if (query_id < slot_queries.size()) { | ||
| 111 | return &slot_queries[query_id]; | ||
| 112 | } | ||
| 113 | return nullptr; | ||
| 114 | } | ||
| 115 | |||
| 116 | virtual void Free(size_t query_id) override { | ||
| 117 | std::scoped_lock lk(guard); | ||
| 118 | ReleaseQuery(query_id); | ||
| 119 | } | ||
| 120 | |||
| 121 | template <typename... Args, typename = decltype(QueryType(std::declval<Args>()...))> | ||
| 122 | size_t BuildQuery(Args&&... args) { | ||
| 123 | std::scoped_lock lk(guard); | ||
| 124 | if (!old_queries.empty()) { | ||
| 125 | size_t new_id = old_queries.front(); | ||
| 126 | old_queries.pop_front(); | ||
| 127 | new (&slot_queries[new_id]) QueryType(std::forward<Args>(args)...); | ||
| 128 | return new_id; | ||
| 129 | } | ||
| 130 | size_t new_id = slot_queries.size(); | ||
| 131 | slot_queries.emplace_back(std::forward<Args>(args)...); | ||
| 132 | return new_id; | ||
| 133 | } | ||
| 134 | |||
| 135 | void ReleaseQuery(size_t query_id) { | ||
| 136 | |||
| 137 | if (query_id < slot_queries.size()) { | ||
| 138 | old_queries.push_back(query_id); | ||
| 139 | return; | ||
| 140 | } | ||
| 141 | UNREACHABLE(); | ||
| 142 | } | ||
| 143 | |||
| 144 | std::mutex guard; | ||
| 145 | std::deque<QueryType> slot_queries; | ||
| 146 | std::deque<size_t> old_queries; | ||
| 147 | }; | ||
| 148 | |||
| 149 | } // namespace VideoCommon \ No newline at end of file | ||
diff --git a/src/video_core/query_cache/types.h b/src/video_core/query_cache/types.h new file mode 100644 index 000000000..e9226bbfc --- /dev/null +++ b/src/video_core/query_cache/types.h | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace VideoCommon { | ||
| 10 | |||
| 11 | enum class QueryPropertiesFlags : u32 { | ||
| 12 | HasTimeout = 1 << 0, | ||
| 13 | IsAFence = 1 << 1, | ||
| 14 | }; | ||
| 15 | DECLARE_ENUM_FLAG_OPERATORS(QueryPropertiesFlags) | ||
| 16 | |||
| 17 | // This should always be equivalent to maxwell3d Report Semaphore Reports | ||
| 18 | enum class QueryType : u32 { | ||
| 19 | Payload = 0, // "None" in docs, but confirmed via hardware to return the payload | ||
| 20 | VerticesGenerated = 1, | ||
| 21 | ZPassPixelCount = 2, | ||
| 22 | PrimitivesGenerated = 3, | ||
| 23 | AlphaBetaClocks = 4, | ||
| 24 | VertexShaderInvocations = 5, | ||
| 25 | StreamingPrimitivesNeededMinusSucceeded = 6, | ||
| 26 | GeometryShaderInvocations = 7, | ||
| 27 | GeometryShaderPrimitivesGenerated = 9, | ||
| 28 | ZCullStats0 = 10, | ||
| 29 | StreamingPrimitivesSucceeded = 11, | ||
| 30 | ZCullStats1 = 12, | ||
| 31 | StreamingPrimitivesNeeded = 13, | ||
| 32 | ZCullStats2 = 14, | ||
| 33 | ClipperInvocations = 15, | ||
| 34 | ZCullStats3 = 16, | ||
| 35 | ClipperPrimitivesGenerated = 17, | ||
| 36 | VtgPrimitivesOut = 18, | ||
| 37 | PixelShaderInvocations = 19, | ||
| 38 | ZPassPixelCount64 = 21, | ||
| 39 | IEEECleanColorTarget = 24, | ||
| 40 | IEEECleanZetaTarget = 25, | ||
| 41 | StreamingByteCount = 26, | ||
| 42 | TessellationInitInvocations = 27, | ||
| 43 | BoundingRectangle = 28, | ||
| 44 | TessellationShaderInvocations = 29, | ||
| 45 | TotalStreamingPrimitivesNeededMinusSucceeded = 30, | ||
| 46 | TessellationShaderPrimitivesGenerated = 31, | ||
| 47 | // max. | ||
| 48 | MaxQueryTypes, | ||
| 49 | }; | ||
| 50 | |||
| 51 | // Comparison modes for Host Conditional Rendering | ||
| 52 | enum class ComparisonMode : u32 { | ||
| 53 | False = 0, | ||
| 54 | True = 1, | ||
| 55 | Conditional = 2, | ||
| 56 | IfEqual = 3, | ||
| 57 | IfNotEqual = 4, | ||
| 58 | MaxComparisonMode, | ||
| 59 | }; | ||
| 60 | |||
| 61 | // Reduction ops. | ||
| 62 | enum class ReductionOp : u32 { | ||
| 63 | RedAdd = 0, | ||
| 64 | RedMin = 1, | ||
| 65 | RedMax = 2, | ||
| 66 | RedInc = 3, | ||
| 67 | RedDec = 4, | ||
| 68 | RedAnd = 5, | ||
| 69 | RedOr = 6, | ||
| 70 | RedXor = 7, | ||
| 71 | MaxReductionOp, | ||
| 72 | }; | ||
| 73 | |||
| 74 | } // namespace VideoCommon \ No newline at end of file | ||
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index cb8029a4f..af1469147 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | #include "video_core/cache_types.h" | 12 | #include "video_core/cache_types.h" |
| 13 | #include "video_core/engines/fermi_2d.h" | 13 | #include "video_core/engines/fermi_2d.h" |
| 14 | #include "video_core/gpu.h" | 14 | #include "video_core/gpu.h" |
| 15 | #include "video_core/query_cache/types.h" | ||
| 15 | #include "video_core/rasterizer_download_area.h" | 16 | #include "video_core/rasterizer_download_area.h" |
| 16 | 17 | ||
| 17 | namespace Tegra { | 18 | namespace Tegra { |
| @@ -26,11 +27,6 @@ struct ChannelState; | |||
| 26 | 27 | ||
| 27 | namespace VideoCore { | 28 | namespace VideoCore { |
| 28 | 29 | ||
| 29 | enum class QueryType { | ||
| 30 | SamplesPassed, | ||
| 31 | }; | ||
| 32 | constexpr std::size_t NumQueryTypes = 1; | ||
| 33 | |||
| 34 | enum class LoadCallbackStage { | 30 | enum class LoadCallbackStage { |
| 35 | Prepare, | 31 | Prepare, |
| 36 | Build, | 32 | Build, |
| @@ -58,10 +54,11 @@ public: | |||
| 58 | virtual void DispatchCompute() = 0; | 54 | virtual void DispatchCompute() = 0; |
| 59 | 55 | ||
| 60 | /// Resets the counter of a query | 56 | /// Resets the counter of a query |
| 61 | virtual void ResetCounter(QueryType type) = 0; | 57 | virtual void ResetCounter(VideoCommon::QueryType type) = 0; |
| 62 | 58 | ||
| 63 | /// Records a GPU query and caches it | 59 | /// Records a GPU query and caches it |
| 64 | virtual void Query(GPUVAddr gpu_addr, QueryType type, std::optional<u64> timestamp) = 0; | 60 | virtual void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type, |
| 61 | VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) = 0; | ||
| 65 | 62 | ||
| 66 | /// Signal an uniform buffer binding | 63 | /// Signal an uniform buffer binding |
| 67 | virtual void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, | 64 | virtual void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, |
| @@ -83,7 +80,7 @@ public: | |||
| 83 | virtual void SignalReference() = 0; | 80 | virtual void SignalReference() = 0; |
| 84 | 81 | ||
| 85 | /// Release all pending fences. | 82 | /// Release all pending fences. |
| 86 | virtual void ReleaseFences() = 0; | 83 | virtual void ReleaseFences(bool force = true) = 0; |
| 87 | 84 | ||
| 88 | /// Notify rasterizer that all caches should be flushed to Switch memory | 85 | /// Notify rasterizer that all caches should be flushed to Switch memory |
| 89 | virtual void FlushAll() = 0; | 86 | virtual void FlushAll() = 0; |
diff --git a/src/video_core/renderer_null/null_rasterizer.cpp b/src/video_core/renderer_null/null_rasterizer.cpp index 92ecf6682..65cd5aa06 100644 --- a/src/video_core/renderer_null/null_rasterizer.cpp +++ b/src/video_core/renderer_null/null_rasterizer.cpp | |||
| @@ -26,16 +26,18 @@ void RasterizerNull::Draw(bool is_indexed, u32 instance_count) {} | |||
| 26 | void RasterizerNull::DrawTexture() {} | 26 | void RasterizerNull::DrawTexture() {} |
| 27 | void RasterizerNull::Clear(u32 layer_count) {} | 27 | void RasterizerNull::Clear(u32 layer_count) {} |
| 28 | void RasterizerNull::DispatchCompute() {} | 28 | void RasterizerNull::DispatchCompute() {} |
| 29 | void RasterizerNull::ResetCounter(VideoCore::QueryType type) {} | 29 | void RasterizerNull::ResetCounter(VideoCommon::QueryType type) {} |
| 30 | void RasterizerNull::Query(GPUVAddr gpu_addr, VideoCore::QueryType type, | 30 | void RasterizerNull::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type, |
| 31 | std::optional<u64> timestamp) { | 31 | VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) { |
| 32 | if (!gpu_memory) { | 32 | if (!gpu_memory) { |
| 33 | return; | 33 | return; |
| 34 | } | 34 | } |
| 35 | 35 | if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) { | |
| 36 | gpu_memory->Write(gpu_addr, u64{0}); | 36 | u64 ticks = m_gpu.GetTicks(); |
| 37 | if (timestamp) { | 37 | gpu_memory->Write<u64>(gpu_addr + 8, ticks); |
| 38 | gpu_memory->Write(gpu_addr + 8, *timestamp); | 38 | gpu_memory->Write<u64>(gpu_addr, static_cast<u64>(payload)); |
| 39 | } else { | ||
| 40 | gpu_memory->Write<u32>(gpu_addr, payload); | ||
| 39 | } | 41 | } |
| 40 | } | 42 | } |
| 41 | void RasterizerNull::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, | 43 | void RasterizerNull::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, |
| @@ -74,7 +76,7 @@ void RasterizerNull::SignalSyncPoint(u32 value) { | |||
| 74 | syncpoint_manager.IncrementHost(value); | 76 | syncpoint_manager.IncrementHost(value); |
| 75 | } | 77 | } |
| 76 | void RasterizerNull::SignalReference() {} | 78 | void RasterizerNull::SignalReference() {} |
| 77 | void RasterizerNull::ReleaseFences() {} | 79 | void RasterizerNull::ReleaseFences(bool) {} |
| 78 | void RasterizerNull::FlushAndInvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType) {} | 80 | void RasterizerNull::FlushAndInvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType) {} |
| 79 | void RasterizerNull::WaitForIdle() {} | 81 | void RasterizerNull::WaitForIdle() {} |
| 80 | void RasterizerNull::FragmentBarrier() {} | 82 | void RasterizerNull::FragmentBarrier() {} |
diff --git a/src/video_core/renderer_null/null_rasterizer.h b/src/video_core/renderer_null/null_rasterizer.h index 93b9a6971..23001eeb8 100644 --- a/src/video_core/renderer_null/null_rasterizer.h +++ b/src/video_core/renderer_null/null_rasterizer.h | |||
| @@ -42,8 +42,9 @@ public: | |||
| 42 | void DrawTexture() override; | 42 | void DrawTexture() override; |
| 43 | void Clear(u32 layer_count) override; | 43 | void Clear(u32 layer_count) override; |
| 44 | void DispatchCompute() override; | 44 | void DispatchCompute() override; |
| 45 | void ResetCounter(VideoCore::QueryType type) override; | 45 | void ResetCounter(VideoCommon::QueryType type) override; |
| 46 | void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; | 46 | void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type, |
| 47 | VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override; | ||
| 47 | void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; | 48 | void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; |
| 48 | void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; | 49 | void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; |
| 49 | void FlushAll() override; | 50 | void FlushAll() override; |
| @@ -63,7 +64,7 @@ public: | |||
| 63 | void SyncOperation(std::function<void()>&& func) override; | 64 | void SyncOperation(std::function<void()>&& func) override; |
| 64 | void SignalSyncPoint(u32 value) override; | 65 | void SignalSyncPoint(u32 value) override; |
| 65 | void SignalReference() override; | 66 | void SignalReference() override; |
| 66 | void ReleaseFences() override; | 67 | void ReleaseFences(bool force) override; |
| 67 | void FlushAndInvalidateRegion( | 68 | void FlushAndInvalidateRegion( |
| 68 | VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; | 69 | VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; |
| 69 | void WaitForIdle() override; | 70 | void WaitForIdle() override; |
diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp index 99d7347f5..ec142d48e 100644 --- a/src/video_core/renderer_opengl/gl_query_cache.cpp +++ b/src/video_core/renderer_opengl/gl_query_cache.cpp | |||
| @@ -27,7 +27,7 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) { | |||
| 27 | } // Anonymous namespace | 27 | } // Anonymous namespace |
| 28 | 28 | ||
| 29 | QueryCache::QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_) | 29 | QueryCache::QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_) |
| 30 | : QueryCacheBase(rasterizer_, cpu_memory_), gl_rasterizer{rasterizer_} {} | 30 | : QueryCacheLegacy(rasterizer_, cpu_memory_), gl_rasterizer{rasterizer_} {} |
| 31 | 31 | ||
| 32 | QueryCache::~QueryCache() = default; | 32 | QueryCache::~QueryCache() = default; |
| 33 | 33 | ||
diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h index 872513f22..0721e0b3d 100644 --- a/src/video_core/renderer_opengl/gl_query_cache.h +++ b/src/video_core/renderer_opengl/gl_query_cache.h | |||
| @@ -26,7 +26,7 @@ class RasterizerOpenGL; | |||
| 26 | using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>; | 26 | using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>; |
| 27 | 27 | ||
| 28 | class QueryCache final | 28 | class QueryCache final |
| 29 | : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> { | 29 | : public VideoCommon::QueryCacheLegacy<QueryCache, CachedQuery, CounterStream, HostCounter> { |
| 30 | public: | 30 | public: |
| 31 | explicit QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_); | 31 | explicit QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_); |
| 32 | ~QueryCache(); | 32 | ~QueryCache(); |
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index dd03efecd..27e2de1bf 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp | |||
| @@ -396,13 +396,39 @@ void RasterizerOpenGL::DispatchCompute() { | |||
| 396 | has_written_global_memory |= pipeline->WritesGlobalMemory(); | 396 | has_written_global_memory |= pipeline->WritesGlobalMemory(); |
| 397 | } | 397 | } |
| 398 | 398 | ||
| 399 | void RasterizerOpenGL::ResetCounter(VideoCore::QueryType type) { | 399 | void RasterizerOpenGL::ResetCounter(VideoCommon::QueryType type) { |
| 400 | query_cache.ResetCounter(type); | 400 | if (type == VideoCommon::QueryType::ZPassPixelCount64) { |
| 401 | query_cache.ResetCounter(VideoCore::QueryType::SamplesPassed); | ||
| 402 | } | ||
| 401 | } | 403 | } |
| 402 | 404 | ||
| 403 | void RasterizerOpenGL::Query(GPUVAddr gpu_addr, VideoCore::QueryType type, | 405 | void RasterizerOpenGL::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type, |
| 404 | std::optional<u64> timestamp) { | 406 | VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) { |
| 405 | query_cache.Query(gpu_addr, type, timestamp); | 407 | if (type == VideoCommon::QueryType::ZPassPixelCount64) { |
| 408 | if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) { | ||
| 409 | query_cache.Query(gpu_addr, VideoCore::QueryType::SamplesPassed, {gpu.GetTicks()}); | ||
| 410 | } else { | ||
| 411 | query_cache.Query(gpu_addr, VideoCore::QueryType::SamplesPassed, std::nullopt); | ||
| 412 | } | ||
| 413 | return; | ||
| 414 | } | ||
| 415 | if (type != VideoCommon::QueryType::Payload) { | ||
| 416 | payload = 1u; | ||
| 417 | } | ||
| 418 | std::function<void()> func([this, gpu_addr, flags, memory_manager = gpu_memory, payload]() { | ||
| 419 | if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) { | ||
| 420 | u64 ticks = gpu.GetTicks(); | ||
| 421 | memory_manager->Write<u64>(gpu_addr + 8, ticks); | ||
| 422 | memory_manager->Write<u64>(gpu_addr, static_cast<u64>(payload)); | ||
| 423 | } else { | ||
| 424 | memory_manager->Write<u32>(gpu_addr, payload); | ||
| 425 | } | ||
| 426 | }); | ||
| 427 | if (True(flags & VideoCommon::QueryPropertiesFlags::IsAFence)) { | ||
| 428 | SignalFence(std::move(func)); | ||
| 429 | return; | ||
| 430 | } | ||
| 431 | func(); | ||
| 406 | } | 432 | } |
| 407 | 433 | ||
| 408 | void RasterizerOpenGL::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, | 434 | void RasterizerOpenGL::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, |
| @@ -573,8 +599,8 @@ void RasterizerOpenGL::SignalReference() { | |||
| 573 | fence_manager.SignalOrdering(); | 599 | fence_manager.SignalOrdering(); |
| 574 | } | 600 | } |
| 575 | 601 | ||
| 576 | void RasterizerOpenGL::ReleaseFences() { | 602 | void RasterizerOpenGL::ReleaseFences(bool force) { |
| 577 | fence_manager.WaitPendingFences(); | 603 | fence_manager.WaitPendingFences(force); |
| 578 | } | 604 | } |
| 579 | 605 | ||
| 580 | void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size, | 606 | void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size, |
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 8eda2ddba..ceffe1f1e 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h | |||
| @@ -86,8 +86,9 @@ public: | |||
| 86 | void DrawTexture() override; | 86 | void DrawTexture() override; |
| 87 | void Clear(u32 layer_count) override; | 87 | void Clear(u32 layer_count) override; |
| 88 | void DispatchCompute() override; | 88 | void DispatchCompute() override; |
| 89 | void ResetCounter(VideoCore::QueryType type) override; | 89 | void ResetCounter(VideoCommon::QueryType type) override; |
| 90 | void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; | 90 | void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type, |
| 91 | VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override; | ||
| 91 | void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; | 92 | void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; |
| 92 | void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; | 93 | void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; |
| 93 | void FlushAll() override; | 94 | void FlushAll() override; |
| @@ -107,7 +108,7 @@ public: | |||
| 107 | void SyncOperation(std::function<void()>&& func) override; | 108 | void SyncOperation(std::function<void()>&& func) override; |
| 108 | void SignalSyncPoint(u32 value) override; | 109 | void SignalSyncPoint(u32 value) override; |
| 109 | void SignalReference() override; | 110 | void SignalReference() override; |
| 110 | void ReleaseFences() override; | 111 | void ReleaseFences(bool force = true) override; |
| 111 | void FlushAndInvalidateRegion( | 112 | void FlushAndInvalidateRegion( |
| 112 | VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; | 113 | VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; |
| 113 | void WaitForIdle() override; | 114 | void WaitForIdle() override; |
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index 35bf80ea3..208e88533 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp | |||
| @@ -185,7 +185,7 @@ struct FormatTuple { | |||
| 185 | {VK_FORMAT_BC2_SRGB_BLOCK}, // BC2_SRGB | 185 | {VK_FORMAT_BC2_SRGB_BLOCK}, // BC2_SRGB |
| 186 | {VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB | 186 | {VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB |
| 187 | {VK_FORMAT_BC7_SRGB_BLOCK}, // BC7_SRGB | 187 | {VK_FORMAT_BC7_SRGB_BLOCK}, // BC7_SRGB |
| 188 | {VK_FORMAT_R4G4B4A4_UNORM_PACK16}, // A4B4G4R4_UNORM | 188 | {VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT}, // A4B4G4R4_UNORM |
| 189 | {VK_FORMAT_R4G4_UNORM_PACK8}, // G4R4_UNORM | 189 | {VK_FORMAT_R4G4_UNORM_PACK8}, // G4R4_UNORM |
| 190 | {VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB | 190 | {VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB |
| 191 | {VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB | 191 | {VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB |
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index e15865d16..d8148e89a 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp | |||
| @@ -61,6 +61,9 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo | |||
| 61 | if (device.IsExtTransformFeedbackSupported()) { | 61 | if (device.IsExtTransformFeedbackSupported()) { |
| 62 | flags |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT; | 62 | flags |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT; |
| 63 | } | 63 | } |
| 64 | if (device.IsExtConditionalRendering()) { | ||
| 65 | flags |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT; | ||
| 66 | } | ||
| 64 | const VkBufferCreateInfo buffer_ci = { | 67 | const VkBufferCreateInfo buffer_ci = { |
| 65 | .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, | 68 | .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, |
| 66 | .pNext = nullptr, | 69 | .pNext = nullptr, |
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 54ee030ce..617f92910 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include <array> | 4 | #include <array> |
| 5 | #include <memory> | 5 | #include <memory> |
| 6 | #include <numeric> | ||
| 6 | #include <optional> | 7 | #include <optional> |
| 7 | #include <utility> | 8 | #include <utility> |
| 8 | 9 | ||
| @@ -11,7 +12,13 @@ | |||
| 11 | #include "common/assert.h" | 12 | #include "common/assert.h" |
| 12 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 13 | #include "common/div_ceil.h" | 14 | #include "common/div_ceil.h" |
| 15 | #include "common/vector_math.h" | ||
| 14 | #include "video_core/host_shaders/astc_decoder_comp_spv.h" | 16 | #include "video_core/host_shaders/astc_decoder_comp_spv.h" |
| 17 | #include "video_core/host_shaders/convert_msaa_to_non_msaa_comp_spv.h" | ||
| 18 | #include "video_core/host_shaders/convert_non_msaa_to_msaa_comp_spv.h" | ||
| 19 | #include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h" | ||
| 20 | #include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h" | ||
| 21 | #include "video_core/host_shaders/resolve_conditional_render_comp_spv.h" | ||
| 15 | #include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h" | 22 | #include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h" |
| 16 | #include "video_core/host_shaders/vulkan_uint8_comp_spv.h" | 23 | #include "video_core/host_shaders/vulkan_uint8_comp_spv.h" |
| 17 | #include "video_core/renderer_vulkan/vk_compute_pass.h" | 24 | #include "video_core/renderer_vulkan/vk_compute_pass.h" |
| @@ -57,6 +64,30 @@ constexpr std::array<VkDescriptorSetLayoutBinding, 2> INPUT_OUTPUT_DESCRIPTOR_SE | |||
| 57 | }, | 64 | }, |
| 58 | }}; | 65 | }}; |
| 59 | 66 | ||
| 67 | constexpr std::array<VkDescriptorSetLayoutBinding, 3> QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS{{ | ||
| 68 | { | ||
| 69 | .binding = 0, | ||
| 70 | .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, | ||
| 71 | .descriptorCount = 1, | ||
| 72 | .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, | ||
| 73 | .pImmutableSamplers = nullptr, | ||
| 74 | }, | ||
| 75 | { | ||
| 76 | .binding = 1, | ||
| 77 | .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, | ||
| 78 | .descriptorCount = 1, | ||
| 79 | .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, | ||
| 80 | .pImmutableSamplers = nullptr, | ||
| 81 | }, | ||
| 82 | { | ||
| 83 | .binding = 2, | ||
| 84 | .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, | ||
| 85 | .descriptorCount = 1, | ||
| 86 | .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, | ||
| 87 | .pImmutableSamplers = nullptr, | ||
| 88 | }, | ||
| 89 | }}; | ||
| 90 | |||
| 60 | constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{ | 91 | constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{ |
| 61 | .uniform_buffers = 0, | 92 | .uniform_buffers = 0, |
| 62 | .storage_buffers = 2, | 93 | .storage_buffers = 2, |
| @@ -67,6 +98,16 @@ constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{ | |||
| 67 | .score = 2, | 98 | .score = 2, |
| 68 | }; | 99 | }; |
| 69 | 100 | ||
| 101 | constexpr DescriptorBankInfo QUERIES_SCAN_BANK_INFO{ | ||
| 102 | .uniform_buffers = 0, | ||
| 103 | .storage_buffers = 3, | ||
| 104 | .texture_buffers = 0, | ||
| 105 | .image_buffers = 0, | ||
| 106 | .textures = 0, | ||
| 107 | .images = 0, | ||
| 108 | .score = 3, | ||
| 109 | }; | ||
| 110 | |||
| 70 | constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> ASTC_DESCRIPTOR_SET_BINDINGS{{ | 111 | constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> ASTC_DESCRIPTOR_SET_BINDINGS{{ |
| 71 | { | 112 | { |
| 72 | .binding = ASTC_BINDING_INPUT_BUFFER, | 113 | .binding = ASTC_BINDING_INPUT_BUFFER, |
| @@ -94,6 +135,33 @@ constexpr DescriptorBankInfo ASTC_BANK_INFO{ | |||
| 94 | .score = 2, | 135 | .score = 2, |
| 95 | }; | 136 | }; |
| 96 | 137 | ||
| 138 | constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> MSAA_DESCRIPTOR_SET_BINDINGS{{ | ||
| 139 | { | ||
| 140 | .binding = 0, | ||
| 141 | .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, | ||
| 142 | .descriptorCount = 1, | ||
| 143 | .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, | ||
| 144 | .pImmutableSamplers = nullptr, | ||
| 145 | }, | ||
| 146 | { | ||
| 147 | .binding = 1, | ||
| 148 | .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, | ||
| 149 | .descriptorCount = 1, | ||
| 150 | .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, | ||
| 151 | .pImmutableSamplers = nullptr, | ||
| 152 | }, | ||
| 153 | }}; | ||
| 154 | |||
| 155 | constexpr DescriptorBankInfo MSAA_BANK_INFO{ | ||
| 156 | .uniform_buffers = 0, | ||
| 157 | .storage_buffers = 0, | ||
| 158 | .texture_buffers = 0, | ||
| 159 | .image_buffers = 0, | ||
| 160 | .textures = 0, | ||
| 161 | .images = 2, | ||
| 162 | .score = 2, | ||
| 163 | }; | ||
| 164 | |||
| 97 | constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE{ | 165 | constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE{ |
| 98 | .dstBinding = 0, | 166 | .dstBinding = 0, |
| 99 | .dstArrayElement = 0, | 167 | .dstArrayElement = 0, |
| @@ -103,6 +171,24 @@ constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLAT | |||
| 103 | .stride = sizeof(DescriptorUpdateEntry), | 171 | .stride = sizeof(DescriptorUpdateEntry), |
| 104 | }; | 172 | }; |
| 105 | 173 | ||
| 174 | constexpr VkDescriptorUpdateTemplateEntry QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLATE{ | ||
| 175 | .dstBinding = 0, | ||
| 176 | .dstArrayElement = 0, | ||
| 177 | .descriptorCount = 3, | ||
| 178 | .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, | ||
| 179 | .offset = 0, | ||
| 180 | .stride = sizeof(DescriptorUpdateEntry), | ||
| 181 | }; | ||
| 182 | |||
| 183 | constexpr VkDescriptorUpdateTemplateEntry MSAA_DESCRIPTOR_UPDATE_TEMPLATE{ | ||
| 184 | .dstBinding = 0, | ||
| 185 | .dstArrayElement = 0, | ||
| 186 | .descriptorCount = 2, | ||
| 187 | .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, | ||
| 188 | .offset = 0, | ||
| 189 | .stride = sizeof(DescriptorUpdateEntry), | ||
| 190 | }; | ||
| 191 | |||
| 106 | constexpr std::array<VkDescriptorUpdateTemplateEntry, ASTC_NUM_BINDINGS> | 192 | constexpr std::array<VkDescriptorUpdateTemplateEntry, ASTC_NUM_BINDINGS> |
| 107 | ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{ | 193 | ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{ |
| 108 | { | 194 | { |
| @@ -131,13 +217,21 @@ struct AstcPushConstants { | |||
| 131 | u32 block_height; | 217 | u32 block_height; |
| 132 | u32 block_height_mask; | 218 | u32 block_height_mask; |
| 133 | }; | 219 | }; |
| 220 | |||
| 221 | struct QueriesPrefixScanPushConstants { | ||
| 222 | u32 min_accumulation_base; | ||
| 223 | u32 max_accumulation_base; | ||
| 224 | u32 accumulation_limit; | ||
| 225 | u32 buffer_offset; | ||
| 226 | }; | ||
| 134 | } // Anonymous namespace | 227 | } // Anonymous namespace |
| 135 | 228 | ||
| 136 | ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool, | 229 | ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool, |
| 137 | vk::Span<VkDescriptorSetLayoutBinding> bindings, | 230 | vk::Span<VkDescriptorSetLayoutBinding> bindings, |
| 138 | vk::Span<VkDescriptorUpdateTemplateEntry> templates, | 231 | vk::Span<VkDescriptorUpdateTemplateEntry> templates, |
| 139 | const DescriptorBankInfo& bank_info, | 232 | const DescriptorBankInfo& bank_info, |
| 140 | vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code) | 233 | vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code, |
| 234 | std::optional<u32> optional_subgroup_size) | ||
| 141 | : device{device_} { | 235 | : device{device_} { |
| 142 | descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout({ | 236 | descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout({ |
| 143 | .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, | 237 | .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, |
| @@ -170,6 +264,9 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool, | |||
| 170 | }); | 264 | }); |
| 171 | descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, bank_info); | 265 | descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, bank_info); |
| 172 | } | 266 | } |
| 267 | if (code.empty()) { | ||
| 268 | return; | ||
| 269 | } | ||
| 173 | module = device.GetLogical().CreateShaderModule({ | 270 | module = device.GetLogical().CreateShaderModule({ |
| 174 | .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, | 271 | .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, |
| 175 | .pNext = nullptr, | 272 | .pNext = nullptr, |
| @@ -178,13 +275,19 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool, | |||
| 178 | .pCode = code.data(), | 275 | .pCode = code.data(), |
| 179 | }); | 276 | }); |
| 180 | device.SaveShader(code); | 277 | device.SaveShader(code); |
| 278 | const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{ | ||
| 279 | .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT, | ||
| 280 | .pNext = nullptr, | ||
| 281 | .requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U, | ||
| 282 | }; | ||
| 283 | bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size; | ||
| 181 | pipeline = device.GetLogical().CreateComputePipeline({ | 284 | pipeline = device.GetLogical().CreateComputePipeline({ |
| 182 | .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, | 285 | .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, |
| 183 | .pNext = nullptr, | 286 | .pNext = nullptr, |
| 184 | .flags = 0, | 287 | .flags = 0, |
| 185 | .stage{ | 288 | .stage{ |
| 186 | .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, | 289 | .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, |
| 187 | .pNext = nullptr, | 290 | .pNext = use_setup_size ? &subgroup_size_ci : nullptr, |
| 188 | .flags = 0, | 291 | .flags = 0, |
| 189 | .stage = VK_SHADER_STAGE_COMPUTE_BIT, | 292 | .stage = VK_SHADER_STAGE_COMPUTE_BIT, |
| 190 | .module = *module, | 293 | .module = *module, |
| @@ -302,6 +405,123 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble( | |||
| 302 | return {staging.buffer, staging.offset}; | 405 | return {staging.buffer, staging.offset}; |
| 303 | } | 406 | } |
| 304 | 407 | ||
| 408 | ConditionalRenderingResolvePass::ConditionalRenderingResolvePass( | ||
| 409 | const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, | ||
| 410 | ComputePassDescriptorQueue& compute_pass_descriptor_queue_) | ||
| 411 | : ComputePass(device_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS, | ||
| 412 | INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO, nullptr, | ||
| 413 | RESOLVE_CONDITIONAL_RENDER_COMP_SPV), | ||
| 414 | scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} | ||
| 415 | |||
| 416 | void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_buffer, | ||
| 417 | u32 src_offset, bool compare_to_zero) { | ||
| 418 | const size_t compare_size = compare_to_zero ? 8 : 24; | ||
| 419 | |||
| 420 | compute_pass_descriptor_queue.Acquire(); | ||
| 421 | compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, compare_size); | ||
| 422 | compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, sizeof(u32)); | ||
| 423 | const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; | ||
| 424 | |||
| 425 | scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 426 | scheduler.Record([this, descriptor_data](vk::CommandBuffer cmdbuf) { | ||
| 427 | static constexpr VkMemoryBarrier read_barrier{ | ||
| 428 | .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, | ||
| 429 | .pNext = nullptr, | ||
| 430 | .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT, | ||
| 431 | .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, | ||
| 432 | }; | ||
| 433 | static constexpr VkMemoryBarrier write_barrier{ | ||
| 434 | .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, | ||
| 435 | .pNext = nullptr, | ||
| 436 | .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, | ||
| 437 | .dstAccessMask = VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT, | ||
| 438 | }; | ||
| 439 | const VkDescriptorSet set = descriptor_allocator.Commit(); | ||
| 440 | device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); | ||
| 441 | |||
| 442 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, | ||
| 443 | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier); | ||
| 444 | cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline); | ||
| 445 | cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); | ||
| 446 | cmdbuf.Dispatch(1, 1, 1); | ||
| 447 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, | ||
| 448 | VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0, write_barrier); | ||
| 449 | }); | ||
| 450 | } | ||
| 451 | |||
| 452 | QueriesPrefixScanPass::QueriesPrefixScanPass( | ||
| 453 | const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, | ||
| 454 | ComputePassDescriptorQueue& compute_pass_descriptor_queue_) | ||
| 455 | : ComputePass( | ||
| 456 | device_, descriptor_pool_, QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS, | ||
| 457 | QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLATE, QUERIES_SCAN_BANK_INFO, | ||
| 458 | COMPUTE_PUSH_CONSTANT_RANGE<sizeof(QueriesPrefixScanPushConstants)>, | ||
| 459 | device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_BASIC_BIT) && | ||
| 460 | device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_ARITHMETIC_BIT) && | ||
| 461 | device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_BIT) && | ||
| 462 | device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT) | ||
| 463 | ? std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_COMP_SPV) | ||
| 464 | : std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_NOSUBGROUPS_COMP_SPV)), | ||
| 465 | scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} | ||
| 466 | |||
| 467 | void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffer, | ||
| 468 | VkBuffer src_buffer, size_t number_of_sums, | ||
| 469 | size_t min_accumulation_limit, size_t max_accumulation_limit) { | ||
| 470 | size_t current_runs = number_of_sums; | ||
| 471 | size_t offset = 0; | ||
| 472 | while (current_runs != 0) { | ||
| 473 | static constexpr size_t DISPATCH_SIZE = 2048U; | ||
| 474 | size_t runs_to_do = std::min<size_t>(current_runs, DISPATCH_SIZE); | ||
| 475 | current_runs -= runs_to_do; | ||
| 476 | compute_pass_descriptor_queue.Acquire(); | ||
| 477 | compute_pass_descriptor_queue.AddBuffer(src_buffer, 0, number_of_sums * sizeof(u64)); | ||
| 478 | compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, number_of_sums * sizeof(u64)); | ||
| 479 | compute_pass_descriptor_queue.AddBuffer(accumulation_buffer, 0, sizeof(u64)); | ||
| 480 | const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; | ||
| 481 | size_t used_offset = offset; | ||
| 482 | offset += runs_to_do; | ||
| 483 | |||
| 484 | scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 485 | scheduler.Record([this, descriptor_data, min_accumulation_limit, max_accumulation_limit, | ||
| 486 | runs_to_do, used_offset](vk::CommandBuffer cmdbuf) { | ||
| 487 | static constexpr VkMemoryBarrier read_barrier{ | ||
| 488 | .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, | ||
| 489 | .pNext = nullptr, | ||
| 490 | .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, | ||
| 491 | .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, | ||
| 492 | }; | ||
| 493 | static constexpr VkMemoryBarrier write_barrier{ | ||
| 494 | .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, | ||
| 495 | .pNext = nullptr, | ||
| 496 | .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, | ||
| 497 | .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | | ||
| 498 | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | | ||
| 499 | VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_INDEX_READ_BIT | | ||
| 500 | VK_ACCESS_UNIFORM_READ_BIT | | ||
| 501 | VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT, | ||
| 502 | }; | ||
| 503 | const QueriesPrefixScanPushConstants uniforms{ | ||
| 504 | .min_accumulation_base = static_cast<u32>(min_accumulation_limit), | ||
| 505 | .max_accumulation_base = static_cast<u32>(max_accumulation_limit), | ||
| 506 | .accumulation_limit = static_cast<u32>(runs_to_do - 1), | ||
| 507 | .buffer_offset = static_cast<u32>(used_offset), | ||
| 508 | }; | ||
| 509 | const VkDescriptorSet set = descriptor_allocator.Commit(); | ||
| 510 | device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); | ||
| 511 | |||
| 512 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, | ||
| 513 | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier); | ||
| 514 | cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline); | ||
| 515 | cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); | ||
| 516 | cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, uniforms); | ||
| 517 | cmdbuf.Dispatch(1, 1, 1); | ||
| 518 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, | ||
| 519 | VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0, | ||
| 520 | write_barrier); | ||
| 521 | }); | ||
| 522 | } | ||
| 523 | } | ||
| 524 | |||
| 305 | ASTCDecoderPass::ASTCDecoderPass(const Device& device_, Scheduler& scheduler_, | 525 | ASTCDecoderPass::ASTCDecoderPass(const Device& device_, Scheduler& scheduler_, |
| 306 | DescriptorPool& descriptor_pool_, | 526 | DescriptorPool& descriptor_pool_, |
| 307 | StagingBufferPool& staging_buffer_pool_, | 527 | StagingBufferPool& staging_buffer_pool_, |
| @@ -413,4 +633,100 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, | |||
| 413 | scheduler.Finish(); | 633 | scheduler.Finish(); |
| 414 | } | 634 | } |
| 415 | 635 | ||
| 636 | MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_, | ||
| 637 | DescriptorPool& descriptor_pool_, | ||
| 638 | StagingBufferPool& staging_buffer_pool_, | ||
| 639 | ComputePassDescriptorQueue& compute_pass_descriptor_queue_) | ||
| 640 | : ComputePass(device_, descriptor_pool_, MSAA_DESCRIPTOR_SET_BINDINGS, | ||
| 641 | MSAA_DESCRIPTOR_UPDATE_TEMPLATE, MSAA_BANK_INFO, {}, | ||
| 642 | CONVERT_NON_MSAA_TO_MSAA_COMP_SPV), | ||
| 643 | scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, | ||
| 644 | compute_pass_descriptor_queue{compute_pass_descriptor_queue_} { | ||
| 645 | const auto make_msaa_pipeline = [this](size_t i, std::span<const u32> code) { | ||
| 646 | modules[i] = device.GetLogical().CreateShaderModule({ | ||
| 647 | .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, | ||
| 648 | .pNext = nullptr, | ||
| 649 | .flags = 0, | ||
| 650 | .codeSize = static_cast<u32>(code.size_bytes()), | ||
| 651 | .pCode = code.data(), | ||
| 652 | }); | ||
| 653 | pipelines[i] = device.GetLogical().CreateComputePipeline({ | ||
| 654 | .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, | ||
| 655 | .pNext = nullptr, | ||
| 656 | .flags = 0, | ||
| 657 | .stage{ | ||
| 658 | .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, | ||
| 659 | .pNext = nullptr, | ||
| 660 | .flags = 0, | ||
| 661 | .stage = VK_SHADER_STAGE_COMPUTE_BIT, | ||
| 662 | .module = *modules[i], | ||
| 663 | .pName = "main", | ||
| 664 | .pSpecializationInfo = nullptr, | ||
| 665 | }, | ||
| 666 | .layout = *layout, | ||
| 667 | .basePipelineHandle = nullptr, | ||
| 668 | .basePipelineIndex = 0, | ||
| 669 | }); | ||
| 670 | }; | ||
| 671 | make_msaa_pipeline(0, CONVERT_NON_MSAA_TO_MSAA_COMP_SPV); | ||
| 672 | make_msaa_pipeline(1, CONVERT_MSAA_TO_NON_MSAA_COMP_SPV); | ||
| 673 | } | ||
| 674 | |||
| 675 | MSAACopyPass::~MSAACopyPass() = default; | ||
| 676 | |||
| 677 | void MSAACopyPass::CopyImage(Image& dst_image, Image& src_image, | ||
| 678 | std::span<const VideoCommon::ImageCopy> copies, | ||
| 679 | bool msaa_to_non_msaa) { | ||
| 680 | const VkPipeline msaa_pipeline = *pipelines[msaa_to_non_msaa ? 1 : 0]; | ||
| 681 | scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 682 | for (const VideoCommon::ImageCopy& copy : copies) { | ||
| 683 | ASSERT(copy.src_subresource.base_layer == 0); | ||
| 684 | ASSERT(copy.src_subresource.num_layers == 1); | ||
| 685 | ASSERT(copy.dst_subresource.base_layer == 0); | ||
| 686 | ASSERT(copy.dst_subresource.num_layers == 1); | ||
| 687 | |||
| 688 | compute_pass_descriptor_queue.Acquire(); | ||
| 689 | compute_pass_descriptor_queue.AddImage( | ||
| 690 | src_image.StorageImageView(copy.src_subresource.base_level)); | ||
| 691 | compute_pass_descriptor_queue.AddImage( | ||
| 692 | dst_image.StorageImageView(copy.dst_subresource.base_level)); | ||
| 693 | const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; | ||
| 694 | |||
| 695 | const Common::Vec3<u32> num_dispatches = { | ||
| 696 | Common::DivCeil(copy.extent.width, 8U), | ||
| 697 | Common::DivCeil(copy.extent.height, 8U), | ||
| 698 | copy.extent.depth, | ||
| 699 | }; | ||
| 700 | |||
| 701 | scheduler.Record([this, dst = dst_image.Handle(), msaa_pipeline, num_dispatches, | ||
| 702 | descriptor_data](vk::CommandBuffer cmdbuf) { | ||
| 703 | const VkDescriptorSet set = descriptor_allocator.Commit(); | ||
| 704 | device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); | ||
| 705 | cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, msaa_pipeline); | ||
| 706 | cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); | ||
| 707 | cmdbuf.Dispatch(num_dispatches.x, num_dispatches.y, num_dispatches.z); | ||
| 708 | const VkImageMemoryBarrier write_barrier{ | ||
| 709 | .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, | ||
| 710 | .pNext = nullptr, | ||
| 711 | .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, | ||
| 712 | .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, | ||
| 713 | .oldLayout = VK_IMAGE_LAYOUT_GENERAL, | ||
| 714 | .newLayout = VK_IMAGE_LAYOUT_GENERAL, | ||
| 715 | .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||
| 716 | .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||
| 717 | .image = dst, | ||
| 718 | .subresourceRange{ | ||
| 719 | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, | ||
| 720 | .baseMipLevel = 0, | ||
| 721 | .levelCount = VK_REMAINING_MIP_LEVELS, | ||
| 722 | .baseArrayLayer = 0, | ||
| 723 | .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||
| 724 | }, | ||
| 725 | }; | ||
| 726 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, | ||
| 727 | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier); | ||
| 728 | }); | ||
| 729 | } | ||
| 730 | } | ||
| 731 | |||
| 416 | } // namespace Vulkan | 732 | } // namespace Vulkan |
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h index dd3927376..7b8f938c1 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <optional> | ||
| 6 | #include <span> | 7 | #include <span> |
| 7 | #include <utility> | 8 | #include <utility> |
| 8 | 9 | ||
| @@ -10,6 +11,7 @@ | |||
| 10 | #include "video_core/engines/maxwell_3d.h" | 11 | #include "video_core/engines/maxwell_3d.h" |
| 11 | #include "video_core/renderer_vulkan/vk_descriptor_pool.h" | 12 | #include "video_core/renderer_vulkan/vk_descriptor_pool.h" |
| 12 | #include "video_core/renderer_vulkan/vk_update_descriptor.h" | 13 | #include "video_core/renderer_vulkan/vk_update_descriptor.h" |
| 14 | #include "video_core/texture_cache/types.h" | ||
| 13 | #include "video_core/vulkan_common/vulkan_memory_allocator.h" | 15 | #include "video_core/vulkan_common/vulkan_memory_allocator.h" |
| 14 | #include "video_core/vulkan_common/vulkan_wrapper.h" | 16 | #include "video_core/vulkan_common/vulkan_wrapper.h" |
| 15 | 17 | ||
| @@ -31,7 +33,8 @@ public: | |||
| 31 | vk::Span<VkDescriptorSetLayoutBinding> bindings, | 33 | vk::Span<VkDescriptorSetLayoutBinding> bindings, |
| 32 | vk::Span<VkDescriptorUpdateTemplateEntry> templates, | 34 | vk::Span<VkDescriptorUpdateTemplateEntry> templates, |
| 33 | const DescriptorBankInfo& bank_info, | 35 | const DescriptorBankInfo& bank_info, |
| 34 | vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code); | 36 | vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code, |
| 37 | std::optional<u32> optional_subgroup_size = std::nullopt); | ||
| 35 | ~ComputePass(); | 38 | ~ComputePass(); |
| 36 | 39 | ||
| 37 | protected: | 40 | protected: |
| @@ -82,6 +85,33 @@ private: | |||
| 82 | ComputePassDescriptorQueue& compute_pass_descriptor_queue; | 85 | ComputePassDescriptorQueue& compute_pass_descriptor_queue; |
| 83 | }; | 86 | }; |
| 84 | 87 | ||
| 88 | class ConditionalRenderingResolvePass final : public ComputePass { | ||
| 89 | public: | ||
| 90 | explicit ConditionalRenderingResolvePass( | ||
| 91 | const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, | ||
| 92 | ComputePassDescriptorQueue& compute_pass_descriptor_queue_); | ||
| 93 | |||
| 94 | void Resolve(VkBuffer dst_buffer, VkBuffer src_buffer, u32 src_offset, bool compare_to_zero); | ||
| 95 | |||
| 96 | private: | ||
| 97 | Scheduler& scheduler; | ||
| 98 | ComputePassDescriptorQueue& compute_pass_descriptor_queue; | ||
| 99 | }; | ||
| 100 | |||
| 101 | class QueriesPrefixScanPass final : public ComputePass { | ||
| 102 | public: | ||
| 103 | explicit QueriesPrefixScanPass(const Device& device_, Scheduler& scheduler_, | ||
| 104 | DescriptorPool& descriptor_pool_, | ||
| 105 | ComputePassDescriptorQueue& compute_pass_descriptor_queue_); | ||
| 106 | |||
| 107 | void Run(VkBuffer accumulation_buffer, VkBuffer dst_buffer, VkBuffer src_buffer, | ||
| 108 | size_t number_of_sums, size_t min_accumulation_limit, size_t max_accumulation_limit); | ||
| 109 | |||
| 110 | private: | ||
| 111 | Scheduler& scheduler; | ||
| 112 | ComputePassDescriptorQueue& compute_pass_descriptor_queue; | ||
| 113 | }; | ||
| 114 | |||
| 85 | class ASTCDecoderPass final : public ComputePass { | 115 | class ASTCDecoderPass final : public ComputePass { |
| 86 | public: | 116 | public: |
| 87 | explicit ASTCDecoderPass(const Device& device_, Scheduler& scheduler_, | 117 | explicit ASTCDecoderPass(const Device& device_, Scheduler& scheduler_, |
| @@ -101,4 +131,22 @@ private: | |||
| 101 | MemoryAllocator& memory_allocator; | 131 | MemoryAllocator& memory_allocator; |
| 102 | }; | 132 | }; |
| 103 | 133 | ||
| 134 | class MSAACopyPass final : public ComputePass { | ||
| 135 | public: | ||
| 136 | explicit MSAACopyPass(const Device& device_, Scheduler& scheduler_, | ||
| 137 | DescriptorPool& descriptor_pool_, StagingBufferPool& staging_buffer_pool_, | ||
| 138 | ComputePassDescriptorQueue& compute_pass_descriptor_queue_); | ||
| 139 | ~MSAACopyPass(); | ||
| 140 | |||
| 141 | void CopyImage(Image& dst_image, Image& src_image, | ||
| 142 | std::span<const VideoCommon::ImageCopy> copies, bool msaa_to_non_msaa); | ||
| 143 | |||
| 144 | private: | ||
| 145 | Scheduler& scheduler; | ||
| 146 | StagingBufferPool& staging_buffer_pool; | ||
| 147 | ComputePassDescriptorQueue& compute_pass_descriptor_queue; | ||
| 148 | std::array<vk::ShaderModule, 2> modules; | ||
| 149 | std::array<vk::Pipeline, 2> pipelines; | ||
| 150 | }; | ||
| 151 | |||
| 104 | } // namespace Vulkan | 152 | } // namespace Vulkan |
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.h b/src/video_core/renderer_vulkan/vk_fence_manager.h index 145359d4e..336573574 100644 --- a/src/video_core/renderer_vulkan/vk_fence_manager.h +++ b/src/video_core/renderer_vulkan/vk_fence_manager.h | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | 7 | ||
| 8 | #include "video_core/fence_manager.h" | 8 | #include "video_core/fence_manager.h" |
| 9 | #include "video_core/renderer_vulkan/vk_buffer_cache.h" | 9 | #include "video_core/renderer_vulkan/vk_buffer_cache.h" |
| 10 | #include "video_core/renderer_vulkan/vk_query_cache.h" | ||
| 10 | #include "video_core/renderer_vulkan/vk_texture_cache.h" | 11 | #include "video_core/renderer_vulkan/vk_texture_cache.h" |
| 11 | 12 | ||
| 12 | namespace Core { | 13 | namespace Core { |
| @@ -20,7 +21,6 @@ class RasterizerInterface; | |||
| 20 | namespace Vulkan { | 21 | namespace Vulkan { |
| 21 | 22 | ||
| 22 | class Device; | 23 | class Device; |
| 23 | class QueryCache; | ||
| 24 | class Scheduler; | 24 | class Scheduler; |
| 25 | 25 | ||
| 26 | class InnerFence : public VideoCommon::FenceBase { | 26 | class InnerFence : public VideoCommon::FenceBase { |
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 29e0b797b..17b2587ad 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp | |||
| @@ -1,139 +1,1554 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 3 | 3 | ||
| 4 | #include <algorithm> | ||
| 5 | #include <cstddef> | 4 | #include <cstddef> |
| 5 | #include <limits> | ||
| 6 | #include <map> | ||
| 7 | #include <memory> | ||
| 8 | #include <span> | ||
| 9 | #include <type_traits> | ||
| 10 | #include <unordered_map> | ||
| 6 | #include <utility> | 11 | #include <utility> |
| 7 | #include <vector> | 12 | #include <vector> |
| 8 | 13 | ||
| 14 | #include "common/bit_util.h" | ||
| 15 | #include "common/common_types.h" | ||
| 16 | #include "core/memory.h" | ||
| 17 | #include "video_core/engines/draw_manager.h" | ||
| 18 | #include "video_core/query_cache/query_cache.h" | ||
| 19 | #include "video_core/renderer_vulkan/vk_buffer_cache.h" | ||
| 20 | #include "video_core/renderer_vulkan/vk_compute_pass.h" | ||
| 9 | #include "video_core/renderer_vulkan/vk_query_cache.h" | 21 | #include "video_core/renderer_vulkan/vk_query_cache.h" |
| 10 | #include "video_core/renderer_vulkan/vk_resource_pool.h" | 22 | #include "video_core/renderer_vulkan/vk_resource_pool.h" |
| 11 | #include "video_core/renderer_vulkan/vk_scheduler.h" | 23 | #include "video_core/renderer_vulkan/vk_scheduler.h" |
| 24 | #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" | ||
| 25 | #include "video_core/renderer_vulkan/vk_update_descriptor.h" | ||
| 12 | #include "video_core/vulkan_common/vulkan_device.h" | 26 | #include "video_core/vulkan_common/vulkan_device.h" |
| 27 | #include "video_core/vulkan_common/vulkan_memory_allocator.h" | ||
| 13 | #include "video_core/vulkan_common/vulkan_wrapper.h" | 28 | #include "video_core/vulkan_common/vulkan_wrapper.h" |
| 14 | 29 | ||
| 15 | namespace Vulkan { | 30 | namespace Vulkan { |
| 16 | 31 | ||
| 17 | using VideoCore::QueryType; | 32 | using Tegra::Engines::Maxwell3D; |
| 33 | using VideoCommon::QueryType; | ||
| 18 | 34 | ||
| 19 | namespace { | 35 | namespace { |
| 36 | class SamplesQueryBank : public VideoCommon::BankBase { | ||
| 37 | public: | ||
| 38 | static constexpr size_t BANK_SIZE = 256; | ||
| 39 | static constexpr size_t QUERY_SIZE = 8; | ||
| 40 | explicit SamplesQueryBank(const Device& device_, size_t index_) | ||
| 41 | : BankBase(BANK_SIZE), device{device_}, index{index_} { | ||
| 42 | const auto& dev = device.GetLogical(); | ||
| 43 | query_pool = dev.CreateQueryPool({ | ||
| 44 | .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, | ||
| 45 | .pNext = nullptr, | ||
| 46 | .flags = 0, | ||
| 47 | .queryType = VK_QUERY_TYPE_OCCLUSION, | ||
| 48 | .queryCount = BANK_SIZE, | ||
| 49 | .pipelineStatistics = 0, | ||
| 50 | }); | ||
| 51 | Reset(); | ||
| 52 | } | ||
| 20 | 53 | ||
| 21 | constexpr std::array QUERY_TARGETS = {VK_QUERY_TYPE_OCCLUSION}; | 54 | ~SamplesQueryBank() = default; |
| 22 | 55 | ||
| 23 | constexpr VkQueryType GetTarget(QueryType type) { | 56 | void Reset() override { |
| 24 | return QUERY_TARGETS[static_cast<std::size_t>(type)]; | 57 | ASSERT(references == 0); |
| 25 | } | 58 | VideoCommon::BankBase::Reset(); |
| 59 | const auto& dev = device.GetLogical(); | ||
| 60 | dev.ResetQueryPool(*query_pool, 0, BANK_SIZE); | ||
| 61 | host_results.fill(0ULL); | ||
| 62 | next_bank = 0; | ||
| 63 | } | ||
| 64 | |||
| 65 | void Sync(size_t start, size_t size) { | ||
| 66 | const auto& dev = device.GetLogical(); | ||
| 67 | const VkResult query_result = dev.GetQueryResults( | ||
| 68 | *query_pool, static_cast<u32>(start), static_cast<u32>(size), sizeof(u64) * size, | ||
| 69 | &host_results[start], sizeof(u64), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); | ||
| 70 | switch (query_result) { | ||
| 71 | case VK_SUCCESS: | ||
| 72 | return; | ||
| 73 | case VK_ERROR_DEVICE_LOST: | ||
| 74 | device.ReportLoss(); | ||
| 75 | [[fallthrough]]; | ||
| 76 | default: | ||
| 77 | throw vk::Exception(query_result); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | VkQueryPool GetInnerPool() { | ||
| 82 | return *query_pool; | ||
| 83 | } | ||
| 84 | |||
| 85 | size_t GetIndex() const { | ||
| 86 | return index; | ||
| 87 | } | ||
| 88 | |||
| 89 | const std::array<u64, BANK_SIZE>& GetResults() const { | ||
| 90 | return host_results; | ||
| 91 | } | ||
| 92 | |||
| 93 | size_t next_bank; | ||
| 94 | |||
| 95 | private: | ||
| 96 | const Device& device; | ||
| 97 | const size_t index; | ||
| 98 | vk::QueryPool query_pool; | ||
| 99 | std::array<u64, BANK_SIZE> host_results; | ||
| 100 | }; | ||
| 101 | |||
| 102 | using BaseStreamer = VideoCommon::SimpleStreamer<VideoCommon::HostQueryBase>; | ||
| 103 | |||
| 104 | struct HostSyncValues { | ||
| 105 | VAddr address; | ||
| 106 | size_t size; | ||
| 107 | size_t offset; | ||
| 108 | |||
| 109 | static constexpr bool GeneratesBaseBuffer = false; | ||
| 110 | }; | ||
| 111 | |||
| 112 | class SamplesStreamer : public BaseStreamer { | ||
| 113 | public: | ||
| 114 | explicit SamplesStreamer(size_t id_, QueryCacheRuntime& runtime_, | ||
| 115 | VideoCore::RasterizerInterface* rasterizer_, const Device& device_, | ||
| 116 | Scheduler& scheduler_, const MemoryAllocator& memory_allocator_, | ||
| 117 | ComputePassDescriptorQueue& compute_pass_descriptor_queue, | ||
| 118 | DescriptorPool& descriptor_pool) | ||
| 119 | : BaseStreamer(id_), runtime{runtime_}, rasterizer{rasterizer_}, device{device_}, | ||
| 120 | scheduler{scheduler_}, memory_allocator{memory_allocator_} { | ||
| 121 | current_bank = nullptr; | ||
| 122 | current_query = nullptr; | ||
| 123 | ammend_value = 0; | ||
| 124 | acumulation_value = 0; | ||
| 125 | queries_prefix_scan_pass = std::make_unique<QueriesPrefixScanPass>( | ||
| 126 | device, scheduler, descriptor_pool, compute_pass_descriptor_queue); | ||
| 127 | |||
| 128 | const VkBufferCreateInfo buffer_ci = { | ||
| 129 | .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, | ||
| 130 | .pNext = nullptr, | ||
| 131 | .flags = 0, | ||
| 132 | .size = 8, | ||
| 133 | .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | | ||
| 134 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, | ||
| 135 | .sharingMode = VK_SHARING_MODE_EXCLUSIVE, | ||
| 136 | .queueFamilyIndexCount = 0, | ||
| 137 | .pQueueFamilyIndices = nullptr, | ||
| 138 | }; | ||
| 139 | accumulation_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal); | ||
| 140 | scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 141 | scheduler.Record([buffer = *accumulation_buffer](vk::CommandBuffer cmdbuf) { | ||
| 142 | cmdbuf.FillBuffer(buffer, 0, 8, 0); | ||
| 143 | }); | ||
| 144 | } | ||
| 145 | |||
| 146 | ~SamplesStreamer() = default; | ||
| 147 | |||
| 148 | void StartCounter() override { | ||
| 149 | if (has_started) { | ||
| 150 | return; | ||
| 151 | } | ||
| 152 | ReserveHostQuery(); | ||
| 153 | scheduler.Record([query_pool = current_query_pool, | ||
| 154 | query_index = current_bank_slot](vk::CommandBuffer cmdbuf) { | ||
| 155 | const bool use_precise = Settings::IsGPULevelHigh(); | ||
| 156 | cmdbuf.BeginQuery(query_pool, static_cast<u32>(query_index), | ||
| 157 | use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0); | ||
| 158 | }); | ||
| 159 | has_started = true; | ||
| 160 | } | ||
| 161 | |||
| 162 | void PauseCounter() override { | ||
| 163 | if (!has_started) { | ||
| 164 | return; | ||
| 165 | } | ||
| 166 | scheduler.Record([query_pool = current_query_pool, | ||
| 167 | query_index = current_bank_slot](vk::CommandBuffer cmdbuf) { | ||
| 168 | cmdbuf.EndQuery(query_pool, static_cast<u32>(query_index)); | ||
| 169 | }); | ||
| 170 | has_started = false; | ||
| 171 | } | ||
| 172 | |||
| 173 | void ResetCounter() override { | ||
| 174 | if (has_started) { | ||
| 175 | PauseCounter(); | ||
| 176 | } | ||
| 177 | AbandonCurrentQuery(); | ||
| 178 | std::function<void()> func([this, counts = pending_flush_queries.size()] { | ||
| 179 | ammend_value = 0; | ||
| 180 | acumulation_value = 0; | ||
| 181 | }); | ||
| 182 | rasterizer->SyncOperation(std::move(func)); | ||
| 183 | accumulation_since_last_sync = false; | ||
| 184 | first_accumulation_checkpoint = std::min(first_accumulation_checkpoint, num_slots_used); | ||
| 185 | last_accumulation_checkpoint = std::max(last_accumulation_checkpoint, num_slots_used); | ||
| 186 | } | ||
| 187 | |||
| 188 | void CloseCounter() override { | ||
| 189 | PauseCounter(); | ||
| 190 | } | ||
| 26 | 191 | ||
| 27 | } // Anonymous namespace | 192 | bool HasPendingSync() const override { |
| 193 | return !pending_sync.empty(); | ||
| 194 | } | ||
| 195 | |||
| 196 | void SyncWrites() override { | ||
| 197 | if (sync_values_stash.empty()) { | ||
| 198 | return; | ||
| 199 | } | ||
| 28 | 200 | ||
| 29 | QueryPool::QueryPool(const Device& device_, Scheduler& scheduler, QueryType type_) | 201 | for (size_t i = 0; i < sync_values_stash.size(); i++) { |
| 30 | : ResourcePool{scheduler.GetMasterSemaphore(), GROW_STEP}, device{device_}, type{type_} {} | 202 | runtime.template SyncValues<HostSyncValues>(sync_values_stash[i], |
| 203 | *buffers[resolve_buffers[i]]); | ||
| 204 | } | ||
| 205 | |||
| 206 | sync_values_stash.clear(); | ||
| 207 | } | ||
| 31 | 208 | ||
| 32 | QueryPool::~QueryPool() = default; | 209 | void PresyncWrites() override { |
| 210 | if (pending_sync.empty()) { | ||
| 211 | return; | ||
| 212 | } | ||
| 213 | PauseCounter(); | ||
| 214 | sync_values_stash.clear(); | ||
| 215 | sync_values_stash.emplace_back(); | ||
| 216 | std::vector<HostSyncValues>* sync_values = &sync_values_stash.back(); | ||
| 217 | sync_values->reserve(num_slots_used); | ||
| 218 | std::unordered_map<size_t, std::pair<size_t, size_t>> offsets; | ||
| 219 | resolve_buffers.clear(); | ||
| 220 | size_t resolve_buffer_index = ObtainBuffer<true>(num_slots_used); | ||
| 221 | resolve_buffers.push_back(resolve_buffer_index); | ||
| 222 | size_t base_offset = 0; | ||
| 33 | 223 | ||
| 34 | std::pair<VkQueryPool, u32> QueryPool::Commit() { | 224 | ApplyBanksWideOp<true>(pending_sync, [&](SamplesQueryBank* bank, size_t start, |
| 35 | std::size_t index; | 225 | size_t amount) { |
| 36 | do { | 226 | size_t bank_id = bank->GetIndex(); |
| 37 | index = CommitResource(); | 227 | auto& resolve_buffer = buffers[resolve_buffer_index]; |
| 38 | } while (usage[index]); | 228 | VkQueryPool query_pool = bank->GetInnerPool(); |
| 39 | usage[index] = true; | 229 | scheduler.RequestOutsideRenderPassOperationContext(); |
| 230 | scheduler.Record([start, amount, base_offset, query_pool, | ||
| 231 | buffer = *resolve_buffer](vk::CommandBuffer cmdbuf) { | ||
| 232 | const VkBufferMemoryBarrier copy_query_pool_barrier{ | ||
| 233 | .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, | ||
| 234 | .pNext = nullptr, | ||
| 235 | .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, | ||
| 236 | .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, | ||
| 237 | .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||
| 238 | .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||
| 239 | .buffer = buffer, | ||
| 240 | .offset = base_offset, | ||
| 241 | .size = amount * SamplesQueryBank::QUERY_SIZE, | ||
| 242 | }; | ||
| 243 | |||
| 244 | cmdbuf.CopyQueryPoolResults( | ||
| 245 | query_pool, static_cast<u32>(start), static_cast<u32>(amount), buffer, | ||
| 246 | static_cast<u32>(base_offset), SamplesQueryBank::QUERY_SIZE, | ||
| 247 | VK_QUERY_RESULT_WAIT_BIT | VK_QUERY_RESULT_64_BIT); | ||
| 248 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, | ||
| 249 | VK_PIPELINE_STAGE_TRANSFER_BIT, 0, copy_query_pool_barrier); | ||
| 250 | }); | ||
| 251 | offsets[bank_id] = {start, base_offset}; | ||
| 252 | base_offset += amount * SamplesQueryBank::QUERY_SIZE; | ||
| 253 | }); | ||
| 254 | |||
| 255 | // Convert queries | ||
| 256 | bool has_multi_queries = false; | ||
| 257 | for (auto q : pending_sync) { | ||
| 258 | auto* query = GetQuery(q); | ||
| 259 | size_t sync_value_slot = 0; | ||
| 260 | if (True(query->flags & VideoCommon::QueryFlagBits::IsRewritten)) { | ||
| 261 | continue; | ||
| 262 | } | ||
| 263 | if (True(query->flags & VideoCommon::QueryFlagBits::IsInvalidated)) { | ||
| 264 | continue; | ||
| 265 | } | ||
| 266 | if (accumulation_since_last_sync || query->size_slots > 1) { | ||
| 267 | if (!has_multi_queries) { | ||
| 268 | has_multi_queries = true; | ||
| 269 | sync_values_stash.emplace_back(); | ||
| 270 | } | ||
| 271 | sync_value_slot = 1; | ||
| 272 | } | ||
| 273 | query->flags |= VideoCommon::QueryFlagBits::IsHostSynced; | ||
| 274 | auto loc_data = offsets[query->start_bank_id]; | ||
| 275 | sync_values_stash[sync_value_slot].emplace_back(HostSyncValues{ | ||
| 276 | .address = query->guest_address, | ||
| 277 | .size = SamplesQueryBank::QUERY_SIZE, | ||
| 278 | .offset = | ||
| 279 | loc_data.second + (query->start_slot - loc_data.first + query->size_slots - 1) * | ||
| 280 | SamplesQueryBank::QUERY_SIZE, | ||
| 281 | }); | ||
| 282 | } | ||
| 283 | |||
| 284 | if (has_multi_queries) { | ||
| 285 | size_t intermediary_buffer_index = ObtainBuffer<false>(num_slots_used); | ||
| 286 | resolve_buffers.push_back(intermediary_buffer_index); | ||
| 287 | queries_prefix_scan_pass->Run(*accumulation_buffer, *buffers[intermediary_buffer_index], | ||
| 288 | *buffers[resolve_buffer_index], num_slots_used, | ||
| 289 | std::min(first_accumulation_checkpoint, num_slots_used), | ||
| 290 | last_accumulation_checkpoint); | ||
| 291 | |||
| 292 | } else { | ||
| 293 | scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 294 | scheduler.Record([buffer = *accumulation_buffer](vk::CommandBuffer cmdbuf) { | ||
| 295 | cmdbuf.FillBuffer(buffer, 0, 8, 0); | ||
| 296 | }); | ||
| 297 | } | ||
| 298 | |||
| 299 | ReplicateCurrentQueryIfNeeded(); | ||
| 300 | std::function<void()> func([this] { ammend_value = acumulation_value; }); | ||
| 301 | rasterizer->SyncOperation(std::move(func)); | ||
| 302 | AbandonCurrentQuery(); | ||
| 303 | num_slots_used = 0; | ||
| 304 | first_accumulation_checkpoint = std::numeric_limits<size_t>::max(); | ||
| 305 | last_accumulation_checkpoint = 0; | ||
| 306 | accumulation_since_last_sync = has_multi_queries; | ||
| 307 | pending_sync.clear(); | ||
| 308 | } | ||
| 40 | 309 | ||
| 41 | return {*pools[index / GROW_STEP], static_cast<u32>(index % GROW_STEP)}; | 310 | size_t WriteCounter(VAddr address, bool has_timestamp, u32 value, |
| 311 | [[maybe_unused]] std::optional<u32> subreport) override { | ||
| 312 | PauseCounter(); | ||
| 313 | auto index = BuildQuery(); | ||
| 314 | auto* new_query = GetQuery(index); | ||
| 315 | new_query->guest_address = address; | ||
| 316 | new_query->value = 0; | ||
| 317 | new_query->flags &= ~VideoCommon::QueryFlagBits::IsOrphan; | ||
| 318 | if (has_timestamp) { | ||
| 319 | new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp; | ||
| 320 | } | ||
| 321 | if (!current_query) { | ||
| 322 | new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced; | ||
| 323 | return index; | ||
| 324 | } | ||
| 325 | new_query->start_bank_id = current_query->start_bank_id; | ||
| 326 | new_query->size_banks = current_query->size_banks; | ||
| 327 | new_query->start_slot = current_query->start_slot; | ||
| 328 | new_query->size_slots = current_query->size_slots; | ||
| 329 | ApplyBankOp(new_query, [](SamplesQueryBank* bank, size_t start, size_t amount) { | ||
| 330 | bank->AddReference(amount); | ||
| 331 | }); | ||
| 332 | pending_sync.push_back(index); | ||
| 333 | pending_flush_queries.push_back(index); | ||
| 334 | return index; | ||
| 335 | } | ||
| 336 | |||
| 337 | bool HasUnsyncedQueries() const override { | ||
| 338 | return !pending_flush_queries.empty(); | ||
| 339 | } | ||
| 340 | |||
| 341 | void PushUnsyncedQueries() override { | ||
| 342 | PauseCounter(); | ||
| 343 | current_bank->Close(); | ||
| 344 | { | ||
| 345 | std::scoped_lock lk(flush_guard); | ||
| 346 | pending_flush_sets.emplace_back(std::move(pending_flush_queries)); | ||
| 347 | } | ||
| 348 | } | ||
| 349 | |||
| 350 | void PopUnsyncedQueries() override { | ||
| 351 | std::vector<size_t> current_flush_queries; | ||
| 352 | { | ||
| 353 | std::scoped_lock lk(flush_guard); | ||
| 354 | current_flush_queries = std::move(pending_flush_sets.front()); | ||
| 355 | pending_flush_sets.pop_front(); | ||
| 356 | } | ||
| 357 | ApplyBanksWideOp<false>( | ||
| 358 | current_flush_queries, | ||
| 359 | [](SamplesQueryBank* bank, size_t start, size_t amount) { bank->Sync(start, amount); }); | ||
| 360 | for (auto q : current_flush_queries) { | ||
| 361 | auto* query = GetQuery(q); | ||
| 362 | u64 total = 0; | ||
| 363 | ApplyBankOp(query, [&total](SamplesQueryBank* bank, size_t start, size_t amount) { | ||
| 364 | const auto& results = bank->GetResults(); | ||
| 365 | for (size_t i = 0; i < amount; i++) { | ||
| 366 | total += results[start + i]; | ||
| 367 | } | ||
| 368 | }); | ||
| 369 | query->value = total; | ||
| 370 | query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced; | ||
| 371 | } | ||
| 372 | } | ||
| 373 | |||
| 374 | private: | ||
| 375 | template <typename Func> | ||
| 376 | void ApplyBankOp(VideoCommon::HostQueryBase* query, Func&& func) { | ||
| 377 | size_t size_slots = query->size_slots; | ||
| 378 | if (size_slots == 0) { | ||
| 379 | return; | ||
| 380 | } | ||
| 381 | size_t bank_id = query->start_bank_id; | ||
| 382 | size_t banks_set = query->size_banks; | ||
| 383 | size_t start_slot = query->start_slot; | ||
| 384 | for (size_t i = 0; i < banks_set; i++) { | ||
| 385 | auto& the_bank = bank_pool.GetBank(bank_id); | ||
| 386 | size_t amount = std::min(the_bank.Size() - start_slot, size_slots); | ||
| 387 | func(&the_bank, start_slot, amount); | ||
| 388 | bank_id = the_bank.next_bank - 1; | ||
| 389 | start_slot = 0; | ||
| 390 | size_slots -= amount; | ||
| 391 | } | ||
| 392 | } | ||
| 393 | |||
| 394 | template <bool is_ordered, typename Func> | ||
| 395 | void ApplyBanksWideOp(std::vector<size_t>& queries, Func&& func) { | ||
| 396 | std::conditional_t<is_ordered, std::map<size_t, std::pair<size_t, size_t>>, | ||
| 397 | std::unordered_map<size_t, std::pair<size_t, size_t>>> | ||
| 398 | indexer; | ||
| 399 | for (auto q : queries) { | ||
| 400 | auto* query = GetQuery(q); | ||
| 401 | ApplyBankOp(query, [&indexer](SamplesQueryBank* bank, size_t start, size_t amount) { | ||
| 402 | auto id_ = bank->GetIndex(); | ||
| 403 | auto pair = indexer.try_emplace(id_, std::numeric_limits<size_t>::max(), | ||
| 404 | std::numeric_limits<size_t>::min()); | ||
| 405 | auto& current_pair = pair.first->second; | ||
| 406 | current_pair.first = std::min(current_pair.first, start); | ||
| 407 | current_pair.second = std::max(current_pair.second, amount + start); | ||
| 408 | }); | ||
| 409 | } | ||
| 410 | for (auto& cont : indexer) { | ||
| 411 | func(&bank_pool.GetBank(cont.first), cont.second.first, | ||
| 412 | cont.second.second - cont.second.first); | ||
| 413 | } | ||
| 414 | } | ||
| 415 | |||
| 416 | void ReserveBank() { | ||
| 417 | current_bank_id = | ||
| 418 | bank_pool.ReserveBank([this](std::deque<SamplesQueryBank>& queue, size_t index) { | ||
| 419 | queue.emplace_back(device, index); | ||
| 420 | }); | ||
| 421 | if (current_bank) { | ||
| 422 | current_bank->next_bank = current_bank_id + 1; | ||
| 423 | } | ||
| 424 | current_bank = &bank_pool.GetBank(current_bank_id); | ||
| 425 | current_query_pool = current_bank->GetInnerPool(); | ||
| 426 | } | ||
| 427 | |||
| 428 | size_t ReserveBankSlot() { | ||
| 429 | if (!current_bank || current_bank->IsClosed()) { | ||
| 430 | ReserveBank(); | ||
| 431 | } | ||
| 432 | auto [built, index] = current_bank->Reserve(); | ||
| 433 | current_bank_slot = index; | ||
| 434 | return index; | ||
| 435 | } | ||
| 436 | |||
| 437 | void ReserveHostQuery() { | ||
| 438 | size_t new_slot = ReserveBankSlot(); | ||
| 439 | current_bank->AddReference(1); | ||
| 440 | num_slots_used++; | ||
| 441 | if (current_query) { | ||
| 442 | size_t bank_id = current_query->start_bank_id; | ||
| 443 | size_t banks_set = current_query->size_banks - 1; | ||
| 444 | bool found = bank_id == current_bank_id; | ||
| 445 | while (!found && banks_set > 0) { | ||
| 446 | SamplesQueryBank& some_bank = bank_pool.GetBank(bank_id); | ||
| 447 | bank_id = some_bank.next_bank - 1; | ||
| 448 | found = bank_id == current_bank_id; | ||
| 449 | banks_set--; | ||
| 450 | } | ||
| 451 | if (!found) { | ||
| 452 | current_query->size_banks++; | ||
| 453 | } | ||
| 454 | current_query->size_slots++; | ||
| 455 | } else { | ||
| 456 | current_query_id = BuildQuery(); | ||
| 457 | current_query = GetQuery(current_query_id); | ||
| 458 | current_query->start_bank_id = static_cast<u32>(current_bank_id); | ||
| 459 | current_query->size_banks = 1; | ||
| 460 | current_query->start_slot = new_slot; | ||
| 461 | current_query->size_slots = 1; | ||
| 462 | } | ||
| 463 | } | ||
| 464 | |||
| 465 | void Free(size_t query_id) override { | ||
| 466 | std::scoped_lock lk(guard); | ||
| 467 | auto* query = GetQuery(query_id); | ||
| 468 | ApplyBankOp(query, [](SamplesQueryBank* bank, size_t start, size_t amount) { | ||
| 469 | bank->CloseReference(amount); | ||
| 470 | }); | ||
| 471 | ReleaseQuery(query_id); | ||
| 472 | } | ||
| 473 | |||
| 474 | void AbandonCurrentQuery() { | ||
| 475 | if (!current_query) { | ||
| 476 | return; | ||
| 477 | } | ||
| 478 | Free(current_query_id); | ||
| 479 | current_query = nullptr; | ||
| 480 | current_query_id = 0; | ||
| 481 | } | ||
| 482 | |||
| 483 | void ReplicateCurrentQueryIfNeeded() { | ||
| 484 | if (pending_sync.empty()) { | ||
| 485 | return; | ||
| 486 | } | ||
| 487 | if (!current_query) { | ||
| 488 | return; | ||
| 489 | } | ||
| 490 | auto index = BuildQuery(); | ||
| 491 | auto* new_query = GetQuery(index); | ||
| 492 | new_query->guest_address = 0; | ||
| 493 | new_query->value = 0; | ||
| 494 | new_query->flags &= ~VideoCommon::QueryFlagBits::IsOrphan; | ||
| 495 | new_query->start_bank_id = current_query->start_bank_id; | ||
| 496 | new_query->size_banks = current_query->size_banks; | ||
| 497 | new_query->start_slot = current_query->start_slot; | ||
| 498 | new_query->size_slots = current_query->size_slots; | ||
| 499 | ApplyBankOp(new_query, [](SamplesQueryBank* bank, size_t start, size_t amount) { | ||
| 500 | bank->AddReference(amount); | ||
| 501 | }); | ||
| 502 | pending_flush_queries.push_back(index); | ||
| 503 | std::function<void()> func([this, index] { | ||
| 504 | auto* query = GetQuery(index); | ||
| 505 | query->value += GetAmmendValue(); | ||
| 506 | SetAccumulationValue(query->value); | ||
| 507 | Free(index); | ||
| 508 | }); | ||
| 509 | } | ||
| 510 | |||
| 511 | template <bool is_resolve> | ||
| 512 | size_t ObtainBuffer(size_t num_needed) { | ||
| 513 | const size_t log_2 = std::max<size_t>(11U, Common::Log2Ceil64(num_needed)); | ||
| 514 | if constexpr (is_resolve) { | ||
| 515 | if (resolve_table[log_2] != 0) { | ||
| 516 | return resolve_table[log_2] - 1; | ||
| 517 | } | ||
| 518 | } else { | ||
| 519 | if (intermediary_table[log_2] != 0) { | ||
| 520 | return intermediary_table[log_2] - 1; | ||
| 521 | } | ||
| 522 | } | ||
| 523 | const VkBufferCreateInfo buffer_ci = { | ||
| 524 | .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, | ||
| 525 | .pNext = nullptr, | ||
| 526 | .flags = 0, | ||
| 527 | .size = SamplesQueryBank::QUERY_SIZE * (1ULL << log_2), | ||
| 528 | .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | | ||
| 529 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, | ||
| 530 | .sharingMode = VK_SHARING_MODE_EXCLUSIVE, | ||
| 531 | .queueFamilyIndexCount = 0, | ||
| 532 | .pQueueFamilyIndices = nullptr, | ||
| 533 | }; | ||
| 534 | buffers.emplace_back(memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal)); | ||
| 535 | if constexpr (is_resolve) { | ||
| 536 | resolve_table[log_2] = buffers.size(); | ||
| 537 | } else { | ||
| 538 | intermediary_table[log_2] = buffers.size(); | ||
| 539 | } | ||
| 540 | return buffers.size() - 1; | ||
| 541 | } | ||
| 542 | |||
| 543 | QueryCacheRuntime& runtime; | ||
| 544 | VideoCore::RasterizerInterface* rasterizer; | ||
| 545 | const Device& device; | ||
| 546 | Scheduler& scheduler; | ||
| 547 | const MemoryAllocator& memory_allocator; | ||
| 548 | VideoCommon::BankPool<SamplesQueryBank> bank_pool; | ||
| 549 | std::deque<vk::Buffer> buffers; | ||
| 550 | std::array<size_t, 32> resolve_table{}; | ||
| 551 | std::array<size_t, 32> intermediary_table{}; | ||
| 552 | vk::Buffer accumulation_buffer; | ||
| 553 | std::deque<std::vector<HostSyncValues>> sync_values_stash; | ||
| 554 | std::vector<size_t> resolve_buffers; | ||
| 555 | |||
| 556 | // syncing queue | ||
| 557 | std::vector<size_t> pending_sync; | ||
| 558 | |||
| 559 | // flush levels | ||
| 560 | std::vector<size_t> pending_flush_queries; | ||
| 561 | std::deque<std::vector<size_t>> pending_flush_sets; | ||
| 562 | |||
| 563 | // State Machine | ||
| 564 | size_t current_bank_slot; | ||
| 565 | size_t current_bank_id; | ||
| 566 | SamplesQueryBank* current_bank; | ||
| 567 | VkQueryPool current_query_pool; | ||
| 568 | size_t current_query_id; | ||
| 569 | size_t num_slots_used{}; | ||
| 570 | size_t first_accumulation_checkpoint{}; | ||
| 571 | size_t last_accumulation_checkpoint{}; | ||
| 572 | bool accumulation_since_last_sync{}; | ||
| 573 | VideoCommon::HostQueryBase* current_query; | ||
| 574 | bool has_started{}; | ||
| 575 | std::mutex flush_guard; | ||
| 576 | |||
| 577 | std::unique_ptr<QueriesPrefixScanPass> queries_prefix_scan_pass; | ||
| 578 | }; | ||
| 579 | |||
| 580 | // Transform feedback queries | ||
| 581 | class TFBQueryBank : public VideoCommon::BankBase { | ||
| 582 | public: | ||
| 583 | static constexpr size_t BANK_SIZE = 1024; | ||
| 584 | static constexpr size_t QUERY_SIZE = 4; | ||
| 585 | explicit TFBQueryBank(Scheduler& scheduler_, const MemoryAllocator& memory_allocator, | ||
| 586 | size_t index_) | ||
| 587 | : BankBase(BANK_SIZE), scheduler{scheduler_}, index{index_} { | ||
| 588 | const VkBufferCreateInfo buffer_ci = { | ||
| 589 | .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, | ||
| 590 | .pNext = nullptr, | ||
| 591 | .flags = 0, | ||
| 592 | .size = QUERY_SIZE * BANK_SIZE, | ||
| 593 | .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, | ||
| 594 | .sharingMode = VK_SHARING_MODE_EXCLUSIVE, | ||
| 595 | .queueFamilyIndexCount = 0, | ||
| 596 | .pQueueFamilyIndices = nullptr, | ||
| 597 | }; | ||
| 598 | buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal); | ||
| 599 | } | ||
| 600 | |||
| 601 | ~TFBQueryBank() = default; | ||
| 602 | |||
| 603 | void Reset() override { | ||
| 604 | ASSERT(references == 0); | ||
| 605 | VideoCommon::BankBase::Reset(); | ||
| 606 | } | ||
| 607 | |||
| 608 | void Sync(StagingBufferRef& stagging_buffer, size_t extra_offset, size_t start, size_t size) { | ||
| 609 | scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 610 | scheduler.Record([this, dst_buffer = stagging_buffer.buffer, extra_offset, start, | ||
| 611 | size](vk::CommandBuffer cmdbuf) { | ||
| 612 | std::array<VkBufferCopy, 1> copy{VkBufferCopy{ | ||
| 613 | .srcOffset = start * QUERY_SIZE, | ||
| 614 | .dstOffset = extra_offset, | ||
| 615 | .size = size * QUERY_SIZE, | ||
| 616 | }}; | ||
| 617 | cmdbuf.CopyBuffer(*buffer, dst_buffer, copy); | ||
| 618 | }); | ||
| 619 | } | ||
| 620 | |||
| 621 | size_t GetIndex() const { | ||
| 622 | return index; | ||
| 623 | } | ||
| 624 | |||
| 625 | VkBuffer GetBuffer() const { | ||
| 626 | return *buffer; | ||
| 627 | } | ||
| 628 | |||
| 629 | private: | ||
| 630 | Scheduler& scheduler; | ||
| 631 | const size_t index; | ||
| 632 | vk::Buffer buffer; | ||
| 633 | }; | ||
| 634 | |||
| 635 | class PrimitivesSucceededStreamer; | ||
| 636 | |||
| 637 | class TFBCounterStreamer : public BaseStreamer { | ||
| 638 | public: | ||
| 639 | explicit TFBCounterStreamer(size_t id_, QueryCacheRuntime& runtime_, const Device& device_, | ||
| 640 | Scheduler& scheduler_, const MemoryAllocator& memory_allocator_, | ||
| 641 | StagingBufferPool& staging_pool_) | ||
| 642 | : BaseStreamer(id_), runtime{runtime_}, device{device_}, scheduler{scheduler_}, | ||
| 643 | memory_allocator{memory_allocator_}, staging_pool{staging_pool_} { | ||
| 644 | buffers_count = 0; | ||
| 645 | current_bank = nullptr; | ||
| 646 | counter_buffers.fill(VK_NULL_HANDLE); | ||
| 647 | offsets.fill(0); | ||
| 648 | last_queries.fill(0); | ||
| 649 | last_queries_stride.fill(1); | ||
| 650 | const VkBufferCreateInfo buffer_ci = { | ||
| 651 | .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, | ||
| 652 | .pNext = nullptr, | ||
| 653 | .flags = 0, | ||
| 654 | .size = TFBQueryBank::QUERY_SIZE * NUM_STREAMS, | ||
| 655 | .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | | ||
| 656 | VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT, | ||
| 657 | .sharingMode = VK_SHARING_MODE_EXCLUSIVE, | ||
| 658 | .queueFamilyIndexCount = 0, | ||
| 659 | .pQueueFamilyIndices = nullptr, | ||
| 660 | }; | ||
| 661 | |||
| 662 | counters_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal); | ||
| 663 | for (auto& c : counter_buffers) { | ||
| 664 | c = *counters_buffer; | ||
| 665 | } | ||
| 666 | size_t base_offset = 0; | ||
| 667 | for (auto& o : offsets) { | ||
| 668 | o = base_offset; | ||
| 669 | base_offset += TFBQueryBank::QUERY_SIZE; | ||
| 670 | } | ||
| 671 | } | ||
| 672 | |||
| 673 | ~TFBCounterStreamer() = default; | ||
| 674 | |||
| 675 | void StartCounter() override { | ||
| 676 | FlushBeginTFB(); | ||
| 677 | has_started = true; | ||
| 678 | } | ||
| 679 | |||
| 680 | void PauseCounter() override { | ||
| 681 | CloseCounter(); | ||
| 682 | } | ||
| 683 | |||
| 684 | void ResetCounter() override { | ||
| 685 | CloseCounter(); | ||
| 686 | } | ||
| 687 | |||
| 688 | void CloseCounter() override { | ||
| 689 | if (has_flushed_end_pending) { | ||
| 690 | FlushEndTFB(); | ||
| 691 | } | ||
| 692 | runtime.View3DRegs([this](Maxwell3D& maxwell3d) { | ||
| 693 | if (maxwell3d.regs.transform_feedback_enabled == 0) { | ||
| 694 | streams_mask = 0; | ||
| 695 | has_started = false; | ||
| 696 | } | ||
| 697 | }); | ||
| 698 | } | ||
| 699 | |||
| 700 | bool HasPendingSync() const override { | ||
| 701 | return !pending_sync.empty(); | ||
| 702 | } | ||
| 703 | |||
| 704 | void SyncWrites() override { | ||
| 705 | CloseCounter(); | ||
| 706 | std::unordered_map<size_t, std::vector<HostSyncValues>> sync_values_stash; | ||
| 707 | for (auto q : pending_sync) { | ||
| 708 | auto* query = GetQuery(q); | ||
| 709 | if (True(query->flags & VideoCommon::QueryFlagBits::IsRewritten)) { | ||
| 710 | continue; | ||
| 711 | } | ||
| 712 | if (True(query->flags & VideoCommon::QueryFlagBits::IsInvalidated)) { | ||
| 713 | continue; | ||
| 714 | } | ||
| 715 | query->flags |= VideoCommon::QueryFlagBits::IsHostSynced; | ||
| 716 | sync_values_stash.try_emplace(query->start_bank_id); | ||
| 717 | sync_values_stash[query->start_bank_id].emplace_back(HostSyncValues{ | ||
| 718 | .address = query->guest_address, | ||
| 719 | .size = TFBQueryBank::QUERY_SIZE, | ||
| 720 | .offset = query->start_slot * TFBQueryBank::QUERY_SIZE, | ||
| 721 | }); | ||
| 722 | } | ||
| 723 | for (auto& p : sync_values_stash) { | ||
| 724 | auto& bank = bank_pool.GetBank(p.first); | ||
| 725 | runtime.template SyncValues<HostSyncValues>(p.second, bank.GetBuffer()); | ||
| 726 | } | ||
| 727 | pending_sync.clear(); | ||
| 728 | } | ||
| 729 | |||
| 730 | size_t WriteCounter(VAddr address, bool has_timestamp, u32 value, | ||
| 731 | std::optional<u32> subreport_) override { | ||
| 732 | auto index = BuildQuery(); | ||
| 733 | auto* new_query = GetQuery(index); | ||
| 734 | new_query->guest_address = address; | ||
| 735 | new_query->value = 0; | ||
| 736 | new_query->flags &= ~VideoCommon::QueryFlagBits::IsOrphan; | ||
| 737 | if (has_timestamp) { | ||
| 738 | new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp; | ||
| 739 | } | ||
| 740 | if (!subreport_) { | ||
| 741 | new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced; | ||
| 742 | return index; | ||
| 743 | } | ||
| 744 | const size_t subreport = static_cast<size_t>(*subreport_); | ||
| 745 | last_queries[subreport] = address; | ||
| 746 | if ((streams_mask & (1ULL << subreport)) == 0) { | ||
| 747 | new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced; | ||
| 748 | return index; | ||
| 749 | } | ||
| 750 | CloseCounter(); | ||
| 751 | auto [bank_slot, data_slot] = ProduceCounterBuffer(subreport); | ||
| 752 | new_query->start_bank_id = static_cast<u32>(bank_slot); | ||
| 753 | new_query->size_banks = 1; | ||
| 754 | new_query->start_slot = static_cast<u32>(data_slot); | ||
| 755 | new_query->size_slots = 1; | ||
| 756 | pending_sync.push_back(index); | ||
| 757 | pending_flush_queries.push_back(index); | ||
| 758 | return index; | ||
| 759 | } | ||
| 760 | |||
| 761 | std::optional<std::pair<VAddr, size_t>> GetLastQueryStream(size_t stream) { | ||
| 762 | if (last_queries[stream] != 0) { | ||
| 763 | std::pair<VAddr, size_t> result(last_queries[stream], last_queries_stride[stream]); | ||
| 764 | return result; | ||
| 765 | } | ||
| 766 | return std::nullopt; | ||
| 767 | } | ||
| 768 | |||
| 769 | Maxwell3D::Regs::PrimitiveTopology GetOutputTopology() const { | ||
| 770 | return out_topology; | ||
| 771 | } | ||
| 772 | |||
| 773 | bool HasUnsyncedQueries() const override { | ||
| 774 | return !pending_flush_queries.empty(); | ||
| 775 | } | ||
| 776 | |||
| 777 | void PushUnsyncedQueries() override { | ||
| 778 | CloseCounter(); | ||
| 779 | auto staging_ref = staging_pool.Request( | ||
| 780 | pending_flush_queries.size() * TFBQueryBank::QUERY_SIZE, MemoryUsage::Download, true); | ||
| 781 | size_t offset_base = staging_ref.offset; | ||
| 782 | for (auto q : pending_flush_queries) { | ||
| 783 | auto* query = GetQuery(q); | ||
| 784 | auto& bank = bank_pool.GetBank(query->start_bank_id); | ||
| 785 | bank.Sync(staging_ref, offset_base, query->start_slot, 1); | ||
| 786 | offset_base += TFBQueryBank::QUERY_SIZE; | ||
| 787 | bank.CloseReference(); | ||
| 788 | } | ||
| 789 | static constexpr VkMemoryBarrier WRITE_BARRIER{ | ||
| 790 | .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, | ||
| 791 | .pNext = nullptr, | ||
| 792 | .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, | ||
| 793 | .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, | ||
| 794 | }; | ||
| 795 | scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 796 | scheduler.Record([](vk::CommandBuffer cmdbuf) { | ||
| 797 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, | ||
| 798 | VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER); | ||
| 799 | }); | ||
| 800 | |||
| 801 | std::scoped_lock lk(flush_guard); | ||
| 802 | for (auto& str : free_queue) { | ||
| 803 | staging_pool.FreeDeferred(str); | ||
| 804 | } | ||
| 805 | free_queue.clear(); | ||
| 806 | download_buffers.emplace_back(staging_ref); | ||
| 807 | pending_flush_sets.emplace_back(std::move(pending_flush_queries)); | ||
| 808 | } | ||
| 809 | |||
| 810 | void PopUnsyncedQueries() override { | ||
| 811 | StagingBufferRef staging_ref; | ||
| 812 | std::vector<size_t> flushed_queries; | ||
| 813 | { | ||
| 814 | std::scoped_lock lk(flush_guard); | ||
| 815 | staging_ref = download_buffers.front(); | ||
| 816 | flushed_queries = std::move(pending_flush_sets.front()); | ||
| 817 | download_buffers.pop_front(); | ||
| 818 | pending_flush_sets.pop_front(); | ||
| 819 | } | ||
| 820 | |||
| 821 | size_t offset_base = staging_ref.offset; | ||
| 822 | for (auto q : flushed_queries) { | ||
| 823 | auto* query = GetQuery(q); | ||
| 824 | u32 result = 0; | ||
| 825 | std::memcpy(&result, staging_ref.mapped_span.data() + offset_base, sizeof(u32)); | ||
| 826 | query->value = static_cast<u64>(result); | ||
| 827 | query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced; | ||
| 828 | offset_base += TFBQueryBank::QUERY_SIZE; | ||
| 829 | } | ||
| 830 | |||
| 831 | { | ||
| 832 | std::scoped_lock lk(flush_guard); | ||
| 833 | free_queue.emplace_back(staging_ref); | ||
| 834 | } | ||
| 835 | } | ||
| 836 | |||
| 837 | private: | ||
| 838 | void FlushBeginTFB() { | ||
| 839 | if (has_flushed_end_pending) [[unlikely]] { | ||
| 840 | return; | ||
| 841 | } | ||
| 842 | has_flushed_end_pending = true; | ||
| 843 | if (!has_started || buffers_count == 0) { | ||
| 844 | scheduler.Record([](vk::CommandBuffer cmdbuf) { | ||
| 845 | cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr); | ||
| 846 | }); | ||
| 847 | UpdateBuffers(); | ||
| 848 | return; | ||
| 849 | } | ||
| 850 | scheduler.Record([this, total = static_cast<u32>(buffers_count)](vk::CommandBuffer cmdbuf) { | ||
| 851 | cmdbuf.BeginTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data()); | ||
| 852 | }); | ||
| 853 | UpdateBuffers(); | ||
| 854 | } | ||
| 855 | |||
| 856 | void FlushEndTFB() { | ||
| 857 | if (!has_flushed_end_pending) [[unlikely]] { | ||
| 858 | UNREACHABLE(); | ||
| 859 | return; | ||
| 860 | } | ||
| 861 | has_flushed_end_pending = false; | ||
| 862 | |||
| 863 | if (buffers_count == 0) { | ||
| 864 | scheduler.Record([](vk::CommandBuffer cmdbuf) { | ||
| 865 | cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); | ||
| 866 | }); | ||
| 867 | } else { | ||
| 868 | scheduler.Record([this, | ||
| 869 | total = static_cast<u32>(buffers_count)](vk::CommandBuffer cmdbuf) { | ||
| 870 | cmdbuf.EndTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data()); | ||
| 871 | }); | ||
| 872 | } | ||
| 873 | } | ||
| 874 | |||
| 875 | void UpdateBuffers() { | ||
| 876 | last_queries.fill(0); | ||
| 877 | last_queries_stride.fill(1); | ||
| 878 | runtime.View3DRegs([this](Maxwell3D& maxwell3d) { | ||
| 879 | buffers_count = 0; | ||
| 880 | out_topology = maxwell3d.draw_manager->GetDrawState().topology; | ||
| 881 | for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) { | ||
| 882 | const auto& tf = maxwell3d.regs.transform_feedback; | ||
| 883 | if (tf.buffers[i].enable == 0) { | ||
| 884 | continue; | ||
| 885 | } | ||
| 886 | const size_t stream = tf.controls[i].stream; | ||
| 887 | last_queries_stride[stream] = tf.controls[i].stride; | ||
| 888 | streams_mask |= 1ULL << stream; | ||
| 889 | buffers_count = std::max<size_t>(buffers_count, stream + 1); | ||
| 890 | } | ||
| 891 | }); | ||
| 892 | } | ||
| 893 | |||
| 894 | std::pair<size_t, size_t> ProduceCounterBuffer(size_t stream) { | ||
| 895 | if (current_bank == nullptr || current_bank->IsClosed()) { | ||
| 896 | current_bank_id = | ||
| 897 | bank_pool.ReserveBank([this](std::deque<TFBQueryBank>& queue, size_t index) { | ||
| 898 | queue.emplace_back(scheduler, memory_allocator, index); | ||
| 899 | }); | ||
| 900 | current_bank = &bank_pool.GetBank(current_bank_id); | ||
| 901 | } | ||
| 902 | auto [dont_care, other] = current_bank->Reserve(); | ||
| 903 | const size_t slot = other; // workaround to compile bug. | ||
| 904 | current_bank->AddReference(); | ||
| 905 | |||
| 906 | static constexpr VkMemoryBarrier READ_BARRIER{ | ||
| 907 | .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, | ||
| 908 | .pNext = nullptr, | ||
| 909 | .srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT, | ||
| 910 | .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, | ||
| 911 | }; | ||
| 912 | static constexpr VkMemoryBarrier WRITE_BARRIER{ | ||
| 913 | .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, | ||
| 914 | .pNext = nullptr, | ||
| 915 | .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, | ||
| 916 | .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, | ||
| 917 | }; | ||
| 918 | scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 919 | scheduler.Record([dst_buffer = current_bank->GetBuffer(), | ||
| 920 | src_buffer = counter_buffers[stream], src_offset = offsets[stream], | ||
| 921 | slot](vk::CommandBuffer cmdbuf) { | ||
| 922 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT, | ||
| 923 | VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER); | ||
| 924 | std::array<VkBufferCopy, 1> copy{VkBufferCopy{ | ||
| 925 | .srcOffset = src_offset, | ||
| 926 | .dstOffset = slot * TFBQueryBank::QUERY_SIZE, | ||
| 927 | .size = TFBQueryBank::QUERY_SIZE, | ||
| 928 | }}; | ||
| 929 | cmdbuf.CopyBuffer(src_buffer, dst_buffer, copy); | ||
| 930 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, | ||
| 931 | 0, WRITE_BARRIER); | ||
| 932 | }); | ||
| 933 | return {current_bank_id, slot}; | ||
| 934 | } | ||
| 935 | |||
| 936 | friend class PrimitivesSucceededStreamer; | ||
| 937 | |||
| 938 | static constexpr size_t NUM_STREAMS = 4; | ||
| 939 | |||
| 940 | QueryCacheRuntime& runtime; | ||
| 941 | const Device& device; | ||
| 942 | Scheduler& scheduler; | ||
| 943 | const MemoryAllocator& memory_allocator; | ||
| 944 | StagingBufferPool& staging_pool; | ||
| 945 | VideoCommon::BankPool<TFBQueryBank> bank_pool; | ||
| 946 | size_t current_bank_id; | ||
| 947 | TFBQueryBank* current_bank; | ||
| 948 | vk::Buffer counters_buffer; | ||
| 949 | |||
| 950 | // syncing queue | ||
| 951 | std::vector<size_t> pending_sync; | ||
| 952 | |||
| 953 | // flush levels | ||
| 954 | std::vector<size_t> pending_flush_queries; | ||
| 955 | std::deque<StagingBufferRef> download_buffers; | ||
| 956 | std::deque<std::vector<size_t>> pending_flush_sets; | ||
| 957 | std::vector<StagingBufferRef> free_queue; | ||
| 958 | std::mutex flush_guard; | ||
| 959 | |||
| 960 | // state machine | ||
| 961 | bool has_started{}; | ||
| 962 | bool has_flushed_end_pending{}; | ||
| 963 | size_t buffers_count{}; | ||
| 964 | std::array<VkBuffer, NUM_STREAMS> counter_buffers{}; | ||
| 965 | std::array<VkDeviceSize, NUM_STREAMS> offsets{}; | ||
| 966 | std::array<VAddr, NUM_STREAMS> last_queries; | ||
| 967 | std::array<size_t, NUM_STREAMS> last_queries_stride; | ||
| 968 | Maxwell3D::Regs::PrimitiveTopology out_topology; | ||
| 969 | u64 streams_mask; | ||
| 970 | }; | ||
| 971 | |||
| 972 | class PrimitivesQueryBase : public VideoCommon::QueryBase { | ||
| 973 | public: | ||
| 974 | // Default constructor | ||
| 975 | PrimitivesQueryBase() | ||
| 976 | : VideoCommon::QueryBase(0, VideoCommon::QueryFlagBits::IsHostManaged, 0) {} | ||
| 977 | |||
| 978 | // Parameterized constructor | ||
| 979 | PrimitivesQueryBase(bool has_timestamp, VAddr address) | ||
| 980 | : VideoCommon::QueryBase(address, VideoCommon::QueryFlagBits::IsHostManaged, 0) { | ||
| 981 | if (has_timestamp) { | ||
| 982 | flags |= VideoCommon::QueryFlagBits::HasTimestamp; | ||
| 983 | } | ||
| 984 | } | ||
| 985 | |||
| 986 | u64 stride{}; | ||
| 987 | VAddr dependant_address{}; | ||
| 988 | Maxwell3D::Regs::PrimitiveTopology topology{Maxwell3D::Regs::PrimitiveTopology::Points}; | ||
| 989 | size_t dependant_index{}; | ||
| 990 | bool dependant_manage{}; | ||
| 991 | }; | ||
| 992 | |||
| 993 | class PrimitivesSucceededStreamer : public VideoCommon::SimpleStreamer<PrimitivesQueryBase> { | ||
| 994 | public: | ||
| 995 | explicit PrimitivesSucceededStreamer(size_t id_, QueryCacheRuntime& runtime_, | ||
| 996 | TFBCounterStreamer& tfb_streamer_, | ||
| 997 | Core::Memory::Memory& cpu_memory_) | ||
| 998 | : VideoCommon::SimpleStreamer<PrimitivesQueryBase>(id_), runtime{runtime_}, | ||
| 999 | tfb_streamer{tfb_streamer_}, cpu_memory{cpu_memory_} { | ||
| 1000 | MakeDependent(&tfb_streamer); | ||
| 1001 | } | ||
| 1002 | |||
| 1003 | ~PrimitivesSucceededStreamer() = default; | ||
| 1004 | |||
| 1005 | size_t WriteCounter(VAddr address, bool has_timestamp, u32 value, | ||
| 1006 | std::optional<u32> subreport_) override { | ||
| 1007 | auto index = BuildQuery(); | ||
| 1008 | auto* new_query = GetQuery(index); | ||
| 1009 | new_query->guest_address = address; | ||
| 1010 | new_query->value = 0; | ||
| 1011 | if (has_timestamp) { | ||
| 1012 | new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp; | ||
| 1013 | } | ||
| 1014 | if (!subreport_) { | ||
| 1015 | new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced; | ||
| 1016 | return index; | ||
| 1017 | } | ||
| 1018 | const size_t subreport = static_cast<size_t>(*subreport_); | ||
| 1019 | auto dependant_address_opt = tfb_streamer.GetLastQueryStream(subreport); | ||
| 1020 | bool must_manage_dependance = false; | ||
| 1021 | new_query->topology = tfb_streamer.GetOutputTopology(); | ||
| 1022 | if (dependant_address_opt) { | ||
| 1023 | auto [dep_address, stride] = *dependant_address_opt; | ||
| 1024 | new_query->dependant_address = dep_address; | ||
| 1025 | new_query->stride = stride; | ||
| 1026 | } else { | ||
| 1027 | new_query->dependant_index = | ||
| 1028 | tfb_streamer.WriteCounter(address, has_timestamp, value, subreport_); | ||
| 1029 | auto* dependant_query = tfb_streamer.GetQuery(new_query->dependant_index); | ||
| 1030 | dependant_query->flags |= VideoCommon::QueryFlagBits::IsInvalidated; | ||
| 1031 | must_manage_dependance = true; | ||
| 1032 | if (True(dependant_query->flags & VideoCommon::QueryFlagBits::IsFinalValueSynced)) { | ||
| 1033 | new_query->value = 0; | ||
| 1034 | new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced; | ||
| 1035 | if (must_manage_dependance) { | ||
| 1036 | tfb_streamer.Free(new_query->dependant_index); | ||
| 1037 | } | ||
| 1038 | return index; | ||
| 1039 | } | ||
| 1040 | new_query->stride = 1; | ||
| 1041 | runtime.View3DRegs([new_query, subreport](Maxwell3D& maxwell3d) { | ||
| 1042 | for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) { | ||
| 1043 | const auto& tf = maxwell3d.regs.transform_feedback; | ||
| 1044 | if (tf.buffers[i].enable == 0) { | ||
| 1045 | continue; | ||
| 1046 | } | ||
| 1047 | if (tf.controls[i].stream != subreport) { | ||
| 1048 | continue; | ||
| 1049 | } | ||
| 1050 | new_query->stride = tf.controls[i].stride; | ||
| 1051 | break; | ||
| 1052 | } | ||
| 1053 | }); | ||
| 1054 | } | ||
| 1055 | |||
| 1056 | new_query->dependant_manage = must_manage_dependance; | ||
| 1057 | pending_flush_queries.push_back(index); | ||
| 1058 | return index; | ||
| 1059 | } | ||
| 1060 | |||
| 1061 | bool HasUnsyncedQueries() const override { | ||
| 1062 | return !pending_flush_queries.empty(); | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | void PushUnsyncedQueries() override { | ||
| 1066 | std::scoped_lock lk(flush_guard); | ||
| 1067 | pending_flush_sets.emplace_back(std::move(pending_flush_queries)); | ||
| 1068 | pending_flush_queries.clear(); | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | void PopUnsyncedQueries() override { | ||
| 1072 | std::vector<size_t> flushed_queries; | ||
| 1073 | { | ||
| 1074 | std::scoped_lock lk(flush_guard); | ||
| 1075 | flushed_queries = std::move(pending_flush_sets.front()); | ||
| 1076 | pending_flush_sets.pop_front(); | ||
| 1077 | } | ||
| 1078 | |||
| 1079 | for (auto q : flushed_queries) { | ||
| 1080 | auto* query = GetQuery(q); | ||
| 1081 | if (True(query->flags & VideoCommon::QueryFlagBits::IsFinalValueSynced)) { | ||
| 1082 | continue; | ||
| 1083 | } | ||
| 1084 | |||
| 1085 | query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced; | ||
| 1086 | u64 num_vertices = 0; | ||
| 1087 | if (query->dependant_manage) { | ||
| 1088 | auto* dependant_query = tfb_streamer.GetQuery(query->dependant_index); | ||
| 1089 | num_vertices = dependant_query->value / query->stride; | ||
| 1090 | tfb_streamer.Free(query->dependant_index); | ||
| 1091 | } else { | ||
| 1092 | u8* pointer = cpu_memory.GetPointer(query->dependant_address); | ||
| 1093 | u32 result; | ||
| 1094 | std::memcpy(&result, pointer, sizeof(u32)); | ||
| 1095 | num_vertices = static_cast<u64>(result) / query->stride; | ||
| 1096 | } | ||
| 1097 | query->value = [&]() -> u64 { | ||
| 1098 | switch (query->topology) { | ||
| 1099 | case Maxwell3D::Regs::PrimitiveTopology::Points: | ||
| 1100 | return num_vertices; | ||
| 1101 | case Maxwell3D::Regs::PrimitiveTopology::Lines: | ||
| 1102 | return num_vertices / 2; | ||
| 1103 | case Maxwell3D::Regs::PrimitiveTopology::LineLoop: | ||
| 1104 | return (num_vertices / 2) + 1; | ||
| 1105 | case Maxwell3D::Regs::PrimitiveTopology::LineStrip: | ||
| 1106 | return num_vertices - 1; | ||
| 1107 | case Maxwell3D::Regs::PrimitiveTopology::Patches: | ||
| 1108 | case Maxwell3D::Regs::PrimitiveTopology::Triangles: | ||
| 1109 | case Maxwell3D::Regs::PrimitiveTopology::TrianglesAdjacency: | ||
| 1110 | return num_vertices / 3; | ||
| 1111 | case Maxwell3D::Regs::PrimitiveTopology::TriangleFan: | ||
| 1112 | case Maxwell3D::Regs::PrimitiveTopology::TriangleStrip: | ||
| 1113 | case Maxwell3D::Regs::PrimitiveTopology::TriangleStripAdjacency: | ||
| 1114 | return num_vertices - 2; | ||
| 1115 | case Maxwell3D::Regs::PrimitiveTopology::Quads: | ||
| 1116 | return num_vertices / 4; | ||
| 1117 | case Maxwell3D::Regs::PrimitiveTopology::Polygon: | ||
| 1118 | return 1U; | ||
| 1119 | default: | ||
| 1120 | return num_vertices; | ||
| 1121 | } | ||
| 1122 | }(); | ||
| 1123 | } | ||
| 1124 | } | ||
| 1125 | |||
| 1126 | private: | ||
| 1127 | QueryCacheRuntime& runtime; | ||
| 1128 | TFBCounterStreamer& tfb_streamer; | ||
| 1129 | Core::Memory::Memory& cpu_memory; | ||
| 1130 | |||
| 1131 | // syncing queue | ||
| 1132 | std::vector<size_t> pending_sync; | ||
| 1133 | |||
| 1134 | // flush levels | ||
| 1135 | std::vector<size_t> pending_flush_queries; | ||
| 1136 | std::deque<std::vector<size_t>> pending_flush_sets; | ||
| 1137 | std::mutex flush_guard; | ||
| 1138 | }; | ||
| 1139 | |||
| 1140 | } // namespace | ||
| 1141 | |||
| 1142 | struct QueryCacheRuntimeImpl { | ||
| 1143 | QueryCacheRuntimeImpl(QueryCacheRuntime& runtime, VideoCore::RasterizerInterface* rasterizer_, | ||
| 1144 | Core::Memory::Memory& cpu_memory_, Vulkan::BufferCache& buffer_cache_, | ||
| 1145 | const Device& device_, const MemoryAllocator& memory_allocator_, | ||
| 1146 | Scheduler& scheduler_, StagingBufferPool& staging_pool_, | ||
| 1147 | ComputePassDescriptorQueue& compute_pass_descriptor_queue, | ||
| 1148 | DescriptorPool& descriptor_pool) | ||
| 1149 | : rasterizer{rasterizer_}, cpu_memory{cpu_memory_}, | ||
| 1150 | buffer_cache{buffer_cache_}, device{device_}, | ||
| 1151 | memory_allocator{memory_allocator_}, scheduler{scheduler_}, staging_pool{staging_pool_}, | ||
| 1152 | guest_streamer(0, runtime), | ||
| 1153 | sample_streamer(static_cast<size_t>(QueryType::ZPassPixelCount64), runtime, rasterizer, | ||
| 1154 | device, scheduler, memory_allocator, compute_pass_descriptor_queue, | ||
| 1155 | descriptor_pool), | ||
| 1156 | tfb_streamer(static_cast<size_t>(QueryType::StreamingByteCount), runtime, device, | ||
| 1157 | scheduler, memory_allocator, staging_pool), | ||
| 1158 | primitives_succeeded_streamer( | ||
| 1159 | static_cast<size_t>(QueryType::StreamingPrimitivesSucceeded), runtime, tfb_streamer, | ||
| 1160 | cpu_memory_), | ||
| 1161 | primitives_needed_minus_suceeded_streamer( | ||
| 1162 | static_cast<size_t>(QueryType::StreamingPrimitivesNeededMinusSucceeded), runtime, 0u), | ||
| 1163 | hcr_setup{}, hcr_is_set{}, is_hcr_running{}, maxwell3d{} { | ||
| 1164 | |||
| 1165 | hcr_setup.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT; | ||
| 1166 | hcr_setup.pNext = nullptr; | ||
| 1167 | hcr_setup.flags = 0; | ||
| 1168 | |||
| 1169 | conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>( | ||
| 1170 | device, scheduler, descriptor_pool, compute_pass_descriptor_queue); | ||
| 1171 | |||
| 1172 | const VkBufferCreateInfo buffer_ci = { | ||
| 1173 | .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, | ||
| 1174 | .pNext = nullptr, | ||
| 1175 | .flags = 0, | ||
| 1176 | .size = sizeof(u32), | ||
| 1177 | .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | | ||
| 1178 | VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT, | ||
| 1179 | .sharingMode = VK_SHARING_MODE_EXCLUSIVE, | ||
| 1180 | .queueFamilyIndexCount = 0, | ||
| 1181 | .pQueueFamilyIndices = nullptr, | ||
| 1182 | }; | ||
| 1183 | hcr_resolve_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal); | ||
| 1184 | } | ||
| 1185 | |||
| 1186 | VideoCore::RasterizerInterface* rasterizer; | ||
| 1187 | Core::Memory::Memory& cpu_memory; | ||
| 1188 | Vulkan::BufferCache& buffer_cache; | ||
| 1189 | |||
| 1190 | const Device& device; | ||
| 1191 | const MemoryAllocator& memory_allocator; | ||
| 1192 | Scheduler& scheduler; | ||
| 1193 | StagingBufferPool& staging_pool; | ||
| 1194 | |||
| 1195 | // Streamers | ||
| 1196 | VideoCommon::GuestStreamer<QueryCacheParams> guest_streamer; | ||
| 1197 | SamplesStreamer sample_streamer; | ||
| 1198 | TFBCounterStreamer tfb_streamer; | ||
| 1199 | PrimitivesSucceededStreamer primitives_succeeded_streamer; | ||
| 1200 | VideoCommon::StubStreamer<QueryCacheParams> primitives_needed_minus_suceeded_streamer; | ||
| 1201 | |||
| 1202 | std::vector<std::pair<VAddr, VAddr>> little_cache; | ||
| 1203 | std::vector<std::pair<VkBuffer, VkDeviceSize>> buffers_to_upload_to; | ||
| 1204 | std::vector<size_t> redirect_cache; | ||
| 1205 | std::vector<std::vector<VkBufferCopy>> copies_setup; | ||
| 1206 | |||
| 1207 | // Host conditional rendering data | ||
| 1208 | std::unique_ptr<ConditionalRenderingResolvePass> conditional_resolve_pass; | ||
| 1209 | vk::Buffer hcr_resolve_buffer; | ||
| 1210 | VkConditionalRenderingBeginInfoEXT hcr_setup; | ||
| 1211 | VkBuffer hcr_buffer; | ||
| 1212 | size_t hcr_offset; | ||
| 1213 | bool hcr_is_set; | ||
| 1214 | bool is_hcr_running; | ||
| 1215 | |||
| 1216 | // maxwell3d | ||
| 1217 | Maxwell3D* maxwell3d; | ||
| 1218 | }; | ||
| 1219 | |||
| 1220 | QueryCacheRuntime::QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer, | ||
| 1221 | Core::Memory::Memory& cpu_memory_, | ||
| 1222 | Vulkan::BufferCache& buffer_cache_, const Device& device_, | ||
| 1223 | const MemoryAllocator& memory_allocator_, | ||
| 1224 | Scheduler& scheduler_, StagingBufferPool& staging_pool_, | ||
| 1225 | ComputePassDescriptorQueue& compute_pass_descriptor_queue, | ||
| 1226 | DescriptorPool& descriptor_pool) { | ||
| 1227 | impl = std::make_unique<QueryCacheRuntimeImpl>( | ||
| 1228 | *this, rasterizer, cpu_memory_, buffer_cache_, device_, memory_allocator_, scheduler_, | ||
| 1229 | staging_pool_, compute_pass_descriptor_queue, descriptor_pool); | ||
| 42 | } | 1230 | } |
| 43 | 1231 | ||
| 44 | void QueryPool::Allocate(std::size_t begin, std::size_t end) { | 1232 | void QueryCacheRuntime::Bind3DEngine(Maxwell3D* maxwell3d) { |
| 45 | usage.resize(end); | 1233 | impl->maxwell3d = maxwell3d; |
| 1234 | } | ||
| 46 | 1235 | ||
| 47 | pools.push_back(device.GetLogical().CreateQueryPool({ | 1236 | template <typename Func> |
| 48 | .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, | 1237 | void QueryCacheRuntime::View3DRegs(Func&& func) { |
| 49 | .pNext = nullptr, | 1238 | if (impl->maxwell3d) { |
| 50 | .flags = 0, | 1239 | func(*impl->maxwell3d); |
| 51 | .queryType = GetTarget(type), | 1240 | } |
| 52 | .queryCount = static_cast<u32>(end - begin), | 1241 | } |
| 53 | .pipelineStatistics = 0, | 1242 | |
| 54 | })); | 1243 | void QueryCacheRuntime::EndHostConditionalRendering() { |
| 1244 | PauseHostConditionalRendering(); | ||
| 1245 | impl->hcr_is_set = false; | ||
| 1246 | impl->is_hcr_running = false; | ||
| 1247 | impl->hcr_buffer = nullptr; | ||
| 1248 | impl->hcr_offset = 0; | ||
| 1249 | } | ||
| 1250 | |||
| 1251 | void QueryCacheRuntime::PauseHostConditionalRendering() { | ||
| 1252 | if (!impl->hcr_is_set) { | ||
| 1253 | return; | ||
| 1254 | } | ||
| 1255 | if (impl->is_hcr_running) { | ||
| 1256 | impl->scheduler.Record( | ||
| 1257 | [](vk::CommandBuffer cmdbuf) { cmdbuf.EndConditionalRenderingEXT(); }); | ||
| 1258 | } | ||
| 1259 | impl->is_hcr_running = false; | ||
| 55 | } | 1260 | } |
| 56 | 1261 | ||
| 57 | void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) { | 1262 | void QueryCacheRuntime::ResumeHostConditionalRendering() { |
| 58 | const auto it = | 1263 | if (!impl->hcr_is_set) { |
| 59 | std::find_if(pools.begin(), pools.end(), [query_pool = query.first](vk::QueryPool& pool) { | 1264 | return; |
| 60 | return query_pool == *pool; | 1265 | } |
| 1266 | if (!impl->is_hcr_running) { | ||
| 1267 | impl->scheduler.Record([hcr_setup = impl->hcr_setup](vk::CommandBuffer cmdbuf) { | ||
| 1268 | cmdbuf.BeginConditionalRenderingEXT(hcr_setup); | ||
| 61 | }); | 1269 | }); |
| 1270 | } | ||
| 1271 | impl->is_hcr_running = true; | ||
| 1272 | } | ||
| 62 | 1273 | ||
| 63 | if (it != std::end(pools)) { | 1274 | void QueryCacheRuntime::HostConditionalRenderingCompareValueImpl(VideoCommon::LookupData object, |
| 64 | const std::ptrdiff_t pool_index = std::distance(std::begin(pools), it); | 1275 | bool is_equal) { |
| 65 | usage[pool_index * GROW_STEP + static_cast<std::ptrdiff_t>(query.second)] = false; | 1276 | { |
| 1277 | std::scoped_lock lk(impl->buffer_cache.mutex); | ||
| 1278 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; | ||
| 1279 | const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing; | ||
| 1280 | const auto [buffer, offset] = | ||
| 1281 | impl->buffer_cache.ObtainCPUBuffer(object.address, 8, sync_info, post_op); | ||
| 1282 | impl->hcr_buffer = buffer->Handle(); | ||
| 1283 | impl->hcr_offset = offset; | ||
| 1284 | } | ||
| 1285 | if (impl->hcr_is_set) { | ||
| 1286 | if (impl->hcr_setup.buffer == impl->hcr_buffer && | ||
| 1287 | impl->hcr_setup.offset == impl->hcr_offset) { | ||
| 1288 | ResumeHostConditionalRendering(); | ||
| 1289 | return; | ||
| 1290 | } | ||
| 1291 | PauseHostConditionalRendering(); | ||
| 66 | } | 1292 | } |
| 1293 | impl->hcr_setup.buffer = impl->hcr_buffer; | ||
| 1294 | impl->hcr_setup.offset = impl->hcr_offset; | ||
| 1295 | impl->hcr_setup.flags = is_equal ? VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT : 0; | ||
| 1296 | impl->hcr_is_set = true; | ||
| 1297 | impl->is_hcr_running = false; | ||
| 1298 | ResumeHostConditionalRendering(); | ||
| 67 | } | 1299 | } |
| 68 | 1300 | ||
| 69 | QueryCache::QueryCache(VideoCore::RasterizerInterface& rasterizer_, | 1301 | void QueryCacheRuntime::HostConditionalRenderingCompareBCImpl(VAddr address, bool is_equal) { |
| 70 | Core::Memory::Memory& cpu_memory_, const Device& device_, | 1302 | VkBuffer to_resolve; |
| 71 | Scheduler& scheduler_) | 1303 | u32 to_resolve_offset; |
| 72 | : QueryCacheBase{rasterizer_, cpu_memory_}, device{device_}, scheduler{scheduler_}, | 1304 | { |
| 73 | query_pools{ | 1305 | std::scoped_lock lk(impl->buffer_cache.mutex); |
| 74 | QueryPool{device_, scheduler_, QueryType::SamplesPassed}, | 1306 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::NoSynchronize; |
| 75 | } {} | 1307 | const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing; |
| 76 | 1308 | const auto [buffer, offset] = | |
| 77 | QueryCache::~QueryCache() { | 1309 | impl->buffer_cache.ObtainCPUBuffer(address, 24, sync_info, post_op); |
| 78 | // TODO(Rodrigo): This is a hack to destroy all HostCounter instances before the base class | 1310 | to_resolve = buffer->Handle(); |
| 79 | // destructor is called. The query cache should be redesigned to have a proper ownership model | 1311 | to_resolve_offset = static_cast<u32>(offset); |
| 80 | // instead of using shared pointers. | ||
| 81 | for (size_t query_type = 0; query_type < VideoCore::NumQueryTypes; ++query_type) { | ||
| 82 | auto& stream = Stream(static_cast<QueryType>(query_type)); | ||
| 83 | stream.Update(false); | ||
| 84 | stream.Reset(); | ||
| 85 | } | 1312 | } |
| 1313 | if (impl->is_hcr_running) { | ||
| 1314 | PauseHostConditionalRendering(); | ||
| 1315 | } | ||
| 1316 | impl->conditional_resolve_pass->Resolve(*impl->hcr_resolve_buffer, to_resolve, | ||
| 1317 | to_resolve_offset, false); | ||
| 1318 | impl->hcr_setup.buffer = *impl->hcr_resolve_buffer; | ||
| 1319 | impl->hcr_setup.offset = 0; | ||
| 1320 | impl->hcr_setup.flags = is_equal ? 0 : VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT; | ||
| 1321 | impl->hcr_is_set = true; | ||
| 1322 | impl->is_hcr_running = false; | ||
| 1323 | ResumeHostConditionalRendering(); | ||
| 86 | } | 1324 | } |
| 87 | 1325 | ||
| 88 | std::pair<VkQueryPool, u32> QueryCache::AllocateQuery(QueryType type) { | 1326 | bool QueryCacheRuntime::HostConditionalRenderingCompareValue(VideoCommon::LookupData object_1, |
| 89 | return query_pools[static_cast<std::size_t>(type)].Commit(); | 1327 | [[maybe_unused]] bool qc_dirty) { |
| 1328 | if (!impl->device.IsExtConditionalRendering()) { | ||
| 1329 | return false; | ||
| 1330 | } | ||
| 1331 | HostConditionalRenderingCompareValueImpl(object_1, false); | ||
| 1332 | return true; | ||
| 90 | } | 1333 | } |
| 91 | 1334 | ||
| 92 | void QueryCache::Reserve(QueryType type, std::pair<VkQueryPool, u32> query) { | 1335 | bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::LookupData object_1, |
| 93 | query_pools[static_cast<std::size_t>(type)].Reserve(query); | 1336 | VideoCommon::LookupData object_2, |
| 1337 | bool qc_dirty, bool equal_check) { | ||
| 1338 | if (!impl->device.IsExtConditionalRendering()) { | ||
| 1339 | return false; | ||
| 1340 | } | ||
| 1341 | |||
| 1342 | const auto check_in_bc = [&](VAddr address) { | ||
| 1343 | return impl->buffer_cache.IsRegionGpuModified(address, 8); | ||
| 1344 | }; | ||
| 1345 | const auto check_value = [&](VAddr address) { | ||
| 1346 | u8* ptr = impl->cpu_memory.GetPointer(address); | ||
| 1347 | u64 value{}; | ||
| 1348 | std::memcpy(&value, ptr, sizeof(value)); | ||
| 1349 | return value == 0; | ||
| 1350 | }; | ||
| 1351 | std::array<VideoCommon::LookupData*, 2> objects{&object_1, &object_2}; | ||
| 1352 | std::array<bool, 2> is_in_bc{}; | ||
| 1353 | std::array<bool, 2> is_in_qc{}; | ||
| 1354 | std::array<bool, 2> is_in_ac{}; | ||
| 1355 | std::array<bool, 2> is_null{}; | ||
| 1356 | { | ||
| 1357 | std::scoped_lock lk(impl->buffer_cache.mutex); | ||
| 1358 | for (size_t i = 0; i < 2; i++) { | ||
| 1359 | is_in_qc[i] = objects[i]->found_query != nullptr; | ||
| 1360 | is_in_bc[i] = !is_in_qc[i] && check_in_bc(objects[i]->address); | ||
| 1361 | is_in_ac[i] = is_in_qc[i] || is_in_bc[i]; | ||
| 1362 | } | ||
| 1363 | } | ||
| 1364 | |||
| 1365 | if (!is_in_ac[0] && !is_in_ac[1]) { | ||
| 1366 | EndHostConditionalRendering(); | ||
| 1367 | return false; | ||
| 1368 | } | ||
| 1369 | |||
| 1370 | if (!qc_dirty && !is_in_bc[0] && !is_in_bc[1]) { | ||
| 1371 | EndHostConditionalRendering(); | ||
| 1372 | return false; | ||
| 1373 | } | ||
| 1374 | |||
| 1375 | const bool is_gpu_high = Settings::IsGPULevelHigh(); | ||
| 1376 | if (!is_gpu_high && impl->device.GetDriverID() == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) { | ||
| 1377 | return true; | ||
| 1378 | } | ||
| 1379 | |||
| 1380 | for (size_t i = 0; i < 2; i++) { | ||
| 1381 | is_null[i] = !is_in_ac[i] && check_value(objects[i]->address); | ||
| 1382 | } | ||
| 1383 | |||
| 1384 | for (size_t i = 0; i < 2; i++) { | ||
| 1385 | if (is_null[i]) { | ||
| 1386 | size_t j = (i + 1) % 2; | ||
| 1387 | HostConditionalRenderingCompareValueImpl(*objects[j], equal_check); | ||
| 1388 | return true; | ||
| 1389 | } | ||
| 1390 | } | ||
| 1391 | |||
| 1392 | if (!is_gpu_high) { | ||
| 1393 | return true; | ||
| 1394 | } | ||
| 1395 | |||
| 1396 | if (!is_in_bc[0] && !is_in_bc[1]) { | ||
| 1397 | // Both queries are in query cache, it's best to just flush. | ||
| 1398 | return true; | ||
| 1399 | } | ||
| 1400 | HostConditionalRenderingCompareBCImpl(object_1.address, equal_check); | ||
| 1401 | return true; | ||
| 94 | } | 1402 | } |
| 95 | 1403 | ||
| 96 | HostCounter::HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_, | 1404 | QueryCacheRuntime::~QueryCacheRuntime() = default; |
| 97 | QueryType type_) | 1405 | |
| 98 | : HostCounterBase{std::move(dependency_)}, cache{cache_}, type{type_}, | 1406 | VideoCommon::StreamerInterface* QueryCacheRuntime::GetStreamerInterface(QueryType query_type) { |
| 99 | query{cache_.AllocateQuery(type_)}, tick{cache_.GetScheduler().CurrentTick()} { | 1407 | switch (query_type) { |
| 100 | const vk::Device* logical = &cache.GetDevice().GetLogical(); | 1408 | case QueryType::Payload: |
| 101 | cache.GetScheduler().Record([logical, query_ = query](vk::CommandBuffer cmdbuf) { | 1409 | return &impl->guest_streamer; |
| 102 | const bool use_precise = Settings::IsGPULevelHigh(); | 1410 | case QueryType::ZPassPixelCount64: |
| 103 | logical->ResetQueryPool(query_.first, query_.second, 1); | 1411 | return &impl->sample_streamer; |
| 104 | cmdbuf.BeginQuery(query_.first, query_.second, | 1412 | case QueryType::StreamingByteCount: |
| 105 | use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0); | 1413 | return &impl->tfb_streamer; |
| 106 | }); | 1414 | case QueryType::StreamingPrimitivesNeeded: |
| 1415 | case QueryType::VtgPrimitivesOut: | ||
| 1416 | case QueryType::StreamingPrimitivesSucceeded: | ||
| 1417 | return &impl->primitives_succeeded_streamer; | ||
| 1418 | case QueryType::StreamingPrimitivesNeededMinusSucceeded: | ||
| 1419 | return &impl->primitives_needed_minus_suceeded_streamer; | ||
| 1420 | default: | ||
| 1421 | return nullptr; | ||
| 1422 | } | ||
| 107 | } | 1423 | } |
| 108 | 1424 | ||
| 109 | HostCounter::~HostCounter() { | 1425 | void QueryCacheRuntime::Barriers(bool is_prebarrier) { |
| 110 | cache.Reserve(type, query); | 1426 | static constexpr VkMemoryBarrier READ_BARRIER{ |
| 1427 | .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, | ||
| 1428 | .pNext = nullptr, | ||
| 1429 | .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, | ||
| 1430 | .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, | ||
| 1431 | }; | ||
| 1432 | static constexpr VkMemoryBarrier WRITE_BARRIER{ | ||
| 1433 | .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, | ||
| 1434 | .pNext = nullptr, | ||
| 1435 | .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, | ||
| 1436 | .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, | ||
| 1437 | }; | ||
| 1438 | if (is_prebarrier) { | ||
| 1439 | impl->scheduler.Record([](vk::CommandBuffer cmdbuf) { | ||
| 1440 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, | ||
| 1441 | VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER); | ||
| 1442 | }); | ||
| 1443 | } else { | ||
| 1444 | impl->scheduler.Record([](vk::CommandBuffer cmdbuf) { | ||
| 1445 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, | ||
| 1446 | VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER); | ||
| 1447 | }); | ||
| 1448 | } | ||
| 111 | } | 1449 | } |
| 112 | 1450 | ||
| 113 | void HostCounter::EndQuery() { | 1451 | template <typename SyncValuesType> |
| 114 | cache.GetScheduler().Record([query_ = query](vk::CommandBuffer cmdbuf) { | 1452 | void QueryCacheRuntime::SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer) { |
| 115 | cmdbuf.EndQuery(query_.first, query_.second); | 1453 | if (values.size() == 0) { |
| 1454 | return; | ||
| 1455 | } | ||
| 1456 | impl->redirect_cache.clear(); | ||
| 1457 | impl->little_cache.clear(); | ||
| 1458 | size_t total_size = 0; | ||
| 1459 | for (auto& sync_val : values) { | ||
| 1460 | total_size += sync_val.size; | ||
| 1461 | bool found = false; | ||
| 1462 | VAddr base = Common::AlignDown(sync_val.address, Core::Memory::YUZU_PAGESIZE); | ||
| 1463 | VAddr base_end = base + Core::Memory::YUZU_PAGESIZE; | ||
| 1464 | for (size_t i = 0; i < impl->little_cache.size(); i++) { | ||
| 1465 | const auto set_found = [&] { | ||
| 1466 | impl->redirect_cache.push_back(i); | ||
| 1467 | found = true; | ||
| 1468 | }; | ||
| 1469 | auto& loc = impl->little_cache[i]; | ||
| 1470 | if (base < loc.second && loc.first < base_end) { | ||
| 1471 | set_found(); | ||
| 1472 | break; | ||
| 1473 | } | ||
| 1474 | if (loc.first == base_end) { | ||
| 1475 | loc.first = base; | ||
| 1476 | set_found(); | ||
| 1477 | break; | ||
| 1478 | } | ||
| 1479 | if (loc.second == base) { | ||
| 1480 | loc.second = base_end; | ||
| 1481 | set_found(); | ||
| 1482 | break; | ||
| 1483 | } | ||
| 1484 | } | ||
| 1485 | if (!found) { | ||
| 1486 | impl->redirect_cache.push_back(impl->little_cache.size()); | ||
| 1487 | impl->little_cache.emplace_back(base, base_end); | ||
| 1488 | } | ||
| 1489 | } | ||
| 1490 | |||
| 1491 | // Vulkan part. | ||
| 1492 | std::scoped_lock lk(impl->buffer_cache.mutex); | ||
| 1493 | impl->buffer_cache.BufferOperations([&] { | ||
| 1494 | impl->buffers_to_upload_to.clear(); | ||
| 1495 | for (auto& pair : impl->little_cache) { | ||
| 1496 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; | ||
| 1497 | const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing; | ||
| 1498 | const auto [buffer, offset] = impl->buffer_cache.ObtainCPUBuffer( | ||
| 1499 | pair.first, static_cast<u32>(pair.second - pair.first), sync_info, post_op); | ||
| 1500 | impl->buffers_to_upload_to.emplace_back(buffer->Handle(), offset); | ||
| 1501 | } | ||
| 116 | }); | 1502 | }); |
| 117 | } | ||
| 118 | 1503 | ||
| 119 | u64 HostCounter::BlockingQuery(bool async) const { | 1504 | VkBuffer src_buffer; |
| 120 | if (!async) { | 1505 | [[maybe_unused]] StagingBufferRef ref; |
| 121 | cache.GetScheduler().Wait(tick); | 1506 | impl->copies_setup.clear(); |
| 122 | } | 1507 | impl->copies_setup.resize(impl->little_cache.size()); |
| 123 | u64 data; | 1508 | if constexpr (SyncValuesType::GeneratesBaseBuffer) { |
| 124 | const VkResult query_result = cache.GetDevice().GetLogical().GetQueryResults( | 1509 | ref = impl->staging_pool.Request(total_size, MemoryUsage::Upload); |
| 125 | query.first, query.second, 1, sizeof(data), &data, sizeof(data), | 1510 | size_t current_offset = ref.offset; |
| 126 | VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); | 1511 | size_t accumulated_size = 0; |
| 127 | 1512 | for (size_t i = 0; i < values.size(); i++) { | |
| 128 | switch (query_result) { | 1513 | size_t which_copy = impl->redirect_cache[i]; |
| 129 | case VK_SUCCESS: | 1514 | impl->copies_setup[which_copy].emplace_back(VkBufferCopy{ |
| 130 | return data; | 1515 | .srcOffset = current_offset + accumulated_size, |
| 131 | case VK_ERROR_DEVICE_LOST: | 1516 | .dstOffset = impl->buffers_to_upload_to[which_copy].second + values[i].address - |
| 132 | cache.GetDevice().ReportLoss(); | 1517 | impl->little_cache[which_copy].first, |
| 133 | [[fallthrough]]; | 1518 | .size = values[i].size, |
| 134 | default: | 1519 | }); |
| 135 | throw vk::Exception(query_result); | 1520 | std::memcpy(ref.mapped_span.data() + accumulated_size, &values[i].value, |
| 1521 | values[i].size); | ||
| 1522 | accumulated_size += values[i].size; | ||
| 1523 | } | ||
| 1524 | src_buffer = ref.buffer; | ||
| 1525 | } else { | ||
| 1526 | for (size_t i = 0; i < values.size(); i++) { | ||
| 1527 | size_t which_copy = impl->redirect_cache[i]; | ||
| 1528 | impl->copies_setup[which_copy].emplace_back(VkBufferCopy{ | ||
| 1529 | .srcOffset = values[i].offset, | ||
| 1530 | .dstOffset = impl->buffers_to_upload_to[which_copy].second + values[i].address - | ||
| 1531 | impl->little_cache[which_copy].first, | ||
| 1532 | .size = values[i].size, | ||
| 1533 | }); | ||
| 1534 | } | ||
| 1535 | src_buffer = base_src_buffer; | ||
| 136 | } | 1536 | } |
| 1537 | |||
| 1538 | impl->scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 1539 | impl->scheduler.Record([src_buffer, dst_buffers = std::move(impl->buffers_to_upload_to), | ||
| 1540 | vk_copies = std::move(impl->copies_setup)](vk::CommandBuffer cmdbuf) { | ||
| 1541 | size_t size = dst_buffers.size(); | ||
| 1542 | for (size_t i = 0; i < size; i++) { | ||
| 1543 | cmdbuf.CopyBuffer(src_buffer, dst_buffers[i].first, vk_copies[i]); | ||
| 1544 | } | ||
| 1545 | }); | ||
| 137 | } | 1546 | } |
| 138 | 1547 | ||
| 139 | } // namespace Vulkan | 1548 | } // namespace Vulkan |
| 1549 | |||
| 1550 | namespace VideoCommon { | ||
| 1551 | |||
| 1552 | template class QueryCacheBase<Vulkan::QueryCacheParams>; | ||
| 1553 | |||
| 1554 | } // namespace VideoCommon | ||
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h index c1b9552eb..e9a1ea169 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.h +++ b/src/video_core/renderer_vulkan/vk_query_cache.h | |||
| @@ -1,101 +1,75 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <cstddef> | ||
| 7 | #include <memory> | 6 | #include <memory> |
| 8 | #include <utility> | ||
| 9 | #include <vector> | ||
| 10 | 7 | ||
| 11 | #include "common/common_types.h" | 8 | #include "video_core/query_cache/query_cache_base.h" |
| 12 | #include "video_core/query_cache.h" | 9 | #include "video_core/renderer_vulkan/vk_buffer_cache.h" |
| 13 | #include "video_core/renderer_vulkan/vk_resource_pool.h" | ||
| 14 | #include "video_core/vulkan_common/vulkan_wrapper.h" | ||
| 15 | 10 | ||
| 16 | namespace VideoCore { | 11 | namespace VideoCore { |
| 17 | class RasterizerInterface; | 12 | class RasterizerInterface; |
| 18 | } | 13 | } |
| 19 | 14 | ||
| 15 | namespace VideoCommon { | ||
| 16 | class StreamerInterface; | ||
| 17 | } | ||
| 18 | |||
| 20 | namespace Vulkan { | 19 | namespace Vulkan { |
| 21 | 20 | ||
| 22 | class CachedQuery; | ||
| 23 | class Device; | 21 | class Device; |
| 24 | class HostCounter; | ||
| 25 | class QueryCache; | ||
| 26 | class Scheduler; | 22 | class Scheduler; |
| 23 | class StagingBufferPool; | ||
| 27 | 24 | ||
| 28 | using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>; | 25 | struct QueryCacheRuntimeImpl; |
| 29 | 26 | ||
| 30 | class QueryPool final : public ResourcePool { | 27 | class QueryCacheRuntime { |
| 31 | public: | 28 | public: |
| 32 | explicit QueryPool(const Device& device, Scheduler& scheduler, VideoCore::QueryType type); | 29 | explicit QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer, |
| 33 | ~QueryPool() override; | 30 | Core::Memory::Memory& cpu_memory_, |
| 31 | Vulkan::BufferCache& buffer_cache_, const Device& device_, | ||
| 32 | const MemoryAllocator& memory_allocator_, Scheduler& scheduler_, | ||
| 33 | StagingBufferPool& staging_pool_, | ||
| 34 | ComputePassDescriptorQueue& compute_pass_descriptor_queue, | ||
| 35 | DescriptorPool& descriptor_pool); | ||
| 36 | ~QueryCacheRuntime(); | ||
| 34 | 37 | ||
| 35 | std::pair<VkQueryPool, u32> Commit(); | 38 | template <typename SyncValuesType> |
| 39 | void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = nullptr); | ||
| 36 | 40 | ||
| 37 | void Reserve(std::pair<VkQueryPool, u32> query); | 41 | void Barriers(bool is_prebarrier); |
| 38 | 42 | ||
| 39 | protected: | 43 | void EndHostConditionalRendering(); |
| 40 | void Allocate(std::size_t begin, std::size_t end) override; | ||
| 41 | 44 | ||
| 42 | private: | 45 | void PauseHostConditionalRendering(); |
| 43 | static constexpr std::size_t GROW_STEP = 512; | ||
| 44 | 46 | ||
| 45 | const Device& device; | 47 | void ResumeHostConditionalRendering(); |
| 46 | const VideoCore::QueryType type; | ||
| 47 | 48 | ||
| 48 | std::vector<vk::QueryPool> pools; | 49 | bool HostConditionalRenderingCompareValue(VideoCommon::LookupData object_1, bool qc_dirty); |
| 49 | std::vector<bool> usage; | ||
| 50 | }; | ||
| 51 | 50 | ||
| 52 | class QueryCache final | 51 | bool HostConditionalRenderingCompareValues(VideoCommon::LookupData object_1, |
| 53 | : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> { | 52 | VideoCommon::LookupData object_2, bool qc_dirty, |
| 54 | public: | 53 | bool equal_check); |
| 55 | explicit QueryCache(VideoCore::RasterizerInterface& rasterizer_, | ||
| 56 | Core::Memory::Memory& cpu_memory_, const Device& device_, | ||
| 57 | Scheduler& scheduler_); | ||
| 58 | ~QueryCache(); | ||
| 59 | |||
| 60 | std::pair<VkQueryPool, u32> AllocateQuery(VideoCore::QueryType type); | ||
| 61 | 54 | ||
| 62 | void Reserve(VideoCore::QueryType type, std::pair<VkQueryPool, u32> query); | 55 | VideoCommon::StreamerInterface* GetStreamerInterface(VideoCommon::QueryType query_type); |
| 63 | 56 | ||
| 64 | const Device& GetDevice() const noexcept { | 57 | void Bind3DEngine(Tegra::Engines::Maxwell3D* maxwell3d); |
| 65 | return device; | ||
| 66 | } | ||
| 67 | 58 | ||
| 68 | Scheduler& GetScheduler() const noexcept { | 59 | template <typename Func> |
| 69 | return scheduler; | 60 | void View3DRegs(Func&& func); |
| 70 | } | ||
| 71 | 61 | ||
| 72 | private: | 62 | private: |
| 73 | const Device& device; | 63 | void HostConditionalRenderingCompareValueImpl(VideoCommon::LookupData object, bool is_equal); |
| 74 | Scheduler& scheduler; | 64 | void HostConditionalRenderingCompareBCImpl(VAddr address, bool is_equal); |
| 75 | std::array<QueryPool, VideoCore::NumQueryTypes> query_pools; | 65 | friend struct QueryCacheRuntimeImpl; |
| 66 | std::unique_ptr<QueryCacheRuntimeImpl> impl; | ||
| 76 | }; | 67 | }; |
| 77 | 68 | ||
| 78 | class HostCounter final : public VideoCommon::HostCounterBase<QueryCache, HostCounter> { | 69 | struct QueryCacheParams { |
| 79 | public: | 70 | using RuntimeType = typename Vulkan::QueryCacheRuntime; |
| 80 | explicit HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_, | ||
| 81 | VideoCore::QueryType type_); | ||
| 82 | ~HostCounter(); | ||
| 83 | |||
| 84 | void EndQuery(); | ||
| 85 | |||
| 86 | private: | ||
| 87 | u64 BlockingQuery(bool async = false) const override; | ||
| 88 | |||
| 89 | QueryCache& cache; | ||
| 90 | const VideoCore::QueryType type; | ||
| 91 | const std::pair<VkQueryPool, u32> query; | ||
| 92 | const u64 tick; | ||
| 93 | }; | 71 | }; |
| 94 | 72 | ||
| 95 | class CachedQuery : public VideoCommon::CachedQueryBase<HostCounter> { | 73 | using QueryCache = VideoCommon::QueryCacheBase<QueryCacheParams>; |
| 96 | public: | ||
| 97 | explicit CachedQuery(QueryCache&, VideoCore::QueryType, VAddr cpu_addr_, u8* host_ptr_) | ||
| 98 | : CachedQueryBase{cpu_addr_, host_ptr_} {} | ||
| 99 | }; | ||
| 100 | 74 | ||
| 101 | } // namespace Vulkan | 75 | } // namespace Vulkan |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 01e76a82c..1628d76d6 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp | |||
| @@ -24,6 +24,7 @@ | |||
| 24 | #include "video_core/renderer_vulkan/vk_compute_pipeline.h" | 24 | #include "video_core/renderer_vulkan/vk_compute_pipeline.h" |
| 25 | #include "video_core/renderer_vulkan/vk_descriptor_pool.h" | 25 | #include "video_core/renderer_vulkan/vk_descriptor_pool.h" |
| 26 | #include "video_core/renderer_vulkan/vk_pipeline_cache.h" | 26 | #include "video_core/renderer_vulkan/vk_pipeline_cache.h" |
| 27 | #include "video_core/renderer_vulkan/vk_query_cache.h" | ||
| 27 | #include "video_core/renderer_vulkan/vk_rasterizer.h" | 28 | #include "video_core/renderer_vulkan/vk_rasterizer.h" |
| 28 | #include "video_core/renderer_vulkan/vk_scheduler.h" | 29 | #include "video_core/renderer_vulkan/vk_scheduler.h" |
| 29 | #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" | 30 | #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" |
| @@ -170,9 +171,11 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra | |||
| 170 | buffer_cache_runtime(device, memory_allocator, scheduler, staging_pool, | 171 | buffer_cache_runtime(device, memory_allocator, scheduler, staging_pool, |
| 171 | guest_descriptor_queue, compute_pass_descriptor_queue, descriptor_pool), | 172 | guest_descriptor_queue, compute_pass_descriptor_queue, descriptor_pool), |
| 172 | buffer_cache(*this, cpu_memory_, buffer_cache_runtime), | 173 | buffer_cache(*this, cpu_memory_, buffer_cache_runtime), |
| 174 | query_cache_runtime(this, cpu_memory_, buffer_cache, device, memory_allocator, scheduler, | ||
| 175 | staging_pool, compute_pass_descriptor_queue, descriptor_pool), | ||
| 176 | query_cache(gpu, *this, cpu_memory_, query_cache_runtime), | ||
| 173 | pipeline_cache(*this, device, scheduler, descriptor_pool, guest_descriptor_queue, | 177 | pipeline_cache(*this, device, scheduler, descriptor_pool, guest_descriptor_queue, |
| 174 | render_pass_cache, buffer_cache, texture_cache, gpu.ShaderNotify()), | 178 | render_pass_cache, buffer_cache, texture_cache, gpu.ShaderNotify()), |
| 175 | query_cache{*this, cpu_memory_, device, scheduler}, | ||
| 176 | accelerate_dma(buffer_cache, texture_cache, scheduler), | 179 | accelerate_dma(buffer_cache, texture_cache, scheduler), |
| 177 | fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler), | 180 | fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler), |
| 178 | wfi_event(device.GetLogical().CreateEvent()) { | 181 | wfi_event(device.GetLogical().CreateEvent()) { |
| @@ -189,14 +192,7 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { | |||
| 189 | FlushWork(); | 192 | FlushWork(); |
| 190 | gpu_memory->FlushCaching(); | 193 | gpu_memory->FlushCaching(); |
| 191 | 194 | ||
| 192 | #if ANDROID | 195 | query_cache.NotifySegment(true); |
| 193 | if (Settings::IsGPULevelHigh()) { | ||
| 194 | // This is problematic on Android, disable on GPU Normal. | ||
| 195 | query_cache.UpdateCounters(); | ||
| 196 | } | ||
| 197 | #else | ||
| 198 | query_cache.UpdateCounters(); | ||
| 199 | #endif | ||
| 200 | 196 | ||
| 201 | GraphicsPipeline* const pipeline{pipeline_cache.CurrentGraphicsPipeline()}; | 197 | GraphicsPipeline* const pipeline{pipeline_cache.CurrentGraphicsPipeline()}; |
| 202 | if (!pipeline) { | 198 | if (!pipeline) { |
| @@ -207,13 +203,12 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { | |||
| 207 | pipeline->SetEngine(maxwell3d, gpu_memory); | 203 | pipeline->SetEngine(maxwell3d, gpu_memory); |
| 208 | pipeline->Configure(is_indexed); | 204 | pipeline->Configure(is_indexed); |
| 209 | 205 | ||
| 210 | BeginTransformFeedback(); | ||
| 211 | |||
| 212 | UpdateDynamicStates(); | 206 | UpdateDynamicStates(); |
| 213 | 207 | ||
| 208 | HandleTransformFeedback(); | ||
| 209 | query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, | ||
| 210 | maxwell3d->regs.zpass_pixel_count_enable); | ||
| 214 | draw_func(); | 211 | draw_func(); |
| 215 | |||
| 216 | EndTransformFeedback(); | ||
| 217 | } | 212 | } |
| 218 | 213 | ||
| 219 | void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) { | 214 | void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) { |
| @@ -241,6 +236,14 @@ void RasterizerVulkan::DrawIndirect() { | |||
| 241 | const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer(); | 236 | const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer(); |
| 242 | const auto& buffer = indirect_buffer.first; | 237 | const auto& buffer = indirect_buffer.first; |
| 243 | const auto& offset = indirect_buffer.second; | 238 | const auto& offset = indirect_buffer.second; |
| 239 | if (params.is_byte_count) { | ||
| 240 | scheduler.Record([buffer_obj = buffer->Handle(), offset, | ||
| 241 | stride = params.stride](vk::CommandBuffer cmdbuf) { | ||
| 242 | cmdbuf.DrawIndirectByteCountEXT(1, 0, buffer_obj, offset, 0, | ||
| 243 | static_cast<u32>(stride)); | ||
| 244 | }); | ||
| 245 | return; | ||
| 246 | } | ||
| 244 | if (params.include_count) { | 247 | if (params.include_count) { |
| 245 | const auto count = buffer_cache.GetDrawIndirectCount(); | 248 | const auto count = buffer_cache.GetDrawIndirectCount(); |
| 246 | const auto& draw_buffer = count.first; | 249 | const auto& draw_buffer = count.first; |
| @@ -280,20 +283,15 @@ void RasterizerVulkan::DrawTexture() { | |||
| 280 | SCOPE_EXIT({ gpu.TickWork(); }); | 283 | SCOPE_EXIT({ gpu.TickWork(); }); |
| 281 | FlushWork(); | 284 | FlushWork(); |
| 282 | 285 | ||
| 283 | #if ANDROID | 286 | query_cache.NotifySegment(true); |
| 284 | if (Settings::IsGPULevelHigh()) { | ||
| 285 | // This is problematic on Android, disable on GPU Normal. | ||
| 286 | query_cache.UpdateCounters(); | ||
| 287 | } | ||
| 288 | #else | ||
| 289 | query_cache.UpdateCounters(); | ||
| 290 | #endif | ||
| 291 | 287 | ||
| 292 | texture_cache.SynchronizeGraphicsDescriptors(); | 288 | texture_cache.SynchronizeGraphicsDescriptors(); |
| 293 | texture_cache.UpdateRenderTargets(false); | 289 | texture_cache.UpdateRenderTargets(false); |
| 294 | 290 | ||
| 295 | UpdateDynamicStates(); | 291 | UpdateDynamicStates(); |
| 296 | 292 | ||
| 293 | query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, | ||
| 294 | maxwell3d->regs.zpass_pixel_count_enable); | ||
| 297 | const auto& draw_texture_state = maxwell3d->draw_manager->GetDrawTextureState(); | 295 | const auto& draw_texture_state = maxwell3d->draw_manager->GetDrawTextureState(); |
| 298 | const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler); | 296 | const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler); |
| 299 | const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture); | 297 | const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture); |
| @@ -316,14 +314,9 @@ void RasterizerVulkan::Clear(u32 layer_count) { | |||
| 316 | FlushWork(); | 314 | FlushWork(); |
| 317 | gpu_memory->FlushCaching(); | 315 | gpu_memory->FlushCaching(); |
| 318 | 316 | ||
| 319 | #if ANDROID | 317 | query_cache.NotifySegment(true); |
| 320 | if (Settings::IsGPULevelHigh()) { | 318 | query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, |
| 321 | // This is problematic on Android, disable on GPU Normal. | 319 | maxwell3d->regs.zpass_pixel_count_enable); |
| 322 | query_cache.UpdateCounters(); | ||
| 323 | } | ||
| 324 | #else | ||
| 325 | query_cache.UpdateCounters(); | ||
| 326 | #endif | ||
| 327 | 320 | ||
| 328 | auto& regs = maxwell3d->regs; | 321 | auto& regs = maxwell3d->regs; |
| 329 | const bool use_color = regs.clear_surface.R || regs.clear_surface.G || regs.clear_surface.B || | 322 | const bool use_color = regs.clear_surface.R || regs.clear_surface.G || regs.clear_surface.B || |
| @@ -482,13 +475,13 @@ void RasterizerVulkan::DispatchCompute() { | |||
| 482 | scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); | 475 | scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); |
| 483 | } | 476 | } |
| 484 | 477 | ||
| 485 | void RasterizerVulkan::ResetCounter(VideoCore::QueryType type) { | 478 | void RasterizerVulkan::ResetCounter(VideoCommon::QueryType type) { |
| 486 | query_cache.ResetCounter(type); | 479 | query_cache.CounterReset(type); |
| 487 | } | 480 | } |
| 488 | 481 | ||
| 489 | void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCore::QueryType type, | 482 | void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type, |
| 490 | std::optional<u64> timestamp) { | 483 | VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) { |
| 491 | query_cache.Query(gpu_addr, type, timestamp); | 484 | query_cache.CounterReport(gpu_addr, type, flags, payload, subreport); |
| 492 | } | 485 | } |
| 493 | 486 | ||
| 494 | void RasterizerVulkan::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, | 487 | void RasterizerVulkan::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, |
| @@ -669,8 +662,8 @@ void RasterizerVulkan::SignalReference() { | |||
| 669 | fence_manager.SignalReference(); | 662 | fence_manager.SignalReference(); |
| 670 | } | 663 | } |
| 671 | 664 | ||
| 672 | void RasterizerVulkan::ReleaseFences() { | 665 | void RasterizerVulkan::ReleaseFences(bool force) { |
| 673 | fence_manager.WaitPendingFences(); | 666 | fence_manager.WaitPendingFences(force); |
| 674 | } | 667 | } |
| 675 | 668 | ||
| 676 | void RasterizerVulkan::FlushAndInvalidateRegion(VAddr addr, u64 size, | 669 | void RasterizerVulkan::FlushAndInvalidateRegion(VAddr addr, u64 size, |
| @@ -694,6 +687,8 @@ void RasterizerVulkan::WaitForIdle() { | |||
| 694 | flags |= VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT; | 687 | flags |= VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT; |
| 695 | } | 688 | } |
| 696 | 689 | ||
| 690 | query_cache.NotifyWFI(); | ||
| 691 | |||
| 697 | scheduler.RequestOutsideRenderPassOperationContext(); | 692 | scheduler.RequestOutsideRenderPassOperationContext(); |
| 698 | scheduler.Record([event = *wfi_event, flags](vk::CommandBuffer cmdbuf) { | 693 | scheduler.Record([event = *wfi_event, flags](vk::CommandBuffer cmdbuf) { |
| 699 | cmdbuf.SetEvent(event, flags); | 694 | cmdbuf.SetEvent(event, flags); |
| @@ -737,19 +732,7 @@ void RasterizerVulkan::TickFrame() { | |||
| 737 | 732 | ||
| 738 | bool RasterizerVulkan::AccelerateConditionalRendering() { | 733 | bool RasterizerVulkan::AccelerateConditionalRendering() { |
| 739 | gpu_memory->FlushCaching(); | 734 | gpu_memory->FlushCaching(); |
| 740 | if (Settings::IsGPULevelHigh()) { | 735 | return query_cache.AccelerateHostConditionalRendering(); |
| 741 | // TODO(Blinkhawk): Reimplement Host conditional rendering. | ||
| 742 | return false; | ||
| 743 | } | ||
| 744 | // Medium / Low Hack: stub any checks on queries written into the buffer cache. | ||
| 745 | const GPUVAddr condition_address{maxwell3d->regs.render_enable.Address()}; | ||
| 746 | Maxwell::ReportSemaphore::Compare cmp; | ||
| 747 | if (gpu_memory->IsMemoryDirty(condition_address, sizeof(cmp), | ||
| 748 | VideoCommon::CacheType::BufferCache | | ||
| 749 | VideoCommon::CacheType::QueryCache)) { | ||
| 750 | return true; | ||
| 751 | } | ||
| 752 | return false; | ||
| 753 | } | 736 | } |
| 754 | 737 | ||
| 755 | bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src, | 738 | bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src, |
| @@ -795,6 +778,7 @@ bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config, | |||
| 795 | if (!image_view) { | 778 | if (!image_view) { |
| 796 | return false; | 779 | return false; |
| 797 | } | 780 | } |
| 781 | query_cache.NotifySegment(false); | ||
| 798 | screen_info.image = image_view->ImageHandle(); | 782 | screen_info.image = image_view->ImageHandle(); |
| 799 | screen_info.image_view = image_view->Handle(Shader::TextureType::Color2D); | 783 | screen_info.image_view = image_view->Handle(Shader::TextureType::Color2D); |
| 800 | screen_info.width = image_view->size.width; | 784 | screen_info.width = image_view->size.width; |
| @@ -933,31 +917,18 @@ void RasterizerVulkan::UpdateDynamicStates() { | |||
| 933 | } | 917 | } |
| 934 | } | 918 | } |
| 935 | 919 | ||
| 936 | void RasterizerVulkan::BeginTransformFeedback() { | 920 | void RasterizerVulkan::HandleTransformFeedback() { |
| 937 | const auto& regs = maxwell3d->regs; | 921 | const auto& regs = maxwell3d->regs; |
| 938 | if (regs.transform_feedback_enabled == 0) { | ||
| 939 | return; | ||
| 940 | } | ||
| 941 | if (!device.IsExtTransformFeedbackSupported()) { | 922 | if (!device.IsExtTransformFeedbackSupported()) { |
| 942 | LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported"); | 923 | LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported"); |
| 943 | return; | 924 | return; |
| 944 | } | 925 | } |
| 945 | UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) || | 926 | query_cache.CounterEnable(VideoCommon::QueryType::StreamingByteCount, |
| 946 | regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation)); | 927 | regs.transform_feedback_enabled); |
| 947 | scheduler.Record( | 928 | if (regs.transform_feedback_enabled != 0) { |
| 948 | [](vk::CommandBuffer cmdbuf) { cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr); }); | 929 | UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) || |
| 949 | } | 930 | regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation)); |
| 950 | |||
| 951 | void RasterizerVulkan::EndTransformFeedback() { | ||
| 952 | const auto& regs = maxwell3d->regs; | ||
| 953 | if (regs.transform_feedback_enabled == 0) { | ||
| 954 | return; | ||
| 955 | } | ||
| 956 | if (!device.IsExtTransformFeedbackSupported()) { | ||
| 957 | return; | ||
| 958 | } | 931 | } |
| 959 | scheduler.Record( | ||
| 960 | [](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); }); | ||
| 961 | } | 932 | } |
| 962 | 933 | ||
| 963 | void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs) { | 934 | void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs) { |
| @@ -1043,15 +1014,37 @@ void RasterizerVulkan::UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs) { | |||
| 1043 | regs.zeta.format == Tegra::DepthFormat::X8Z24_UNORM || | 1014 | regs.zeta.format == Tegra::DepthFormat::X8Z24_UNORM || |
| 1044 | regs.zeta.format == Tegra::DepthFormat::S8Z24_UNORM || | 1015 | regs.zeta.format == Tegra::DepthFormat::S8Z24_UNORM || |
| 1045 | regs.zeta.format == Tegra::DepthFormat::V8Z24_UNORM; | 1016 | regs.zeta.format == Tegra::DepthFormat::V8Z24_UNORM; |
| 1046 | if (is_d24 && !device.SupportsD24DepthBuffer()) { | 1017 | bool force_unorm = ([&] { |
| 1018 | if (!is_d24 || device.SupportsD24DepthBuffer()) { | ||
| 1019 | return false; | ||
| 1020 | } | ||
| 1021 | if (device.IsExtDepthBiasControlSupported()) { | ||
| 1022 | return true; | ||
| 1023 | } | ||
| 1024 | if (!Settings::values.renderer_amdvlk_depth_bias_workaround) { | ||
| 1025 | return false; | ||
| 1026 | } | ||
| 1047 | // the base formulas can be obtained from here: | 1027 | // the base formulas can be obtained from here: |
| 1048 | // https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias | 1028 | // https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias |
| 1049 | const double rescale_factor = | 1029 | const double rescale_factor = |
| 1050 | static_cast<double>(1ULL << (32 - 24)) / (static_cast<double>(0x1.ep+127)); | 1030 | static_cast<double>(1ULL << (32 - 24)) / (static_cast<double>(0x1.ep+127)); |
| 1051 | units = static_cast<float>(static_cast<double>(units) * rescale_factor); | 1031 | units = static_cast<float>(static_cast<double>(units) * rescale_factor); |
| 1052 | } | 1032 | return false; |
| 1033 | })(); | ||
| 1053 | scheduler.Record([constant = units, clamp = regs.depth_bias_clamp, | 1034 | scheduler.Record([constant = units, clamp = regs.depth_bias_clamp, |
| 1054 | factor = regs.slope_scale_depth_bias](vk::CommandBuffer cmdbuf) { | 1035 | factor = regs.slope_scale_depth_bias, force_unorm, |
| 1036 | precise = device.HasExactDepthBiasControl()](vk::CommandBuffer cmdbuf) { | ||
| 1037 | if (force_unorm) { | ||
| 1038 | VkDepthBiasRepresentationInfoEXT info{ | ||
| 1039 | .sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_REPRESENTATION_INFO_EXT, | ||
| 1040 | .pNext = nullptr, | ||
| 1041 | .depthBiasRepresentation = | ||
| 1042 | VK_DEPTH_BIAS_REPRESENTATION_LEAST_REPRESENTABLE_VALUE_FORCE_UNORM_EXT, | ||
| 1043 | .depthBiasExact = precise ? VK_TRUE : VK_FALSE, | ||
| 1044 | }; | ||
| 1045 | cmdbuf.SetDepthBias(constant, clamp, factor, &info); | ||
| 1046 | return; | ||
| 1047 | } | ||
| 1055 | cmdbuf.SetDepthBias(constant, clamp, factor); | 1048 | cmdbuf.SetDepthBias(constant, clamp, factor); |
| 1056 | }); | 1049 | }); |
| 1057 | } | 1050 | } |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index b31982485..ad069556c 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h | |||
| @@ -84,8 +84,9 @@ public: | |||
| 84 | void DrawTexture() override; | 84 | void DrawTexture() override; |
| 85 | void Clear(u32 layer_count) override; | 85 | void Clear(u32 layer_count) override; |
| 86 | void DispatchCompute() override; | 86 | void DispatchCompute() override; |
| 87 | void ResetCounter(VideoCore::QueryType type) override; | 87 | void ResetCounter(VideoCommon::QueryType type) override; |
| 88 | void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; | 88 | void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type, |
| 89 | VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override; | ||
| 89 | void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; | 90 | void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; |
| 90 | void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; | 91 | void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; |
| 91 | void FlushAll() override; | 92 | void FlushAll() override; |
| @@ -106,7 +107,7 @@ public: | |||
| 106 | void SyncOperation(std::function<void()>&& func) override; | 107 | void SyncOperation(std::function<void()>&& func) override; |
| 107 | void SignalSyncPoint(u32 value) override; | 108 | void SignalSyncPoint(u32 value) override; |
| 108 | void SignalReference() override; | 109 | void SignalReference() override; |
| 109 | void ReleaseFences() override; | 110 | void ReleaseFences(bool force = true) override; |
| 110 | void FlushAndInvalidateRegion( | 111 | void FlushAndInvalidateRegion( |
| 111 | VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; | 112 | VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; |
| 112 | void WaitForIdle() override; | 113 | void WaitForIdle() override; |
| @@ -146,9 +147,7 @@ private: | |||
| 146 | 147 | ||
| 147 | void UpdateDynamicStates(); | 148 | void UpdateDynamicStates(); |
| 148 | 149 | ||
| 149 | void BeginTransformFeedback(); | 150 | void HandleTransformFeedback(); |
| 150 | |||
| 151 | void EndTransformFeedback(); | ||
| 152 | 151 | ||
| 153 | void UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs); | 152 | void UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs); |
| 154 | void UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs); | 153 | void UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs); |
| @@ -195,8 +194,9 @@ private: | |||
| 195 | TextureCache texture_cache; | 194 | TextureCache texture_cache; |
| 196 | BufferCacheRuntime buffer_cache_runtime; | 195 | BufferCacheRuntime buffer_cache_runtime; |
| 197 | BufferCache buffer_cache; | 196 | BufferCache buffer_cache; |
| 198 | PipelineCache pipeline_cache; | 197 | QueryCacheRuntime query_cache_runtime; |
| 199 | QueryCache query_cache; | 198 | QueryCache query_cache; |
| 199 | PipelineCache pipeline_cache; | ||
| 200 | AccelerateDMA accelerate_dma; | 200 | AccelerateDMA accelerate_dma; |
| 201 | FenceManager fence_manager; | 201 | FenceManager fence_manager; |
| 202 | 202 | ||
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 89fd31b4f..3be7837f4 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp | |||
| @@ -243,10 +243,10 @@ void Scheduler::AllocateNewContext() { | |||
| 243 | #if ANDROID | 243 | #if ANDROID |
| 244 | if (Settings::IsGPULevelHigh()) { | 244 | if (Settings::IsGPULevelHigh()) { |
| 245 | // This is problematic on Android, disable on GPU Normal. | 245 | // This is problematic on Android, disable on GPU Normal. |
| 246 | query_cache->UpdateCounters(); | 246 | query_cache->NotifySegment(true); |
| 247 | } | 247 | } |
| 248 | #else | 248 | #else |
| 249 | query_cache->UpdateCounters(); | 249 | query_cache->NotifySegment(true); |
| 250 | #endif | 250 | #endif |
| 251 | } | 251 | } |
| 252 | } | 252 | } |
| @@ -261,11 +261,12 @@ void Scheduler::EndPendingOperations() { | |||
| 261 | #if ANDROID | 261 | #if ANDROID |
| 262 | if (Settings::IsGPULevelHigh()) { | 262 | if (Settings::IsGPULevelHigh()) { |
| 263 | // This is problematic on Android, disable on GPU Normal. | 263 | // This is problematic on Android, disable on GPU Normal. |
| 264 | query_cache->DisableStreams(); | 264 | // query_cache->DisableStreams(); |
| 265 | } | 265 | } |
| 266 | #else | 266 | #else |
| 267 | query_cache->DisableStreams(); | 267 | // query_cache->DisableStreams(); |
| 268 | #endif | 268 | #endif |
| 269 | query_cache->NotifySegment(false); | ||
| 269 | EndRenderPass(); | 270 | EndRenderPass(); |
| 270 | } | 271 | } |
| 271 | 272 | ||
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 475c682eb..da03803aa 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h | |||
| @@ -17,6 +17,11 @@ | |||
| 17 | #include "video_core/renderer_vulkan/vk_master_semaphore.h" | 17 | #include "video_core/renderer_vulkan/vk_master_semaphore.h" |
| 18 | #include "video_core/vulkan_common/vulkan_wrapper.h" | 18 | #include "video_core/vulkan_common/vulkan_wrapper.h" |
| 19 | 19 | ||
| 20 | namespace VideoCommon { | ||
| 21 | template <typename Trait> | ||
| 22 | class QueryCacheBase; | ||
| 23 | } | ||
| 24 | |||
| 20 | namespace Vulkan { | 25 | namespace Vulkan { |
| 21 | 26 | ||
| 22 | class CommandPool; | 27 | class CommandPool; |
| @@ -24,7 +29,8 @@ class Device; | |||
| 24 | class Framebuffer; | 29 | class Framebuffer; |
| 25 | class GraphicsPipeline; | 30 | class GraphicsPipeline; |
| 26 | class StateTracker; | 31 | class StateTracker; |
| 27 | class QueryCache; | 32 | |
| 33 | struct QueryCacheParams; | ||
| 28 | 34 | ||
| 29 | /// The scheduler abstracts command buffer and fence management with an interface that's able to do | 35 | /// The scheduler abstracts command buffer and fence management with an interface that's able to do |
| 30 | /// OpenGL-like operations on Vulkan command buffers. | 36 | /// OpenGL-like operations on Vulkan command buffers. |
| @@ -63,7 +69,7 @@ public: | |||
| 63 | void InvalidateState(); | 69 | void InvalidateState(); |
| 64 | 70 | ||
| 65 | /// Assigns the query cache. | 71 | /// Assigns the query cache. |
| 66 | void SetQueryCache(QueryCache& query_cache_) { | 72 | void SetQueryCache(VideoCommon::QueryCacheBase<QueryCacheParams>& query_cache_) { |
| 67 | query_cache = &query_cache_; | 73 | query_cache = &query_cache_; |
| 68 | } | 74 | } |
| 69 | 75 | ||
| @@ -219,7 +225,7 @@ private: | |||
| 219 | std::unique_ptr<MasterSemaphore> master_semaphore; | 225 | std::unique_ptr<MasterSemaphore> master_semaphore; |
| 220 | std::unique_ptr<CommandPool> command_pool; | 226 | std::unique_ptr<CommandPool> command_pool; |
| 221 | 227 | ||
| 222 | QueryCache* query_cache = nullptr; | 228 | VideoCommon::QueryCacheBase<QueryCacheParams>* query_cache = nullptr; |
| 223 | 229 | ||
| 224 | vk::CommandBuffer current_cmdbuf; | 230 | vk::CommandBuffer current_cmdbuf; |
| 225 | 231 | ||
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index b3e17c332..71fdec809 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp | |||
| @@ -120,19 +120,9 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { | |||
| 120 | return usage; | 120 | return usage; |
| 121 | } | 121 | } |
| 122 | 122 | ||
| 123 | /// Returns the preferred format for a VkImage | ||
| 124 | [[nodiscard]] PixelFormat StorageFormat(PixelFormat format) { | ||
| 125 | switch (format) { | ||
| 126 | case PixelFormat::A8B8G8R8_SRGB: | ||
| 127 | return PixelFormat::A8B8G8R8_UNORM; | ||
| 128 | default: | ||
| 129 | return format; | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | [[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) { | 123 | [[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) { |
| 134 | const PixelFormat format = StorageFormat(info.format); | 124 | const auto format_info = |
| 135 | const auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, format); | 125 | MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, info.format); |
| 136 | VkImageCreateFlags flags{}; | 126 | VkImageCreateFlags flags{}; |
| 137 | if (info.type == ImageType::e2D && info.resources.layers >= 6 && | 127 | if (info.type == ImageType::e2D && info.resources.layers >= 6 && |
| 138 | info.size.width == info.size.height && !device.HasBrokenCubeImageCompability()) { | 128 | info.size.width == info.size.height && !device.HasBrokenCubeImageCompability()) { |
| @@ -157,7 +147,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { | |||
| 157 | .arrayLayers = static_cast<u32>(info.resources.layers), | 147 | .arrayLayers = static_cast<u32>(info.resources.layers), |
| 158 | .samples = ConvertSampleCount(info.num_samples), | 148 | .samples = ConvertSampleCount(info.num_samples), |
| 159 | .tiling = VK_IMAGE_TILING_OPTIMAL, | 149 | .tiling = VK_IMAGE_TILING_OPTIMAL, |
| 160 | .usage = ImageUsageFlags(format_info, format), | 150 | .usage = ImageUsageFlags(format_info, info.format), |
| 161 | .sharingMode = VK_SHARING_MODE_EXCLUSIVE, | 151 | .sharingMode = VK_SHARING_MODE_EXCLUSIVE, |
| 162 | .queueFamilyIndexCount = 0, | 152 | .queueFamilyIndexCount = 0, |
| 163 | .pQueueFamilyIndices = nullptr, | 153 | .pQueueFamilyIndices = nullptr, |
| @@ -186,6 +176,36 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { | |||
| 186 | return allocator.CreateImage(image_ci); | 176 | return allocator.CreateImage(image_ci); |
| 187 | } | 177 | } |
| 188 | 178 | ||
| 179 | [[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image, | ||
| 180 | VkFormat format) { | ||
| 181 | static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{ | ||
| 182 | .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, | ||
| 183 | .pNext = nullptr, | ||
| 184 | .usage = VK_IMAGE_USAGE_STORAGE_BIT, | ||
| 185 | }; | ||
| 186 | return device.CreateImageView(VkImageViewCreateInfo{ | ||
| 187 | .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, | ||
| 188 | .pNext = &storage_image_view_usage_create_info, | ||
| 189 | .flags = 0, | ||
| 190 | .image = image, | ||
| 191 | .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY, | ||
| 192 | .format = format, | ||
| 193 | .components{ | ||
| 194 | .r = VK_COMPONENT_SWIZZLE_IDENTITY, | ||
| 195 | .g = VK_COMPONENT_SWIZZLE_IDENTITY, | ||
| 196 | .b = VK_COMPONENT_SWIZZLE_IDENTITY, | ||
| 197 | .a = VK_COMPONENT_SWIZZLE_IDENTITY, | ||
| 198 | }, | ||
| 199 | .subresourceRange{ | ||
| 200 | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, | ||
| 201 | .baseMipLevel = level, | ||
| 202 | .levelCount = 1, | ||
| 203 | .baseArrayLayer = 0, | ||
| 204 | .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||
| 205 | }, | ||
| 206 | }); | ||
| 207 | } | ||
| 208 | |||
| 189 | [[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) { | 209 | [[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) { |
| 190 | switch (VideoCore::Surface::GetFormatType(format)) { | 210 | switch (VideoCore::Surface::GetFormatType(format)) { |
| 191 | case VideoCore::Surface::SurfaceType::ColorTexture: | 211 | case VideoCore::Surface::SurfaceType::ColorTexture: |
| @@ -600,7 +620,7 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im | |||
| 600 | } | 620 | } |
| 601 | 621 | ||
| 602 | void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle, | 622 | void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle, |
| 603 | bool emulate_bgr565) { | 623 | bool emulate_bgr565, bool emulate_a4b4g4r4) { |
| 604 | switch (format) { | 624 | switch (format) { |
| 605 | case PixelFormat::A1B5G5R5_UNORM: | 625 | case PixelFormat::A1B5G5R5_UNORM: |
| 606 | std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed); | 626 | std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed); |
| @@ -616,6 +636,11 @@ void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4 | |||
| 616 | case PixelFormat::G4R4_UNORM: | 636 | case PixelFormat::G4R4_UNORM: |
| 617 | std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed); | 637 | std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed); |
| 618 | break; | 638 | break; |
| 639 | case PixelFormat::A4B4G4R4_UNORM: | ||
| 640 | if (emulate_a4b4g4r4) { | ||
| 641 | std::ranges::reverse(swizzle); | ||
| 642 | } | ||
| 643 | break; | ||
| 619 | default: | 644 | default: |
| 620 | break; | 645 | break; |
| 621 | } | 646 | } |
| @@ -822,6 +847,10 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched | |||
| 822 | astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, | 847 | astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, |
| 823 | compute_pass_descriptor_queue, memory_allocator); | 848 | compute_pass_descriptor_queue, memory_allocator); |
| 824 | } | 849 | } |
| 850 | if (device.IsStorageImageMultisampleSupported()) { | ||
| 851 | msaa_copy_pass = std::make_unique<MSAACopyPass>( | ||
| 852 | device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue); | ||
| 853 | } | ||
| 825 | if (!device.IsKhrImageFormatListSupported()) { | 854 | if (!device.IsKhrImageFormatListSupported()) { |
| 826 | return; | 855 | return; |
| 827 | } | 856 | } |
| @@ -1044,15 +1073,27 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst | |||
| 1044 | dst_region, src_region, filter, operation); | 1073 | dst_region, src_region, filter, operation); |
| 1045 | return; | 1074 | return; |
| 1046 | } | 1075 | } |
| 1076 | ASSERT(src.format == dst.format); | ||
| 1047 | if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { | 1077 | if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { |
| 1048 | if (!device.IsBlitDepthStencilSupported()) { | 1078 | const auto format = src.format; |
| 1079 | const auto can_blit_depth_stencil = [this, format] { | ||
| 1080 | switch (format) { | ||
| 1081 | case VideoCore::Surface::PixelFormat::D24_UNORM_S8_UINT: | ||
| 1082 | case VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM: | ||
| 1083 | return device.IsBlitDepth24Stencil8Supported(); | ||
| 1084 | case VideoCore::Surface::PixelFormat::D32_FLOAT_S8_UINT: | ||
| 1085 | return device.IsBlitDepth32Stencil8Supported(); | ||
| 1086 | default: | ||
| 1087 | UNREACHABLE(); | ||
| 1088 | } | ||
| 1089 | }(); | ||
| 1090 | if (!can_blit_depth_stencil) { | ||
| 1049 | UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa); | 1091 | UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa); |
| 1050 | blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(), | 1092 | blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(), |
| 1051 | dst_region, src_region, filter, operation); | 1093 | dst_region, src_region, filter, operation); |
| 1052 | return; | 1094 | return; |
| 1053 | } | 1095 | } |
| 1054 | } | 1096 | } |
| 1055 | ASSERT(src.format == dst.format); | ||
| 1056 | ASSERT(!(is_dst_msaa && !is_src_msaa)); | 1097 | ASSERT(!(is_dst_msaa && !is_src_msaa)); |
| 1057 | ASSERT(operation == Fermi2D::Operation::SrcCopy); | 1098 | ASSERT(operation == Fermi2D::Operation::SrcCopy); |
| 1058 | 1099 | ||
| @@ -1278,7 +1319,11 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src, | |||
| 1278 | 1319 | ||
| 1279 | void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src, | 1320 | void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src, |
| 1280 | std::span<const VideoCommon::ImageCopy> copies) { | 1321 | std::span<const VideoCommon::ImageCopy> copies) { |
| 1281 | UNIMPLEMENTED_MSG("Copying images with different samples is not implemented in Vulkan."); | 1322 | const bool msaa_to_non_msaa = src.info.num_samples > 1 && dst.info.num_samples == 1; |
| 1323 | if (msaa_copy_pass) { | ||
| 1324 | return msaa_copy_pass->CopyImage(dst, src, copies, msaa_to_non_msaa); | ||
| 1325 | } | ||
| 1326 | UNIMPLEMENTED_MSG("Copying images with different samples is not supported."); | ||
| 1282 | } | 1327 | } |
| 1283 | 1328 | ||
| 1284 | u64 TextureCacheRuntime::GetDeviceLocalMemory() const { | 1329 | u64 TextureCacheRuntime::GetDeviceLocalMemory() const { |
| @@ -1326,39 +1371,15 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu | |||
| 1326 | if (runtime->device.HasDebuggingToolAttached()) { | 1371 | if (runtime->device.HasDebuggingToolAttached()) { |
| 1327 | original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); | 1372 | original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); |
| 1328 | } | 1373 | } |
| 1329 | static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{ | ||
| 1330 | .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, | ||
| 1331 | .pNext = nullptr, | ||
| 1332 | .usage = VK_IMAGE_USAGE_STORAGE_BIT, | ||
| 1333 | }; | ||
| 1334 | current_image = *original_image; | 1374 | current_image = *original_image; |
| 1375 | storage_image_views.resize(info.resources.levels); | ||
| 1335 | if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() && | 1376 | if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() && |
| 1336 | Settings::values.astc_recompression.GetValue() == | 1377 | Settings::values.astc_recompression.GetValue() == |
| 1337 | Settings::AstcRecompression::Uncompressed) { | 1378 | Settings::AstcRecompression::Uncompressed) { |
| 1338 | const auto& device = runtime->device.GetLogical(); | 1379 | const auto& device = runtime->device.GetLogical(); |
| 1339 | storage_image_views.reserve(info.resources.levels); | ||
| 1340 | for (s32 level = 0; level < info.resources.levels; ++level) { | 1380 | for (s32 level = 0; level < info.resources.levels; ++level) { |
| 1341 | storage_image_views.push_back(device.CreateImageView(VkImageViewCreateInfo{ | 1381 | storage_image_views[level] = |
| 1342 | .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, | 1382 | MakeStorageView(device, level, *original_image, VK_FORMAT_A8B8G8R8_UNORM_PACK32); |
| 1343 | .pNext = &storage_image_view_usage_create_info, | ||
| 1344 | .flags = 0, | ||
| 1345 | .image = *original_image, | ||
| 1346 | .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY, | ||
| 1347 | .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32, | ||
| 1348 | .components{ | ||
| 1349 | .r = VK_COMPONENT_SWIZZLE_IDENTITY, | ||
| 1350 | .g = VK_COMPONENT_SWIZZLE_IDENTITY, | ||
| 1351 | .b = VK_COMPONENT_SWIZZLE_IDENTITY, | ||
| 1352 | .a = VK_COMPONENT_SWIZZLE_IDENTITY, | ||
| 1353 | }, | ||
| 1354 | .subresourceRange{ | ||
| 1355 | .aspectMask = aspect_mask, | ||
| 1356 | .baseMipLevel = static_cast<u32>(level), | ||
| 1357 | .levelCount = 1, | ||
| 1358 | .baseArrayLayer = 0, | ||
| 1359 | .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||
| 1360 | }, | ||
| 1361 | })); | ||
| 1362 | } | 1383 | } |
| 1363 | } | 1384 | } |
| 1364 | } | 1385 | } |
| @@ -1489,6 +1510,17 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferIm | |||
| 1489 | DownloadMemory(buffers, offsets, copies); | 1510 | DownloadMemory(buffers, offsets, copies); |
| 1490 | } | 1511 | } |
| 1491 | 1512 | ||
| 1513 | VkImageView Image::StorageImageView(s32 level) noexcept { | ||
| 1514 | auto& view = storage_image_views[level]; | ||
| 1515 | if (!view) { | ||
| 1516 | const auto format_info = | ||
| 1517 | MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format); | ||
| 1518 | view = | ||
| 1519 | MakeStorageView(runtime->device.GetLogical(), level, current_image, format_info.format); | ||
| 1520 | } | ||
| 1521 | return *view; | ||
| 1522 | } | ||
| 1523 | |||
| 1492 | bool Image::IsRescaled() const noexcept { | 1524 | bool Image::IsRescaled() const noexcept { |
| 1493 | return True(flags & ImageFlagBits::Rescaled); | 1525 | return True(flags & ImageFlagBits::Rescaled); |
| 1494 | } | 1526 | } |
| @@ -1626,8 +1658,8 @@ bool Image::NeedsScaleHelper() const { | |||
| 1626 | return true; | 1658 | return true; |
| 1627 | } | 1659 | } |
| 1628 | static constexpr auto OPTIMAL_FORMAT = FormatType::Optimal; | 1660 | static constexpr auto OPTIMAL_FORMAT = FormatType::Optimal; |
| 1629 | const PixelFormat format = StorageFormat(info.format); | 1661 | const auto vk_format = |
| 1630 | const auto vk_format = MaxwellToVK::SurfaceFormat(device, OPTIMAL_FORMAT, false, format).format; | 1662 | MaxwellToVK::SurfaceFormat(device, OPTIMAL_FORMAT, false, info.format).format; |
| 1631 | const auto blit_usage = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; | 1663 | const auto blit_usage = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; |
| 1632 | const bool needs_blit_helper = !device.IsFormatSupported(vk_format, blit_usage, OPTIMAL_FORMAT); | 1664 | const bool needs_blit_helper = !device.IsFormatSupported(vk_format, blit_usage, OPTIMAL_FORMAT); |
| 1633 | return needs_blit_helper; | 1665 | return needs_blit_helper; |
| @@ -1649,7 +1681,8 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI | |||
| 1649 | }; | 1681 | }; |
| 1650 | if (!info.IsRenderTarget()) { | 1682 | if (!info.IsRenderTarget()) { |
| 1651 | swizzle = info.Swizzle(); | 1683 | swizzle = info.Swizzle(); |
| 1652 | TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565()); | 1684 | TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565(), |
| 1685 | !device->IsExt4444FormatsSupported()); | ||
| 1653 | if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { | 1686 | if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { |
| 1654 | std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed); | 1687 | std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed); |
| 1655 | } | 1688 | } |
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 565ce19a9..d6c5a15cc 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h | |||
| @@ -117,6 +117,7 @@ public: | |||
| 117 | BlitImageHelper& blit_image_helper; | 117 | BlitImageHelper& blit_image_helper; |
| 118 | RenderPassCache& render_pass_cache; | 118 | RenderPassCache& render_pass_cache; |
| 119 | std::optional<ASTCDecoderPass> astc_decoder_pass; | 119 | std::optional<ASTCDecoderPass> astc_decoder_pass; |
| 120 | std::unique_ptr<MSAACopyPass> msaa_copy_pass; | ||
| 120 | const Settings::ResolutionScalingInfo& resolution; | 121 | const Settings::ResolutionScalingInfo& resolution; |
| 121 | std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats; | 122 | std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats; |
| 122 | 123 | ||
| @@ -161,15 +162,13 @@ public: | |||
| 161 | return aspect_mask; | 162 | return aspect_mask; |
| 162 | } | 163 | } |
| 163 | 164 | ||
| 164 | [[nodiscard]] VkImageView StorageImageView(s32 level) const noexcept { | ||
| 165 | return *storage_image_views[level]; | ||
| 166 | } | ||
| 167 | |||
| 168 | /// Returns true when the image is already initialized and mark it as initialized | 165 | /// Returns true when the image is already initialized and mark it as initialized |
| 169 | [[nodiscard]] bool ExchangeInitialization() noexcept { | 166 | [[nodiscard]] bool ExchangeInitialization() noexcept { |
| 170 | return std::exchange(initialized, true); | 167 | return std::exchange(initialized, true); |
| 171 | } | 168 | } |
| 172 | 169 | ||
| 170 | VkImageView StorageImageView(s32 level) noexcept; | ||
| 171 | |||
| 173 | bool IsRescaled() const noexcept; | 172 | bool IsRescaled() const noexcept; |
| 174 | 173 | ||
| 175 | bool ScaleUp(bool ignore = false); | 174 | bool ScaleUp(bool ignore = false); |
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 617417040..3960b135a 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp | |||
| @@ -76,6 +76,11 @@ constexpr std::array VK_FORMAT_R32G32B32_SFLOAT{ | |||
| 76 | VK_FORMAT_UNDEFINED, | 76 | VK_FORMAT_UNDEFINED, |
| 77 | }; | 77 | }; |
| 78 | 78 | ||
| 79 | constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{ | ||
| 80 | VK_FORMAT_R4G4B4A4_UNORM_PACK16, | ||
| 81 | VK_FORMAT_UNDEFINED, | ||
| 82 | }; | ||
| 83 | |||
| 79 | } // namespace Alternatives | 84 | } // namespace Alternatives |
| 80 | 85 | ||
| 81 | enum class NvidiaArchitecture { | 86 | enum class NvidiaArchitecture { |
| @@ -110,6 +115,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) { | |||
| 110 | return Alternatives::R8G8B8_SSCALED.data(); | 115 | return Alternatives::R8G8B8_SSCALED.data(); |
| 111 | case VK_FORMAT_R32G32B32_SFLOAT: | 116 | case VK_FORMAT_R32G32B32_SFLOAT: |
| 112 | return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data(); | 117 | return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data(); |
| 118 | case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT: | ||
| 119 | return Alternatives::VK_FORMAT_A4B4G4R4_UNORM_PACK16.data(); | ||
| 113 | default: | 120 | default: |
| 114 | return nullptr; | 121 | return nullptr; |
| 115 | } | 122 | } |
| @@ -238,6 +245,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica | |||
| 238 | VK_FORMAT_R32_SINT, | 245 | VK_FORMAT_R32_SINT, |
| 239 | VK_FORMAT_R32_UINT, | 246 | VK_FORMAT_R32_UINT, |
| 240 | VK_FORMAT_R4G4B4A4_UNORM_PACK16, | 247 | VK_FORMAT_R4G4B4A4_UNORM_PACK16, |
| 248 | VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT, | ||
| 241 | VK_FORMAT_R4G4_UNORM_PACK8, | 249 | VK_FORMAT_R4G4_UNORM_PACK8, |
| 242 | VK_FORMAT_R5G5B5A1_UNORM_PACK16, | 250 | VK_FORMAT_R5G5B5A1_UNORM_PACK16, |
| 243 | VK_FORMAT_R5G6B5_UNORM_PACK16, | 251 | VK_FORMAT_R5G6B5_UNORM_PACK16, |
| @@ -420,7 +428,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 420 | first_next = &diagnostics_nv; | 428 | first_next = &diagnostics_nv; |
| 421 | } | 429 | } |
| 422 | 430 | ||
| 423 | is_blit_depth_stencil_supported = TestDepthStencilBlits(); | 431 | is_blit_depth24_stencil8_supported = TestDepthStencilBlits(VK_FORMAT_D24_UNORM_S8_UINT); |
| 432 | is_blit_depth32_stencil8_supported = TestDepthStencilBlits(VK_FORMAT_D32_SFLOAT_S8_UINT); | ||
| 424 | is_optimal_astc_supported = ComputeIsOptimalAstcSupported(); | 433 | is_optimal_astc_supported = ComputeIsOptimalAstcSupported(); |
| 425 | is_warp_potentially_bigger = !extensions.subgroup_size_control || | 434 | is_warp_potentially_bigger = !extensions.subgroup_size_control || |
| 426 | properties.subgroup_size_control.maxSubgroupSize > GuestWarpSize; | 435 | properties.subgroup_size_control.maxSubgroupSize > GuestWarpSize; |
| @@ -774,14 +783,13 @@ bool Device::ComputeIsOptimalAstcSupported() const { | |||
| 774 | return true; | 783 | return true; |
| 775 | } | 784 | } |
| 776 | 785 | ||
| 777 | bool Device::TestDepthStencilBlits() const { | 786 | bool Device::TestDepthStencilBlits(VkFormat format) const { |
| 778 | static constexpr VkFormatFeatureFlags required_features = | 787 | static constexpr VkFormatFeatureFlags required_features = |
| 779 | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; | 788 | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; |
| 780 | const auto test_features = [](VkFormatProperties props) { | 789 | const auto test_features = [](VkFormatProperties props) { |
| 781 | return (props.optimalTilingFeatures & required_features) == required_features; | 790 | return (props.optimalTilingFeatures & required_features) == required_features; |
| 782 | }; | 791 | }; |
| 783 | return test_features(format_properties.at(VK_FORMAT_D32_SFLOAT_S8_UINT)) && | 792 | return test_features(format_properties.at(format)); |
| 784 | test_features(format_properties.at(VK_FORMAT_D24_UNORM_S8_UINT)); | ||
| 785 | } | 793 | } |
| 786 | 794 | ||
| 787 | bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, | 795 | bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, |
| @@ -1051,6 +1059,13 @@ void Device::RemoveUnsuitableExtensions() { | |||
| 1051 | RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color, | 1059 | RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color, |
| 1052 | VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); | 1060 | VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); |
| 1053 | 1061 | ||
| 1062 | // VK_EXT_depth_bias_control | ||
| 1063 | extensions.depth_bias_control = | ||
| 1064 | features.depth_bias_control.depthBiasControl && | ||
| 1065 | features.depth_bias_control.leastRepresentableValueForceUnormRepresentation; | ||
| 1066 | RemoveExtensionFeatureIfUnsuitable(extensions.depth_bias_control, features.depth_bias_control, | ||
| 1067 | VK_EXT_DEPTH_BIAS_CONTROL_EXTENSION_NAME); | ||
| 1068 | |||
| 1054 | // VK_EXT_depth_clip_control | 1069 | // VK_EXT_depth_clip_control |
| 1055 | extensions.depth_clip_control = features.depth_clip_control.depthClipControl; | 1070 | extensions.depth_clip_control = features.depth_clip_control.depthClipControl; |
| 1056 | RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control, | 1071 | RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control, |
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 488fdd313..9be612392 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h | |||
| @@ -41,10 +41,12 @@ VK_DEFINE_HANDLE(VmaAllocator) | |||
| 41 | // Define all features which may be used by the implementation and require an extension here. | 41 | // Define all features which may be used by the implementation and require an extension here. |
| 42 | #define FOR_EACH_VK_FEATURE_EXT(FEATURE) \ | 42 | #define FOR_EACH_VK_FEATURE_EXT(FEATURE) \ |
| 43 | FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \ | 43 | FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \ |
| 44 | FEATURE(EXT, DepthBiasControl, DEPTH_BIAS_CONTROL, depth_bias_control) \ | ||
| 44 | FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \ | 45 | FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \ |
| 45 | FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \ | 46 | FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \ |
| 46 | FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \ | 47 | FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \ |
| 47 | FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \ | 48 | FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \ |
| 49 | FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \ | ||
| 48 | FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \ | 50 | FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \ |
| 49 | FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \ | 51 | FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \ |
| 50 | FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \ | 52 | FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \ |
| @@ -60,6 +62,7 @@ VK_DEFINE_HANDLE(VmaAllocator) | |||
| 60 | 62 | ||
| 61 | // Define miscellaneous extensions which may be used by the implementation here. | 63 | // Define miscellaneous extensions which may be used by the implementation here. |
| 62 | #define FOR_EACH_VK_EXTENSION(EXTENSION) \ | 64 | #define FOR_EACH_VK_EXTENSION(EXTENSION) \ |
| 65 | EXTENSION(EXT, CONDITIONAL_RENDERING, conditional_rendering) \ | ||
| 63 | EXTENSION(EXT, CONSERVATIVE_RASTERIZATION, conservative_rasterization) \ | 66 | EXTENSION(EXT, CONSERVATIVE_RASTERIZATION, conservative_rasterization) \ |
| 64 | EXTENSION(EXT, DEPTH_RANGE_UNRESTRICTED, depth_range_unrestricted) \ | 67 | EXTENSION(EXT, DEPTH_RANGE_UNRESTRICTED, depth_range_unrestricted) \ |
| 65 | EXTENSION(EXT, MEMORY_BUDGET, memory_budget) \ | 68 | EXTENSION(EXT, MEMORY_BUDGET, memory_budget) \ |
| @@ -92,11 +95,14 @@ VK_DEFINE_HANDLE(VmaAllocator) | |||
| 92 | 95 | ||
| 93 | // Define extensions where the absence of the extension may result in a degraded experience. | 96 | // Define extensions where the absence of the extension may result in a degraded experience. |
| 94 | #define FOR_EACH_VK_RECOMMENDED_EXTENSION(EXTENSION_NAME) \ | 97 | #define FOR_EACH_VK_RECOMMENDED_EXTENSION(EXTENSION_NAME) \ |
| 98 | EXTENSION_NAME(VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME) \ | ||
| 95 | EXTENSION_NAME(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME) \ | 99 | EXTENSION_NAME(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME) \ |
| 100 | EXTENSION_NAME(VK_EXT_DEPTH_BIAS_CONTROL_EXTENSION_NAME) \ | ||
| 96 | EXTENSION_NAME(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME) \ | 101 | EXTENSION_NAME(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME) \ |
| 97 | EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) \ | 102 | EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) \ |
| 98 | EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \ | 103 | EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \ |
| 99 | EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \ | 104 | EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \ |
| 105 | EXTENSION_NAME(VK_EXT_4444_FORMATS_EXTENSION_NAME) \ | ||
| 100 | EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \ | 106 | EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \ |
| 101 | EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \ | 107 | EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \ |
| 102 | EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \ | 108 | EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \ |
| @@ -143,7 +149,11 @@ VK_DEFINE_HANDLE(VmaAllocator) | |||
| 143 | // Define features where the absence of the feature may result in a degraded experience. | 149 | // Define features where the absence of the feature may result in a degraded experience. |
| 144 | #define FOR_EACH_VK_RECOMMENDED_FEATURE(FEATURE_NAME) \ | 150 | #define FOR_EACH_VK_RECOMMENDED_FEATURE(FEATURE_NAME) \ |
| 145 | FEATURE_NAME(custom_border_color, customBorderColors) \ | 151 | FEATURE_NAME(custom_border_color, customBorderColors) \ |
| 152 | FEATURE_NAME(depth_bias_control, depthBiasControl) \ | ||
| 153 | FEATURE_NAME(depth_bias_control, leastRepresentableValueForceUnormRepresentation) \ | ||
| 154 | FEATURE_NAME(depth_bias_control, depthBiasExact) \ | ||
| 146 | FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \ | 155 | FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \ |
| 156 | FEATURE_NAME(format_a4b4g4r4, formatA4B4G4R4) \ | ||
| 147 | FEATURE_NAME(index_type_uint8, indexTypeUint8) \ | 157 | FEATURE_NAME(index_type_uint8, indexTypeUint8) \ |
| 148 | FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \ | 158 | FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \ |
| 149 | FEATURE_NAME(provoking_vertex, provokingVertexLast) \ | 159 | FEATURE_NAME(provoking_vertex, provokingVertexLast) \ |
| @@ -319,6 +329,11 @@ public: | |||
| 319 | return features.shader_float16_int8.shaderInt8; | 329 | return features.shader_float16_int8.shaderInt8; |
| 320 | } | 330 | } |
| 321 | 331 | ||
| 332 | /// Returns true if the device supports binding multisample images as storage images. | ||
| 333 | bool IsStorageImageMultisampleSupported() const { | ||
| 334 | return features.features.shaderStorageImageMultisample; | ||
| 335 | } | ||
| 336 | |||
| 322 | /// Returns true if the device warp size can potentially be bigger than guest's warp size. | 337 | /// Returns true if the device warp size can potentially be bigger than guest's warp size. |
| 323 | bool IsWarpSizePotentiallyBiggerThanGuest() const { | 338 | bool IsWarpSizePotentiallyBiggerThanGuest() const { |
| 324 | return is_warp_potentially_bigger; | 339 | return is_warp_potentially_bigger; |
| @@ -359,9 +374,14 @@ public: | |||
| 359 | return features.features.depthBounds; | 374 | return features.features.depthBounds; |
| 360 | } | 375 | } |
| 361 | 376 | ||
| 362 | /// Returns true when blitting from and to depth stencil images is supported. | 377 | /// Returns true when blitting from and to D24S8 images is supported. |
| 363 | bool IsBlitDepthStencilSupported() const { | 378 | bool IsBlitDepth24Stencil8Supported() const { |
| 364 | return is_blit_depth_stencil_supported; | 379 | return is_blit_depth24_stencil8_supported; |
| 380 | } | ||
| 381 | |||
| 382 | /// Returns true when blitting from and to D32S8 images is supported. | ||
| 383 | bool IsBlitDepth32Stencil8Supported() const { | ||
| 384 | return is_blit_depth32_stencil8_supported; | ||
| 365 | } | 385 | } |
| 366 | 386 | ||
| 367 | /// Returns true if the device supports VK_NV_viewport_swizzle. | 387 | /// Returns true if the device supports VK_NV_viewport_swizzle. |
| @@ -449,6 +469,11 @@ public: | |||
| 449 | return extensions.depth_clip_control; | 469 | return extensions.depth_clip_control; |
| 450 | } | 470 | } |
| 451 | 471 | ||
| 472 | /// Returns true if the device supports VK_EXT_depth_bias_control. | ||
| 473 | bool IsExtDepthBiasControlSupported() const { | ||
| 474 | return extensions.depth_bias_control; | ||
| 475 | } | ||
| 476 | |||
| 452 | /// Returns true if the device supports VK_EXT_shader_viewport_index_layer. | 477 | /// Returns true if the device supports VK_EXT_shader_viewport_index_layer. |
| 453 | bool IsExtShaderViewportIndexLayerSupported() const { | 478 | bool IsExtShaderViewportIndexLayerSupported() const { |
| 454 | return extensions.shader_viewport_index_layer; | 479 | return extensions.shader_viewport_index_layer; |
| @@ -488,6 +513,11 @@ public: | |||
| 488 | return extensions.extended_dynamic_state3; | 513 | return extensions.extended_dynamic_state3; |
| 489 | } | 514 | } |
| 490 | 515 | ||
| 516 | /// Returns true if the device supports VK_EXT_4444_formats. | ||
| 517 | bool IsExt4444FormatsSupported() const { | ||
| 518 | return features.format_a4b4g4r4.formatA4B4G4R4; | ||
| 519 | } | ||
| 520 | |||
| 491 | /// Returns true if the device supports VK_EXT_extended_dynamic_state3. | 521 | /// Returns true if the device supports VK_EXT_extended_dynamic_state3. |
| 492 | bool IsExtExtendedDynamicState3BlendingSupported() const { | 522 | bool IsExtExtendedDynamicState3BlendingSupported() const { |
| 493 | return dynamic_state3_blending; | 523 | return dynamic_state3_blending; |
| @@ -528,6 +558,10 @@ public: | |||
| 528 | return extensions.shader_atomic_int64; | 558 | return extensions.shader_atomic_int64; |
| 529 | } | 559 | } |
| 530 | 560 | ||
| 561 | bool IsExtConditionalRendering() const { | ||
| 562 | return extensions.conditional_rendering; | ||
| 563 | } | ||
| 564 | |||
| 531 | bool HasTimelineSemaphore() const; | 565 | bool HasTimelineSemaphore() const; |
| 532 | 566 | ||
| 533 | /// Returns the minimum supported version of SPIR-V. | 567 | /// Returns the minimum supported version of SPIR-V. |
| @@ -600,6 +634,10 @@ public: | |||
| 600 | return features.robustness2.nullDescriptor; | 634 | return features.robustness2.nullDescriptor; |
| 601 | } | 635 | } |
| 602 | 636 | ||
| 637 | bool HasExactDepthBiasControl() const { | ||
| 638 | return features.depth_bias_control.depthBiasExact; | ||
| 639 | } | ||
| 640 | |||
| 603 | u32 GetMaxVertexInputAttributes() const { | 641 | u32 GetMaxVertexInputAttributes() const { |
| 604 | return properties.properties.limits.maxVertexInputAttributes; | 642 | return properties.properties.limits.maxVertexInputAttributes; |
| 605 | } | 643 | } |
| @@ -666,7 +704,7 @@ private: | |||
| 666 | bool ComputeIsOptimalAstcSupported() const; | 704 | bool ComputeIsOptimalAstcSupported() const; |
| 667 | 705 | ||
| 668 | /// Returns true if the device natively supports blitting depth stencil images. | 706 | /// Returns true if the device natively supports blitting depth stencil images. |
| 669 | bool TestDepthStencilBlits() const; | 707 | bool TestDepthStencilBlits(VkFormat format) const; |
| 670 | 708 | ||
| 671 | private: | 709 | private: |
| 672 | VkInstance instance; ///< Vulkan instance. | 710 | VkInstance instance; ///< Vulkan instance. |
| @@ -730,25 +768,26 @@ private: | |||
| 730 | VkPhysicalDeviceProperties2 properties2{}; | 768 | VkPhysicalDeviceProperties2 properties2{}; |
| 731 | 769 | ||
| 732 | // Misc features | 770 | // Misc features |
| 733 | bool is_optimal_astc_supported{}; ///< Support for all guest ASTC formats. | 771 | bool is_optimal_astc_supported{}; ///< Support for all guest ASTC formats. |
| 734 | bool is_blit_depth_stencil_supported{}; ///< Support for blitting from and to depth stencil. | 772 | bool is_blit_depth24_stencil8_supported{}; ///< Support for blitting from and to D24S8. |
| 735 | bool is_warp_potentially_bigger{}; ///< Host warp size can be bigger than guest. | 773 | bool is_blit_depth32_stencil8_supported{}; ///< Support for blitting from and to D32S8. |
| 736 | bool is_integrated{}; ///< Is GPU an iGPU. | 774 | bool is_warp_potentially_bigger{}; ///< Host warp size can be bigger than guest. |
| 737 | bool is_virtual{}; ///< Is GPU a virtual GPU. | 775 | bool is_integrated{}; ///< Is GPU an iGPU. |
| 738 | bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device. | 776 | bool is_virtual{}; ///< Is GPU a virtual GPU. |
| 739 | bool has_broken_compute{}; ///< Compute shaders can cause crashes | 777 | bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device. |
| 740 | bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit | 778 | bool has_broken_compute{}; ///< Compute shaders can cause crashes |
| 741 | bool has_renderdoc{}; ///< Has RenderDoc attached | 779 | bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit |
| 742 | bool has_nsight_graphics{}; ///< Has Nsight Graphics attached | 780 | bool has_renderdoc{}; ///< Has RenderDoc attached |
| 743 | bool supports_d24_depth{}; ///< Supports D24 depth buffers. | 781 | bool has_nsight_graphics{}; ///< Has Nsight Graphics attached |
| 744 | bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting. | 782 | bool supports_d24_depth{}; ///< Supports D24 depth buffers. |
| 745 | bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation | 783 | bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting. |
| 746 | bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format. | 784 | bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation |
| 747 | bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3. | 785 | bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format. |
| 748 | bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3. | 786 | bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3. |
| 749 | bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow. | 787 | bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3. |
| 750 | u64 device_access_memory{}; ///< Total size of device local memory in bytes. | 788 | bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow. |
| 751 | u32 sets_per_pool{}; ///< Sets per Description Pool | 789 | u64 device_access_memory{}; ///< Total size of device local memory in bytes. |
| 790 | u32 sets_per_pool{}; ///< Sets per Description Pool | ||
| 752 | 791 | ||
| 753 | // Telemetry parameters | 792 | // Telemetry parameters |
| 754 | std::set<std::string, std::less<>> supported_extensions; ///< Reported Vulkan extensions. | 793 | std::set<std::string, std::less<>> supported_extensions; ///< Reported Vulkan extensions. |
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index c3f388d89..2f3254a97 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp | |||
| @@ -75,6 +75,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { | |||
| 75 | X(vkBeginCommandBuffer); | 75 | X(vkBeginCommandBuffer); |
| 76 | X(vkBindBufferMemory); | 76 | X(vkBindBufferMemory); |
| 77 | X(vkBindImageMemory); | 77 | X(vkBindImageMemory); |
| 78 | X(vkCmdBeginConditionalRenderingEXT); | ||
| 78 | X(vkCmdBeginQuery); | 79 | X(vkCmdBeginQuery); |
| 79 | X(vkCmdBeginRenderPass); | 80 | X(vkCmdBeginRenderPass); |
| 80 | X(vkCmdBeginTransformFeedbackEXT); | 81 | X(vkCmdBeginTransformFeedbackEXT); |
| @@ -91,6 +92,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { | |||
| 91 | X(vkCmdCopyBufferToImage); | 92 | X(vkCmdCopyBufferToImage); |
| 92 | X(vkCmdCopyImage); | 93 | X(vkCmdCopyImage); |
| 93 | X(vkCmdCopyImageToBuffer); | 94 | X(vkCmdCopyImageToBuffer); |
| 95 | X(vkCmdCopyQueryPoolResults); | ||
| 94 | X(vkCmdDispatch); | 96 | X(vkCmdDispatch); |
| 95 | X(vkCmdDispatchIndirect); | 97 | X(vkCmdDispatchIndirect); |
| 96 | X(vkCmdDraw); | 98 | X(vkCmdDraw); |
| @@ -99,6 +101,8 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { | |||
| 99 | X(vkCmdDrawIndexedIndirect); | 101 | X(vkCmdDrawIndexedIndirect); |
| 100 | X(vkCmdDrawIndirectCount); | 102 | X(vkCmdDrawIndirectCount); |
| 101 | X(vkCmdDrawIndexedIndirectCount); | 103 | X(vkCmdDrawIndexedIndirectCount); |
| 104 | X(vkCmdDrawIndirectByteCountEXT); | ||
| 105 | X(vkCmdEndConditionalRenderingEXT); | ||
| 102 | X(vkCmdEndQuery); | 106 | X(vkCmdEndQuery); |
| 103 | X(vkCmdEndRenderPass); | 107 | X(vkCmdEndRenderPass); |
| 104 | X(vkCmdEndTransformFeedbackEXT); | 108 | X(vkCmdEndTransformFeedbackEXT); |
| @@ -109,6 +113,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { | |||
| 109 | X(vkCmdPushDescriptorSetWithTemplateKHR); | 113 | X(vkCmdPushDescriptorSetWithTemplateKHR); |
| 110 | X(vkCmdSetBlendConstants); | 114 | X(vkCmdSetBlendConstants); |
| 111 | X(vkCmdSetDepthBias); | 115 | X(vkCmdSetDepthBias); |
| 116 | X(vkCmdSetDepthBias2EXT); | ||
| 112 | X(vkCmdSetDepthBounds); | 117 | X(vkCmdSetDepthBounds); |
| 113 | X(vkCmdSetEvent); | 118 | X(vkCmdSetEvent); |
| 114 | X(vkCmdSetScissor); | 119 | X(vkCmdSetScissor); |
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 049fa8038..1e3c0fa64 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h | |||
| @@ -185,6 +185,7 @@ struct DeviceDispatch : InstanceDispatch { | |||
| 185 | PFN_vkBeginCommandBuffer vkBeginCommandBuffer{}; | 185 | PFN_vkBeginCommandBuffer vkBeginCommandBuffer{}; |
| 186 | PFN_vkBindBufferMemory vkBindBufferMemory{}; | 186 | PFN_vkBindBufferMemory vkBindBufferMemory{}; |
| 187 | PFN_vkBindImageMemory vkBindImageMemory{}; | 187 | PFN_vkBindImageMemory vkBindImageMemory{}; |
| 188 | PFN_vkCmdBeginConditionalRenderingEXT vkCmdBeginConditionalRenderingEXT{}; | ||
| 188 | PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT{}; | 189 | PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT{}; |
| 189 | PFN_vkCmdBeginQuery vkCmdBeginQuery{}; | 190 | PFN_vkCmdBeginQuery vkCmdBeginQuery{}; |
| 190 | PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass{}; | 191 | PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass{}; |
| @@ -202,6 +203,7 @@ struct DeviceDispatch : InstanceDispatch { | |||
| 202 | PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage{}; | 203 | PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage{}; |
| 203 | PFN_vkCmdCopyImage vkCmdCopyImage{}; | 204 | PFN_vkCmdCopyImage vkCmdCopyImage{}; |
| 204 | PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; | 205 | PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; |
| 206 | PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults{}; | ||
| 205 | PFN_vkCmdDispatch vkCmdDispatch{}; | 207 | PFN_vkCmdDispatch vkCmdDispatch{}; |
| 206 | PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{}; | 208 | PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{}; |
| 207 | PFN_vkCmdDraw vkCmdDraw{}; | 209 | PFN_vkCmdDraw vkCmdDraw{}; |
| @@ -210,6 +212,8 @@ struct DeviceDispatch : InstanceDispatch { | |||
| 210 | PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect{}; | 212 | PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect{}; |
| 211 | PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount{}; | 213 | PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount{}; |
| 212 | PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount{}; | 214 | PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount{}; |
| 215 | PFN_vkCmdDrawIndirectByteCountEXT vkCmdDrawIndirectByteCountEXT{}; | ||
| 216 | PFN_vkCmdEndConditionalRenderingEXT vkCmdEndConditionalRenderingEXT{}; | ||
| 213 | PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{}; | 217 | PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{}; |
| 214 | PFN_vkCmdEndQuery vkCmdEndQuery{}; | 218 | PFN_vkCmdEndQuery vkCmdEndQuery{}; |
| 215 | PFN_vkCmdEndRenderPass vkCmdEndRenderPass{}; | 219 | PFN_vkCmdEndRenderPass vkCmdEndRenderPass{}; |
| @@ -222,6 +226,7 @@ struct DeviceDispatch : InstanceDispatch { | |||
| 222 | PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{}; | 226 | PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{}; |
| 223 | PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{}; | 227 | PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{}; |
| 224 | PFN_vkCmdSetDepthBias vkCmdSetDepthBias{}; | 228 | PFN_vkCmdSetDepthBias vkCmdSetDepthBias{}; |
| 229 | PFN_vkCmdSetDepthBias2EXT vkCmdSetDepthBias2EXT{}; | ||
| 225 | PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds{}; | 230 | PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds{}; |
| 226 | PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT{}; | 231 | PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT{}; |
| 227 | PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{}; | 232 | PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{}; |
| @@ -1182,6 +1187,13 @@ public: | |||
| 1182 | count_offset, draw_count, stride); | 1187 | count_offset, draw_count, stride); |
| 1183 | } | 1188 | } |
| 1184 | 1189 | ||
| 1190 | void DrawIndirectByteCountEXT(u32 instance_count, u32 first_instance, VkBuffer counter_buffer, | ||
| 1191 | VkDeviceSize counter_buffer_offset, u32 counter_offset, | ||
| 1192 | u32 stride) { | ||
| 1193 | dld->vkCmdDrawIndirectByteCountEXT(handle, instance_count, first_instance, counter_buffer, | ||
| 1194 | counter_buffer_offset, counter_offset, stride); | ||
| 1195 | } | ||
| 1196 | |||
| 1185 | void ClearAttachments(Span<VkClearAttachment> attachments, | 1197 | void ClearAttachments(Span<VkClearAttachment> attachments, |
| 1186 | Span<VkClearRect> rects) const noexcept { | 1198 | Span<VkClearRect> rects) const noexcept { |
| 1187 | dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(), | 1199 | dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(), |
| @@ -1270,6 +1282,13 @@ public: | |||
| 1270 | regions.data()); | 1282 | regions.data()); |
| 1271 | } | 1283 | } |
| 1272 | 1284 | ||
| 1285 | void CopyQueryPoolResults(VkQueryPool query_pool, u32 first_query, u32 query_count, | ||
| 1286 | VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize stride, | ||
| 1287 | VkQueryResultFlags flags) const noexcept { | ||
| 1288 | dld->vkCmdCopyQueryPoolResults(handle, query_pool, first_query, query_count, dst_buffer, | ||
| 1289 | dst_offset, stride, flags); | ||
| 1290 | } | ||
| 1291 | |||
| 1273 | void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size, | 1292 | void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size, |
| 1274 | u32 data) const noexcept { | 1293 | u32 data) const noexcept { |
| 1275 | dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data); | 1294 | dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data); |
| @@ -1315,6 +1334,18 @@ public: | |||
| 1315 | dld->vkCmdSetDepthBias(handle, constant_factor, clamp, slope_factor); | 1334 | dld->vkCmdSetDepthBias(handle, constant_factor, clamp, slope_factor); |
| 1316 | } | 1335 | } |
| 1317 | 1336 | ||
| 1337 | void SetDepthBias(float constant_factor, float clamp, float slope_factor, | ||
| 1338 | VkDepthBiasRepresentationInfoEXT* extra) const noexcept { | ||
| 1339 | VkDepthBiasInfoEXT info{ | ||
| 1340 | .sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_INFO_EXT, | ||
| 1341 | .pNext = extra, | ||
| 1342 | .depthBiasConstantFactor = constant_factor, | ||
| 1343 | .depthBiasClamp = clamp, | ||
| 1344 | .depthBiasSlopeFactor = slope_factor, | ||
| 1345 | }; | ||
| 1346 | dld->vkCmdSetDepthBias2EXT(handle, &info); | ||
| 1347 | } | ||
| 1348 | |||
| 1318 | void SetDepthBounds(float min_depth_bounds, float max_depth_bounds) const noexcept { | 1349 | void SetDepthBounds(float min_depth_bounds, float max_depth_bounds) const noexcept { |
| 1319 | dld->vkCmdSetDepthBounds(handle, min_depth_bounds, max_depth_bounds); | 1350 | dld->vkCmdSetDepthBounds(handle, min_depth_bounds, max_depth_bounds); |
| 1320 | } | 1351 | } |
| @@ -1448,6 +1479,15 @@ public: | |||
| 1448 | counter_buffers, counter_buffer_offsets); | 1479 | counter_buffers, counter_buffer_offsets); |
| 1449 | } | 1480 | } |
| 1450 | 1481 | ||
| 1482 | void BeginConditionalRenderingEXT( | ||
| 1483 | const VkConditionalRenderingBeginInfoEXT& info) const noexcept { | ||
| 1484 | dld->vkCmdBeginConditionalRenderingEXT(handle, &info); | ||
| 1485 | } | ||
| 1486 | |||
| 1487 | void EndConditionalRenderingEXT() const noexcept { | ||
| 1488 | dld->vkCmdEndConditionalRenderingEXT(handle); | ||
| 1489 | } | ||
| 1490 | |||
| 1451 | void BeginDebugUtilsLabelEXT(const char* label, std::span<float, 4> color) const noexcept { | 1491 | void BeginDebugUtilsLabelEXT(const char* label, std::span<float, 4> color) const noexcept { |
| 1452 | const VkDebugUtilsLabelEXT label_info{ | 1492 | const VkDebugUtilsLabelEXT label_info{ |
| 1453 | .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, | 1493 | .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index d32aa9615..adb7b332f 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -1551,6 +1551,7 @@ void GMainWindow::ConnectMenuEvents() { | |||
| 1551 | // Tools | 1551 | // Tools |
| 1552 | connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, | 1552 | connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, |
| 1553 | ReinitializeKeyBehavior::Warning)); | 1553 | ReinitializeKeyBehavior::Warning)); |
| 1554 | connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit); | ||
| 1554 | connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot); | 1555 | connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot); |
| 1555 | 1556 | ||
| 1556 | // TAS | 1557 | // TAS |
| @@ -1590,6 +1591,8 @@ void GMainWindow::UpdateMenuState() { | |||
| 1590 | } | 1591 | } |
| 1591 | 1592 | ||
| 1592 | multiplayer_state->UpdateNotificationStatus(); | 1593 | multiplayer_state->UpdateNotificationStatus(); |
| 1594 | |||
| 1595 | ui->action_Load_Mii_Edit->setEnabled(CheckFirmwarePresence()); | ||
| 1593 | } | 1596 | } |
| 1594 | 1597 | ||
| 1595 | void GMainWindow::OnDisplayTitleBars(bool show) { | 1598 | void GMainWindow::OnDisplayTitleBars(bool show) { |
| @@ -3110,10 +3113,9 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 3110 | QFuture<InstallResult> future; | 3113 | QFuture<InstallResult> future; |
| 3111 | InstallResult result; | 3114 | InstallResult result; |
| 3112 | 3115 | ||
| 3113 | if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || | 3116 | if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { |
| 3114 | file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { | ||
| 3115 | 3117 | ||
| 3116 | future = QtConcurrent::run([this, &file] { return InstallNSPXCI(file); }); | 3118 | future = QtConcurrent::run([this, &file] { return InstallNSP(file); }); |
| 3117 | 3119 | ||
| 3118 | while (!future.isFinished()) { | 3120 | while (!future.isFinished()) { |
| 3119 | QCoreApplication::processEvents(); | 3121 | QCoreApplication::processEvents(); |
| @@ -3172,7 +3174,7 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 3172 | ui->action_Install_File_NAND->setEnabled(true); | 3174 | ui->action_Install_File_NAND->setEnabled(true); |
| 3173 | } | 3175 | } |
| 3174 | 3176 | ||
| 3175 | InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { | 3177 | InstallResult GMainWindow::InstallNSP(const QString& filename) { |
| 3176 | const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, | 3178 | const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, |
| 3177 | const FileSys::VirtualFile& dest, std::size_t block_size) { | 3179 | const FileSys::VirtualFile& dest, std::size_t block_size) { |
| 3178 | if (src == nullptr || dest == nullptr) { | 3180 | if (src == nullptr || dest == nullptr) { |
| @@ -3206,9 +3208,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { | |||
| 3206 | return InstallResult::Failure; | 3208 | return InstallResult::Failure; |
| 3207 | } | 3209 | } |
| 3208 | } else { | 3210 | } else { |
| 3209 | const auto xci = std::make_shared<FileSys::XCI>( | 3211 | return InstallResult::Failure; |
| 3210 | vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); | ||
| 3211 | nsp = xci->GetSecurePartitionNSP(); | ||
| 3212 | } | 3212 | } |
| 3213 | 3213 | ||
| 3214 | if (nsp->GetStatus() != Loader::ResultStatus::Success) { | 3214 | if (nsp->GetStatus() != Loader::ResultStatus::Success) { |
| @@ -4134,6 +4134,27 @@ void GMainWindow::OnToggleStatusBar() { | |||
| 4134 | statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); | 4134 | statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); |
| 4135 | } | 4135 | } |
| 4136 | 4136 | ||
| 4137 | void GMainWindow::OnMiiEdit() { | ||
| 4138 | constexpr u64 MiiEditId = 0x0100000000001009ull; | ||
| 4139 | auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); | ||
| 4140 | if (!bis_system) { | ||
| 4141 | QMessageBox::warning(this, tr("No firmware available"), | ||
| 4142 | tr("Please install the firmware to use the Mii editor.")); | ||
| 4143 | return; | ||
| 4144 | } | ||
| 4145 | |||
| 4146 | auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program); | ||
| 4147 | if (!mii_applet_nca) { | ||
| 4148 | QMessageBox::warning(this, tr("Mii Edit Applet"), | ||
| 4149 | tr("Mii editor is not available. Please reinstall firmware.")); | ||
| 4150 | return; | ||
| 4151 | } | ||
| 4152 | |||
| 4153 | const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath())); | ||
| 4154 | UISettings::values.roms_path = QFileInfo(filename).path(); | ||
| 4155 | BootGame(filename); | ||
| 4156 | } | ||
| 4157 | |||
| 4137 | void GMainWindow::OnCaptureScreenshot() { | 4158 | void GMainWindow::OnCaptureScreenshot() { |
| 4138 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { | 4159 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { |
| 4139 | return; | 4160 | return; |
| @@ -4540,6 +4561,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { | |||
| 4540 | if (behavior == ReinitializeKeyBehavior::Warning) { | 4561 | if (behavior == ReinitializeKeyBehavior::Warning) { |
| 4541 | game_list->PopulateAsync(UISettings::values.game_dirs); | 4562 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 4542 | } | 4563 | } |
| 4564 | |||
| 4565 | UpdateMenuState(); | ||
| 4543 | } | 4566 | } |
| 4544 | 4567 | ||
| 4545 | bool GMainWindow::CheckSystemArchiveDecryption() { | 4568 | bool GMainWindow::CheckSystemArchiveDecryption() { |
| @@ -4561,6 +4584,22 @@ bool GMainWindow::CheckSystemArchiveDecryption() { | |||
| 4561 | return mii_nca->GetRomFS().get() != nullptr; | 4584 | return mii_nca->GetRomFS().get() != nullptr; |
| 4562 | } | 4585 | } |
| 4563 | 4586 | ||
| 4587 | bool GMainWindow::CheckFirmwarePresence() { | ||
| 4588 | constexpr u64 MiiEditId = 0x0100000000001009ull; | ||
| 4589 | |||
| 4590 | auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); | ||
| 4591 | if (!bis_system) { | ||
| 4592 | return false; | ||
| 4593 | } | ||
| 4594 | |||
| 4595 | auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program); | ||
| 4596 | if (!mii_applet_nca) { | ||
| 4597 | return false; | ||
| 4598 | } | ||
| 4599 | |||
| 4600 | return true; | ||
| 4601 | } | ||
| 4602 | |||
| 4564 | bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, | 4603 | bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, |
| 4565 | u64* selected_title_id, u8* selected_content_record_type) { | 4604 | u64* selected_title_id, u8* selected_content_record_type) { |
| 4566 | using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>; | 4605 | using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>; |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index cf191f698..ba318eb11 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -365,6 +365,7 @@ private slots: | |||
| 365 | void ResetWindowSize720(); | 365 | void ResetWindowSize720(); |
| 366 | void ResetWindowSize900(); | 366 | void ResetWindowSize900(); |
| 367 | void ResetWindowSize1080(); | 367 | void ResetWindowSize1080(); |
| 368 | void OnMiiEdit(); | ||
| 368 | void OnCaptureScreenshot(); | 369 | void OnCaptureScreenshot(); |
| 369 | void OnReinitializeKeys(ReinitializeKeyBehavior behavior); | 370 | void OnReinitializeKeys(ReinitializeKeyBehavior behavior); |
| 370 | void OnLanguageChanged(const QString& locale); | 371 | void OnLanguageChanged(const QString& locale); |
| @@ -386,7 +387,7 @@ private: | |||
| 386 | void RemoveCacheStorage(u64 program_id); | 387 | void RemoveCacheStorage(u64 program_id); |
| 387 | bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, | 388 | bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, |
| 388 | u64* selected_title_id, u8* selected_content_record_type); | 389 | u64* selected_title_id, u8* selected_content_record_type); |
| 389 | InstallResult InstallNSPXCI(const QString& filename); | 390 | InstallResult InstallNSP(const QString& filename); |
| 390 | InstallResult InstallNCA(const QString& filename); | 391 | InstallResult InstallNCA(const QString& filename); |
| 391 | void MigrateConfigFiles(); | 392 | void MigrateConfigFiles(); |
| 392 | void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, | 393 | void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, |
| @@ -409,6 +410,7 @@ private: | |||
| 409 | void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); | 410 | void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); |
| 410 | bool CheckDarkMode(); | 411 | bool CheckDarkMode(); |
| 411 | bool CheckSystemArchiveDecryption(); | 412 | bool CheckSystemArchiveDecryption(); |
| 413 | bool CheckFirmwarePresence(); | ||
| 412 | void ConfigureFilesystemProvider(const std::string& filepath); | 414 | void ConfigureFilesystemProvider(const std::string& filepath); |
| 413 | 415 | ||
| 414 | QString GetTasStateDescription() const; | 416 | QString GetTasStateDescription() const; |
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index e54d7d75d..91d6c5ef3 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui | |||
| @@ -150,6 +150,8 @@ | |||
| 150 | <addaction name="action_Rederive"/> | 150 | <addaction name="action_Rederive"/> |
| 151 | <addaction name="action_Verify_installed_contents"/> | 151 | <addaction name="action_Verify_installed_contents"/> |
| 152 | <addaction name="separator"/> | 152 | <addaction name="separator"/> |
| 153 | <addaction name="action_Load_Mii_Edit"/> | ||
| 154 | <addaction name="separator"/> | ||
| 153 | <addaction name="action_Capture_Screenshot"/> | 155 | <addaction name="action_Capture_Screenshot"/> |
| 154 | <addaction name="menuTAS"/> | 156 | <addaction name="menuTAS"/> |
| 155 | </widget> | 157 | </widget> |
| @@ -217,7 +219,7 @@ | |||
| 217 | </action> | 219 | </action> |
| 218 | <action name="action_Verify_installed_contents"> | 220 | <action name="action_Verify_installed_contents"> |
| 219 | <property name="text"> | 221 | <property name="text"> |
| 220 | <string>Verify installed contents</string> | 222 | <string>&Verify Installed Contents</string> |
| 221 | </property> | 223 | </property> |
| 222 | </action> | 224 | </action> |
| 223 | <action name="action_About"> | 225 | <action name="action_About"> |
| @@ -368,6 +370,11 @@ | |||
| 368 | <string>&Capture Screenshot</string> | 370 | <string>&Capture Screenshot</string> |
| 369 | </property> | 371 | </property> |
| 370 | </action> | 372 | </action> |
| 373 | <action name="action_Load_Mii_Edit"> | ||
| 374 | <property name="text"> | ||
| 375 | <string>Open &Mii Editor</string> | ||
| 376 | </property> | ||
| 377 | </action> | ||
| 371 | <action name="action_Configure_Tas"> | 378 | <action name="action_Configure_Tas"> |
| 372 | <property name="text"> | 379 | <property name="text"> |
| 373 | <string>&Configure TAS...</string> | 380 | <string>&Configure TAS...</string> |