diff options
Diffstat (limited to 'src')
207 files changed, 9776 insertions, 1854 deletions
diff --git a/src/android/.gitignore b/src/android/.gitignore index 121cc8484..ff7121acd 100644 --- a/src/android/.gitignore +++ b/src/android/.gitignore | |||
| @@ -63,3 +63,6 @@ fastlane/Preview.html | |||
| 63 | fastlane/screenshots | 63 | fastlane/screenshots |
| 64 | fastlane/test_output | 64 | fastlane/test_output |
| 65 | fastlane/readme.md | 65 | fastlane/readme.md |
| 66 | |||
| 67 | # Autogenerated library for vulkan validation layers | ||
| 68 | libVkLayer_khronos_validation.so | ||
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index fe79a701c..84a3308b7 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts | |||
| @@ -77,13 +77,30 @@ android { | |||
| 77 | buildConfigField("String", "BRANCH", "\"${getBranch()}\"") | 77 | buildConfigField("String", "BRANCH", "\"${getBranch()}\"") |
| 78 | } | 78 | } |
| 79 | 79 | ||
| 80 | val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE") | ||
| 81 | if (keystoreFile != null) { | ||
| 82 | signingConfigs { | ||
| 83 | create("release") { | ||
| 84 | storeFile = file(keystoreFile) | ||
| 85 | storePassword = System.getenv("ANDROID_KEYSTORE_PASS") | ||
| 86 | keyAlias = System.getenv("ANDROID_KEY_ALIAS") | ||
| 87 | keyPassword = System.getenv("ANDROID_KEYSTORE_PASS") | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 80 | // Define build types, which are orthogonal to product flavors. | 92 | // Define build types, which are orthogonal to product flavors. |
| 81 | buildTypes { | 93 | buildTypes { |
| 82 | 94 | ||
| 83 | // Signed by release key, allowing for upload to Play Store. | 95 | // Signed by release key, allowing for upload to Play Store. |
| 84 | release { | 96 | release { |
| 97 | signingConfig = if (keystoreFile != null) { | ||
| 98 | signingConfigs.getByName("release") | ||
| 99 | } else { | ||
| 100 | signingConfigs.getByName("debug") | ||
| 101 | } | ||
| 102 | |||
| 85 | resValue("string", "app_name_suffixed", "yuzu") | 103 | resValue("string", "app_name_suffixed", "yuzu") |
| 86 | signingConfig = signingConfigs.getByName("debug") | ||
| 87 | isMinifyEnabled = true | 104 | isMinifyEnabled = true |
| 88 | isDebuggable = false | 105 | isDebuggable = false |
| 89 | proguardFiles( | 106 | proguardFiles( |
| @@ -197,7 +214,7 @@ dependencies { | |||
| 197 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") | 214 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") |
| 198 | implementation("io.coil-kt:coil:2.2.2") | 215 | implementation("io.coil-kt:coil:2.2.2") |
| 199 | implementation("androidx.core:core-splashscreen:1.0.1") | 216 | implementation("androidx.core:core-splashscreen:1.0.1") |
| 200 | implementation("androidx.window:window:1.1.0") | 217 | implementation("androidx.window:window:1.2.0-beta03") |
| 201 | implementation("org.ini4j:ini4j:0.5.4") | 218 | implementation("org.ini4j:ini4j:0.5.4") |
| 202 | implementation("androidx.constraintlayout:constraintlayout:2.1.4") | 219 | implementation("androidx.constraintlayout:constraintlayout:2.1.4") |
| 203 | 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/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index d4ae39661..e96a2059b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.activities | 4 | package org.yuzu.yuzu_emu.activities |
| 5 | 5 | ||
| 6 | import android.annotation.SuppressLint | ||
| 6 | import android.app.Activity | 7 | import android.app.Activity |
| 7 | import android.app.PendingIntent | 8 | import android.app.PendingIntent |
| 8 | import android.app.PictureInPictureParams | 9 | import android.app.PictureInPictureParams |
| @@ -397,6 +398,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 397 | } | 398 | } |
| 398 | } | 399 | } |
| 399 | 400 | ||
| 401 | @SuppressLint("UnspecifiedRegisterReceiverFlag") | ||
| 400 | override fun onPictureInPictureModeChanged( | 402 | override fun onPictureInPictureModeChanged( |
| 401 | isInPictureInPictureMode: Boolean, | 403 | isInPictureInPictureMode: Boolean, |
| 402 | newConfig: Configuration | 404 | newConfig: Configuration |
| @@ -409,7 +411,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 409 | addAction(actionMute) | 411 | addAction(actionMute) |
| 410 | addAction(actionUnmute) | 412 | addAction(actionUnmute) |
| 411 | }.also { | 413 | }.also { |
| 412 | registerReceiver(pictureInPictureReceiver, it) | 414 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| 415 | registerReceiver(pictureInPictureReceiver, it, RECEIVER_EXPORTED) | ||
| 416 | } else { | ||
| 417 | registerReceiver(pictureInPictureReceiver, it) | ||
| 418 | } | ||
| 413 | } | 419 | } |
| 414 | } else { | 420 | } else { |
| 415 | try { | 421 | try { |
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 3e6c157c7..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 | |||
| @@ -15,9 +15,9 @@ import android.net.Uri | |||
| 15 | import android.os.Bundle | 15 | import android.os.Bundle |
| 16 | import android.os.Handler | 16 | import android.os.Handler |
| 17 | import android.os.Looper | 17 | import android.os.Looper |
| 18 | import android.util.Rational | ||
| 19 | import android.view.* | 18 | import android.view.* |
| 20 | import android.widget.TextView | 19 | import android.widget.TextView |
| 20 | import android.widget.Toast | ||
| 21 | import androidx.activity.OnBackPressedCallback | 21 | import androidx.activity.OnBackPressedCallback |
| 22 | import androidx.appcompat.widget.PopupMenu | 22 | import androidx.appcompat.widget.PopupMenu |
| 23 | import androidx.core.content.res.ResourcesCompat | 23 | import androidx.core.content.res.ResourcesCompat |
| @@ -54,6 +54,7 @@ import org.yuzu.yuzu_emu.model.Game | |||
| 54 | import org.yuzu.yuzu_emu.model.EmulationViewModel | 54 | import org.yuzu.yuzu_emu.model.EmulationViewModel |
| 55 | import org.yuzu.yuzu_emu.overlay.InputOverlay | 55 | import org.yuzu.yuzu_emu.overlay.InputOverlay |
| 56 | import org.yuzu.yuzu_emu.utils.* | 56 | import org.yuzu.yuzu_emu.utils.* |
| 57 | import java.lang.NullPointerException | ||
| 57 | 58 | ||
| 58 | class EmulationFragment : Fragment(), SurfaceHolder.Callback { | 59 | class EmulationFragment : Fragment(), SurfaceHolder.Callback { |
| 59 | private lateinit var preferences: SharedPreferences | 60 | private lateinit var preferences: SharedPreferences |
| @@ -105,10 +106,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 105 | null | 106 | null |
| 106 | } | 107 | } |
| 107 | } | 108 | } |
| 108 | game = if (args.game != null) { | 109 | |
| 109 | args.game!! | 110 | try { |
| 110 | } else { | 111 | game = if (args.game != null) { |
| 111 | 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 | ||
| 112 | } | 124 | } |
| 113 | 125 | ||
| 114 | // 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. |
| @@ -132,6 +144,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 132 | // This is using the correct scope, lint is just acting up | 144 | // This is using the correct scope, lint is just acting up |
| 133 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | 145 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") |
| 134 | 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 | |||
| 135 | binding.surfaceEmulation.holder.addCallback(this) | 152 | binding.surfaceEmulation.holder.addCallback(this) |
| 136 | binding.showFpsText.setTextColor(Color.YELLOW) | 153 | binding.showFpsText.setTextColor(Color.YELLOW) |
| 137 | binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } | 154 | binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } |
| @@ -287,24 +304,23 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 287 | 304 | ||
| 288 | override fun onConfigurationChanged(newConfig: Configuration) { | 305 | override fun onConfigurationChanged(newConfig: Configuration) { |
| 289 | super.onConfigurationChanged(newConfig) | 306 | super.onConfigurationChanged(newConfig) |
| 307 | if (_binding == null) { | ||
| 308 | return | ||
| 309 | } | ||
| 310 | |||
| 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.VISIBLE | ||
| 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) { |
| @@ -328,7 +344,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 328 | } | 344 | } |
| 329 | 345 | ||
| 330 | override fun onPause() { | 346 | override fun onPause() { |
| 331 | if (emulationState.isRunning) { | 347 | if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) { |
| 332 | emulationState.pause() | 348 | emulationState.pause() |
| 333 | } | 349 | } |
| 334 | super.onPause() | 350 | super.onPause() |
| @@ -394,16 +410,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 394 | } | 410 | } |
| 395 | 411 | ||
| 396 | private fun updateScreenLayout() { | 412 | private fun updateScreenLayout() { |
| 397 | binding.surfaceEmulation.setAspectRatio( | 413 | binding.surfaceEmulation.setAspectRatio(null) |
| 398 | when (IntSetting.RENDERER_ASPECT_RATIO.int) { | ||
| 399 | 0 -> Rational(16, 9) | ||
| 400 | 1 -> Rational(4, 3) | ||
| 401 | 2 -> Rational(21, 9) | ||
| 402 | 3 -> Rational(16, 10) | ||
| 403 | 4 -> null // Stretch | ||
| 404 | else -> Rational(16, 9) | ||
| 405 | } | ||
| 406 | ) | ||
| 407 | emulationActivity?.buildPictureInPictureParams() | 414 | emulationActivity?.buildPictureInPictureParams() |
| 408 | updateOrientation() | 415 | updateOrientation() |
| 409 | } | 416 | } |
| @@ -693,7 +700,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 693 | private class EmulationState(private val gamePath: String) { | 700 | private class EmulationState(private val gamePath: String) { |
| 694 | private var state: State | 701 | private var state: State |
| 695 | private var surface: Surface? = null | 702 | private var surface: Surface? = null |
| 696 | private var runWhenSurfaceIsValid = false | ||
| 697 | 703 | ||
| 698 | init { | 704 | init { |
| 699 | // Starting state is stopped. | 705 | // Starting state is stopped. |
| @@ -751,8 +757,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 751 | // If the surface is set, run now. Otherwise, wait for it to get set. | 757 | // If the surface is set, run now. Otherwise, wait for it to get set. |
| 752 | if (surface != null) { | 758 | if (surface != null) { |
| 753 | runWithValidSurface() | 759 | runWithValidSurface() |
| 754 | } else { | ||
| 755 | runWhenSurfaceIsValid = true | ||
| 756 | } | 760 | } |
| 757 | } | 761 | } |
| 758 | 762 | ||
| @@ -760,7 +764,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 760 | @Synchronized | 764 | @Synchronized |
| 761 | fun newSurface(surface: Surface?) { | 765 | fun newSurface(surface: Surface?) { |
| 762 | this.surface = surface | 766 | this.surface = surface |
| 763 | if (runWhenSurfaceIsValid) { | 767 | if (this.surface != null) { |
| 764 | runWithValidSurface() | 768 | runWithValidSurface() |
| 765 | } | 769 | } |
| 766 | } | 770 | } |
| @@ -788,10 +792,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 788 | } | 792 | } |
| 789 | 793 | ||
| 790 | private fun runWithValidSurface() { | 794 | private fun runWithValidSurface() { |
| 791 | runWhenSurfaceIsValid = false | 795 | NativeLibrary.surfaceChanged(surface) |
| 792 | when (state) { | 796 | when (state) { |
| 793 | State.STOPPED -> { | 797 | State.STOPPED -> { |
| 794 | NativeLibrary.surfaceChanged(surface) | ||
| 795 | val emulationThread = Thread({ | 798 | val emulationThread = Thread({ |
| 796 | Log.debug("[EmulationFragment] Starting emulation thread.") | 799 | Log.debug("[EmulationFragment] Starting emulation thread.") |
| 797 | NativeLibrary.run(gamePath) | 800 | NativeLibrary.run(gamePath) |
| @@ -801,7 +804,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 801 | 804 | ||
| 802 | State.PAUSED -> { | 805 | State.PAUSED -> { |
| 803 | Log.debug("[EmulationFragment] Resuming emulation.") | 806 | Log.debug("[EmulationFragment] Resuming emulation.") |
| 804 | NativeLibrary.surfaceChanged(surface) | ||
| 805 | NativeLibrary.unpauseEmulation() | 807 | NativeLibrary.unpauseEmulation() |
| 806 | } | 808 | } |
| 807 | 809 | ||
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 ea8eb073a..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 | |||
| @@ -5,7 +5,11 @@ package org.yuzu.yuzu_emu.fragments | |||
| 5 | 5 | ||
| 6 | import android.app.Dialog | 6 | import android.app.Dialog |
| 7 | import android.os.Bundle | 7 | import android.os.Bundle |
| 8 | import android.view.LayoutInflater | ||
| 9 | import android.view.View | ||
| 10 | import android.view.ViewGroup | ||
| 8 | import android.widget.Toast | 11 | import android.widget.Toast |
| 12 | import androidx.appcompat.app.AlertDialog | ||
| 9 | import androidx.appcompat.app.AppCompatActivity | 13 | import androidx.appcompat.app.AppCompatActivity |
| 10 | import androidx.fragment.app.DialogFragment | 14 | import androidx.fragment.app.DialogFragment |
| 11 | import androidx.fragment.app.activityViewModels | 15 | import androidx.fragment.app.activityViewModels |
| @@ -15,63 +19,112 @@ import androidx.lifecycle.lifecycleScope | |||
| 15 | import androidx.lifecycle.repeatOnLifecycle | 19 | import androidx.lifecycle.repeatOnLifecycle |
| 16 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 20 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 17 | import kotlinx.coroutines.launch | 21 | import kotlinx.coroutines.launch |
| 22 | import org.yuzu.yuzu_emu.R | ||
| 18 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | 23 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding |
| 19 | import org.yuzu.yuzu_emu.model.TaskViewModel | 24 | import org.yuzu.yuzu_emu.model.TaskViewModel |
| 20 | 25 | ||
| 21 | class IndeterminateProgressDialogFragment : DialogFragment() { | 26 | class IndeterminateProgressDialogFragment : DialogFragment() { |
| 22 | private val taskViewModel: TaskViewModel by activityViewModels() | 27 | private val taskViewModel: TaskViewModel by activityViewModels() |
| 23 | 28 | ||
| 29 | private lateinit var binding: DialogProgressBarBinding | ||
| 30 | |||
| 24 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | 31 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { |
| 25 | val titleId = requireArguments().getInt(TITLE) | 32 | val titleId = requireArguments().getInt(TITLE) |
| 33 | val cancellable = requireArguments().getBoolean(CANCELLABLE) | ||
| 26 | 34 | ||
| 27 | val progressBinding = DialogProgressBarBinding.inflate(layoutInflater) | 35 | binding = DialogProgressBarBinding.inflate(layoutInflater) |
| 28 | progressBinding.progressBar.isIndeterminate = true | 36 | binding.progressBar.isIndeterminate = true |
| 29 | val dialog = MaterialAlertDialogBuilder(requireContext()) | 37 | val dialog = MaterialAlertDialogBuilder(requireContext()) |
| 30 | .setTitle(titleId) | 38 | .setTitle(titleId) |
| 31 | .setView(progressBinding.root) | 39 | .setView(binding.root) |
| 32 | .create() | 40 | |
| 33 | dialog.setCanceledOnTouchOutside(false) | 41 | if (cancellable) { |
| 34 | 42 | dialog.setNegativeButton(android.R.string.cancel, null) | |
| 35 | viewLifecycleOwner.lifecycleScope.launch { | 43 | } |
| 36 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 44 | |
| 37 | taskViewModel.isComplete.collect { | 45 | val alertDialog = dialog.create() |
| 38 | if (it) { | 46 | alertDialog.setCanceledOnTouchOutside(false) |
| 39 | dialog.dismiss() | 47 | |
| 40 | when (val result = taskViewModel.result.value) { | 48 | if (!taskViewModel.isRunning.value) { |
| 41 | is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG) | 49 | taskViewModel.runTask() |
| 42 | .show() | 50 | } |
| 43 | 51 | return alertDialog | |
| 44 | is MessageDialogFragment -> result.show( | 52 | } |
| 45 | requireActivity().supportFragmentManager, | 53 | |
| 46 | MessageDialogFragment.TAG | 54 | override fun onCreateView( |
| 47 | ) | 55 | inflater: LayoutInflater, |
| 56 | container: ViewGroup?, | ||
| 57 | savedInstanceState: Bundle? | ||
| 58 | ): View { | ||
| 59 | return binding.root | ||
| 60 | } | ||
| 61 | |||
| 62 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| 63 | super.onViewCreated(view, savedInstanceState) | ||
| 64 | viewLifecycleOwner.lifecycleScope.apply { | ||
| 65 | launch { | ||
| 66 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 67 | taskViewModel.isComplete.collect { | ||
| 68 | if (it) { | ||
| 69 | dismiss() | ||
| 70 | when (val result = taskViewModel.result.value) { | ||
| 71 | is String -> Toast.makeText( | ||
| 72 | requireContext(), | ||
| 73 | result, | ||
| 74 | Toast.LENGTH_LONG | ||
| 75 | ).show() | ||
| 76 | |||
| 77 | is MessageDialogFragment -> result.show( | ||
| 78 | requireActivity().supportFragmentManager, | ||
| 79 | MessageDialogFragment.TAG | ||
| 80 | ) | ||
| 81 | } | ||
| 82 | taskViewModel.clear() | ||
| 83 | } | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
| 87 | launch { | ||
| 88 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 89 | taskViewModel.cancelled.collect { | ||
| 90 | if (it) { | ||
| 91 | dialog?.setTitle(R.string.cancelling) | ||
| 48 | } | 92 | } |
| 49 | taskViewModel.clear() | ||
| 50 | } | 93 | } |
| 51 | } | 94 | } |
| 52 | } | 95 | } |
| 53 | } | 96 | } |
| 97 | } | ||
| 54 | 98 | ||
| 55 | if (!taskViewModel.isRunning.value) { | 99 | // By default, the ProgressDialog will immediately dismiss itself upon a button being pressed. |
| 56 | taskViewModel.runTask() | 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) | ||
| 57 | } | 108 | } |
| 58 | return dialog | ||
| 59 | } | 109 | } |
| 60 | 110 | ||
| 61 | companion object { | 111 | companion object { |
| 62 | const val TAG = "IndeterminateProgressDialogFragment" | 112 | const val TAG = "IndeterminateProgressDialogFragment" |
| 63 | 113 | ||
| 64 | private const val TITLE = "Title" | 114 | private const val TITLE = "Title" |
| 115 | private const val CANCELLABLE = "Cancellable" | ||
| 65 | 116 | ||
| 66 | fun newInstance( | 117 | fun newInstance( |
| 67 | activity: AppCompatActivity, | 118 | activity: AppCompatActivity, |
| 68 | titleId: Int, | 119 | titleId: Int, |
| 120 | cancellable: Boolean = false, | ||
| 69 | task: () -> Any | 121 | task: () -> Any |
| 70 | ): IndeterminateProgressDialogFragment { | 122 | ): IndeterminateProgressDialogFragment { |
| 71 | val dialog = IndeterminateProgressDialogFragment() | 123 | val dialog = IndeterminateProgressDialogFragment() |
| 72 | val args = Bundle() | 124 | val args = Bundle() |
| 73 | ViewModelProvider(activity)[TaskViewModel::class.java].task = task | 125 | ViewModelProvider(activity)[TaskViewModel::class.java].task = task |
| 74 | args.putInt(TITLE, titleId) | 126 | args.putInt(TITLE, titleId) |
| 127 | args.putBoolean(CANCELLABLE, cancellable) | ||
| 75 | dialog.arguments = args | 128 | dialog.arguments = args |
| 76 | return dialog | 129 | return dialog |
| 77 | } | 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/config.cpp b/src/android/app/src/main/jni/config.cpp index 34b425cb4..81120ab0f 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp | |||
| @@ -282,7 +282,7 @@ void Config::ReadValues() { | |||
| 282 | std::stringstream ss(title_list); | 282 | std::stringstream ss(title_list); |
| 283 | std::string line; | 283 | std::string line; |
| 284 | while (std::getline(ss, line, '|')) { | 284 | while (std::getline(ss, line, '|')) { |
| 285 | const auto title_id = std::stoul(line, nullptr, 16); | 285 | const auto title_id = std::strtoul(line.c_str(), nullptr, 16); |
| 286 | const auto disabled_list = config->Get("AddOns", "disabled_" + line, ""); | 286 | const auto disabled_list = config->Get("AddOns", "disabled_" + line, ""); |
| 287 | 287 | ||
| 288 | std::stringstream inner_ss(disabled_list); | 288 | std::stringstream inner_ss(disabled_list); |
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index a890c6604..a7e414b81 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp | |||
| @@ -11,6 +11,12 @@ | |||
| 11 | #include "jni/emu_window/emu_window.h" | 11 | #include "jni/emu_window/emu_window.h" |
| 12 | 12 | ||
| 13 | void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { | 13 | void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { |
| 14 | m_window_width = ANativeWindow_getWidth(surface); | ||
| 15 | m_window_height = ANativeWindow_getHeight(surface); | ||
| 16 | |||
| 17 | // Ensures that we emulate with the correct aspect ratio. | ||
| 18 | UpdateCurrentFramebufferLayout(m_window_width, m_window_height); | ||
| 19 | |||
| 14 | window_info.render_surface = reinterpret_cast<void*>(surface); | 20 | window_info.render_surface = reinterpret_cast<void*>(surface); |
| 15 | } | 21 | } |
| 16 | 22 | ||
| @@ -62,14 +68,8 @@ EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsyste | |||
| 62 | return; | 68 | return; |
| 63 | } | 69 | } |
| 64 | 70 | ||
| 65 | m_window_width = ANativeWindow_getWidth(surface); | 71 | OnSurfaceChanged(surface); |
| 66 | m_window_height = ANativeWindow_getHeight(surface); | ||
| 67 | |||
| 68 | // Ensures that we emulate with the correct aspect ratio. | ||
| 69 | UpdateCurrentFramebufferLayout(m_window_width, m_window_height); | ||
| 70 | |||
| 71 | window_info.type = Core::Frontend::WindowSystemType::Android; | 72 | window_info.type = Core::Frontend::WindowSystemType::Android; |
| 72 | window_info.render_surface = reinterpret_cast<void*>(surface); | ||
| 73 | 73 | ||
| 74 | m_input_subsystem->Initialize(); | 74 | m_input_subsystem->Initialize(); |
| 75 | } | 75 | } |
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/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 67dfe0290..400988c5f 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt | |||
| @@ -10,6 +10,13 @@ add_library(audio_core STATIC | |||
| 10 | adsp/apps/audio_renderer/command_buffer.h | 10 | adsp/apps/audio_renderer/command_buffer.h |
| 11 | adsp/apps/audio_renderer/command_list_processor.cpp | 11 | adsp/apps/audio_renderer/command_list_processor.cpp |
| 12 | adsp/apps/audio_renderer/command_list_processor.h | 12 | adsp/apps/audio_renderer/command_list_processor.h |
| 13 | adsp/apps/opus/opus_decoder.cpp | ||
| 14 | adsp/apps/opus/opus_decoder.h | ||
| 15 | adsp/apps/opus/opus_decode_object.cpp | ||
| 16 | adsp/apps/opus/opus_decode_object.h | ||
| 17 | adsp/apps/opus/opus_multistream_decode_object.cpp | ||
| 18 | adsp/apps/opus/opus_multistream_decode_object.h | ||
| 19 | adsp/apps/opus/shared_memory.h | ||
| 13 | audio_core.cpp | 20 | audio_core.cpp |
| 14 | audio_core.h | 21 | audio_core.h |
| 15 | audio_event.h | 22 | audio_event.h |
| @@ -35,6 +42,13 @@ add_library(audio_core STATIC | |||
| 35 | in/audio_in.h | 42 | in/audio_in.h |
| 36 | in/audio_in_system.cpp | 43 | in/audio_in_system.cpp |
| 37 | in/audio_in_system.h | 44 | in/audio_in_system.h |
| 45 | opus/hardware_opus.cpp | ||
| 46 | opus/hardware_opus.h | ||
| 47 | opus/decoder_manager.cpp | ||
| 48 | opus/decoder_manager.h | ||
| 49 | opus/decoder.cpp | ||
| 50 | opus/decoder.h | ||
| 51 | opus/parameters.h | ||
| 38 | out/audio_out.cpp | 52 | out/audio_out.cpp |
| 39 | out/audio_out.h | 53 | out/audio_out.h |
| 40 | out/audio_out_system.cpp | 54 | out/audio_out_system.cpp |
| @@ -214,7 +228,7 @@ else() | |||
| 214 | ) | 228 | ) |
| 215 | endif() | 229 | endif() |
| 216 | 230 | ||
| 217 | target_link_libraries(audio_core PUBLIC common core) | 231 | target_link_libraries(audio_core PUBLIC common core Opus::opus) |
| 218 | if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) | 232 | if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) |
| 219 | target_link_libraries(audio_core PRIVATE dynarmic::dynarmic) | 233 | target_link_libraries(audio_core PRIVATE dynarmic::dynarmic) |
| 220 | endif() | 234 | endif() |
diff --git a/src/audio_core/adsp/adsp.cpp b/src/audio_core/adsp/adsp.cpp index 0580990f5..6c53c98fd 100644 --- a/src/audio_core/adsp/adsp.cpp +++ b/src/audio_core/adsp/adsp.cpp | |||
| @@ -7,12 +7,21 @@ | |||
| 7 | namespace AudioCore::ADSP { | 7 | namespace AudioCore::ADSP { |
| 8 | 8 | ||
| 9 | ADSP::ADSP(Core::System& system, Sink::Sink& sink) { | 9 | ADSP::ADSP(Core::System& system, Sink::Sink& sink) { |
| 10 | audio_renderer = | 10 | audio_renderer = std::make_unique<AudioRenderer::AudioRenderer>(system, sink); |
| 11 | std::make_unique<AudioRenderer::AudioRenderer>(system, system.ApplicationMemory(), sink); | 11 | opus_decoder = std::make_unique<OpusDecoder::OpusDecoder>(system); |
| 12 | opus_decoder->Send(Direction::DSP, OpusDecoder::Message::Start); | ||
| 13 | if (opus_decoder->Receive(Direction::Host) != OpusDecoder::Message::StartOK) { | ||
| 14 | LOG_ERROR(Service_Audio, "OpusDeocder failed to initialize."); | ||
| 15 | return; | ||
| 16 | } | ||
| 12 | } | 17 | } |
| 13 | 18 | ||
| 14 | AudioRenderer::AudioRenderer& ADSP::AudioRenderer() { | 19 | AudioRenderer::AudioRenderer& ADSP::AudioRenderer() { |
| 15 | return *audio_renderer.get(); | 20 | return *audio_renderer.get(); |
| 16 | } | 21 | } |
| 17 | 22 | ||
| 23 | OpusDecoder::OpusDecoder& ADSP::OpusDecoder() { | ||
| 24 | return *opus_decoder.get(); | ||
| 25 | } | ||
| 26 | |||
| 18 | } // namespace AudioCore::ADSP | 27 | } // namespace AudioCore::ADSP |
diff --git a/src/audio_core/adsp/adsp.h b/src/audio_core/adsp/adsp.h index bd5bcc63b..a0c24a16a 100644 --- a/src/audio_core/adsp/adsp.h +++ b/src/audio_core/adsp/adsp.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include "audio_core/adsp/apps/audio_renderer/audio_renderer.h" | 6 | #include "audio_core/adsp/apps/audio_renderer/audio_renderer.h" |
| 7 | #include "audio_core/adsp/apps/opus/opus_decoder.h" | ||
| 7 | #include "common/common_types.h" | 8 | #include "common/common_types.h" |
| 8 | 9 | ||
| 9 | namespace Core { | 10 | namespace Core { |
| @@ -40,10 +41,12 @@ public: | |||
| 40 | ~ADSP() = default; | 41 | ~ADSP() = default; |
| 41 | 42 | ||
| 42 | AudioRenderer::AudioRenderer& AudioRenderer(); | 43 | AudioRenderer::AudioRenderer& AudioRenderer(); |
| 44 | OpusDecoder::OpusDecoder& OpusDecoder(); | ||
| 43 | 45 | ||
| 44 | private: | 46 | private: |
| 45 | /// AudioRenderer app | 47 | /// AudioRenderer app |
| 46 | std::unique_ptr<AudioRenderer::AudioRenderer> audio_renderer{}; | 48 | std::unique_ptr<AudioRenderer::AudioRenderer> audio_renderer{}; |
| 49 | std::unique_ptr<OpusDecoder::OpusDecoder> opus_decoder{}; | ||
| 47 | }; | 50 | }; |
| 48 | 51 | ||
| 49 | } // namespace ADSP | 52 | } // namespace ADSP |
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp index 2e549bc6f..972d5e45b 100644 --- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp | |||
| @@ -14,13 +14,12 @@ | |||
| 14 | #include "core/core.h" | 14 | #include "core/core.h" |
| 15 | #include "core/core_timing.h" | 15 | #include "core/core_timing.h" |
| 16 | 16 | ||
| 17 | MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97)); | 17 | MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP_AudioRenderer", MP_RGB(60, 19, 97)); |
| 18 | 18 | ||
| 19 | namespace AudioCore::ADSP::AudioRenderer { | 19 | namespace AudioCore::ADSP::AudioRenderer { |
| 20 | 20 | ||
| 21 | AudioRenderer::AudioRenderer(Core::System& system_, Core::Memory::Memory& memory_, | 21 | AudioRenderer::AudioRenderer(Core::System& system_, Sink::Sink& sink_) |
| 22 | Sink::Sink& sink_) | 22 | : system{system_}, sink{sink_} {} |
| 23 | : system{system_}, memory{memory_}, sink{sink_} {} | ||
| 24 | 23 | ||
| 25 | AudioRenderer::~AudioRenderer() { | 24 | AudioRenderer::~AudioRenderer() { |
| 26 | Stop(); | 25 | Stop(); |
| @@ -33,8 +32,8 @@ void AudioRenderer::Start() { | |||
| 33 | 32 | ||
| 34 | main_thread = std::jthread([this](std::stop_token stop_token) { Main(stop_token); }); | 33 | main_thread = std::jthread([this](std::stop_token stop_token) { Main(stop_token); }); |
| 35 | 34 | ||
| 36 | mailbox.Send(Direction::DSP, {Message::InitializeOK, {}}); | 35 | mailbox.Send(Direction::DSP, Message::InitializeOK); |
| 37 | if (mailbox.Receive(Direction::Host).msg != Message::InitializeOK) { | 36 | if (mailbox.Receive(Direction::Host) != Message::InitializeOK) { |
| 38 | LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " | 37 | LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " |
| 39 | "message response from ADSP!"); | 38 | "message response from ADSP!"); |
| 40 | return; | 39 | return; |
| @@ -47,8 +46,8 @@ void AudioRenderer::Stop() { | |||
| 47 | return; | 46 | return; |
| 48 | } | 47 | } |
| 49 | 48 | ||
| 50 | mailbox.Send(Direction::DSP, {Message::Shutdown, {}}); | 49 | mailbox.Send(Direction::DSP, Message::Shutdown); |
| 51 | if (mailbox.Receive(Direction::Host).msg != Message::Shutdown) { | 50 | if (mailbox.Receive(Direction::Host) != Message::Shutdown) { |
| 52 | LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " | 51 | LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " |
| 53 | "message response from ADSP!"); | 52 | "message response from ADSP!"); |
| 54 | } | 53 | } |
| @@ -67,25 +66,25 @@ void AudioRenderer::Stop() { | |||
| 67 | 66 | ||
| 68 | void AudioRenderer::Signal() { | 67 | void AudioRenderer::Signal() { |
| 69 | signalled_tick = system.CoreTiming().GetGlobalTimeNs().count(); | 68 | signalled_tick = system.CoreTiming().GetGlobalTimeNs().count(); |
| 70 | Send(Direction::DSP, {Message::Render, {}}); | 69 | Send(Direction::DSP, Message::Render); |
| 71 | } | 70 | } |
| 72 | 71 | ||
| 73 | void AudioRenderer::Wait() { | 72 | void AudioRenderer::Wait() { |
| 74 | auto received = Receive(Direction::Host); | 73 | auto msg = Receive(Direction::Host); |
| 75 | if (received.msg != Message::RenderResponse) { | 74 | if (msg != Message::RenderResponse) { |
| 76 | LOG_ERROR(Service_Audio, | 75 | LOG_ERROR(Service_Audio, |
| 77 | "Did not receive the expected render response from the AudioRenderer! Expected " | 76 | "Did not receive the expected render response from the AudioRenderer! Expected " |
| 78 | "{}, got {}", | 77 | "{}, got {}", |
| 79 | Message::RenderResponse, received.msg); | 78 | Message::RenderResponse, msg); |
| 80 | } | 79 | } |
| 81 | } | 80 | } |
| 82 | 81 | ||
| 83 | void AudioRenderer::Send(Direction dir, MailboxMessage message) { | 82 | void AudioRenderer::Send(Direction dir, u32 message) { |
| 84 | mailbox.Send(dir, std::move(message)); | 83 | mailbox.Send(dir, std::move(message)); |
| 85 | } | 84 | } |
| 86 | 85 | ||
| 87 | MailboxMessage AudioRenderer::Receive(Direction dir, bool block) { | 86 | u32 AudioRenderer::Receive(Direction dir) { |
| 88 | return mailbox.Receive(dir, block); | 87 | return mailbox.Receive(dir); |
| 89 | } | 88 | } |
| 90 | 89 | ||
| 91 | void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, | 90 | void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, |
| @@ -120,7 +119,7 @@ void AudioRenderer::CreateSinkStreams() { | |||
| 120 | } | 119 | } |
| 121 | 120 | ||
| 122 | void AudioRenderer::Main(std::stop_token stop_token) { | 121 | void AudioRenderer::Main(std::stop_token stop_token) { |
| 123 | static constexpr char name[]{"AudioRenderer"}; | 122 | static constexpr char name[]{"DSP_AudioRenderer_Main"}; |
| 124 | MicroProfileOnThreadCreate(name); | 123 | MicroProfileOnThreadCreate(name); |
| 125 | Common::SetCurrentThreadName(name); | 124 | Common::SetCurrentThreadName(name); |
| 126 | Common::SetCurrentThreadPriority(Common::ThreadPriority::High); | 125 | Common::SetCurrentThreadPriority(Common::ThreadPriority::High); |
| @@ -128,28 +127,28 @@ void AudioRenderer::Main(std::stop_token stop_token) { | |||
| 128 | // TODO: Create buffer map/unmap thread + mailbox | 127 | // TODO: Create buffer map/unmap thread + mailbox |
| 129 | // TODO: Create gMix devices, initialize them here | 128 | // TODO: Create gMix devices, initialize them here |
| 130 | 129 | ||
| 131 | if (mailbox.Receive(Direction::DSP).msg != Message::InitializeOK) { | 130 | if (mailbox.Receive(Direction::DSP) != Message::InitializeOK) { |
| 132 | LOG_ERROR(Service_Audio, | 131 | LOG_ERROR(Service_Audio, |
| 133 | "ADSP Audio Renderer -- Failed to receive initialize message from host!"); | 132 | "ADSP Audio Renderer -- Failed to receive initialize message from host!"); |
| 134 | return; | 133 | return; |
| 135 | } | 134 | } |
| 136 | 135 | ||
| 137 | mailbox.Send(Direction::Host, {Message::InitializeOK, {}}); | 136 | mailbox.Send(Direction::Host, Message::InitializeOK); |
| 138 | 137 | ||
| 139 | // 0.12 seconds (2,304,000 / 19,200,000) | 138 | // 0.12 seconds (2,304,000 / 19,200,000) |
| 140 | constexpr u64 max_process_time{2'304'000ULL}; | 139 | constexpr u64 max_process_time{2'304'000ULL}; |
| 141 | 140 | ||
| 142 | while (!stop_token.stop_requested()) { | 141 | while (!stop_token.stop_requested()) { |
| 143 | auto received{mailbox.Receive(Direction::DSP)}; | 142 | auto msg{mailbox.Receive(Direction::DSP)}; |
| 144 | switch (received.msg) { | 143 | switch (msg) { |
| 145 | case Message::Shutdown: | 144 | case Message::Shutdown: |
| 146 | mailbox.Send(Direction::Host, {Message::Shutdown, {}}); | 145 | mailbox.Send(Direction::Host, Message::Shutdown); |
| 147 | return; | 146 | return; |
| 148 | 147 | ||
| 149 | case Message::Render: { | 148 | case Message::Render: { |
| 150 | if (system.IsShuttingDown()) [[unlikely]] { | 149 | if (system.IsShuttingDown()) [[unlikely]] { |
| 151 | std::this_thread::sleep_for(std::chrono::milliseconds(5)); | 150 | std::this_thread::sleep_for(std::chrono::milliseconds(5)); |
| 152 | mailbox.Send(Direction::Host, {Message::RenderResponse, {}}); | 151 | mailbox.Send(Direction::Host, Message::RenderResponse); |
| 153 | continue; | 152 | continue; |
| 154 | } | 153 | } |
| 155 | std::array<bool, MaxRendererSessions> buffers_reset{}; | 154 | std::array<bool, MaxRendererSessions> buffers_reset{}; |
| @@ -205,13 +204,12 @@ void AudioRenderer::Main(std::stop_token stop_token) { | |||
| 205 | } | 204 | } |
| 206 | } | 205 | } |
| 207 | 206 | ||
| 208 | mailbox.Send(Direction::Host, {Message::RenderResponse, {}}); | 207 | mailbox.Send(Direction::Host, Message::RenderResponse); |
| 209 | } break; | 208 | } break; |
| 210 | 209 | ||
| 211 | default: | 210 | default: |
| 212 | LOG_WARNING(Service_Audio, | 211 | LOG_WARNING(Service_Audio, |
| 213 | "ADSP AudioRenderer received an invalid message, msg={:02X}!", | 212 | "ADSP AudioRenderer received an invalid message, msg={:02X}!", msg); |
| 214 | received.msg); | ||
| 215 | break; | 213 | break; |
| 216 | } | 214 | } |
| 217 | } | 215 | } |
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h index 3f5b7dca2..85874d88a 100644 --- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h | |||
| @@ -17,13 +17,6 @@ | |||
| 17 | 17 | ||
| 18 | namespace Core { | 18 | namespace Core { |
| 19 | class System; | 19 | class System; |
| 20 | namespace Timing { | ||
| 21 | struct EventType; | ||
| 22 | } | ||
| 23 | namespace Memory { | ||
| 24 | class Memory; | ||
| 25 | } | ||
| 26 | class System; | ||
| 27 | } // namespace Core | 20 | } // namespace Core |
| 28 | 21 | ||
| 29 | namespace AudioCore { | 22 | namespace AudioCore { |
| @@ -34,19 +27,19 @@ class Sink; | |||
| 34 | namespace ADSP::AudioRenderer { | 27 | namespace ADSP::AudioRenderer { |
| 35 | 28 | ||
| 36 | enum Message : u32 { | 29 | enum Message : u32 { |
| 37 | Invalid = 0x00, | 30 | Invalid = 0, |
| 38 | MapUnmap_Map = 0x01, | 31 | MapUnmap_Map = 1, |
| 39 | MapUnmap_MapResponse = 0x02, | 32 | MapUnmap_MapResponse = 2, |
| 40 | MapUnmap_Unmap = 0x03, | 33 | MapUnmap_Unmap = 3, |
| 41 | MapUnmap_UnmapResponse = 0x04, | 34 | MapUnmap_UnmapResponse = 4, |
| 42 | MapUnmap_InvalidateCache = 0x05, | 35 | MapUnmap_InvalidateCache = 5, |
| 43 | MapUnmap_InvalidateCacheResponse = 0x06, | 36 | MapUnmap_InvalidateCacheResponse = 6, |
| 44 | MapUnmap_Shutdown = 0x07, | 37 | MapUnmap_Shutdown = 7, |
| 45 | MapUnmap_ShutdownResponse = 0x08, | 38 | MapUnmap_ShutdownResponse = 8, |
| 46 | InitializeOK = 0x16, | 39 | InitializeOK = 22, |
| 47 | RenderResponse = 0x20, | 40 | RenderResponse = 32, |
| 48 | Render = 0x2A, | 41 | Render = 42, |
| 49 | Shutdown = 0x34, | 42 | Shutdown = 52, |
| 50 | }; | 43 | }; |
| 51 | 44 | ||
| 52 | /** | 45 | /** |
| @@ -54,7 +47,7 @@ enum Message : u32 { | |||
| 54 | */ | 47 | */ |
| 55 | class AudioRenderer { | 48 | class AudioRenderer { |
| 56 | public: | 49 | public: |
| 57 | explicit AudioRenderer(Core::System& system, Core::Memory::Memory& memory, Sink::Sink& sink); | 50 | explicit AudioRenderer(Core::System& system, Sink::Sink& sink); |
| 58 | ~AudioRenderer(); | 51 | ~AudioRenderer(); |
| 59 | 52 | ||
| 60 | /** | 53 | /** |
| @@ -72,8 +65,8 @@ public: | |||
| 72 | void Signal(); | 65 | void Signal(); |
| 73 | void Wait(); | 66 | void Wait(); |
| 74 | 67 | ||
| 75 | void Send(Direction dir, MailboxMessage message); | 68 | void Send(Direction dir, u32 message); |
| 76 | MailboxMessage Receive(Direction dir, bool block = true); | 69 | u32 Receive(Direction dir); |
| 77 | 70 | ||
| 78 | void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, | 71 | void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, |
| 79 | u64 applet_resource_user_id, bool reset) noexcept; | 72 | u64 applet_resource_user_id, bool reset) noexcept; |
| @@ -94,9 +87,7 @@ private: | |||
| 94 | 87 | ||
| 95 | /// Core system | 88 | /// Core system |
| 96 | Core::System& system; | 89 | Core::System& system; |
| 97 | /// Memory | 90 | /// The output sink the AudioRenderer will send samples to |
| 98 | Core::Memory::Memory& memory; | ||
| 99 | /// The output sink the AudioRenderer will use | ||
| 100 | Sink::Sink& sink; | 91 | Sink::Sink& sink; |
| 101 | /// The active mailbox | 92 | /// The active mailbox |
| 102 | Mailbox mailbox; | 93 | Mailbox mailbox; |
| @@ -104,11 +95,13 @@ private: | |||
| 104 | std::jthread main_thread{}; | 95 | std::jthread main_thread{}; |
| 105 | /// The current state | 96 | /// The current state |
| 106 | std::atomic<bool> running{}; | 97 | std::atomic<bool> running{}; |
| 98 | /// Shared memory of input command buffers, set by host, read by DSP | ||
| 107 | std::array<CommandBuffer, MaxRendererSessions> command_buffers{}; | 99 | std::array<CommandBuffer, MaxRendererSessions> command_buffers{}; |
| 108 | /// The command lists to process | 100 | /// The command lists to process |
| 109 | std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{}; | 101 | std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{}; |
| 110 | /// The streams which will receive the processed samples | 102 | /// The streams which will receive the processed samples |
| 111 | std::array<Sink::SinkStream*, MaxRendererSessions> streams{}; | 103 | std::array<Sink::SinkStream*, MaxRendererSessions> streams{}; |
| 104 | /// CPU Tick when the DSP was signalled to process, uses time rather than tick | ||
| 112 | u64 signalled_tick{0}; | 105 | u64 signalled_tick{0}; |
| 113 | }; | 106 | }; |
| 114 | 107 | ||
diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp new file mode 100644 index 000000000..2c16d3769 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp | |||
| @@ -0,0 +1,107 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/adsp/apps/opus/opus_decode_object.h" | ||
| 5 | #include "common/assert.h" | ||
| 6 | |||
| 7 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 8 | namespace { | ||
| 9 | bool IsValidChannelCount(u32 channel_count) { | ||
| 10 | return channel_count == 1 || channel_count == 2; | ||
| 11 | } | ||
| 12 | } // namespace | ||
| 13 | |||
| 14 | u32 OpusDecodeObject::GetWorkBufferSize(u32 channel_count) { | ||
| 15 | if (!IsValidChannelCount(channel_count)) { | ||
| 16 | return 0; | ||
| 17 | } | ||
| 18 | return static_cast<u32>(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count); | ||
| 19 | } | ||
| 20 | |||
| 21 | OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) { | ||
| 22 | auto* new_decoder = reinterpret_cast<OpusDecodeObject*>(buffer); | ||
| 23 | auto* comparison = reinterpret_cast<OpusDecodeObject*>(buffer2); | ||
| 24 | |||
| 25 | if (new_decoder->magic == DecodeObjectMagic) { | ||
| 26 | if (!new_decoder->initialized || | ||
| 27 | (new_decoder->initialized && new_decoder->self == comparison)) { | ||
| 28 | new_decoder->state_valid = true; | ||
| 29 | } | ||
| 30 | } else { | ||
| 31 | new_decoder->initialized = false; | ||
| 32 | new_decoder->state_valid = true; | ||
| 33 | } | ||
| 34 | return *new_decoder; | ||
| 35 | } | ||
| 36 | |||
| 37 | s32 OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) { | ||
| 38 | if (!state_valid) { | ||
| 39 | return OPUS_INVALID_STATE; | ||
| 40 | } | ||
| 41 | |||
| 42 | if (initialized) { | ||
| 43 | return OPUS_OK; | ||
| 44 | } | ||
| 45 | |||
| 46 | // Unfortunately libopus does not expose the OpusDecoder struct publicly, so we can't include | ||
| 47 | // it in this class. Nintendo does not allocate memory, which is why we have a workbuffer | ||
| 48 | // provided. | ||
| 49 | // We could use _create and have libopus allocate it for us, but then we have to separately | ||
| 50 | // track which decoder is being used between this and multistream in order to call the correct | ||
| 51 | // destroy from the host side. | ||
| 52 | // This is a bit cringe, but is safe as these objects are only ever initialized inside the given | ||
| 53 | // workbuffer, and GetWorkBufferSize will guarantee there's enough space to follow. | ||
| 54 | decoder = (LibOpusDecoder*)(this + 1); | ||
| 55 | s32 ret = opus_decoder_init(decoder, sample_rate, channel_count); | ||
| 56 | if (ret == OPUS_OK) { | ||
| 57 | magic = DecodeObjectMagic; | ||
| 58 | initialized = true; | ||
| 59 | state_valid = true; | ||
| 60 | self = this; | ||
| 61 | final_range = 0; | ||
| 62 | } | ||
| 63 | return ret; | ||
| 64 | } | ||
| 65 | |||
| 66 | s32 OpusDecodeObject::Shutdown() { | ||
| 67 | if (!state_valid) { | ||
| 68 | return OPUS_INVALID_STATE; | ||
| 69 | } | ||
| 70 | |||
| 71 | if (initialized) { | ||
| 72 | magic = 0x0; | ||
| 73 | initialized = false; | ||
| 74 | state_valid = false; | ||
| 75 | self = nullptr; | ||
| 76 | final_range = 0; | ||
| 77 | decoder = nullptr; | ||
| 78 | } | ||
| 79 | return OPUS_OK; | ||
| 80 | } | ||
| 81 | |||
| 82 | s32 OpusDecodeObject::ResetDecoder() { | ||
| 83 | return opus_decoder_ctl(decoder, OPUS_RESET_STATE); | ||
| 84 | } | ||
| 85 | |||
| 86 | s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, | ||
| 87 | u64 input_data, u64 input_data_size) { | ||
| 88 | ASSERT(initialized); | ||
| 89 | out_sample_count = 0; | ||
| 90 | |||
| 91 | if (!state_valid) { | ||
| 92 | return OPUS_INVALID_STATE; | ||
| 93 | } | ||
| 94 | |||
| 95 | auto ret_code_or_samples = opus_decode( | ||
| 96 | decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size), | ||
| 97 | reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0); | ||
| 98 | |||
| 99 | if (ret_code_or_samples < OPUS_OK) { | ||
| 100 | return ret_code_or_samples; | ||
| 101 | } | ||
| 102 | |||
| 103 | out_sample_count = ret_code_or_samples; | ||
| 104 | return opus_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range); | ||
| 105 | } | ||
| 106 | |||
| 107 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.h b/src/audio_core/adsp/apps/opus/opus_decode_object.h new file mode 100644 index 000000000..6425f987c --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decode_object.h | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <opus.h> | ||
| 7 | |||
| 8 | #include "common/common_types.h" | ||
| 9 | |||
| 10 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 11 | using LibOpusDecoder = ::OpusDecoder; | ||
| 12 | static constexpr u32 DecodeObjectMagic = 0xDEADBEEF; | ||
| 13 | |||
| 14 | class OpusDecodeObject { | ||
| 15 | public: | ||
| 16 | static u32 GetWorkBufferSize(u32 channel_count); | ||
| 17 | static OpusDecodeObject& Initialize(u64 buffer, u64 buffer2); | ||
| 18 | |||
| 19 | s32 InitializeDecoder(u32 sample_rate, u32 channel_count); | ||
| 20 | s32 Shutdown(); | ||
| 21 | s32 ResetDecoder(); | ||
| 22 | s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, | ||
| 23 | u64 input_data_size); | ||
| 24 | u32 GetFinalRange() const noexcept { | ||
| 25 | return final_range; | ||
| 26 | } | ||
| 27 | |||
| 28 | private: | ||
| 29 | u32 magic; | ||
| 30 | bool initialized; | ||
| 31 | bool state_valid; | ||
| 32 | OpusDecodeObject* self; | ||
| 33 | u32 final_range; | ||
| 34 | LibOpusDecoder* decoder; | ||
| 35 | }; | ||
| 36 | static_assert(std::is_trivially_constructible_v<OpusDecodeObject>); | ||
| 37 | |||
| 38 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.cpp b/src/audio_core/adsp/apps/opus/opus_decoder.cpp new file mode 100644 index 000000000..2084de128 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decoder.cpp | |||
| @@ -0,0 +1,269 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <array> | ||
| 5 | #include <chrono> | ||
| 6 | |||
| 7 | #include "audio_core/adsp/apps/opus/opus_decode_object.h" | ||
| 8 | #include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h" | ||
| 9 | #include "audio_core/adsp/apps/opus/shared_memory.h" | ||
| 10 | #include "audio_core/audio_core.h" | ||
| 11 | #include "audio_core/common/common.h" | ||
| 12 | #include "common/logging/log.h" | ||
| 13 | #include "common/microprofile.h" | ||
| 14 | #include "common/thread.h" | ||
| 15 | #include "core/core.h" | ||
| 16 | #include "core/core_timing.h" | ||
| 17 | |||
| 18 | MICROPROFILE_DEFINE(OpusDecoder, "Audio", "DSP_OpusDecoder", MP_RGB(60, 19, 97)); | ||
| 19 | |||
| 20 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 21 | |||
| 22 | namespace { | ||
| 23 | constexpr size_t OpusStreamCountMax = 255; | ||
| 24 | |||
| 25 | bool IsValidChannelCount(u32 channel_count) { | ||
| 26 | return channel_count == 1 || channel_count == 2; | ||
| 27 | } | ||
| 28 | |||
| 29 | bool IsValidMultiStreamChannelCount(u32 channel_count) { | ||
| 30 | return channel_count <= OpusStreamCountMax; | ||
| 31 | } | ||
| 32 | |||
| 33 | bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 sterero_stream_count) { | ||
| 34 | return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 && | ||
| 35 | sterero_stream_count > 0 && sterero_stream_count <= total_stream_count; | ||
| 36 | } | ||
| 37 | } // namespace | ||
| 38 | |||
| 39 | OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} { | ||
| 40 | init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); }); | ||
| 41 | } | ||
| 42 | |||
| 43 | OpusDecoder::~OpusDecoder() { | ||
| 44 | if (!running) { | ||
| 45 | init_thread.request_stop(); | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | |||
| 49 | // Shutdown the thread | ||
| 50 | Send(Direction::DSP, Message::Shutdown); | ||
| 51 | auto msg = Receive(Direction::Host); | ||
| 52 | ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", | ||
| 53 | Message::ShutdownOK, msg); | ||
| 54 | main_thread.request_stop(); | ||
| 55 | main_thread.join(); | ||
| 56 | running = false; | ||
| 57 | } | ||
| 58 | |||
| 59 | void OpusDecoder::Send(Direction dir, u32 message) { | ||
| 60 | mailbox.Send(dir, std::move(message)); | ||
| 61 | } | ||
| 62 | |||
| 63 | u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) { | ||
| 64 | return mailbox.Receive(dir, stop_token); | ||
| 65 | } | ||
| 66 | |||
| 67 | void OpusDecoder::Init(std::stop_token stop_token) { | ||
| 68 | Common::SetCurrentThreadName("DSP_OpusDecoder_Init"); | ||
| 69 | |||
| 70 | if (Receive(Direction::DSP, stop_token) != Message::Start) { | ||
| 71 | LOG_ERROR(Service_Audio, | ||
| 72 | "DSP OpusDecoder failed to receive Start message. Opus initialization failed."); | ||
| 73 | return; | ||
| 74 | } | ||
| 75 | main_thread = std::jthread([this](std::stop_token st) { Main(st); }); | ||
| 76 | running = true; | ||
| 77 | Send(Direction::Host, Message::StartOK); | ||
| 78 | } | ||
| 79 | |||
| 80 | void OpusDecoder::Main(std::stop_token stop_token) { | ||
| 81 | Common::SetCurrentThreadName("DSP_OpusDecoder_Main"); | ||
| 82 | |||
| 83 | while (!stop_token.stop_requested()) { | ||
| 84 | auto msg = Receive(Direction::DSP, stop_token); | ||
| 85 | switch (msg) { | ||
| 86 | case Shutdown: | ||
| 87 | Send(Direction::Host, Message::ShutdownOK); | ||
| 88 | return; | ||
| 89 | |||
| 90 | case GetWorkBufferSize: { | ||
| 91 | auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]); | ||
| 92 | |||
| 93 | ASSERT(IsValidChannelCount(channel_count)); | ||
| 94 | |||
| 95 | shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count); | ||
| 96 | Send(Direction::Host, Message::GetWorkBufferSizeOK); | ||
| 97 | } break; | ||
| 98 | |||
| 99 | case InitializeDecodeObject: { | ||
| 100 | auto buffer = shared_memory->host_send_data[0]; | ||
| 101 | auto buffer_size = shared_memory->host_send_data[1]; | ||
| 102 | auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]); | ||
| 103 | auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]); | ||
| 104 | |||
| 105 | ASSERT(sample_rate >= 0); | ||
| 106 | ASSERT(IsValidChannelCount(channel_count)); | ||
| 107 | ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count)); | ||
| 108 | |||
| 109 | auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); | ||
| 110 | shared_memory->dsp_return_data[0] = | ||
| 111 | decoder_object.InitializeDecoder(sample_rate, channel_count); | ||
| 112 | |||
| 113 | Send(Direction::Host, Message::InitializeDecodeObjectOK); | ||
| 114 | } break; | ||
| 115 | |||
| 116 | case ShutdownDecodeObject: { | ||
| 117 | auto buffer = shared_memory->host_send_data[0]; | ||
| 118 | [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; | ||
| 119 | |||
| 120 | auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); | ||
| 121 | shared_memory->dsp_return_data[0] = decoder_object.Shutdown(); | ||
| 122 | |||
| 123 | Send(Direction::Host, Message::ShutdownDecodeObjectOK); | ||
| 124 | } break; | ||
| 125 | |||
| 126 | case DecodeInterleaved: { | ||
| 127 | auto start_time = system.CoreTiming().GetGlobalTimeUs(); | ||
| 128 | |||
| 129 | auto buffer = shared_memory->host_send_data[0]; | ||
| 130 | auto input_data = shared_memory->host_send_data[1]; | ||
| 131 | auto input_data_size = shared_memory->host_send_data[2]; | ||
| 132 | auto output_data = shared_memory->host_send_data[3]; | ||
| 133 | auto output_data_size = shared_memory->host_send_data[4]; | ||
| 134 | auto final_range = static_cast<u32>(shared_memory->host_send_data[5]); | ||
| 135 | auto reset_requested = shared_memory->host_send_data[6]; | ||
| 136 | |||
| 137 | u32 decoded_samples{0}; | ||
| 138 | |||
| 139 | auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); | ||
| 140 | s32 error_code{OPUS_OK}; | ||
| 141 | if (reset_requested) { | ||
| 142 | error_code = decoder_object.ResetDecoder(); | ||
| 143 | } | ||
| 144 | |||
| 145 | if (error_code == OPUS_OK) { | ||
| 146 | error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size, | ||
| 147 | input_data, input_data_size); | ||
| 148 | } | ||
| 149 | |||
| 150 | if (error_code == OPUS_OK) { | ||
| 151 | if (final_range && decoder_object.GetFinalRange() != final_range) { | ||
| 152 | error_code = OPUS_INVALID_PACKET; | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | auto end_time = system.CoreTiming().GetGlobalTimeUs(); | ||
| 157 | shared_memory->dsp_return_data[0] = error_code; | ||
| 158 | shared_memory->dsp_return_data[1] = decoded_samples; | ||
| 159 | shared_memory->dsp_return_data[2] = (end_time - start_time).count(); | ||
| 160 | |||
| 161 | Send(Direction::Host, Message::DecodeInterleavedOK); | ||
| 162 | } break; | ||
| 163 | |||
| 164 | case MapMemory: { | ||
| 165 | [[maybe_unused]] auto buffer = shared_memory->host_send_data[0]; | ||
| 166 | [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; | ||
| 167 | Send(Direction::Host, Message::MapMemoryOK); | ||
| 168 | } break; | ||
| 169 | |||
| 170 | case UnmapMemory: { | ||
| 171 | [[maybe_unused]] auto buffer = shared_memory->host_send_data[0]; | ||
| 172 | [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; | ||
| 173 | Send(Direction::Host, Message::UnmapMemoryOK); | ||
| 174 | } break; | ||
| 175 | |||
| 176 | case GetWorkBufferSizeForMultiStream: { | ||
| 177 | auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]); | ||
| 178 | auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]); | ||
| 179 | |||
| 180 | ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count)); | ||
| 181 | |||
| 182 | shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize( | ||
| 183 | total_stream_count, stereo_stream_count); | ||
| 184 | Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK); | ||
| 185 | } break; | ||
| 186 | |||
| 187 | case InitializeMultiStreamDecodeObject: { | ||
| 188 | auto buffer = shared_memory->host_send_data[0]; | ||
| 189 | auto buffer_size = shared_memory->host_send_data[1]; | ||
| 190 | auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]); | ||
| 191 | auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]); | ||
| 192 | auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[4]); | ||
| 193 | auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[5]); | ||
| 194 | // Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel | ||
| 195 | // mappings, but [6] is never set, and there is not enough room in the argument data for | ||
| 196 | // more than 40 channels, when 255 are possible. | ||
| 197 | // It also means the mapping values are undefined, though likely always 0, | ||
| 198 | // and the mappings given by the game are ignored. The mappings are copied to this | ||
| 199 | // dedicated buffer host side, so let's do as intended. | ||
| 200 | auto mappings = shared_memory->channel_mapping.data(); | ||
| 201 | |||
| 202 | ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count)); | ||
| 203 | ASSERT(sample_rate >= 0); | ||
| 204 | ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize( | ||
| 205 | total_stream_count, stereo_stream_count)); | ||
| 206 | |||
| 207 | auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); | ||
| 208 | shared_memory->dsp_return_data[0] = decoder_object.InitializeDecoder( | ||
| 209 | sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings); | ||
| 210 | |||
| 211 | Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK); | ||
| 212 | } break; | ||
| 213 | |||
| 214 | case ShutdownMultiStreamDecodeObject: { | ||
| 215 | auto buffer = shared_memory->host_send_data[0]; | ||
| 216 | [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; | ||
| 217 | |||
| 218 | auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); | ||
| 219 | shared_memory->dsp_return_data[0] = decoder_object.Shutdown(); | ||
| 220 | |||
| 221 | Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK); | ||
| 222 | } break; | ||
| 223 | |||
| 224 | case DecodeInterleavedForMultiStream: { | ||
| 225 | auto start_time = system.CoreTiming().GetGlobalTimeUs(); | ||
| 226 | |||
| 227 | auto buffer = shared_memory->host_send_data[0]; | ||
| 228 | auto input_data = shared_memory->host_send_data[1]; | ||
| 229 | auto input_data_size = shared_memory->host_send_data[2]; | ||
| 230 | auto output_data = shared_memory->host_send_data[3]; | ||
| 231 | auto output_data_size = shared_memory->host_send_data[4]; | ||
| 232 | auto final_range = static_cast<u32>(shared_memory->host_send_data[5]); | ||
| 233 | auto reset_requested = shared_memory->host_send_data[6]; | ||
| 234 | |||
| 235 | u32 decoded_samples{0}; | ||
| 236 | |||
| 237 | auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); | ||
| 238 | s32 error_code{OPUS_OK}; | ||
| 239 | if (reset_requested) { | ||
| 240 | error_code = decoder_object.ResetDecoder(); | ||
| 241 | } | ||
| 242 | |||
| 243 | if (error_code == OPUS_OK) { | ||
| 244 | error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size, | ||
| 245 | input_data, input_data_size); | ||
| 246 | } | ||
| 247 | |||
| 248 | if (error_code == OPUS_OK) { | ||
| 249 | if (final_range && decoder_object.GetFinalRange() != final_range) { | ||
| 250 | error_code = OPUS_INVALID_PACKET; | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | auto end_time = system.CoreTiming().GetGlobalTimeUs(); | ||
| 255 | shared_memory->dsp_return_data[0] = error_code; | ||
| 256 | shared_memory->dsp_return_data[1] = decoded_samples; | ||
| 257 | shared_memory->dsp_return_data[2] = (end_time - start_time).count(); | ||
| 258 | |||
| 259 | Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK); | ||
| 260 | } break; | ||
| 261 | |||
| 262 | default: | ||
| 263 | LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg); | ||
| 264 | continue; | ||
| 265 | } | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.h b/src/audio_core/adsp/apps/opus/opus_decoder.h new file mode 100644 index 000000000..fcb89bb40 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decoder.h | |||
| @@ -0,0 +1,92 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <memory> | ||
| 7 | #include <thread> | ||
| 8 | |||
| 9 | #include "audio_core/adsp/apps/opus/shared_memory.h" | ||
| 10 | #include "audio_core/adsp/mailbox.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace Core { | ||
| 14 | class System; | ||
| 15 | } // namespace Core | ||
| 16 | |||
| 17 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 18 | |||
| 19 | enum Message : u32 { | ||
| 20 | Invalid = 0, | ||
| 21 | Start = 1, | ||
| 22 | Shutdown = 2, | ||
| 23 | StartOK = 11, | ||
| 24 | ShutdownOK = 12, | ||
| 25 | GetWorkBufferSize = 21, | ||
| 26 | InitializeDecodeObject = 22, | ||
| 27 | ShutdownDecodeObject = 23, | ||
| 28 | DecodeInterleaved = 24, | ||
| 29 | MapMemory = 25, | ||
| 30 | UnmapMemory = 26, | ||
| 31 | GetWorkBufferSizeForMultiStream = 27, | ||
| 32 | InitializeMultiStreamDecodeObject = 28, | ||
| 33 | ShutdownMultiStreamDecodeObject = 29, | ||
| 34 | DecodeInterleavedForMultiStream = 30, | ||
| 35 | |||
| 36 | GetWorkBufferSizeOK = 41, | ||
| 37 | InitializeDecodeObjectOK = 42, | ||
| 38 | ShutdownDecodeObjectOK = 43, | ||
| 39 | DecodeInterleavedOK = 44, | ||
| 40 | MapMemoryOK = 45, | ||
| 41 | UnmapMemoryOK = 46, | ||
| 42 | GetWorkBufferSizeForMultiStreamOK = 47, | ||
| 43 | InitializeMultiStreamDecodeObjectOK = 48, | ||
| 44 | ShutdownMultiStreamDecodeObjectOK = 49, | ||
| 45 | DecodeInterleavedForMultiStreamOK = 50, | ||
| 46 | }; | ||
| 47 | |||
| 48 | /** | ||
| 49 | * The AudioRenderer application running on the ADSP. | ||
| 50 | */ | ||
| 51 | class OpusDecoder { | ||
| 52 | public: | ||
| 53 | explicit OpusDecoder(Core::System& system); | ||
| 54 | ~OpusDecoder(); | ||
| 55 | |||
| 56 | bool IsRunning() const noexcept { | ||
| 57 | return running; | ||
| 58 | } | ||
| 59 | |||
| 60 | void Send(Direction dir, u32 message); | ||
| 61 | u32 Receive(Direction dir, std::stop_token stop_token = {}); | ||
| 62 | |||
| 63 | void SetSharedMemory(SharedMemory& shared_memory_) { | ||
| 64 | shared_memory = &shared_memory_; | ||
| 65 | } | ||
| 66 | |||
| 67 | private: | ||
| 68 | /** | ||
| 69 | * Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread. | ||
| 70 | */ | ||
| 71 | void Init(std::stop_token stop_token); | ||
| 72 | /** | ||
| 73 | * Main OpusDecoder thread, responsible for processing the incoming Opus packets. | ||
| 74 | */ | ||
| 75 | void Main(std::stop_token stop_token); | ||
| 76 | |||
| 77 | /// Core system | ||
| 78 | Core::System& system; | ||
| 79 | /// Mailbox to communicate messages with the host, drives the main thread | ||
| 80 | Mailbox mailbox; | ||
| 81 | /// Init thread | ||
| 82 | std::jthread init_thread{}; | ||
| 83 | /// Main thread | ||
| 84 | std::jthread main_thread{}; | ||
| 85 | /// The current state | ||
| 86 | bool running{}; | ||
| 87 | /// Structure shared with the host, input data set by the host before sending a mailbox message, | ||
| 88 | /// and the responses are written back by the OpusDecoder. | ||
| 89 | SharedMemory* shared_memory{}; | ||
| 90 | }; | ||
| 91 | |||
| 92 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp new file mode 100644 index 000000000..f6d362e68 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp | |||
| @@ -0,0 +1,111 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h" | ||
| 5 | #include "common/assert.h" | ||
| 6 | |||
| 7 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | bool IsValidChannelCount(u32 channel_count) { | ||
| 11 | return channel_count == 1 || channel_count == 2; | ||
| 12 | } | ||
| 13 | |||
| 14 | bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) { | ||
| 15 | return total_stream_count > 0 && stereo_stream_count > 0 && | ||
| 16 | stereo_stream_count <= total_stream_count && IsValidChannelCount(total_stream_count); | ||
| 17 | } | ||
| 18 | } // namespace | ||
| 19 | |||
| 20 | u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count, | ||
| 21 | u32 stereo_stream_count) { | ||
| 22 | if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) { | ||
| 23 | return static_cast<u32>(sizeof(OpusMultiStreamDecodeObject)) + | ||
| 24 | opus_multistream_decoder_get_size(total_stream_count, stereo_stream_count); | ||
| 25 | } | ||
| 26 | return 0; | ||
| 27 | } | ||
| 28 | |||
| 29 | OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) { | ||
| 30 | auto* new_decoder = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer); | ||
| 31 | auto* comparison = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer2); | ||
| 32 | |||
| 33 | if (new_decoder->magic == DecodeMultiStreamObjectMagic) { | ||
| 34 | if (!new_decoder->initialized || | ||
| 35 | (new_decoder->initialized && new_decoder->self == comparison)) { | ||
| 36 | new_decoder->state_valid = true; | ||
| 37 | } | ||
| 38 | } else { | ||
| 39 | new_decoder->initialized = false; | ||
| 40 | new_decoder->state_valid = true; | ||
| 41 | } | ||
| 42 | return *new_decoder; | ||
| 43 | } | ||
| 44 | |||
| 45 | s32 OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count, | ||
| 46 | u32 channel_count, u32 stereo_stream_count, | ||
| 47 | u8* mappings) { | ||
| 48 | if (!state_valid) { | ||
| 49 | return OPUS_INVALID_STATE; | ||
| 50 | } | ||
| 51 | |||
| 52 | if (initialized) { | ||
| 53 | return OPUS_OK; | ||
| 54 | } | ||
| 55 | |||
| 56 | // See OpusDecodeObject::InitializeDecoder for an explanation of this | ||
| 57 | decoder = (LibOpusMSDecoder*)(this + 1); | ||
| 58 | s32 ret = opus_multistream_decoder_init(decoder, sample_rate, channel_count, total_stream_count, | ||
| 59 | stereo_stream_count, mappings); | ||
| 60 | if (ret == OPUS_OK) { | ||
| 61 | magic = DecodeMultiStreamObjectMagic; | ||
| 62 | initialized = true; | ||
| 63 | state_valid = true; | ||
| 64 | self = this; | ||
| 65 | final_range = 0; | ||
| 66 | } | ||
| 67 | return ret; | ||
| 68 | } | ||
| 69 | |||
| 70 | s32 OpusMultiStreamDecodeObject::Shutdown() { | ||
| 71 | if (!state_valid) { | ||
| 72 | return OPUS_INVALID_STATE; | ||
| 73 | } | ||
| 74 | |||
| 75 | if (initialized) { | ||
| 76 | magic = 0x0; | ||
| 77 | initialized = false; | ||
| 78 | state_valid = false; | ||
| 79 | self = nullptr; | ||
| 80 | final_range = 0; | ||
| 81 | decoder = nullptr; | ||
| 82 | } | ||
| 83 | return OPUS_OK; | ||
| 84 | } | ||
| 85 | |||
| 86 | s32 OpusMultiStreamDecodeObject::ResetDecoder() { | ||
| 87 | return opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE); | ||
| 88 | } | ||
| 89 | |||
| 90 | s32 OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data, | ||
| 91 | u64 output_data_size, u64 input_data, u64 input_data_size) { | ||
| 92 | ASSERT(initialized); | ||
| 93 | out_sample_count = 0; | ||
| 94 | |||
| 95 | if (!state_valid) { | ||
| 96 | return OPUS_INVALID_STATE; | ||
| 97 | } | ||
| 98 | |||
| 99 | auto ret_code_or_samples = opus_multistream_decode( | ||
| 100 | decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size), | ||
| 101 | reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0); | ||
| 102 | |||
| 103 | if (ret_code_or_samples < OPUS_OK) { | ||
| 104 | return ret_code_or_samples; | ||
| 105 | } | ||
| 106 | |||
| 107 | out_sample_count = ret_code_or_samples; | ||
| 108 | return opus_multistream_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range); | ||
| 109 | } | ||
| 110 | |||
| 111 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h new file mode 100644 index 000000000..93558ded5 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <opus_multistream.h> | ||
| 7 | |||
| 8 | #include "common/common_types.h" | ||
| 9 | |||
| 10 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 11 | using LibOpusMSDecoder = ::OpusMSDecoder; | ||
| 12 | static constexpr u32 DecodeMultiStreamObjectMagic = 0xDEADBEEF; | ||
| 13 | |||
| 14 | class OpusMultiStreamDecodeObject { | ||
| 15 | public: | ||
| 16 | static u32 GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count); | ||
| 17 | static OpusMultiStreamDecodeObject& Initialize(u64 buffer, u64 buffer2); | ||
| 18 | |||
| 19 | s32 InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, | ||
| 20 | u32 stereo_stream_count, u8* mappings); | ||
| 21 | s32 Shutdown(); | ||
| 22 | s32 ResetDecoder(); | ||
| 23 | s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, | ||
| 24 | u64 input_data_size); | ||
| 25 | u32 GetFinalRange() const noexcept { | ||
| 26 | return final_range; | ||
| 27 | } | ||
| 28 | |||
| 29 | private: | ||
| 30 | u32 magic; | ||
| 31 | bool initialized; | ||
| 32 | bool state_valid; | ||
| 33 | OpusMultiStreamDecodeObject* self; | ||
| 34 | u32 final_range; | ||
| 35 | LibOpusMSDecoder* decoder; | ||
| 36 | }; | ||
| 37 | static_assert(std::is_trivially_constructible_v<OpusMultiStreamDecodeObject>); | ||
| 38 | |||
| 39 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/apps/opus/shared_memory.h b/src/audio_core/adsp/apps/opus/shared_memory.h new file mode 100644 index 000000000..c696731ed --- /dev/null +++ b/src/audio_core/adsp/apps/opus/shared_memory.h | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace AudioCore::ADSP::OpusDecoder { | ||
| 10 | |||
| 11 | struct SharedMemory { | ||
| 12 | std::array<u8, 0x100> channel_mapping{}; | ||
| 13 | std::array<u64, 16> host_send_data{}; | ||
| 14 | std::array<u64, 16> dsp_return_data{}; | ||
| 15 | }; | ||
| 16 | |||
| 17 | } // namespace AudioCore::ADSP::OpusDecoder | ||
diff --git a/src/audio_core/adsp/mailbox.h b/src/audio_core/adsp/mailbox.h index c31b73717..1dd40ebfa 100644 --- a/src/audio_core/adsp/mailbox.h +++ b/src/audio_core/adsp/mailbox.h | |||
| @@ -3,6 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <span> | ||
| 7 | |||
| 6 | #include "common/bounded_threadsafe_queue.h" | 8 | #include "common/bounded_threadsafe_queue.h" |
| 7 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 8 | 10 | ||
| @@ -19,11 +21,6 @@ enum class Direction : u32 { | |||
| 19 | DSP, | 21 | DSP, |
| 20 | }; | 22 | }; |
| 21 | 23 | ||
| 22 | struct MailboxMessage { | ||
| 23 | u32 msg; | ||
| 24 | std::span<u8> data; | ||
| 25 | }; | ||
| 26 | |||
| 27 | class Mailbox { | 24 | class Mailbox { |
| 28 | public: | 25 | public: |
| 29 | void Initialize(AppMailboxId id_) { | 26 | void Initialize(AppMailboxId id_) { |
| @@ -35,25 +32,19 @@ public: | |||
| 35 | return id; | 32 | return id; |
| 36 | } | 33 | } |
| 37 | 34 | ||
| 38 | void Send(Direction dir, MailboxMessage&& message) { | 35 | void Send(Direction dir, u32 message) { |
| 39 | auto& queue = dir == Direction::Host ? host_queue : adsp_queue; | 36 | auto& queue = dir == Direction::Host ? host_queue : adsp_queue; |
| 40 | queue.EmplaceWait(std::move(message)); | 37 | queue.EmplaceWait(message); |
| 41 | } | 38 | } |
| 42 | 39 | ||
| 43 | MailboxMessage Receive(Direction dir, bool block = true) { | 40 | u32 Receive(Direction dir, std::stop_token stop_token = {}) { |
| 44 | auto& queue = dir == Direction::Host ? host_queue : adsp_queue; | 41 | auto& queue = dir == Direction::Host ? host_queue : adsp_queue; |
| 45 | MailboxMessage t; | 42 | return queue.PopWait(stop_token); |
| 46 | if (block) { | ||
| 47 | queue.PopWait(t); | ||
| 48 | } else { | ||
| 49 | queue.TryPop(t); | ||
| 50 | } | ||
| 51 | return t; | ||
| 52 | } | 43 | } |
| 53 | 44 | ||
| 54 | void Reset() { | 45 | void Reset() { |
| 55 | id = AppMailboxId::Invalid; | 46 | id = AppMailboxId::Invalid; |
| 56 | MailboxMessage t; | 47 | u32 t{}; |
| 57 | while (host_queue.TryPop(t)) { | 48 | while (host_queue.TryPop(t)) { |
| 58 | } | 49 | } |
| 59 | while (adsp_queue.TryPop(t)) { | 50 | while (adsp_queue.TryPop(t)) { |
| @@ -62,8 +53,8 @@ public: | |||
| 62 | 53 | ||
| 63 | private: | 54 | private: |
| 64 | AppMailboxId id{0}; | 55 | AppMailboxId id{0}; |
| 65 | Common::SPSCQueue<MailboxMessage> host_queue; | 56 | Common::SPSCQueue<u32> host_queue; |
| 66 | Common::SPSCQueue<MailboxMessage> adsp_queue; | 57 | Common::SPSCQueue<u32> adsp_queue; |
| 67 | }; | 58 | }; |
| 68 | 59 | ||
| 69 | } // namespace AudioCore::ADSP | 60 | } // namespace AudioCore::ADSP |
diff --git a/src/audio_core/opus/decoder.cpp b/src/audio_core/opus/decoder.cpp new file mode 100644 index 000000000..5b23fce14 --- /dev/null +++ b/src/audio_core/opus/decoder.cpp | |||
| @@ -0,0 +1,179 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/opus/decoder.h" | ||
| 5 | #include "audio_core/opus/hardware_opus.h" | ||
| 6 | #include "audio_core/opus/parameters.h" | ||
| 7 | #include "common/alignment.h" | ||
| 8 | #include "common/swap.h" | ||
| 9 | #include "core/core.h" | ||
| 10 | |||
| 11 | namespace AudioCore::OpusDecoder { | ||
| 12 | using namespace Service::Audio; | ||
| 13 | namespace { | ||
| 14 | OpusPacketHeader ReverseHeader(OpusPacketHeader header) { | ||
| 15 | OpusPacketHeader out; | ||
| 16 | out.size = Common::swap32(header.size); | ||
| 17 | out.final_range = Common::swap32(header.final_range); | ||
| 18 | return out; | ||
| 19 | } | ||
| 20 | } // namespace | ||
| 21 | |||
| 22 | OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_) | ||
| 23 | : system{system_}, hardware_opus{hardware_opus_} {} | ||
| 24 | |||
| 25 | OpusDecoder::~OpusDecoder() { | ||
| 26 | if (decode_object_initialized) { | ||
| 27 | hardware_opus.ShutdownDecodeObject(shared_buffer.get(), shared_buffer_size); | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | Result OpusDecoder::Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, | ||
| 32 | u64 transfer_memory_size) { | ||
| 33 | auto frame_size{params.use_large_frame_size ? 5760 : 1920}; | ||
| 34 | shared_buffer_size = transfer_memory_size; | ||
| 35 | shared_buffer = std::make_unique<u8[]>(shared_buffer_size); | ||
| 36 | shared_memory_mapped = true; | ||
| 37 | |||
| 38 | buffer_size = | ||
| 39 | Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16); | ||
| 40 | |||
| 41 | out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size}; | ||
| 42 | size_t in_data_size{0x600u}; | ||
| 43 | in_data = {out_data.data() - in_data_size, in_data_size}; | ||
| 44 | |||
| 45 | ON_RESULT_FAILURE { | ||
| 46 | if (shared_memory_mapped) { | ||
| 47 | shared_memory_mapped = false; | ||
| 48 | ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size))); | ||
| 49 | } | ||
| 50 | }; | ||
| 51 | |||
| 52 | R_TRY(hardware_opus.InitializeDecodeObject(params.sample_rate, params.channel_count, | ||
| 53 | shared_buffer.get(), shared_buffer_size)); | ||
| 54 | |||
| 55 | sample_rate = params.sample_rate; | ||
| 56 | channel_count = params.channel_count; | ||
| 57 | use_large_frame_size = params.use_large_frame_size; | ||
| 58 | decode_object_initialized = true; | ||
| 59 | R_SUCCEED(); | ||
| 60 | } | ||
| 61 | |||
| 62 | Result OpusDecoder::Initialize(OpusMultiStreamParametersEx& params, | ||
| 63 | Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) { | ||
| 64 | auto frame_size{params.use_large_frame_size ? 5760 : 1920}; | ||
| 65 | shared_buffer_size = transfer_memory_size; | ||
| 66 | shared_buffer = std::make_unique<u8[]>(shared_buffer_size); | ||
| 67 | shared_memory_mapped = true; | ||
| 68 | |||
| 69 | buffer_size = | ||
| 70 | Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16); | ||
| 71 | |||
| 72 | out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size}; | ||
| 73 | size_t in_data_size{Common::AlignUp(1500ull * params.total_stream_count, 64u)}; | ||
| 74 | in_data = {out_data.data() - in_data_size, in_data_size}; | ||
| 75 | |||
| 76 | ON_RESULT_FAILURE { | ||
| 77 | if (shared_memory_mapped) { | ||
| 78 | shared_memory_mapped = false; | ||
| 79 | ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size))); | ||
| 80 | } | ||
| 81 | }; | ||
| 82 | |||
| 83 | R_TRY(hardware_opus.InitializeMultiStreamDecodeObject( | ||
| 84 | params.sample_rate, params.channel_count, params.total_stream_count, | ||
| 85 | params.stereo_stream_count, params.mappings.data(), shared_buffer.get(), | ||
| 86 | shared_buffer_size)); | ||
| 87 | |||
| 88 | sample_rate = params.sample_rate; | ||
| 89 | channel_count = params.channel_count; | ||
| 90 | total_stream_count = params.total_stream_count; | ||
| 91 | stereo_stream_count = params.stereo_stream_count; | ||
| 92 | use_large_frame_size = params.use_large_frame_size; | ||
| 93 | decode_object_initialized = true; | ||
| 94 | R_SUCCEED(); | ||
| 95 | } | ||
| 96 | |||
| 97 | Result OpusDecoder::DecodeInterleaved(u32* out_data_size, u64* out_time_taken, | ||
| 98 | u32* out_sample_count, std::span<const u8> input_data, | ||
| 99 | std::span<u8> output_data, bool reset) { | ||
| 100 | u32 out_samples; | ||
| 101 | u64 time_taken{}; | ||
| 102 | |||
| 103 | R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall); | ||
| 104 | |||
| 105 | auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())}; | ||
| 106 | OpusPacketHeader header{ReverseHeader(*header_p)}; | ||
| 107 | |||
| 108 | R_UNLESS(in_data.size_bytes() >= header.size && | ||
| 109 | header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(), | ||
| 110 | ResultBufferTooSmall); | ||
| 111 | |||
| 112 | if (!shared_memory_mapped) { | ||
| 113 | R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size)); | ||
| 114 | shared_memory_mapped = true; | ||
| 115 | } | ||
| 116 | |||
| 117 | std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size); | ||
| 118 | |||
| 119 | R_TRY(hardware_opus.DecodeInterleaved(out_samples, out_data.data(), out_data.size_bytes(), | ||
| 120 | channel_count, in_data.data(), header.size, | ||
| 121 | shared_buffer.get(), time_taken, reset)); | ||
| 122 | |||
| 123 | std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16)); | ||
| 124 | |||
| 125 | *out_data_size = header.size + sizeof(OpusPacketHeader); | ||
| 126 | *out_sample_count = out_samples; | ||
| 127 | if (out_time_taken) { | ||
| 128 | *out_time_taken = time_taken / 1000; | ||
| 129 | } | ||
| 130 | R_SUCCEED(); | ||
| 131 | } | ||
| 132 | |||
| 133 | Result OpusDecoder::SetContext([[maybe_unused]] std::span<const u8> context) { | ||
| 134 | R_SUCCEED_IF(shared_memory_mapped); | ||
| 135 | shared_memory_mapped = true; | ||
| 136 | R_RETURN(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size)); | ||
| 137 | } | ||
| 138 | |||
| 139 | Result OpusDecoder::DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken, | ||
| 140 | u32* out_sample_count, | ||
| 141 | std::span<const u8> input_data, | ||
| 142 | std::span<u8> output_data, bool reset) { | ||
| 143 | u32 out_samples; | ||
| 144 | u64 time_taken{}; | ||
| 145 | |||
| 146 | R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall); | ||
| 147 | |||
| 148 | auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())}; | ||
| 149 | OpusPacketHeader header{ReverseHeader(*header_p)}; | ||
| 150 | |||
| 151 | LOG_ERROR(Service_Audio, "header size 0x{:X} input data size 0x{:X} in_data size 0x{:X}", | ||
| 152 | header.size, input_data.size_bytes(), in_data.size_bytes()); | ||
| 153 | |||
| 154 | R_UNLESS(in_data.size_bytes() >= header.size && | ||
| 155 | header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(), | ||
| 156 | ResultBufferTooSmall); | ||
| 157 | |||
| 158 | if (!shared_memory_mapped) { | ||
| 159 | R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size)); | ||
| 160 | shared_memory_mapped = true; | ||
| 161 | } | ||
| 162 | |||
| 163 | std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size); | ||
| 164 | |||
| 165 | R_TRY(hardware_opus.DecodeInterleavedForMultiStream( | ||
| 166 | out_samples, out_data.data(), out_data.size_bytes(), channel_count, in_data.data(), | ||
| 167 | header.size, shared_buffer.get(), time_taken, reset)); | ||
| 168 | |||
| 169 | std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16)); | ||
| 170 | |||
| 171 | *out_data_size = header.size + sizeof(OpusPacketHeader); | ||
| 172 | *out_sample_count = out_samples; | ||
| 173 | if (out_time_taken) { | ||
| 174 | *out_time_taken = time_taken / 1000; | ||
| 175 | } | ||
| 176 | R_SUCCEED(); | ||
| 177 | } | ||
| 178 | |||
| 179 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/opus/decoder.h b/src/audio_core/opus/decoder.h new file mode 100644 index 000000000..d08d8a4a4 --- /dev/null +++ b/src/audio_core/opus/decoder.h | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <span> | ||
| 7 | |||
| 8 | #include "audio_core/opus/parameters.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | #include "core/hle/kernel/k_transfer_memory.h" | ||
| 11 | #include "core/hle/service/audio/errors.h" | ||
| 12 | |||
| 13 | namespace Core { | ||
| 14 | class System; | ||
| 15 | } | ||
| 16 | |||
| 17 | namespace AudioCore::OpusDecoder { | ||
| 18 | class HardwareOpus; | ||
| 19 | |||
| 20 | class OpusDecoder { | ||
| 21 | public: | ||
| 22 | explicit OpusDecoder(Core::System& system, HardwareOpus& hardware_opus_); | ||
| 23 | ~OpusDecoder(); | ||
| 24 | |||
| 25 | Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, | ||
| 26 | u64 transfer_memory_size); | ||
| 27 | Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, | ||
| 28 | u64 transfer_memory_size); | ||
| 29 | Result DecodeInterleaved(u32* out_data_size, u64* out_time_taken, u32* out_sample_count, | ||
| 30 | std::span<const u8> input_data, std::span<u8> output_data, bool reset); | ||
| 31 | Result SetContext([[maybe_unused]] std::span<const u8> context); | ||
| 32 | Result DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken, | ||
| 33 | u32* out_sample_count, std::span<const u8> input_data, | ||
| 34 | std::span<u8> output_data, bool reset); | ||
| 35 | |||
| 36 | private: | ||
| 37 | Core::System& system; | ||
| 38 | HardwareOpus& hardware_opus; | ||
| 39 | std::unique_ptr<u8[]> shared_buffer{}; | ||
| 40 | u64 shared_buffer_size; | ||
| 41 | std::span<u8> in_data{}; | ||
| 42 | std::span<u8> out_data{}; | ||
| 43 | u64 buffer_size{}; | ||
| 44 | s32 sample_rate{}; | ||
| 45 | s32 channel_count{}; | ||
| 46 | bool use_large_frame_size{false}; | ||
| 47 | s32 total_stream_count{}; | ||
| 48 | s32 stereo_stream_count{}; | ||
| 49 | bool shared_memory_mapped{false}; | ||
| 50 | bool decode_object_initialized{false}; | ||
| 51 | }; | ||
| 52 | |||
| 53 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/opus/decoder_manager.cpp b/src/audio_core/opus/decoder_manager.cpp new file mode 100644 index 000000000..4a5382973 --- /dev/null +++ b/src/audio_core/opus/decoder_manager.cpp | |||
| @@ -0,0 +1,102 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/adsp/apps/opus/opus_decoder.h" | ||
| 5 | #include "audio_core/opus/decoder_manager.h" | ||
| 6 | #include "common/alignment.h" | ||
| 7 | #include "core/core.h" | ||
| 8 | |||
| 9 | namespace AudioCore::OpusDecoder { | ||
| 10 | using namespace Service::Audio; | ||
| 11 | |||
| 12 | namespace { | ||
| 13 | bool IsValidChannelCount(u32 channel_count) { | ||
| 14 | return channel_count == 1 || channel_count == 2; | ||
| 15 | } | ||
| 16 | |||
| 17 | bool IsValidMultiStreamChannelCount(u32 channel_count) { | ||
| 18 | return channel_count > 0 && channel_count <= OpusStreamCountMax; | ||
| 19 | } | ||
| 20 | |||
| 21 | bool IsValidSampleRate(u32 sample_rate) { | ||
| 22 | return sample_rate == 8'000 || sample_rate == 12'000 || sample_rate == 16'000 || | ||
| 23 | sample_rate == 24'000 || sample_rate == 48'000; | ||
| 24 | } | ||
| 25 | |||
| 26 | bool IsValidStreamCount(u32 channel_count, u32 total_stream_count, u32 stereo_stream_count) { | ||
| 27 | return total_stream_count > 0 && stereo_stream_count > 0 && | ||
| 28 | stereo_stream_count <= total_stream_count && | ||
| 29 | total_stream_count + stereo_stream_count <= channel_count; | ||
| 30 | } | ||
| 31 | |||
| 32 | } // namespace | ||
| 33 | |||
| 34 | OpusDecoderManager::OpusDecoderManager(Core::System& system_) | ||
| 35 | : system{system_}, hardware_opus{system} { | ||
| 36 | for (u32 i = 0; i < MaxChannels; i++) { | ||
| 37 | required_workbuffer_sizes[i] = hardware_opus.GetWorkBufferSize(1 + i); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | Result OpusDecoderManager::GetWorkBufferSize(OpusParameters& params, u64& out_size) { | ||
| 42 | OpusParametersEx ex{ | ||
| 43 | .sample_rate = params.sample_rate, | ||
| 44 | .channel_count = params.channel_count, | ||
| 45 | .use_large_frame_size = false, | ||
| 46 | }; | ||
| 47 | R_RETURN(GetWorkBufferSizeExEx(ex, out_size)); | ||
| 48 | } | ||
| 49 | |||
| 50 | Result OpusDecoderManager::GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size) { | ||
| 51 | R_RETURN(GetWorkBufferSizeExEx(params, out_size)); | ||
| 52 | } | ||
| 53 | |||
| 54 | Result OpusDecoderManager::GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size) { | ||
| 55 | R_UNLESS(IsValidChannelCount(params.channel_count), ResultInvalidOpusChannelCount); | ||
| 56 | R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate); | ||
| 57 | |||
| 58 | auto work_buffer_size{required_workbuffer_sizes[params.channel_count - 1]}; | ||
| 59 | auto frame_size{params.use_large_frame_size ? 5760 : 1920}; | ||
| 60 | work_buffer_size += | ||
| 61 | Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64); | ||
| 62 | out_size = work_buffer_size + 0x600; | ||
| 63 | R_SUCCEED(); | ||
| 64 | } | ||
| 65 | |||
| 66 | Result OpusDecoderManager::GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params, | ||
| 67 | u64& out_size) { | ||
| 68 | OpusMultiStreamParametersEx ex{ | ||
| 69 | .sample_rate = params.sample_rate, | ||
| 70 | .channel_count = params.channel_count, | ||
| 71 | .total_stream_count = params.total_stream_count, | ||
| 72 | .stereo_stream_count = params.stereo_stream_count, | ||
| 73 | .use_large_frame_size = false, | ||
| 74 | .mappings = {}, | ||
| 75 | }; | ||
| 76 | R_RETURN(GetWorkBufferSizeForMultiStreamExEx(ex, out_size)); | ||
| 77 | } | ||
| 78 | |||
| 79 | Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params, | ||
| 80 | u64& out_size) { | ||
| 81 | R_RETURN(GetWorkBufferSizeForMultiStreamExEx(params, out_size)); | ||
| 82 | } | ||
| 83 | |||
| 84 | Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params, | ||
| 85 | u64& out_size) { | ||
| 86 | R_UNLESS(IsValidMultiStreamChannelCount(params.channel_count), ResultInvalidOpusChannelCount); | ||
| 87 | R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate); | ||
| 88 | R_UNLESS(IsValidStreamCount(params.channel_count, params.total_stream_count, | ||
| 89 | params.stereo_stream_count), | ||
| 90 | ResultInvalidOpusSampleRate); | ||
| 91 | |||
| 92 | auto work_buffer_size{hardware_opus.GetWorkBufferSizeForMultiStream( | ||
| 93 | params.total_stream_count, params.stereo_stream_count)}; | ||
| 94 | auto frame_size{params.use_large_frame_size ? 5760 : 1920}; | ||
| 95 | work_buffer_size += Common::AlignUp(1500 * params.total_stream_count, 64); | ||
| 96 | work_buffer_size += | ||
| 97 | Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64); | ||
| 98 | out_size = work_buffer_size; | ||
| 99 | R_SUCCEED(); | ||
| 100 | } | ||
| 101 | |||
| 102 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/opus/decoder_manager.h b/src/audio_core/opus/decoder_manager.h new file mode 100644 index 000000000..466e1967b --- /dev/null +++ b/src/audio_core/opus/decoder_manager.h | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "audio_core/opus/hardware_opus.h" | ||
| 7 | #include "audio_core/opus/parameters.h" | ||
| 8 | #include "common/common_types.h" | ||
| 9 | #include "core/hle/service/audio/errors.h" | ||
| 10 | |||
| 11 | namespace Core { | ||
| 12 | class System; | ||
| 13 | } | ||
| 14 | |||
| 15 | namespace AudioCore::OpusDecoder { | ||
| 16 | |||
| 17 | class OpusDecoderManager { | ||
| 18 | public: | ||
| 19 | OpusDecoderManager(Core::System& system); | ||
| 20 | |||
| 21 | HardwareOpus& GetHardwareOpus() { | ||
| 22 | return hardware_opus; | ||
| 23 | } | ||
| 24 | |||
| 25 | Result GetWorkBufferSize(OpusParameters& params, u64& out_size); | ||
| 26 | Result GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size); | ||
| 27 | Result GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size); | ||
| 28 | Result GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params, u64& out_size); | ||
| 29 | Result GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params, u64& out_size); | ||
| 30 | Result GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params, u64& out_size); | ||
| 31 | |||
| 32 | private: | ||
| 33 | Core::System& system; | ||
| 34 | HardwareOpus hardware_opus; | ||
| 35 | std::array<u64, MaxChannels> required_workbuffer_sizes{}; | ||
| 36 | }; | ||
| 37 | |||
| 38 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/opus/hardware_opus.cpp b/src/audio_core/opus/hardware_opus.cpp new file mode 100644 index 000000000..d6544dcb0 --- /dev/null +++ b/src/audio_core/opus/hardware_opus.cpp | |||
| @@ -0,0 +1,241 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <array> | ||
| 5 | |||
| 6 | #include "audio_core/audio_core.h" | ||
| 7 | #include "audio_core/opus/hardware_opus.h" | ||
| 8 | #include "core/core.h" | ||
| 9 | |||
| 10 | namespace AudioCore::OpusDecoder { | ||
| 11 | namespace { | ||
| 12 | using namespace Service::Audio; | ||
| 13 | |||
| 14 | static constexpr Result ResultCodeFromLibOpusErrorCode(u64 error_code) { | ||
| 15 | s32 error{static_cast<s32>(error_code)}; | ||
| 16 | ASSERT(error <= OPUS_OK); | ||
| 17 | switch (error) { | ||
| 18 | case OPUS_ALLOC_FAIL: | ||
| 19 | R_THROW(ResultLibOpusAllocFail); | ||
| 20 | case OPUS_INVALID_STATE: | ||
| 21 | R_THROW(ResultLibOpusInvalidState); | ||
| 22 | case OPUS_UNIMPLEMENTED: | ||
| 23 | R_THROW(ResultLibOpusUnimplemented); | ||
| 24 | case OPUS_INVALID_PACKET: | ||
| 25 | R_THROW(ResultLibOpusInvalidPacket); | ||
| 26 | case OPUS_INTERNAL_ERROR: | ||
| 27 | R_THROW(ResultLibOpusInternalError); | ||
| 28 | case OPUS_BUFFER_TOO_SMALL: | ||
| 29 | R_THROW(ResultBufferTooSmall); | ||
| 30 | case OPUS_BAD_ARG: | ||
| 31 | R_THROW(ResultLibOpusBadArg); | ||
| 32 | case OPUS_OK: | ||
| 33 | R_RETURN(ResultSuccess); | ||
| 34 | } | ||
| 35 | UNREACHABLE(); | ||
| 36 | } | ||
| 37 | |||
| 38 | } // namespace | ||
| 39 | |||
| 40 | HardwareOpus::HardwareOpus(Core::System& system_) | ||
| 41 | : system{system_}, opus_decoder{system.AudioCore().ADSP().OpusDecoder()} { | ||
| 42 | opus_decoder.SetSharedMemory(shared_memory); | ||
| 43 | } | ||
| 44 | |||
| 45 | u64 HardwareOpus::GetWorkBufferSize(u32 channel) { | ||
| 46 | if (!opus_decoder.IsRunning()) { | ||
| 47 | return 0; | ||
| 48 | } | ||
| 49 | std::scoped_lock l{mutex}; | ||
| 50 | shared_memory.host_send_data[0] = channel; | ||
| 51 | opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::GetWorkBufferSize); | ||
| 52 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 53 | if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeOK) { | ||
| 54 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 55 | ADSP::OpusDecoder::Message::GetWorkBufferSizeOK, msg); | ||
| 56 | return 0; | ||
| 57 | } | ||
| 58 | return shared_memory.dsp_return_data[0]; | ||
| 59 | } | ||
| 60 | |||
| 61 | u64 HardwareOpus::GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count) { | ||
| 62 | std::scoped_lock l{mutex}; | ||
| 63 | shared_memory.host_send_data[0] = total_stream_count; | ||
| 64 | shared_memory.host_send_data[1] = stereo_stream_count; | ||
| 65 | opus_decoder.Send(ADSP::Direction::DSP, | ||
| 66 | ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStream); | ||
| 67 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 68 | if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK) { | ||
| 69 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 70 | ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK, msg); | ||
| 71 | return 0; | ||
| 72 | } | ||
| 73 | return shared_memory.dsp_return_data[0]; | ||
| 74 | } | ||
| 75 | |||
| 76 | Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer, | ||
| 77 | u64 buffer_size) { | ||
| 78 | std::scoped_lock l{mutex}; | ||
| 79 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 80 | shared_memory.host_send_data[1] = buffer_size; | ||
| 81 | shared_memory.host_send_data[2] = sample_rate; | ||
| 82 | shared_memory.host_send_data[3] = channel_count; | ||
| 83 | |||
| 84 | opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::InitializeDecodeObject); | ||
| 85 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 86 | if (msg != ADSP::OpusDecoder::Message::InitializeDecodeObjectOK) { | ||
| 87 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 88 | ADSP::OpusDecoder::Message::InitializeDecodeObjectOK, msg); | ||
| 89 | R_THROW(ResultInvalidOpusDSPReturnCode); | ||
| 90 | } | ||
| 91 | |||
| 92 | R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); | ||
| 93 | } | ||
| 94 | |||
| 95 | Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count, | ||
| 96 | u32 total_stream_count, | ||
| 97 | u32 stereo_stream_count, void* mappings, | ||
| 98 | void* buffer, u64 buffer_size) { | ||
| 99 | std::scoped_lock l{mutex}; | ||
| 100 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 101 | shared_memory.host_send_data[1] = buffer_size; | ||
| 102 | shared_memory.host_send_data[2] = sample_rate; | ||
| 103 | shared_memory.host_send_data[3] = channel_count; | ||
| 104 | shared_memory.host_send_data[4] = total_stream_count; | ||
| 105 | shared_memory.host_send_data[5] = stereo_stream_count; | ||
| 106 | |||
| 107 | ASSERT(channel_count <= MaxChannels); | ||
| 108 | std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8)); | ||
| 109 | |||
| 110 | opus_decoder.Send(ADSP::Direction::DSP, | ||
| 111 | ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObject); | ||
| 112 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 113 | if (msg != ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK) { | ||
| 114 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 115 | ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK, msg); | ||
| 116 | R_THROW(ResultInvalidOpusDSPReturnCode); | ||
| 117 | } | ||
| 118 | |||
| 119 | R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); | ||
| 120 | } | ||
| 121 | |||
| 122 | Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) { | ||
| 123 | std::scoped_lock l{mutex}; | ||
| 124 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 125 | shared_memory.host_send_data[1] = buffer_size; | ||
| 126 | |||
| 127 | opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::ShutdownDecodeObject); | ||
| 128 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 129 | ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, | ||
| 130 | "Expected Opus shutdown code {}, got {}", | ||
| 131 | ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg); | ||
| 132 | |||
| 133 | R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); | ||
| 134 | } | ||
| 135 | |||
| 136 | Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) { | ||
| 137 | std::scoped_lock l{mutex}; | ||
| 138 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 139 | shared_memory.host_send_data[1] = buffer_size; | ||
| 140 | |||
| 141 | opus_decoder.Send(ADSP::Direction::DSP, | ||
| 142 | ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObject); | ||
| 143 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 144 | ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, | ||
| 145 | "Expected Opus shutdown code {}, got {}", | ||
| 146 | ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg); | ||
| 147 | |||
| 148 | R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); | ||
| 149 | } | ||
| 150 | |||
| 151 | Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data, | ||
| 152 | u64 output_data_size, u32 channel_count, void* input_data, | ||
| 153 | u64 input_data_size, void* buffer, u64& out_time_taken, | ||
| 154 | bool reset) { | ||
| 155 | std::scoped_lock l{mutex}; | ||
| 156 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 157 | shared_memory.host_send_data[1] = (u64)input_data; | ||
| 158 | shared_memory.host_send_data[2] = input_data_size; | ||
| 159 | shared_memory.host_send_data[3] = (u64)output_data; | ||
| 160 | shared_memory.host_send_data[4] = output_data_size; | ||
| 161 | shared_memory.host_send_data[5] = 0; | ||
| 162 | shared_memory.host_send_data[6] = reset; | ||
| 163 | |||
| 164 | opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::DecodeInterleaved); | ||
| 165 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 166 | if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedOK) { | ||
| 167 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 168 | ADSP::OpusDecoder::Message::DecodeInterleavedOK, msg); | ||
| 169 | R_THROW(ResultInvalidOpusDSPReturnCode); | ||
| 170 | } | ||
| 171 | |||
| 172 | auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])}; | ||
| 173 | if (error_code == OPUS_OK) { | ||
| 174 | out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]); | ||
| 175 | out_time_taken = 1000 * shared_memory.dsp_return_data[2]; | ||
| 176 | } | ||
| 177 | R_RETURN(ResultCodeFromLibOpusErrorCode(error_code)); | ||
| 178 | } | ||
| 179 | |||
| 180 | Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data, | ||
| 181 | u64 output_data_size, u32 channel_count, | ||
| 182 | void* input_data, u64 input_data_size, | ||
| 183 | void* buffer, u64& out_time_taken, | ||
| 184 | bool reset) { | ||
| 185 | std::scoped_lock l{mutex}; | ||
| 186 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 187 | shared_memory.host_send_data[1] = (u64)input_data; | ||
| 188 | shared_memory.host_send_data[2] = input_data_size; | ||
| 189 | shared_memory.host_send_data[3] = (u64)output_data; | ||
| 190 | shared_memory.host_send_data[4] = output_data_size; | ||
| 191 | shared_memory.host_send_data[5] = 0; | ||
| 192 | shared_memory.host_send_data[6] = reset; | ||
| 193 | |||
| 194 | opus_decoder.Send(ADSP::Direction::DSP, | ||
| 195 | ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream); | ||
| 196 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 197 | if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) { | ||
| 198 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 199 | ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg); | ||
| 200 | R_THROW(ResultInvalidOpusDSPReturnCode); | ||
| 201 | } | ||
| 202 | |||
| 203 | auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])}; | ||
| 204 | if (error_code == OPUS_OK) { | ||
| 205 | out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]); | ||
| 206 | out_time_taken = 1000 * shared_memory.dsp_return_data[2]; | ||
| 207 | } | ||
| 208 | R_RETURN(ResultCodeFromLibOpusErrorCode(error_code)); | ||
| 209 | } | ||
| 210 | |||
| 211 | Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) { | ||
| 212 | std::scoped_lock l{mutex}; | ||
| 213 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 214 | shared_memory.host_send_data[1] = buffer_size; | ||
| 215 | |||
| 216 | opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory); | ||
| 217 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 218 | if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) { | ||
| 219 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 220 | ADSP::OpusDecoder::Message::MapMemoryOK, msg); | ||
| 221 | R_THROW(ResultInvalidOpusDSPReturnCode); | ||
| 222 | } | ||
| 223 | R_SUCCEED(); | ||
| 224 | } | ||
| 225 | |||
| 226 | Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) { | ||
| 227 | std::scoped_lock l{mutex}; | ||
| 228 | shared_memory.host_send_data[0] = (u64)buffer; | ||
| 229 | shared_memory.host_send_data[1] = buffer_size; | ||
| 230 | |||
| 231 | opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory); | ||
| 232 | auto msg = opus_decoder.Receive(ADSP::Direction::Host); | ||
| 233 | if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) { | ||
| 234 | LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", | ||
| 235 | ADSP::OpusDecoder::Message::UnmapMemoryOK, msg); | ||
| 236 | R_THROW(ResultInvalidOpusDSPReturnCode); | ||
| 237 | } | ||
| 238 | R_SUCCEED(); | ||
| 239 | } | ||
| 240 | |||
| 241 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/opus/hardware_opus.h b/src/audio_core/opus/hardware_opus.h new file mode 100644 index 000000000..7013a6b40 --- /dev/null +++ b/src/audio_core/opus/hardware_opus.h | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <mutex> | ||
| 7 | #include <opus.h> | ||
| 8 | |||
| 9 | #include "audio_core/adsp/apps/opus/opus_decoder.h" | ||
| 10 | #include "audio_core/adsp/apps/opus/shared_memory.h" | ||
| 11 | #include "audio_core/adsp/mailbox.h" | ||
| 12 | #include "core/hle/service/audio/errors.h" | ||
| 13 | |||
| 14 | namespace AudioCore::OpusDecoder { | ||
| 15 | class HardwareOpus { | ||
| 16 | public: | ||
| 17 | HardwareOpus(Core::System& system); | ||
| 18 | |||
| 19 | u64 GetWorkBufferSize(u32 channel); | ||
| 20 | u64 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count); | ||
| 21 | |||
| 22 | Result InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer, | ||
| 23 | u64 buffer_size); | ||
| 24 | Result InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count, | ||
| 25 | u32 totaL_stream_count, u32 stereo_stream_count, | ||
| 26 | void* mappings, void* buffer, u64 buffer_size); | ||
| 27 | Result ShutdownDecodeObject(void* buffer, u64 buffer_size); | ||
| 28 | Result ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size); | ||
| 29 | Result DecodeInterleaved(u32& out_sample_count, void* output_data, u64 output_data_size, | ||
| 30 | u32 channel_count, void* input_data, u64 input_data_size, void* buffer, | ||
| 31 | u64& out_time_taken, bool reset); | ||
| 32 | Result DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data, | ||
| 33 | u64 output_data_size, u32 channel_count, | ||
| 34 | void* input_data, u64 input_data_size, void* buffer, | ||
| 35 | u64& out_time_taken, bool reset); | ||
| 36 | Result MapMemory(void* buffer, u64 buffer_size); | ||
| 37 | Result UnmapMemory(void* buffer, u64 buffer_size); | ||
| 38 | |||
| 39 | private: | ||
| 40 | Core::System& system; | ||
| 41 | std::mutex mutex; | ||
| 42 | ADSP::OpusDecoder::OpusDecoder& opus_decoder; | ||
| 43 | ADSP::OpusDecoder::SharedMemory shared_memory; | ||
| 44 | }; | ||
| 45 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/opus/parameters.h b/src/audio_core/opus/parameters.h new file mode 100644 index 000000000..4c54b2825 --- /dev/null +++ b/src/audio_core/opus/parameters.h | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace AudioCore::OpusDecoder { | ||
| 10 | constexpr size_t OpusStreamCountMax = 255; | ||
| 11 | constexpr size_t MaxChannels = 2; | ||
| 12 | |||
| 13 | struct OpusParameters { | ||
| 14 | /* 0x00 */ u32 sample_rate; | ||
| 15 | /* 0x04 */ u32 channel_count; | ||
| 16 | }; // size = 0x8 | ||
| 17 | static_assert(sizeof(OpusParameters) == 0x8, "OpusParameters has the wrong size!"); | ||
| 18 | |||
| 19 | struct OpusParametersEx { | ||
| 20 | /* 0x00 */ u32 sample_rate; | ||
| 21 | /* 0x04 */ u32 channel_count; | ||
| 22 | /* 0x08 */ bool use_large_frame_size; | ||
| 23 | /* 0x09 */ INSERT_PADDING_BYTES(7); | ||
| 24 | }; // size = 0x10 | ||
| 25 | static_assert(sizeof(OpusParametersEx) == 0x10, "OpusParametersEx has the wrong size!"); | ||
| 26 | |||
| 27 | struct OpusMultiStreamParameters { | ||
| 28 | /* 0x00 */ u32 sample_rate; | ||
| 29 | /* 0x04 */ u32 channel_count; | ||
| 30 | /* 0x08 */ u32 total_stream_count; | ||
| 31 | /* 0x0C */ u32 stereo_stream_count; | ||
| 32 | /* 0x10 */ std::array<u8, OpusStreamCountMax + 1> mappings; | ||
| 33 | }; // size = 0x110 | ||
| 34 | static_assert(sizeof(OpusMultiStreamParameters) == 0x110, | ||
| 35 | "OpusMultiStreamParameters has the wrong size!"); | ||
| 36 | |||
| 37 | struct OpusMultiStreamParametersEx { | ||
| 38 | /* 0x00 */ u32 sample_rate; | ||
| 39 | /* 0x04 */ u32 channel_count; | ||
| 40 | /* 0x08 */ u32 total_stream_count; | ||
| 41 | /* 0x0C */ u32 stereo_stream_count; | ||
| 42 | /* 0x10 */ bool use_large_frame_size; | ||
| 43 | /* 0x11 */ INSERT_PADDING_BYTES(7); | ||
| 44 | /* 0x18 */ std::array<u8, OpusStreamCountMax + 1> mappings; | ||
| 45 | }; // size = 0x118 | ||
| 46 | static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118, | ||
| 47 | "OpusMultiStreamParametersEx has the wrong size!"); | ||
| 48 | |||
| 49 | struct OpusPacketHeader { | ||
| 50 | /* 0x00 */ u32 size; | ||
| 51 | /* 0x04 */ u32 final_range; | ||
| 52 | }; // size = 0x8 | ||
| 53 | static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusPacketHeader has the wrong size!"); | ||
| 54 | } // namespace AudioCore::OpusDecoder | ||
diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.cpp b/src/audio_core/renderer/command/command_processing_time_estimator.cpp index a48a016b1..0f7aff1b4 100644 --- a/src/audio_core/renderer/command/command_processing_time_estimator.cpp +++ b/src/audio_core/renderer/command/command_processing_time_estimator.cpp | |||
| @@ -27,12 +27,12 @@ u32 CommandProcessingTimeEstimatorVersion1::Estimate( | |||
| 27 | 27 | ||
| 28 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | 28 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( |
| 29 | const AdpcmDataSourceVersion1Command& command) const { | 29 | const AdpcmDataSourceVersion1Command& command) const { |
| 30 | return static_cast<u32>(command.pitch * 0.25f * 1.2f); | 30 | return static_cast<u32>(command.pitch * 0.46f * 1.2f); |
| 31 | } | 31 | } |
| 32 | 32 | ||
| 33 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | 33 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( |
| 34 | const AdpcmDataSourceVersion2Command& command) const { | 34 | const AdpcmDataSourceVersion2Command& command) const { |
| 35 | return static_cast<u32>(command.pitch * 0.25f * 1.2f); | 35 | return static_cast<u32>(command.pitch * 0.46f * 1.2f); |
| 36 | } | 36 | } |
| 37 | 37 | ||
| 38 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | 38 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( |
diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp index d29754634..31f92087c 100644 --- a/src/audio_core/renderer/system.cpp +++ b/src/audio_core/renderer/system.cpp | |||
| @@ -684,11 +684,11 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer, | |||
| 684 | sink_context, splitter_context, perf_manager}; | 684 | sink_context, splitter_context, perf_manager}; |
| 685 | 685 | ||
| 686 | voice_context.SortInfo(); | 686 | voice_context.SortInfo(); |
| 687 | command_generator.GenerateVoiceCommands(); | ||
| 687 | 688 | ||
| 688 | const auto start_estimated_time{drop_voice_param * | 689 | const auto start_estimated_time{drop_voice_param * |
| 689 | static_cast<f32>(command_buffer.estimated_process_time)}; | 690 | static_cast<f32>(command_buffer.estimated_process_time)}; |
| 690 | 691 | ||
| 691 | command_generator.GenerateVoiceCommands(); | ||
| 692 | command_generator.GenerateSubMixCommands(); | 692 | command_generator.GenerateSubMixCommands(); |
| 693 | command_generator.GenerateFinalMixCommands(); | 693 | command_generator.GenerateFinalMixCommands(); |
| 694 | command_generator.GenerateSinkCommands(); | 694 | command_generator.GenerateSinkCommands(); |
| @@ -708,11 +708,13 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer, | |||
| 708 | 708 | ||
| 709 | const auto end_estimated_time{drop_voice_param * | 709 | const auto end_estimated_time{drop_voice_param * |
| 710 | static_cast<f32>(command_buffer.estimated_process_time)}; | 710 | static_cast<f32>(command_buffer.estimated_process_time)}; |
| 711 | |||
| 712 | const auto dsp_time_limit{((time_limit_percent / 100.0f) * 2'880'000.0f) * | ||
| 713 | (static_cast<f32>(render_time_limit_percent) / 100.0f)}; | ||
| 714 | |||
| 711 | const auto estimated_time{start_estimated_time - end_estimated_time}; | 715 | const auto estimated_time{start_estimated_time - end_estimated_time}; |
| 712 | 716 | ||
| 713 | const auto time_limit{static_cast<u32>( | 717 | const auto time_limit{static_cast<u32>(std::max(dsp_time_limit + estimated_time, 0.0f))}; |
| 714 | estimated_time + (((time_limit_percent / 100.0f) * 2'880'000.0) * | ||
| 715 | (static_cast<f32>(render_time_limit_percent) / 100.0f)))}; | ||
| 716 | num_voices_dropped = | 718 | num_voices_dropped = |
| 717 | DropVoices(command_buffer, static_cast<u32>(start_estimated_time), time_limit); | 719 | DropVoices(command_buffer, static_cast<u32>(start_estimated_time), time_limit); |
| 718 | } | 720 | } |
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 34877b461..416203c59 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -26,12 +26,11 @@ add_library(common STATIC | |||
| 26 | assert.h | 26 | assert.h |
| 27 | atomic_helpers.h | 27 | atomic_helpers.h |
| 28 | atomic_ops.h | 28 | atomic_ops.h |
| 29 | detached_tasks.cpp | ||
| 30 | detached_tasks.h | ||
| 31 | bit_cast.h | 29 | bit_cast.h |
| 32 | bit_field.h | 30 | bit_field.h |
| 33 | bit_set.h | 31 | bit_set.h |
| 34 | bit_util.h | 32 | bit_util.h |
| 33 | bounded_threadsafe_queue.h | ||
| 35 | cityhash.cpp | 34 | cityhash.cpp |
| 36 | cityhash.h | 35 | cityhash.h |
| 37 | common_funcs.h | 36 | common_funcs.h |
| @@ -41,6 +40,8 @@ add_library(common STATIC | |||
| 41 | container_hash.h | 40 | container_hash.h |
| 42 | demangle.cpp | 41 | demangle.cpp |
| 43 | demangle.h | 42 | demangle.h |
| 43 | detached_tasks.cpp | ||
| 44 | detached_tasks.h | ||
| 44 | div_ceil.h | 45 | div_ceil.h |
| 45 | dynamic_library.cpp | 46 | dynamic_library.cpp |
| 46 | dynamic_library.h | 47 | dynamic_library.h |
diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h index bd87aa09b..b36fc1de9 100644 --- a/src/common/bounded_threadsafe_queue.h +++ b/src/common/bounded_threadsafe_queue.h | |||
| @@ -45,13 +45,13 @@ public: | |||
| 45 | } | 45 | } |
| 46 | 46 | ||
| 47 | T PopWait() { | 47 | T PopWait() { |
| 48 | T t; | 48 | T t{}; |
| 49 | Pop<PopMode::Wait>(t); | 49 | Pop<PopMode::Wait>(t); |
| 50 | return t; | 50 | return t; |
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | T PopWait(std::stop_token stop_token) { | 53 | T PopWait(std::stop_token stop_token) { |
| 54 | T t; | 54 | T t{}; |
| 55 | Pop<PopMode::WaitWithStopToken>(t, stop_token); | 55 | Pop<PopMode::WaitWithStopToken>(t, stop_token); |
| 56 | return t; | 56 | return t; |
| 57 | } | 57 | } |
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp index 36e67c145..174aed49b 100644 --- a/src/common/fs/fs.cpp +++ b/src/common/fs/fs.cpp | |||
| @@ -528,38 +528,41 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path, | |||
| 528 | // Generic Filesystem Operations | 528 | // Generic Filesystem Operations |
| 529 | 529 | ||
| 530 | bool Exists(const fs::path& path) { | 530 | bool Exists(const fs::path& path) { |
| 531 | std::error_code ec; | ||
| 531 | #ifdef ANDROID | 532 | #ifdef ANDROID |
| 532 | if (Android::IsContentUri(path)) { | 533 | if (Android::IsContentUri(path)) { |
| 533 | return Android::Exists(path); | 534 | return Android::Exists(path); |
| 534 | } else { | 535 | } else { |
| 535 | return fs::exists(path); | 536 | return fs::exists(path, ec); |
| 536 | } | 537 | } |
| 537 | #else | 538 | #else |
| 538 | return fs::exists(path); | 539 | return fs::exists(path, ec); |
| 539 | #endif | 540 | #endif |
| 540 | } | 541 | } |
| 541 | 542 | ||
| 542 | bool IsFile(const fs::path& path) { | 543 | bool IsFile(const fs::path& path) { |
| 544 | std::error_code ec; | ||
| 543 | #ifdef ANDROID | 545 | #ifdef ANDROID |
| 544 | if (Android::IsContentUri(path)) { | 546 | if (Android::IsContentUri(path)) { |
| 545 | return !Android::IsDirectory(path); | 547 | return !Android::IsDirectory(path); |
| 546 | } else { | 548 | } else { |
| 547 | return fs::is_regular_file(path); | 549 | return fs::is_regular_file(path, ec); |
| 548 | } | 550 | } |
| 549 | #else | 551 | #else |
| 550 | return fs::is_regular_file(path); | 552 | return fs::is_regular_file(path, ec); |
| 551 | #endif | 553 | #endif |
| 552 | } | 554 | } |
| 553 | 555 | ||
| 554 | bool IsDir(const fs::path& path) { | 556 | bool IsDir(const fs::path& path) { |
| 557 | std::error_code ec; | ||
| 555 | #ifdef ANDROID | 558 | #ifdef ANDROID |
| 556 | if (Android::IsContentUri(path)) { | 559 | if (Android::IsContentUri(path)) { |
| 557 | return Android::IsDirectory(path); | 560 | return Android::IsDirectory(path); |
| 558 | } else { | 561 | } else { |
| 559 | return fs::is_directory(path); | 562 | return fs::is_directory(path, ec); |
| 560 | } | 563 | } |
| 561 | #else | 564 | #else |
| 562 | return fs::is_directory(path); | 565 | return fs::is_directory(path, ec); |
| 563 | #endif | 566 | #endif |
| 564 | } | 567 | } |
| 565 | 568 | ||
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 b15213bd7..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}; |
| @@ -348,6 +349,10 @@ struct Values { | |||
| 348 | Category::RendererDebug}; | 349 | Category::RendererDebug}; |
| 349 | Setting<bool> disable_shader_loop_safety_checks{ | 350 | Setting<bool> disable_shader_loop_safety_checks{ |
| 350 | linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; | 351 | linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; |
| 352 | Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey", | ||
| 353 | Category::RendererDebug}; | ||
| 354 | // TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control | ||
| 355 | bool renderer_amdvlk_depth_bias_workaround{}; | ||
| 351 | 356 | ||
| 352 | // System | 357 | // System |
| 353 | SwitchableSetting<Language, true> language_index{linkage, | 358 | SwitchableSetting<Language, true> language_index{linkage, |
| @@ -520,6 +525,7 @@ struct Values { | |||
| 520 | 525 | ||
| 521 | extern Values values; | 526 | extern Values values; |
| 522 | 527 | ||
| 528 | void UpdateGPUAccuracy(); | ||
| 523 | bool IsGPULevelExtreme(); | 529 | bool IsGPULevelExtreme(); |
| 524 | bool IsGPULevelHigh(); | 530 | bool IsGPULevelHigh(); |
| 525 | 531 | ||
diff --git a/src/common/settings_common.h b/src/common/settings_common.h index 5b170dfd5..1800ab10d 100644 --- a/src/common/settings_common.h +++ b/src/common/settings_common.h | |||
| @@ -225,6 +225,16 @@ public: | |||
| 225 | */ | 225 | */ |
| 226 | [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0; | 226 | [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0; |
| 227 | 227 | ||
| 228 | /** | ||
| 229 | * @returns True if the underlying type is a floating point storage | ||
| 230 | */ | ||
| 231 | [[nodiscard]] virtual constexpr bool IsFloatingPoint() const = 0; | ||
| 232 | |||
| 233 | /** | ||
| 234 | * @returns True if the underlying type is an integer storage | ||
| 235 | */ | ||
| 236 | [[nodiscard]] virtual constexpr bool IsIntegral() const = 0; | ||
| 237 | |||
| 228 | /* | 238 | /* |
| 229 | * Switchable settings | 239 | * Switchable settings |
| 230 | */ | 240 | */ |
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h index e10843c73..3175ab07d 100644 --- a/src/common/settings_setting.h +++ b/src/common/settings_setting.h | |||
| @@ -10,6 +10,7 @@ | |||
| 10 | #include <string> | 10 | #include <string> |
| 11 | #include <typeindex> | 11 | #include <typeindex> |
| 12 | #include <typeinfo> | 12 | #include <typeinfo> |
| 13 | #include <fmt/core.h> | ||
| 13 | #include "common/common_types.h" | 14 | #include "common/common_types.h" |
| 14 | #include "common/settings_common.h" | 15 | #include "common/settings_common.h" |
| 15 | #include "common/settings_enums.h" | 16 | #include "common/settings_enums.h" |
| @@ -115,8 +116,12 @@ protected: | |||
| 115 | } else if constexpr (std::is_same_v<Type, AudioEngine>) { | 116 | } else if constexpr (std::is_same_v<Type, AudioEngine>) { |
| 116 | // Compatibility with old AudioEngine setting being a string | 117 | // Compatibility with old AudioEngine setting being a string |
| 117 | return CanonicalizeEnum(value_); | 118 | return CanonicalizeEnum(value_); |
| 119 | } else if constexpr (std::is_floating_point_v<Type>) { | ||
| 120 | return fmt::format("{:f}", value_); | ||
| 121 | } else if constexpr (std::is_enum_v<Type>) { | ||
| 122 | return std::to_string(static_cast<u32>(value_)); | ||
| 118 | } else { | 123 | } else { |
| 119 | return std::to_string(static_cast<u64>(value_)); | 124 | return std::to_string(value_); |
| 120 | } | 125 | } |
| 121 | } | 126 | } |
| 122 | 127 | ||
| @@ -180,13 +185,17 @@ public: | |||
| 180 | this->SetValue(static_cast<u32>(std::stoul(input))); | 185 | this->SetValue(static_cast<u32>(std::stoul(input))); |
| 181 | } else if constexpr (std::is_same_v<Type, bool>) { | 186 | } else if constexpr (std::is_same_v<Type, bool>) { |
| 182 | this->SetValue(input == "true"); | 187 | this->SetValue(input == "true"); |
| 188 | } else if constexpr (std::is_same_v<Type, float>) { | ||
| 189 | this->SetValue(std::stof(input)); | ||
| 183 | } else if constexpr (std::is_same_v<Type, AudioEngine>) { | 190 | } else if constexpr (std::is_same_v<Type, AudioEngine>) { |
| 184 | this->SetValue(ToEnum<Type>(input)); | 191 | this->SetValue(ToEnum<AudioEngine>(input)); |
| 185 | } else { | 192 | } else { |
| 186 | this->SetValue(static_cast<Type>(std::stoll(input))); | 193 | this->SetValue(static_cast<Type>(std::stoll(input))); |
| 187 | } | 194 | } |
| 188 | } catch (std::invalid_argument&) { | 195 | } catch (std::invalid_argument&) { |
| 189 | this->SetValue(this->GetDefault()); | 196 | this->SetValue(this->GetDefault()); |
| 197 | } catch (std::out_of_range&) { | ||
| 198 | this->SetValue(this->GetDefault()); | ||
| 190 | } | 199 | } |
| 191 | } | 200 | } |
| 192 | 201 | ||
| @@ -215,11 +224,27 @@ public: | |||
| 215 | } | 224 | } |
| 216 | } | 225 | } |
| 217 | 226 | ||
| 227 | [[nodiscard]] constexpr bool IsFloatingPoint() const final { | ||
| 228 | return std::is_floating_point_v<Type>; | ||
| 229 | } | ||
| 230 | |||
| 231 | [[nodiscard]] constexpr bool IsIntegral() const final { | ||
| 232 | return std::is_integral_v<Type>; | ||
| 233 | } | ||
| 234 | |||
| 218 | [[nodiscard]] std::string MinVal() const override final { | 235 | [[nodiscard]] std::string MinVal() const override final { |
| 219 | return this->ToString(minimum); | 236 | if constexpr (std::is_arithmetic_v<Type> && !ranged) { |
| 237 | return this->ToString(std::numeric_limits<Type>::min()); | ||
| 238 | } else { | ||
| 239 | return this->ToString(minimum); | ||
| 240 | } | ||
| 220 | } | 241 | } |
| 221 | [[nodiscard]] std::string MaxVal() const override final { | 242 | [[nodiscard]] std::string MaxVal() const override final { |
| 222 | return this->ToString(maximum); | 243 | if constexpr (std::is_arithmetic_v<Type> && !ranged) { |
| 244 | return this->ToString(std::numeric_limits<Type>::max()); | ||
| 245 | } else { | ||
| 246 | return this->ToString(maximum); | ||
| 247 | } | ||
| 223 | } | 248 | } |
| 224 | 249 | ||
| 225 | [[nodiscard]] constexpr bool Ranged() const override { | 250 | [[nodiscard]] constexpr bool Ranged() const override { |
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 6cd1a28f2..d0f76e57e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -596,6 +596,10 @@ add_library(core STATIC | |||
| 596 | hle/service/mii/types/ver3_store_data.h | 596 | hle/service/mii/types/ver3_store_data.h |
| 597 | hle/service/mii/mii.cpp | 597 | hle/service/mii/mii.cpp |
| 598 | hle/service/mii/mii.h | 598 | hle/service/mii/mii.h |
| 599 | hle/service/mii/mii_database.cpp | ||
| 600 | hle/service/mii/mii_database.h | ||
| 601 | hle/service/mii/mii_database_manager.cpp | ||
| 602 | hle/service/mii/mii_database_manager.h | ||
| 599 | hle/service/mii/mii_manager.cpp | 603 | hle/service/mii/mii_manager.cpp |
| 600 | hle/service/mii/mii_manager.h | 604 | hle/service/mii/mii_manager.h |
| 601 | hle/service/mii/mii_result.h | 605 | hle/service/mii/mii_result.h |
| @@ -864,6 +868,8 @@ add_library(core STATIC | |||
| 864 | telemetry_session.h | 868 | telemetry_session.h |
| 865 | tools/freezer.cpp | 869 | tools/freezer.cpp |
| 866 | tools/freezer.h | 870 | tools/freezer.h |
| 871 | tools/renderdoc.cpp | ||
| 872 | tools/renderdoc.h | ||
| 867 | ) | 873 | ) |
| 868 | 874 | ||
| 869 | if (MSVC) | 875 | if (MSVC) |
| @@ -879,6 +885,7 @@ else() | |||
| 879 | -Werror=conversion | 885 | -Werror=conversion |
| 880 | 886 | ||
| 881 | -Wno-sign-conversion | 887 | -Wno-sign-conversion |
| 888 | -Wno-cast-function-type | ||
| 882 | 889 | ||
| 883 | $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> | 890 | $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> |
| 884 | ) | 891 | ) |
| @@ -887,7 +894,7 @@ endif() | |||
| 887 | create_target_directory_groups(core) | 894 | create_target_directory_groups(core) |
| 888 | 895 | ||
| 889 | target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) | 896 | target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) |
| 890 | target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus) | 897 | target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls RenderDoc::API) |
| 891 | if (MINGW) | 898 | if (MINGW) |
| 892 | target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) | 899 | target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) |
| 893 | endif() | 900 | endif() |
diff --git a/src/core/core.cpp b/src/core/core.cpp index 2d6e61398..08cbb8978 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -51,6 +51,7 @@ | |||
| 51 | #include "core/reporter.h" | 51 | #include "core/reporter.h" |
| 52 | #include "core/telemetry_session.h" | 52 | #include "core/telemetry_session.h" |
| 53 | #include "core/tools/freezer.h" | 53 | #include "core/tools/freezer.h" |
| 54 | #include "core/tools/renderdoc.h" | ||
| 54 | #include "network/network.h" | 55 | #include "network/network.h" |
| 55 | #include "video_core/host1x/host1x.h" | 56 | #include "video_core/host1x/host1x.h" |
| 56 | #include "video_core/renderer_base.h" | 57 | #include "video_core/renderer_base.h" |
| @@ -281,6 +282,10 @@ struct System::Impl { | |||
| 281 | microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2); | 282 | microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2); |
| 282 | microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3); | 283 | microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3); |
| 283 | 284 | ||
| 285 | if (Settings::values.enable_renderdoc_hotkey) { | ||
| 286 | renderdoc_api = std::make_unique<Tools::RenderdocAPI>(); | ||
| 287 | } | ||
| 288 | |||
| 284 | LOG_DEBUG(Core, "Initialized OK"); | 289 | LOG_DEBUG(Core, "Initialized OK"); |
| 285 | 290 | ||
| 286 | return SystemResultStatus::Success; | 291 | return SystemResultStatus::Success; |
| @@ -376,6 +381,10 @@ struct System::Impl { | |||
| 376 | room_member->SendGameInfo(game_info); | 381 | room_member->SendGameInfo(game_info); |
| 377 | } | 382 | } |
| 378 | 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 | |||
| 379 | status = SystemResultStatus::Success; | 388 | status = SystemResultStatus::Success; |
| 380 | return status; | 389 | return status; |
| 381 | } | 390 | } |
| @@ -435,6 +444,9 @@ struct System::Impl { | |||
| 435 | room_member->SendGameInfo(game_info); | 444 | room_member->SendGameInfo(game_info); |
| 436 | } | 445 | } |
| 437 | 446 | ||
| 447 | // Workarounds | ||
| 448 | Settings::values.renderer_amdvlk_depth_bias_workaround = false; | ||
| 449 | |||
| 438 | LOG_DEBUG(Core, "Shutdown OK"); | 450 | LOG_DEBUG(Core, "Shutdown OK"); |
| 439 | } | 451 | } |
| 440 | 452 | ||
| @@ -521,6 +533,8 @@ struct System::Impl { | |||
| 521 | std::unique_ptr<Tools::Freezer> memory_freezer; | 533 | std::unique_ptr<Tools::Freezer> memory_freezer; |
| 522 | std::array<u8, 0x20> build_id{}; | 534 | std::array<u8, 0x20> build_id{}; |
| 523 | 535 | ||
| 536 | std::unique_ptr<Tools::RenderdocAPI> renderdoc_api; | ||
| 537 | |||
| 524 | /// Frontend applets | 538 | /// Frontend applets |
| 525 | Service::AM::Applets::AppletManager applet_manager; | 539 | Service::AM::Applets::AppletManager applet_manager; |
| 526 | 540 | ||
| @@ -1024,6 +1038,10 @@ const Network::RoomNetwork& System::GetRoomNetwork() const { | |||
| 1024 | return impl->room_network; | 1038 | return impl->room_network; |
| 1025 | } | 1039 | } |
| 1026 | 1040 | ||
| 1041 | Tools::RenderdocAPI& System::GetRenderdocAPI() { | ||
| 1042 | return *impl->renderdoc_api; | ||
| 1043 | } | ||
| 1044 | |||
| 1027 | void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { | 1045 | void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { |
| 1028 | return impl->kernel.RunServer(std::move(server_manager)); | 1046 | return impl->kernel.RunServer(std::move(server_manager)); |
| 1029 | } | 1047 | } |
diff --git a/src/core/core.h b/src/core/core.h index fba312125..df20f26f3 100644 --- a/src/core/core.h +++ b/src/core/core.h | |||
| @@ -102,6 +102,10 @@ namespace Network { | |||
| 102 | class RoomNetwork; | 102 | class RoomNetwork; |
| 103 | } | 103 | } |
| 104 | 104 | ||
| 105 | namespace Tools { | ||
| 106 | class RenderdocAPI; | ||
| 107 | } | ||
| 108 | |||
| 105 | namespace Core { | 109 | namespace Core { |
| 106 | 110 | ||
| 107 | class ARM_Interface; | 111 | class ARM_Interface; |
| @@ -413,6 +417,8 @@ public: | |||
| 413 | /// Gets an immutable reference to the Room Network. | 417 | /// Gets an immutable reference to the Room Network. |
| 414 | [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; | 418 | [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; |
| 415 | 419 | ||
| 420 | [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI(); | ||
| 421 | |||
| 416 | void SetExitLocked(bool locked); | 422 | void SetExitLocked(bool locked); |
| 417 | bool GetExitLocked() const; | 423 | bool GetExitLocked() const; |
| 418 | 424 | ||
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index e13c5cdc7..43a3c5ffd 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp | |||
| @@ -724,14 +724,14 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti | |||
| 724 | continue; | 724 | continue; |
| 725 | } | 725 | } |
| 726 | 726 | ||
| 727 | const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16); | 727 | const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16); |
| 728 | keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); | 728 | keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); |
| 729 | } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { | 729 | } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { |
| 730 | if (!ValidCryptoRevisionString(out[0], 18, 2)) { | 730 | if (!ValidCryptoRevisionString(out[0], 18, 2)) { |
| 731 | continue; | 731 | continue; |
| 732 | } | 732 | } |
| 733 | 733 | ||
| 734 | const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); | 734 | const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16); |
| 735 | encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); | 735 | encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); |
| 736 | } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { | 736 | } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { |
| 737 | eticket_extended_kek = Common::HexStringToArray<576>(out[1]); | 737 | eticket_extended_kek = Common::HexStringToArray<576>(out[1]); |
| @@ -750,7 +750,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti | |||
| 750 | } | 750 | } |
| 751 | if (out[0].compare(0, kv.second.size(), kv.second) == 0) { | 751 | if (out[0].compare(0, kv.second.size(), kv.second) == 0) { |
| 752 | const auto index = | 752 | const auto index = |
| 753 | std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16); | 753 | std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16); |
| 754 | const auto sub = kv.first.second; | 754 | const auto sub = kv.first.second; |
| 755 | if (sub == 0) { | 755 | if (sub == 0) { |
| 756 | s128_keys[{kv.first.first, index, 0}] = | 756 | s128_keys[{kv.first.first, index, 0}] = |
| @@ -770,7 +770,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti | |||
| 770 | const auto& match = kak_names[j]; | 770 | const auto& match = kak_names[j]; |
| 771 | if (out[0].compare(0, std::strlen(match), match) == 0) { | 771 | if (out[0].compare(0, std::strlen(match), match) == 0) { |
| 772 | const auto index = | 772 | const auto index = |
| 773 | std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16); | 773 | std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16); |
| 774 | s128_keys[{S128KeyType::KeyArea, index, j}] = | 774 | s128_keys[{S128KeyType::KeyArea, index, j}] = |
| 775 | Common::HexStringToArray<16>(out[1]); | 775 | Common::HexStringToArray<16>(out[1]); |
| 776 | } | 776 | } |
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index efdf18cee..7be1322cc 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp | |||
| @@ -165,7 +165,7 @@ static std::string EscapeStringSequences(std::string in) { | |||
| 165 | void IPSwitchCompiler::ParseFlag(const std::string& line) { | 165 | void IPSwitchCompiler::ParseFlag(const std::string& line) { |
| 166 | if (StartsWith(line, "@flag offset_shift ")) { | 166 | if (StartsWith(line, "@flag offset_shift ")) { |
| 167 | // Offset Shift Flag | 167 | // Offset Shift Flag |
| 168 | offset_shift = std::stoll(line.substr(19), nullptr, 0); | 168 | offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0); |
| 169 | } else if (StartsWith(line, "@little-endian")) { | 169 | } else if (StartsWith(line, "@little-endian")) { |
| 170 | // Set values to read as little endian | 170 | // Set values to read as little endian |
| 171 | is_little_endian = true; | 171 | is_little_endian = true; |
| @@ -263,7 +263,7 @@ void IPSwitchCompiler::Parse() { | |||
| 263 | // 11 - 8 hex digit offset + space + minimum two digit overwrite val | 263 | // 11 - 8 hex digit offset + space + minimum two digit overwrite val |
| 264 | if (patch_line.length() < 11) | 264 | if (patch_line.length() < 11) |
| 265 | break; | 265 | break; |
| 266 | auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); | 266 | auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16); |
| 267 | offset += static_cast<unsigned long>(offset_shift); | 267 | offset += static_cast<unsigned long>(offset_shift); |
| 268 | 268 | ||
| 269 | std::vector<u8> replace; | 269 | std::vector<u8> replace; |
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/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index a4baddb15..8e475f25a 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp | |||
| @@ -294,11 +294,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st | |||
| 294 | return out; | 294 | return out; |
| 295 | } | 295 | } |
| 296 | 296 | ||
| 297 | bool PatchManager::HasNSOPatch(const BuildID& build_id_) const { | 297 | bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) const { |
| 298 | const auto build_id_raw = Common::HexToString(build_id_); | 298 | const auto build_id_raw = Common::HexToString(build_id_); |
| 299 | const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); | 299 | const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); |
| 300 | 300 | ||
| 301 | LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); | 301 | LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name); |
| 302 | 302 | ||
| 303 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); | 303 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); |
| 304 | if (load_dir == nullptr) { | 304 | if (load_dir == nullptr) { |
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index adcde7b7d..03e9c7301 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h | |||
| @@ -52,7 +52,7 @@ public: | |||
| 52 | 52 | ||
| 53 | // Checks to see if PatchNSO() will have any effect given the NSO's build ID. | 53 | // Checks to see if PatchNSO() will have any effect given the NSO's build ID. |
| 54 | // Used to prevent expensive copies in NSO loader. | 54 | // Used to prevent expensive copies in NSO loader. |
| 55 | [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const; | 55 | [[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const; |
| 56 | 56 | ||
| 57 | // Creates a CheatList object with all | 57 | // Creates a CheatList object with all |
| 58 | [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList( | 58 | [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList( |
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index e33b00d89..04da93d5c 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp | |||
| @@ -752,7 +752,9 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { | |||
| 752 | for (u8 i = 0; i < 0x10; i++) { | 752 | for (u8 i = 0; i < 0x10; i++) { |
| 753 | const auto meta_dir = dir->CreateDirectoryRelative("yuzu_meta"); | 753 | const auto meta_dir = dir->CreateDirectoryRelative("yuzu_meta"); |
| 754 | const auto filename = GetCNMTName(TitleType::Update, title_id + i); | 754 | const auto filename = GetCNMTName(TitleType::Update, title_id + i); |
| 755 | removed_data |= meta_dir->DeleteFile(filename); | 755 | if (meta_dir->GetFile(filename)) { |
| 756 | removed_data |= meta_dir->DeleteFile(filename); | ||
| 757 | } | ||
| 756 | } | 758 | } |
| 757 | 759 | ||
| 758 | return removed_data; | 760 | return removed_data; |
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 94bd656fe..2af3f06fc 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp | |||
| @@ -542,6 +542,7 @@ void EmulatedController::UnloadInput() { | |||
| 542 | } | 542 | } |
| 543 | 543 | ||
| 544 | void EmulatedController::EnableConfiguration() { | 544 | void EmulatedController::EnableConfiguration() { |
| 545 | std::scoped_lock lock{connect_mutex, npad_mutex}; | ||
| 545 | is_configuring = true; | 546 | is_configuring = true; |
| 546 | tmp_is_connected = is_connected; | 547 | tmp_is_connected = is_connected; |
| 547 | tmp_npad_type = npad_type; | 548 | tmp_npad_type = npad_type; |
| @@ -1556,7 +1557,7 @@ void EmulatedController::Connect(bool use_temporary_value) { | |||
| 1556 | 1557 | ||
| 1557 | auto trigger_guard = | 1558 | auto trigger_guard = |
| 1558 | SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); }); | 1559 | SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); }); |
| 1559 | std::scoped_lock lock{mutex}; | 1560 | std::scoped_lock lock{connect_mutex, mutex}; |
| 1560 | if (is_configuring) { | 1561 | if (is_configuring) { |
| 1561 | tmp_is_connected = true; | 1562 | tmp_is_connected = true; |
| 1562 | return; | 1563 | return; |
| @@ -1572,7 +1573,7 @@ void EmulatedController::Connect(bool use_temporary_value) { | |||
| 1572 | void EmulatedController::Disconnect() { | 1573 | void EmulatedController::Disconnect() { |
| 1573 | auto trigger_guard = | 1574 | auto trigger_guard = |
| 1574 | SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); }); | 1575 | SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); }); |
| 1575 | std::scoped_lock lock{mutex}; | 1576 | std::scoped_lock lock{connect_mutex, mutex}; |
| 1576 | if (is_configuring) { | 1577 | if (is_configuring) { |
| 1577 | tmp_is_connected = false; | 1578 | tmp_is_connected = false; |
| 1578 | return; | 1579 | return; |
| @@ -1586,7 +1587,7 @@ void EmulatedController::Disconnect() { | |||
| 1586 | } | 1587 | } |
| 1587 | 1588 | ||
| 1588 | bool EmulatedController::IsConnected(bool get_temporary_value) const { | 1589 | bool EmulatedController::IsConnected(bool get_temporary_value) const { |
| 1589 | std::scoped_lock lock{mutex}; | 1590 | std::scoped_lock lock{connect_mutex}; |
| 1590 | if (get_temporary_value && is_configuring) { | 1591 | if (get_temporary_value && is_configuring) { |
| 1591 | return tmp_is_connected; | 1592 | return tmp_is_connected; |
| 1592 | } | 1593 | } |
| @@ -1599,7 +1600,7 @@ NpadIdType EmulatedController::GetNpadIdType() const { | |||
| 1599 | } | 1600 | } |
| 1600 | 1601 | ||
| 1601 | NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const { | 1602 | NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const { |
| 1602 | std::scoped_lock lock{mutex}; | 1603 | std::scoped_lock lock{npad_mutex}; |
| 1603 | if (get_temporary_value && is_configuring) { | 1604 | if (get_temporary_value && is_configuring) { |
| 1604 | return tmp_npad_type; | 1605 | return tmp_npad_type; |
| 1605 | } | 1606 | } |
| @@ -1609,7 +1610,7 @@ NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) c | |||
| 1609 | void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) { | 1610 | void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) { |
| 1610 | auto trigger_guard = | 1611 | auto trigger_guard = |
| 1611 | SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); }); | 1612 | SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); }); |
| 1612 | std::scoped_lock lock{mutex}; | 1613 | std::scoped_lock lock{mutex, npad_mutex}; |
| 1613 | 1614 | ||
| 1614 | if (is_configuring) { | 1615 | if (is_configuring) { |
| 1615 | if (tmp_npad_type == npad_type_) { | 1616 | if (tmp_npad_type == npad_type_) { |
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index 88d77db8d..d4500583e 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h | |||
| @@ -603,6 +603,8 @@ private: | |||
| 603 | 603 | ||
| 604 | mutable std::mutex mutex; | 604 | mutable std::mutex mutex; |
| 605 | mutable std::mutex callback_mutex; | 605 | mutable std::mutex callback_mutex; |
| 606 | mutable std::mutex npad_mutex; | ||
| 607 | mutable std::mutex connect_mutex; | ||
| 606 | std::unordered_map<int, ControllerUpdateCallback> callback_list; | 608 | std::unordered_map<int, ControllerUpdateCallback> callback_list; |
| 607 | int last_callback_key = 0; | 609 | int last_callback_key = 0; |
| 608 | 610 | ||
diff --git a/src/core/hid/hid_core.cpp b/src/core/hid/hid_core.cpp index 7d6373414..cf53c04d9 100644 --- a/src/core/hid/hid_core.cpp +++ b/src/core/hid/hid_core.cpp | |||
| @@ -154,6 +154,14 @@ NpadIdType HIDCore::GetFirstDisconnectedNpadId() const { | |||
| 154 | return NpadIdType::Player1; | 154 | return NpadIdType::Player1; |
| 155 | } | 155 | } |
| 156 | 156 | ||
| 157 | void HIDCore::SetLastActiveController(NpadIdType npad_id) { | ||
| 158 | last_active_controller = npad_id; | ||
| 159 | } | ||
| 160 | |||
| 161 | NpadIdType HIDCore::GetLastActiveController() const { | ||
| 162 | return last_active_controller; | ||
| 163 | } | ||
| 164 | |||
| 157 | void HIDCore::EnableAllControllerConfiguration() { | 165 | void HIDCore::EnableAllControllerConfiguration() { |
| 158 | player_1->EnableConfiguration(); | 166 | player_1->EnableConfiguration(); |
| 159 | player_2->EnableConfiguration(); | 167 | player_2->EnableConfiguration(); |
diff --git a/src/core/hid/hid_core.h b/src/core/hid/hid_core.h index 5fe36551e..80abab18b 100644 --- a/src/core/hid/hid_core.h +++ b/src/core/hid/hid_core.h | |||
| @@ -48,6 +48,12 @@ public: | |||
| 48 | /// Returns the first disconnected npad id | 48 | /// Returns the first disconnected npad id |
| 49 | NpadIdType GetFirstDisconnectedNpadId() const; | 49 | NpadIdType GetFirstDisconnectedNpadId() const; |
| 50 | 50 | ||
| 51 | /// Sets the npad id of the last active controller | ||
| 52 | void SetLastActiveController(NpadIdType npad_id); | ||
| 53 | |||
| 54 | /// Returns the npad id of the last controller that pushed a button | ||
| 55 | NpadIdType GetLastActiveController() const; | ||
| 56 | |||
| 51 | /// Sets all emulated controllers into configuring mode. | 57 | /// Sets all emulated controllers into configuring mode. |
| 52 | void EnableAllControllerConfiguration(); | 58 | void EnableAllControllerConfiguration(); |
| 53 | 59 | ||
| @@ -77,6 +83,7 @@ private: | |||
| 77 | std::unique_ptr<EmulatedConsole> console; | 83 | std::unique_ptr<EmulatedConsole> console; |
| 78 | std::unique_ptr<EmulatedDevices> devices; | 84 | std::unique_ptr<EmulatedDevices> devices; |
| 79 | NpadStyleTag supported_style_tag{NpadStyleSet::All}; | 85 | NpadStyleTag supported_style_tag{NpadStyleSet::All}; |
| 86 | NpadIdType last_active_controller{NpadIdType::Handheld}; | ||
| 80 | }; | 87 | }; |
| 81 | 88 | ||
| 82 | } // namespace Core::HID | 89 | } // namespace Core::HID |
diff --git a/src/core/hle/kernel/k_hardware_timer.cpp b/src/core/hle/kernel/k_hardware_timer.cpp index 4dcd53821..8e2e40307 100644 --- a/src/core/hle/kernel/k_hardware_timer.cpp +++ b/src/core/hle/kernel/k_hardware_timer.cpp | |||
| @@ -35,7 +35,9 @@ void KHardwareTimer::DoTask() { | |||
| 35 | } | 35 | } |
| 36 | 36 | ||
| 37 | // Disable the timer interrupt while we handle this. | 37 | // Disable the timer interrupt while we handle this. |
| 38 | this->DisableInterrupt(); | 38 | // Not necessary due to core timing already having popped this event to call it. |
| 39 | // this->DisableInterrupt(); | ||
| 40 | m_wakeup_time = std::numeric_limits<s64>::max(); | ||
| 39 | 41 | ||
| 40 | if (const s64 next_time = this->DoInterruptTaskImpl(GetTick()); | 42 | if (const s64 next_time = this->DoInterruptTaskImpl(GetTick()); |
| 41 | 0 < next_time && next_time <= m_wakeup_time) { | 43 | 0 < next_time && next_time <= m_wakeup_time) { |
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 703049ede..4a099286b 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp | |||
| @@ -96,6 +96,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string | |||
| 96 | process->m_is_suspended = false; | 96 | process->m_is_suspended = false; |
| 97 | process->m_schedule_count = 0; | 97 | process->m_schedule_count = 0; |
| 98 | process->m_is_handle_table_initialized = false; | 98 | process->m_is_handle_table_initialized = false; |
| 99 | process->m_is_hbl = false; | ||
| 99 | 100 | ||
| 100 | // Open a reference to the resource limit. | 101 | // Open a reference to the resource limit. |
| 101 | process->m_resource_limit->Open(); | 102 | process->m_resource_limit->Open(); |
| @@ -351,12 +352,14 @@ Result KProcess::SetActivity(ProcessActivity activity) { | |||
| 351 | R_SUCCEED(); | 352 | R_SUCCEED(); |
| 352 | } | 353 | } |
| 353 | 354 | ||
| 354 | Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) { | 355 | Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, |
| 356 | bool is_hbl) { | ||
| 355 | m_program_id = metadata.GetTitleID(); | 357 | m_program_id = metadata.GetTitleID(); |
| 356 | m_ideal_core = metadata.GetMainThreadCore(); | 358 | m_ideal_core = metadata.GetMainThreadCore(); |
| 357 | m_is_64bit_process = metadata.Is64BitProgram(); | 359 | m_is_64bit_process = metadata.Is64BitProgram(); |
| 358 | m_system_resource_size = metadata.GetSystemResourceSize(); | 360 | m_system_resource_size = metadata.GetSystemResourceSize(); |
| 359 | m_image_size = code_size; | 361 | m_image_size = code_size; |
| 362 | m_is_hbl = is_hbl; | ||
| 360 | 363 | ||
| 361 | if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) { | 364 | if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) { |
| 362 | // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large. | 365 | // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large. |
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index 4fdeaf11a..146e07a57 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h | |||
| @@ -338,7 +338,8 @@ public: | |||
| 338 | * @returns ResultSuccess if all relevant metadata was able to be | 338 | * @returns ResultSuccess if all relevant metadata was able to be |
| 339 | * loaded and parsed. Otherwise, an error code is returned. | 339 | * loaded and parsed. Otherwise, an error code is returned. |
| 340 | */ | 340 | */ |
| 341 | Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size); | 341 | Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, |
| 342 | bool is_hbl); | ||
| 342 | 343 | ||
| 343 | /** | 344 | /** |
| 344 | * Starts the main application thread for this process. | 345 | * Starts the main application thread for this process. |
| @@ -368,6 +369,10 @@ public: | |||
| 368 | return GetProcessId(); | 369 | return GetProcessId(); |
| 369 | } | 370 | } |
| 370 | 371 | ||
| 372 | bool IsHbl() const { | ||
| 373 | return m_is_hbl; | ||
| 374 | } | ||
| 375 | |||
| 371 | bool IsSignaled() const override; | 376 | bool IsSignaled() const override; |
| 372 | 377 | ||
| 373 | void DoWorkerTaskImpl(); | 378 | void DoWorkerTaskImpl(); |
| @@ -525,6 +530,7 @@ private: | |||
| 525 | bool m_is_immortal{}; | 530 | bool m_is_immortal{}; |
| 526 | bool m_is_handle_table_initialized{}; | 531 | bool m_is_handle_table_initialized{}; |
| 527 | bool m_is_initialized{}; | 532 | bool m_is_initialized{}; |
| 533 | bool m_is_hbl{}; | ||
| 528 | 534 | ||
| 529 | std::atomic<u16> m_num_running_threads{}; | 535 | std::atomic<u16> m_num_running_threads{}; |
| 530 | 536 | ||
diff --git a/src/core/hle/kernel/svc/svc_debug_string.cpp b/src/core/hle/kernel/svc/svc_debug_string.cpp index 4c14ce668..00b65429b 100644 --- a/src/core/hle/kernel/svc/svc_debug_string.cpp +++ b/src/core/hle/kernel/svc/svc_debug_string.cpp | |||
| @@ -14,7 +14,7 @@ Result OutputDebugString(Core::System& system, u64 address, u64 len) { | |||
| 14 | 14 | ||
| 15 | std::string str(len, '\0'); | 15 | std::string str(len, '\0'); |
| 16 | GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size()); | 16 | GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size()); |
| 17 | LOG_DEBUG(Debug_Emulated, "{}", str); | 17 | LOG_INFO(Debug_Emulated, "{}", str); |
| 18 | 18 | ||
| 19 | R_SUCCEED(); | 19 | R_SUCCEED(); |
| 20 | } | 20 | } |
diff --git a/src/core/hle/kernel/svc/svc_exception.cpp b/src/core/hle/kernel/svc/svc_exception.cpp index 580cf2f75..c581c086b 100644 --- a/src/core/hle/kernel/svc/svc_exception.cpp +++ b/src/core/hle/kernel/svc/svc_exception.cpp | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "core/core.h" | 4 | #include "core/core.h" |
| 5 | #include "core/debugger/debugger.h" | 5 | #include "core/debugger/debugger.h" |
| 6 | #include "core/hle/kernel/k_process.h" | ||
| 6 | #include "core/hle/kernel/k_thread.h" | 7 | #include "core/hle/kernel/k_thread.h" |
| 7 | #include "core/hle/kernel/svc.h" | 8 | #include "core/hle/kernel/svc.h" |
| 8 | #include "core/hle/kernel/svc_types.h" | 9 | #include "core/hle/kernel/svc_types.h" |
| @@ -107,7 +108,10 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) { | |||
| 107 | system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); | 108 | system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); |
| 108 | } | 109 | } |
| 109 | 110 | ||
| 110 | if (system.DebuggerEnabled()) { | 111 | const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl(); |
| 112 | const bool should_break = is_hbl || !notification_only; | ||
| 113 | |||
| 114 | if (system.DebuggerEnabled() && should_break) { | ||
| 111 | auto* thread = system.Kernel().GetCurrentEmuThread(); | 115 | auto* thread = system.Kernel().GetCurrentEmuThread(); |
| 112 | system.GetDebugger().NotifyThreadStopped(thread); | 116 | system.GetDebugger().NotifyThreadStopped(thread); |
| 113 | thread->RequestSuspend(Kernel::SuspendType::Debug); | 117 | thread->RequestSuspend(Kernel::SuspendType::Debug); |
diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 92a1439eb..dd0b27f47 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h | |||
| @@ -62,7 +62,7 @@ enum class ErrorModule : u32 { | |||
| 62 | XCD = 108, | 62 | XCD = 108, |
| 63 | TMP451 = 109, | 63 | TMP451 = 109, |
| 64 | NIFM = 110, | 64 | NIFM = 110, |
| 65 | Hwopus = 111, | 65 | HwOpus = 111, |
| 66 | LSM6DS3 = 112, | 66 | LSM6DS3 = 112, |
| 67 | Bluetooth = 113, | 67 | Bluetooth = 113, |
| 68 | VI = 114, | 68 | VI = 114, |
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index f9c4f9678..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, |
| @@ -1386,7 +1539,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) | |||
| 1386 | {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, | 1539 | {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, |
| 1387 | {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, | 1540 | {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, |
| 1388 | {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"}, | 1541 | {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"}, |
| 1389 | {28, nullptr, "GetSaveDataSizeMax"}, | 1542 | {28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"}, |
| 1390 | {29, nullptr, "GetCacheStorageMax"}, | 1543 | {29, nullptr, "GetCacheStorageMax"}, |
| 1391 | {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, | 1544 | {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, |
| 1392 | {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, | 1545 | {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, |
| @@ -1821,6 +1974,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) { | |||
| 1821 | rb.PushRaw(resp); | 1974 | rb.PushRaw(resp); |
| 1822 | } | 1975 | } |
| 1823 | 1976 | ||
| 1977 | void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) { | ||
| 1978 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 1979 | |||
| 1980 | constexpr u64 size_max_normal = 0xFFFFFFF; | ||
| 1981 | constexpr u64 size_max_journal = 0xFFFFFFF; | ||
| 1982 | |||
| 1983 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 1984 | rb.Push(ResultSuccess); | ||
| 1985 | rb.Push(size_max_normal); | ||
| 1986 | rb.Push(size_max_journal); | ||
| 1987 | } | ||
| 1988 | |||
| 1824 | void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { | 1989 | void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { |
| 1825 | LOG_WARNING(Service_AM, "(STUBBED) called"); | 1990 | LOG_WARNING(Service_AM, "(STUBBED) called"); |
| 1826 | 1991 | ||
| @@ -1929,9 +2094,6 @@ void IApplicationFunctions::PrepareForJit(HLERequestContext& ctx) { | |||
| 1929 | 2094 | ||
| 1930 | void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) { | 2095 | void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) { |
| 1931 | auto message_queue = std::make_shared<AppletMessageQueue>(system); | 2096 | auto message_queue = std::make_shared<AppletMessageQueue>(system); |
| 1932 | // Needed on game boot | ||
| 1933 | message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); | ||
| 1934 | |||
| 1935 | auto server_manager = std::make_unique<ServerManager>(system); | 2097 | auto server_manager = std::make_unique<ServerManager>(system); |
| 1936 | 2098 | ||
| 1937 | server_manager->RegisterNamedService( | 2099 | server_manager->RegisterNamedService( |
| @@ -2037,8 +2199,8 @@ IProcessWindingController::IProcessWindingController(Core::System& system_) | |||
| 2037 | : ServiceFramework{system_, "IProcessWindingController"} { | 2199 | : ServiceFramework{system_, "IProcessWindingController"} { |
| 2038 | // clang-format off | 2200 | // clang-format off |
| 2039 | static const FunctionInfo functions[] = { | 2201 | static const FunctionInfo functions[] = { |
| 2040 | {0, nullptr, "GetLaunchReason"}, | 2202 | {0, &IProcessWindingController::GetLaunchReason, "GetLaunchReason"}, |
| 2041 | {11, nullptr, "OpenCallingLibraryApplet"}, | 2203 | {11, &IProcessWindingController::OpenCallingLibraryApplet, "OpenCallingLibraryApplet"}, |
| 2042 | {21, nullptr, "PushContext"}, | 2204 | {21, nullptr, "PushContext"}, |
| 2043 | {22, nullptr, "PopContext"}, | 2205 | {22, nullptr, "PopContext"}, |
| 2044 | {23, nullptr, "CancelWindingReservation"}, | 2206 | {23, nullptr, "CancelWindingReservation"}, |
| @@ -2052,4 +2214,46 @@ IProcessWindingController::IProcessWindingController(Core::System& system_) | |||
| 2052 | } | 2214 | } |
| 2053 | 2215 | ||
| 2054 | 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 | } | ||
| 2055 | } // 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 f75a665b2..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> { |
| @@ -316,6 +345,7 @@ private: | |||
| 316 | void ExtendSaveData(HLERequestContext& ctx); | 345 | void ExtendSaveData(HLERequestContext& ctx); |
| 317 | void GetSaveDataSize(HLERequestContext& ctx); | 346 | void GetSaveDataSize(HLERequestContext& ctx); |
| 318 | void CreateCacheStorage(HLERequestContext& ctx); | 347 | void CreateCacheStorage(HLERequestContext& ctx); |
| 348 | void GetSaveDataSizeMax(HLERequestContext& ctx); | ||
| 319 | void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); | 349 | void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); |
| 320 | void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); | 350 | void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); |
| 321 | void BeginBlockingHomeButton(HLERequestContext& ctx); | 351 | void BeginBlockingHomeButton(HLERequestContext& ctx); |
| @@ -377,6 +407,10 @@ class IProcessWindingController final : public ServiceFramework<IProcessWindingC | |||
| 377 | public: | 407 | public: |
| 378 | explicit IProcessWindingController(Core::System& system_); | 408 | explicit IProcessWindingController(Core::System& system_); |
| 379 | ~IProcessWindingController() override; | 409 | ~IProcessWindingController() override; |
| 410 | |||
| 411 | private: | ||
| 412 | void GetLaunchReason(HLERequestContext& ctx); | ||
| 413 | void OpenCallingLibraryApplet(HLERequestContext& ctx); | ||
| 380 | }; | 414 | }; |
| 381 | 415 | ||
| 382 | 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/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 38c2138e8..7075ab800 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp | |||
| @@ -22,6 +22,8 @@ | |||
| 22 | 22 | ||
| 23 | namespace Service::AOC { | 23 | namespace Service::AOC { |
| 24 | 24 | ||
| 25 | constexpr Result ResultNoPurchasedProductInfoAvailable{ErrorModule::NIMShop, 400}; | ||
| 26 | |||
| 25 | static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) { | 27 | static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) { |
| 26 | return FileSys::GetBaseTitleID(title_id) == base; | 28 | return FileSys::GetBaseTitleID(title_id) == base; |
| 27 | } | 29 | } |
| @@ -54,8 +56,8 @@ public: | |||
| 54 | {0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"}, | 56 | {0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"}, |
| 55 | {1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"}, | 57 | {1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"}, |
| 56 | {2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"}, | 58 | {2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"}, |
| 57 | {3, nullptr, "PopPurchasedProductInfo"}, | 59 | {3, &IPurchaseEventManager::PopPurchasedProductInfo, "PopPurchasedProductInfo"}, |
| 58 | {4, nullptr, "PopPurchasedProductInfoWithUid"}, | 60 | {4, &IPurchaseEventManager::PopPurchasedProductInfoWithUid, "PopPurchasedProductInfoWithUid"}, |
| 59 | }; | 61 | }; |
| 60 | // clang-format on | 62 | // clang-format on |
| 61 | 63 | ||
| @@ -101,6 +103,20 @@ private: | |||
| 101 | rb.PushCopyObjects(purchased_event->GetReadableEvent()); | 103 | rb.PushCopyObjects(purchased_event->GetReadableEvent()); |
| 102 | } | 104 | } |
| 103 | 105 | ||
| 106 | void PopPurchasedProductInfo(HLERequestContext& ctx) { | ||
| 107 | LOG_DEBUG(Service_AOC, "(STUBBED) called"); | ||
| 108 | |||
| 109 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 110 | rb.Push(ResultNoPurchasedProductInfoAvailable); | ||
| 111 | } | ||
| 112 | |||
| 113 | void PopPurchasedProductInfoWithUid(HLERequestContext& ctx) { | ||
| 114 | LOG_DEBUG(Service_AOC, "(STUBBED) called"); | ||
| 115 | |||
| 116 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 117 | rb.Push(ResultNoPurchasedProductInfoAvailable); | ||
| 118 | } | ||
| 119 | |||
| 104 | KernelHelpers::ServiceContext service_context; | 120 | KernelHelpers::ServiceContext service_context; |
| 105 | 121 | ||
| 106 | Kernel::KEvent* purchased_event; | 122 | Kernel::KEvent* purchased_event; |
diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h index 3d3d3d97a..c41345f7e 100644 --- a/src/core/hle/service/audio/errors.h +++ b/src/core/hle/service/audio/errors.h | |||
| @@ -20,4 +20,16 @@ constexpr Result ResultNotSupported{ErrorModule::Audio, 513}; | |||
| 20 | constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; | 20 | constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; |
| 21 | constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; | 21 | constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; |
| 22 | 22 | ||
| 23 | constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7}; | ||
| 24 | constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8}; | ||
| 25 | constexpr Result ResultLibOpusInvalidState{ErrorModule::HwOpus, 6}; | ||
| 26 | constexpr Result ResultLibOpusUnimplemented{ErrorModule::HwOpus, 5}; | ||
| 27 | constexpr Result ResultLibOpusInvalidPacket{ErrorModule::HwOpus, 17}; | ||
| 28 | constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4}; | ||
| 29 | constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3}; | ||
| 30 | constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2}; | ||
| 31 | constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259}; | ||
| 32 | constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001}; | ||
| 33 | constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002}; | ||
| 34 | |||
| 23 | } // namespace Service::Audio | 35 | } // namespace Service::Audio |
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 1557e6088..6a7bf9416 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp | |||
| @@ -1,420 +1,506 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <chrono> | ||
| 5 | #include <cstring> | ||
| 6 | #include <memory> | 4 | #include <memory> |
| 7 | #include <vector> | 5 | #include <vector> |
| 8 | 6 | ||
| 9 | #include <opus.h> | 7 | #include "audio_core/opus/decoder.h" |
| 10 | #include <opus_multistream.h> | 8 | #include "audio_core/opus/parameters.h" |
| 11 | |||
| 12 | #include "common/assert.h" | 9 | #include "common/assert.h" |
| 13 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 14 | #include "common/scratch_buffer.h" | 11 | #include "common/scratch_buffer.h" |
| 12 | #include "core/core.h" | ||
| 15 | #include "core/hle/service/audio/hwopus.h" | 13 | #include "core/hle/service/audio/hwopus.h" |
| 16 | #include "core/hle/service/ipc_helpers.h" | 14 | #include "core/hle/service/ipc_helpers.h" |
| 17 | 15 | ||
| 18 | namespace Service::Audio { | 16 | namespace Service::Audio { |
| 19 | namespace { | 17 | using namespace AudioCore::OpusDecoder; |
| 20 | struct OpusDeleter { | 18 | |
| 21 | void operator()(OpusMSDecoder* ptr) const { | 19 | class IHardwareOpusDecoder final : public ServiceFramework<IHardwareOpusDecoder> { |
| 22 | opus_multistream_decoder_destroy(ptr); | 20 | public: |
| 21 | explicit IHardwareOpusDecoder(Core::System& system_, HardwareOpus& hardware_opus) | ||
| 22 | : ServiceFramework{system_, "IHardwareOpusDecoder"}, | ||
| 23 | impl{std::make_unique<AudioCore::OpusDecoder::OpusDecoder>(system_, hardware_opus)} { | ||
| 24 | // clang-format off | ||
| 25 | static const FunctionInfo functions[] = { | ||
| 26 | {0, &IHardwareOpusDecoder::DecodeInterleavedOld, "DecodeInterleavedOld"}, | ||
| 27 | {1, &IHardwareOpusDecoder::SetContext, "SetContext"}, | ||
| 28 | {2, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamOld, "DecodeInterleavedForMultiStreamOld"}, | ||
| 29 | {3, &IHardwareOpusDecoder::SetContextForMultiStream, "SetContextForMultiStream"}, | ||
| 30 | {4, &IHardwareOpusDecoder::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, | ||
| 31 | {5, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfOld, "DecodeInterleavedForMultiStreamWithPerfOld"}, | ||
| 32 | {6, &IHardwareOpusDecoder::DecodeInterleavedWithPerfAndResetOld, "DecodeInterleavedWithPerfAndResetOld"}, | ||
| 33 | {7, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfAndResetOld, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, | ||
| 34 | {8, &IHardwareOpusDecoder::DecodeInterleaved, "DecodeInterleaved"}, | ||
| 35 | {9, &IHardwareOpusDecoder::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"}, | ||
| 36 | }; | ||
| 37 | // clang-format on | ||
| 38 | |||
| 39 | RegisterHandlers(functions); | ||
| 23 | } | 40 | } |
| 24 | }; | ||
| 25 | 41 | ||
| 26 | using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>; | 42 | Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, |
| 43 | u64 transfer_memory_size) { | ||
| 44 | return impl->Initialize(params, transfer_memory, transfer_memory_size); | ||
| 45 | } | ||
| 27 | 46 | ||
| 28 | struct OpusPacketHeader { | 47 | Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, |
| 29 | // Packet size in bytes. | 48 | u64 transfer_memory_size) { |
| 30 | u32_be size; | 49 | return impl->Initialize(params, transfer_memory, transfer_memory_size); |
| 31 | // Indicates the final range of the codec's entropy coder. | 50 | } |
| 32 | u32_be final_range; | ||
| 33 | }; | ||
| 34 | static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size"); | ||
| 35 | 51 | ||
| 36 | class OpusDecoderState { | 52 | private: |
| 37 | public: | 53 | void DecodeInterleavedOld(HLERequestContext& ctx) { |
| 38 | /// Describes extra behavior that may be asked of the decoding context. | 54 | IPC::RequestParser rp{ctx}; |
| 39 | enum class ExtraBehavior { | ||
| 40 | /// No extra behavior. | ||
| 41 | None, | ||
| 42 | 55 | ||
| 43 | /// Resets the decoder context back to a freshly initialized state. | 56 | auto input_data{ctx.ReadBuffer(0)}; |
| 44 | ResetContext, | 57 | output_data.resize_destructive(ctx.GetWriteBufferSize()); |
| 45 | }; | ||
| 46 | 58 | ||
| 47 | enum class PerfTime { | 59 | u32 size{}; |
| 48 | Disabled, | 60 | u32 sample_count{}; |
| 49 | Enabled, | 61 | auto result = |
| 50 | }; | 62 | impl->DecodeInterleaved(&size, nullptr, &sample_count, input_data, output_data, false); |
| 51 | 63 | ||
| 52 | explicit OpusDecoderState(OpusDecoderPtr decoder_, u32 sample_rate_, u32 channel_count_) | 64 | LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); |
| 53 | : decoder{std::move(decoder_)}, sample_rate{sample_rate_}, channel_count{channel_count_} {} | 65 | |
| 54 | 66 | ctx.WriteBuffer(output_data); | |
| 55 | // Decodes interleaved Opus packets. Optionally allows reporting time taken to | 67 | |
| 56 | // perform the decoding, as well as any relevant extra behavior. | 68 | IPC::ResponseBuilder rb{ctx, 4}; |
| 57 | void DecodeInterleaved(HLERequestContext& ctx, PerfTime perf_time, | 69 | rb.Push(result); |
| 58 | ExtraBehavior extra_behavior) { | 70 | rb.Push(size); |
| 59 | if (perf_time == PerfTime::Disabled) { | 71 | rb.Push(sample_count); |
| 60 | DecodeInterleavedHelper(ctx, nullptr, extra_behavior); | ||
| 61 | } else { | ||
| 62 | u64 performance = 0; | ||
| 63 | DecodeInterleavedHelper(ctx, &performance, extra_behavior); | ||
| 64 | } | ||
| 65 | } | 72 | } |
| 66 | 73 | ||
| 67 | private: | 74 | void SetContext(HLERequestContext& ctx) { |
| 68 | void DecodeInterleavedHelper(HLERequestContext& ctx, u64* performance, | 75 | IPC::RequestParser rp{ctx}; |
| 69 | ExtraBehavior extra_behavior) { | 76 | |
| 70 | u32 consumed = 0; | 77 | LOG_DEBUG(Service_Audio, "called"); |
| 71 | u32 sample_count = 0; | 78 | |
| 72 | samples.resize_destructive(ctx.GetWriteBufferNumElements<opus_int16>()); | 79 | auto input_data{ctx.ReadBuffer(0)}; |
| 73 | 80 | auto result = impl->SetContext(input_data); | |
| 74 | if (extra_behavior == ExtraBehavior::ResetContext) { | 81 | |
| 75 | ResetDecoderContext(); | 82 | IPC::ResponseBuilder rb{ctx, 2}; |
| 76 | } | 83 | rb.Push(result); |
| 77 | 84 | } | |
| 78 | if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) { | 85 | |
| 79 | LOG_ERROR(Audio, "Failed to decode opus data"); | 86 | void DecodeInterleavedForMultiStreamOld(HLERequestContext& ctx) { |
| 80 | IPC::ResponseBuilder rb{ctx, 2}; | 87 | IPC::RequestParser rp{ctx}; |
| 81 | // TODO(ogniK): Use correct error code | 88 | |
| 82 | rb.Push(ResultUnknown); | 89 | auto input_data{ctx.ReadBuffer(0)}; |
| 83 | return; | 90 | output_data.resize_destructive(ctx.GetWriteBufferSize()); |
| 84 | } | 91 | |
| 85 | 92 | u32 size{}; | |
| 86 | const u32 param_size = performance != nullptr ? 6 : 4; | 93 | u32 sample_count{}; |
| 87 | IPC::ResponseBuilder rb{ctx, param_size}; | 94 | auto result = impl->DecodeInterleavedForMultiStream(&size, nullptr, &sample_count, |
| 88 | rb.Push(ResultSuccess); | 95 | input_data, output_data, false); |
| 89 | rb.Push<u32>(consumed); | 96 | |
| 90 | rb.Push<u32>(sample_count); | 97 | LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); |
| 91 | if (performance) { | 98 | |
| 92 | rb.Push<u64>(*performance); | 99 | ctx.WriteBuffer(output_data); |
| 93 | } | 100 | |
| 94 | ctx.WriteBuffer(samples); | 101 | IPC::ResponseBuilder rb{ctx, 4}; |
| 102 | rb.Push(result); | ||
| 103 | rb.Push(size); | ||
| 104 | rb.Push(sample_count); | ||
| 95 | } | 105 | } |
| 96 | 106 | ||
| 97 | bool DecodeOpusData(u32& consumed, u32& sample_count, std::span<const u8> input, | 107 | void SetContextForMultiStream(HLERequestContext& ctx) { |
| 98 | std::span<opus_int16> output, u64* out_performance_time) const { | 108 | IPC::RequestParser rp{ctx}; |
| 99 | const auto start_time = std::chrono::steady_clock::now(); | 109 | |
| 100 | const std::size_t raw_output_sz = output.size() * sizeof(opus_int16); | 110 | LOG_DEBUG(Service_Audio, "called"); |
| 101 | if (sizeof(OpusPacketHeader) > input.size()) { | 111 | |
| 102 | LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}", | 112 | auto input_data{ctx.ReadBuffer(0)}; |
| 103 | sizeof(OpusPacketHeader), input.size()); | 113 | auto result = impl->SetContext(input_data); |
| 104 | return false; | 114 | |
| 105 | } | 115 | IPC::ResponseBuilder rb{ctx, 2}; |
| 106 | 116 | rb.Push(result); | |
| 107 | OpusPacketHeader hdr{}; | ||
| 108 | std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader)); | ||
| 109 | if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) { | ||
| 110 | LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}", | ||
| 111 | sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size()); | ||
| 112 | return false; | ||
| 113 | } | ||
| 114 | |||
| 115 | const auto frame = input.data() + sizeof(OpusPacketHeader); | ||
| 116 | const auto decoded_sample_count = opus_packet_get_nb_samples( | ||
| 117 | frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)), | ||
| 118 | static_cast<opus_int32>(sample_rate)); | ||
| 119 | if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) { | ||
| 120 | LOG_ERROR( | ||
| 121 | Audio, | ||
| 122 | "Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}", | ||
| 123 | decoded_sample_count * channel_count * sizeof(u16), raw_output_sz); | ||
| 124 | return false; | ||
| 125 | } | ||
| 126 | |||
| 127 | const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)); | ||
| 128 | const auto out_sample_count = | ||
| 129 | opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0); | ||
| 130 | if (out_sample_count < 0) { | ||
| 131 | LOG_ERROR(Audio, | ||
| 132 | "Incorrect sample count received from opus_decode, " | ||
| 133 | "output_sample_count={}, frame_size={}, data_sz_from_hdr={}", | ||
| 134 | out_sample_count, frame_size, static_cast<u32>(hdr.size)); | ||
| 135 | return false; | ||
| 136 | } | ||
| 137 | |||
| 138 | const auto end_time = std::chrono::steady_clock::now() - start_time; | ||
| 139 | sample_count = out_sample_count; | ||
| 140 | consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size); | ||
| 141 | if (out_performance_time != nullptr) { | ||
| 142 | *out_performance_time = | ||
| 143 | std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count(); | ||
| 144 | } | ||
| 145 | |||
| 146 | return true; | ||
| 147 | } | 117 | } |
| 148 | 118 | ||
| 149 | void ResetDecoderContext() { | 119 | void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { |
| 150 | ASSERT(decoder != nullptr); | 120 | IPC::RequestParser rp{ctx}; |
| 121 | |||
| 122 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 123 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 124 | |||
| 125 | u32 size{}; | ||
| 126 | u32 sample_count{}; | ||
| 127 | u64 time_taken{}; | ||
| 128 | auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, | ||
| 129 | output_data, false); | ||
| 130 | |||
| 131 | LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, | ||
| 132 | sample_count, time_taken); | ||
| 151 | 133 | ||
| 152 | opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE); | 134 | ctx.WriteBuffer(output_data); |
| 135 | |||
| 136 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 137 | rb.Push(result); | ||
| 138 | rb.Push(size); | ||
| 139 | rb.Push(sample_count); | ||
| 140 | rb.Push(time_taken); | ||
| 153 | } | 141 | } |
| 154 | 142 | ||
| 155 | OpusDecoderPtr decoder; | 143 | void DecodeInterleavedForMultiStreamWithPerfOld(HLERequestContext& ctx) { |
| 156 | u32 sample_rate; | 144 | IPC::RequestParser rp{ctx}; |
| 157 | u32 channel_count; | ||
| 158 | Common::ScratchBuffer<opus_int16> samples; | ||
| 159 | }; | ||
| 160 | 145 | ||
| 161 | class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { | 146 | auto input_data{ctx.ReadBuffer(0)}; |
| 162 | public: | 147 | output_data.resize_destructive(ctx.GetWriteBufferSize()); |
| 163 | explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state_) | ||
| 164 | : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{ | ||
| 165 | std::move(decoder_state_)} { | ||
| 166 | // clang-format off | ||
| 167 | static const FunctionInfo functions[] = { | ||
| 168 | {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"}, | ||
| 169 | {1, nullptr, "SetContext"}, | ||
| 170 | {2, nullptr, "DecodeInterleavedForMultiStreamOld"}, | ||
| 171 | {3, nullptr, "SetContextForMultiStream"}, | ||
| 172 | {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, | ||
| 173 | {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"}, | ||
| 174 | {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"}, | ||
| 175 | {7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, | ||
| 176 | {8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, | ||
| 177 | {9, &IHardwareOpusDecoderManager::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"}, | ||
| 178 | }; | ||
| 179 | // clang-format on | ||
| 180 | 148 | ||
| 181 | RegisterHandlers(functions); | 149 | u32 size{}; |
| 150 | u32 sample_count{}; | ||
| 151 | u64 time_taken{}; | ||
| 152 | auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, | ||
| 153 | input_data, output_data, false); | ||
| 154 | |||
| 155 | LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, | ||
| 156 | sample_count, time_taken); | ||
| 157 | |||
| 158 | ctx.WriteBuffer(output_data); | ||
| 159 | |||
| 160 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 161 | rb.Push(result); | ||
| 162 | rb.Push(size); | ||
| 163 | rb.Push(sample_count); | ||
| 164 | rb.Push(time_taken); | ||
| 182 | } | 165 | } |
| 183 | 166 | ||
| 184 | private: | 167 | void DecodeInterleavedWithPerfAndResetOld(HLERequestContext& ctx) { |
| 185 | void DecodeInterleavedOld(HLERequestContext& ctx) { | 168 | IPC::RequestParser rp{ctx}; |
| 186 | LOG_DEBUG(Audio, "called"); | 169 | |
| 170 | auto reset{rp.Pop<bool>()}; | ||
| 171 | |||
| 172 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 173 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 174 | |||
| 175 | u32 size{}; | ||
| 176 | u32 sample_count{}; | ||
| 177 | u64 time_taken{}; | ||
| 178 | auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, | ||
| 179 | output_data, reset); | ||
| 180 | |||
| 181 | LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", | ||
| 182 | reset, size, sample_count, time_taken); | ||
| 183 | |||
| 184 | ctx.WriteBuffer(output_data); | ||
| 187 | 185 | ||
| 188 | decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled, | 186 | IPC::ResponseBuilder rb{ctx, 6}; |
| 189 | OpusDecoderState::ExtraBehavior::None); | 187 | rb.Push(result); |
| 188 | rb.Push(size); | ||
| 189 | rb.Push(sample_count); | ||
| 190 | rb.Push(time_taken); | ||
| 190 | } | 191 | } |
| 191 | 192 | ||
| 192 | void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { | 193 | void DecodeInterleavedForMultiStreamWithPerfAndResetOld(HLERequestContext& ctx) { |
| 193 | LOG_DEBUG(Audio, "called"); | 194 | IPC::RequestParser rp{ctx}; |
| 195 | |||
| 196 | auto reset{rp.Pop<bool>()}; | ||
| 197 | |||
| 198 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 199 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 200 | |||
| 201 | u32 size{}; | ||
| 202 | u32 sample_count{}; | ||
| 203 | u64 time_taken{}; | ||
| 204 | auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, | ||
| 205 | input_data, output_data, reset); | ||
| 194 | 206 | ||
| 195 | decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, | 207 | LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", |
| 196 | OpusDecoderState::ExtraBehavior::None); | 208 | reset, size, sample_count, time_taken); |
| 209 | |||
| 210 | ctx.WriteBuffer(output_data); | ||
| 211 | |||
| 212 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 213 | rb.Push(result); | ||
| 214 | rb.Push(size); | ||
| 215 | rb.Push(sample_count); | ||
| 216 | rb.Push(time_taken); | ||
| 197 | } | 217 | } |
| 198 | 218 | ||
| 199 | void DecodeInterleaved(HLERequestContext& ctx) { | 219 | void DecodeInterleaved(HLERequestContext& ctx) { |
| 200 | LOG_DEBUG(Audio, "called"); | ||
| 201 | |||
| 202 | IPC::RequestParser rp{ctx}; | 220 | IPC::RequestParser rp{ctx}; |
| 203 | const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext | ||
| 204 | : OpusDecoderState::ExtraBehavior::None; | ||
| 205 | 221 | ||
| 206 | decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); | 222 | auto reset{rp.Pop<bool>()}; |
| 223 | |||
| 224 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 225 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 226 | |||
| 227 | u32 size{}; | ||
| 228 | u32 sample_count{}; | ||
| 229 | u64 time_taken{}; | ||
| 230 | auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, | ||
| 231 | output_data, reset); | ||
| 232 | |||
| 233 | LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", | ||
| 234 | reset, size, sample_count, time_taken); | ||
| 235 | |||
| 236 | ctx.WriteBuffer(output_data); | ||
| 237 | |||
| 238 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 239 | rb.Push(result); | ||
| 240 | rb.Push(size); | ||
| 241 | rb.Push(sample_count); | ||
| 242 | rb.Push(time_taken); | ||
| 207 | } | 243 | } |
| 208 | 244 | ||
| 209 | void DecodeInterleavedForMultiStream(HLERequestContext& ctx) { | 245 | void DecodeInterleavedForMultiStream(HLERequestContext& ctx) { |
| 210 | LOG_DEBUG(Audio, "called"); | ||
| 211 | |||
| 212 | IPC::RequestParser rp{ctx}; | 246 | IPC::RequestParser rp{ctx}; |
| 213 | const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext | ||
| 214 | : OpusDecoderState::ExtraBehavior::None; | ||
| 215 | 247 | ||
| 216 | decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); | 248 | auto reset{rp.Pop<bool>()}; |
| 249 | |||
| 250 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 251 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 252 | |||
| 253 | u32 size{}; | ||
| 254 | u32 sample_count{}; | ||
| 255 | u64 time_taken{}; | ||
| 256 | auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, | ||
| 257 | input_data, output_data, reset); | ||
| 258 | |||
| 259 | LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", | ||
| 260 | reset, size, sample_count, time_taken); | ||
| 261 | |||
| 262 | ctx.WriteBuffer(output_data); | ||
| 263 | |||
| 264 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 265 | rb.Push(result); | ||
| 266 | rb.Push(size); | ||
| 267 | rb.Push(sample_count); | ||
| 268 | rb.Push(time_taken); | ||
| 217 | } | 269 | } |
| 218 | 270 | ||
| 219 | OpusDecoderState decoder_state; | 271 | std::unique_ptr<AudioCore::OpusDecoder::OpusDecoder> impl; |
| 272 | Common::ScratchBuffer<u8> output_data; | ||
| 220 | }; | 273 | }; |
| 221 | 274 | ||
| 222 | std::size_t WorkerBufferSize(u32 channel_count) { | 275 | void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { |
| 223 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | 276 | IPC::RequestParser rp{ctx}; |
| 224 | constexpr int num_streams = 1; | ||
| 225 | const int num_stereo_streams = channel_count == 2 ? 1 : 0; | ||
| 226 | return opus_multistream_decoder_get_size(num_streams, num_stereo_streams); | ||
| 227 | } | ||
| 228 | 277 | ||
| 229 | // Creates the mapping table that maps the input channels to the particular | 278 | auto params = rp.PopRaw<OpusParameters>(); |
| 230 | // output channels. In the stereo case, we map the left and right input channels | 279 | auto transfer_memory_size{rp.Pop<u32>()}; |
| 231 | // to the left and right output channels respectively. | 280 | auto transfer_memory_handle{ctx.GetCopyHandle(0)}; |
| 232 | // | 281 | auto transfer_memory{ |
| 233 | // However, in the monophonic case, we only map the one available channel | 282 | system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( |
| 234 | // to the sole output channel. We specify 255 for the would-be right channel | 283 | transfer_memory_handle)}; |
| 235 | // as this is a special value defined by Opus to indicate to the decoder to | 284 | |
| 236 | // ignore that channel. | 285 | LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", |
| 237 | std::array<u8, 2> CreateMappingTable(u32 channel_count) { | 286 | params.sample_rate, params.channel_count, transfer_memory_size); |
| 238 | if (channel_count == 2) { | ||
| 239 | return {{0, 1}}; | ||
| 240 | } | ||
| 241 | 287 | ||
| 242 | return {{0, 255}}; | 288 | auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; |
| 289 | |||
| 290 | OpusParametersEx ex{ | ||
| 291 | .sample_rate = params.sample_rate, | ||
| 292 | .channel_count = params.channel_count, | ||
| 293 | .use_large_frame_size = false, | ||
| 294 | }; | ||
| 295 | auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); | ||
| 296 | |||
| 297 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 298 | rb.Push(result); | ||
| 299 | rb.PushIpcInterface(decoder); | ||
| 243 | } | 300 | } |
| 244 | } // Anonymous namespace | ||
| 245 | 301 | ||
| 246 | void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) { | 302 | void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) { |
| 247 | IPC::RequestParser rp{ctx}; | 303 | IPC::RequestParser rp{ctx}; |
| 248 | const auto sample_rate = rp.Pop<u32>(); | 304 | auto params = rp.PopRaw<OpusParameters>(); |
| 249 | const auto channel_count = rp.Pop<u32>(); | ||
| 250 | 305 | ||
| 251 | LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count); | 306 | u64 size{}; |
| 307 | auto result = impl.GetWorkBufferSize(params, size); | ||
| 252 | 308 | ||
| 253 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | 309 | LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} -- returned size 0x{:X}", |
| 254 | sample_rate == 12000 || sample_rate == 8000, | 310 | params.sample_rate, params.channel_count, size); |
| 255 | "Invalid sample rate"); | ||
| 256 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | ||
| 257 | 311 | ||
| 258 | const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count)); | 312 | IPC::ResponseBuilder rb{ctx, 4}; |
| 259 | LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz); | 313 | rb.Push(result); |
| 260 | 314 | rb.Push(size); | |
| 261 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 262 | rb.Push(ResultSuccess); | ||
| 263 | rb.Push<u32>(worker_buffer_sz); | ||
| 264 | } | 315 | } |
| 265 | 316 | ||
| 266 | void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { | 317 | void HwOpus::OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx) { |
| 267 | GetWorkBufferSize(ctx); | 318 | IPC::RequestParser rp{ctx}; |
| 319 | |||
| 320 | auto input{ctx.ReadBuffer()}; | ||
| 321 | OpusMultiStreamParameters params; | ||
| 322 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); | ||
| 323 | |||
| 324 | auto transfer_memory_size{rp.Pop<u32>()}; | ||
| 325 | auto transfer_memory_handle{ctx.GetCopyHandle(0)}; | ||
| 326 | auto transfer_memory{ | ||
| 327 | system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( | ||
| 328 | transfer_memory_handle)}; | ||
| 329 | |||
| 330 | LOG_DEBUG(Service_Audio, | ||
| 331 | "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " | ||
| 332 | "transfer_memory_size 0x{:X}", | ||
| 333 | params.sample_rate, params.channel_count, params.total_stream_count, | ||
| 334 | params.stereo_stream_count, transfer_memory_size); | ||
| 335 | |||
| 336 | auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; | ||
| 337 | |||
| 338 | OpusMultiStreamParametersEx ex{ | ||
| 339 | .sample_rate = params.sample_rate, | ||
| 340 | .channel_count = params.channel_count, | ||
| 341 | .total_stream_count = params.total_stream_count, | ||
| 342 | .stereo_stream_count = params.stereo_stream_count, | ||
| 343 | .use_large_frame_size = false, | ||
| 344 | .mappings{}, | ||
| 345 | }; | ||
| 346 | std::memcpy(ex.mappings.data(), params.mappings.data(), sizeof(params.mappings)); | ||
| 347 | auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); | ||
| 348 | |||
| 349 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 350 | rb.Push(result); | ||
| 351 | rb.PushIpcInterface(decoder); | ||
| 268 | } | 352 | } |
| 269 | 353 | ||
| 270 | void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) { | 354 | void HwOpus::GetWorkBufferSizeForMultiStream(HLERequestContext& ctx) { |
| 271 | GetWorkBufferSizeEx(ctx); | 355 | IPC::RequestParser rp{ctx}; |
| 356 | |||
| 357 | auto input{ctx.ReadBuffer()}; | ||
| 358 | OpusMultiStreamParameters params; | ||
| 359 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); | ||
| 360 | |||
| 361 | u64 size{}; | ||
| 362 | auto result = impl.GetWorkBufferSizeForMultiStream(params, size); | ||
| 363 | |||
| 364 | LOG_DEBUG(Service_Audio, "size 0x{:X}", size); | ||
| 365 | |||
| 366 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 367 | rb.Push(result); | ||
| 368 | rb.Push(size); | ||
| 272 | } | 369 | } |
| 273 | 370 | ||
| 274 | void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { | 371 | void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { |
| 275 | OpusMultiStreamParametersEx param; | 372 | IPC::RequestParser rp{ctx}; |
| 276 | std::memcpy(¶m, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); | ||
| 277 | 373 | ||
| 278 | const auto sample_rate = param.sample_rate; | 374 | auto params = rp.PopRaw<OpusParametersEx>(); |
| 279 | const auto channel_count = param.channel_count; | 375 | auto transfer_memory_size{rp.Pop<u32>()}; |
| 280 | const auto number_streams = param.number_streams; | 376 | auto transfer_memory_handle{ctx.GetCopyHandle(0)}; |
| 281 | const auto number_stereo_streams = param.number_stereo_streams; | 377 | auto transfer_memory{ |
| 378 | system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( | ||
| 379 | transfer_memory_handle)}; | ||
| 282 | 380 | ||
| 283 | LOG_DEBUG( | 381 | LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", |
| 284 | Audio, | 382 | params.sample_rate, params.channel_count, transfer_memory_size); |
| 285 | "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}", | ||
| 286 | sample_rate, channel_count, number_streams, number_stereo_streams); | ||
| 287 | 383 | ||
| 288 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | 384 | auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; |
| 289 | sample_rate == 12000 || sample_rate == 8000, | ||
| 290 | "Invalid sample rate"); | ||
| 291 | 385 | ||
| 292 | const u32 worker_buffer_sz = | 386 | auto result = |
| 293 | static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams)); | 387 | decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); |
| 294 | 388 | ||
| 295 | IPC::ResponseBuilder rb{ctx, 3}; | 389 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 296 | rb.Push(ResultSuccess); | 390 | rb.Push(result); |
| 297 | rb.Push<u32>(worker_buffer_sz); | 391 | rb.PushIpcInterface(decoder); |
| 298 | } | 392 | } |
| 299 | 393 | ||
| 300 | void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { | 394 | void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { |
| 301 | IPC::RequestParser rp{ctx}; | 395 | IPC::RequestParser rp{ctx}; |
| 302 | const auto sample_rate = rp.Pop<u32>(); | 396 | auto params = rp.PopRaw<OpusParametersEx>(); |
| 303 | const auto channel_count = rp.Pop<u32>(); | ||
| 304 | const auto buffer_sz = rp.Pop<u32>(); | ||
| 305 | |||
| 306 | LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate, | ||
| 307 | channel_count, buffer_sz); | ||
| 308 | |||
| 309 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | ||
| 310 | sample_rate == 12000 || sample_rate == 8000, | ||
| 311 | "Invalid sample rate"); | ||
| 312 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | ||
| 313 | |||
| 314 | const std::size_t worker_sz = WorkerBufferSize(channel_count); | ||
| 315 | ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); | ||
| 316 | |||
| 317 | const int num_stereo_streams = channel_count == 2 ? 1 : 0; | ||
| 318 | const auto mapping_table = CreateMappingTable(channel_count); | ||
| 319 | |||
| 320 | int error = 0; | ||
| 321 | OpusDecoderPtr decoder{ | ||
| 322 | opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, | ||
| 323 | num_stereo_streams, mapping_table.data(), &error)}; | ||
| 324 | if (error != OPUS_OK || decoder == nullptr) { | ||
| 325 | LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); | ||
| 326 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 327 | // TODO(ogniK): Use correct error code | ||
| 328 | rb.Push(ResultUnknown); | ||
| 329 | return; | ||
| 330 | } | ||
| 331 | 397 | ||
| 332 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 398 | u64 size{}; |
| 333 | rb.Push(ResultSuccess); | 399 | auto result = impl.GetWorkBufferSizeEx(params, size); |
| 334 | rb.PushIpcInterface<IHardwareOpusDecoderManager>( | 400 | |
| 335 | system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); | 401 | LOG_DEBUG(Service_Audio, "size 0x{:X}", size); |
| 402 | |||
| 403 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 404 | rb.Push(result); | ||
| 405 | rb.Push(size); | ||
| 336 | } | 406 | } |
| 337 | 407 | ||
| 338 | void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { | 408 | void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) { |
| 339 | IPC::RequestParser rp{ctx}; | 409 | IPC::RequestParser rp{ctx}; |
| 340 | const auto sample_rate = rp.Pop<u32>(); | ||
| 341 | const auto channel_count = rp.Pop<u32>(); | ||
| 342 | 410 | ||
| 343 | LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); | 411 | auto input{ctx.ReadBuffer()}; |
| 412 | OpusMultiStreamParametersEx params; | ||
| 413 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); | ||
| 414 | |||
| 415 | auto transfer_memory_size{rp.Pop<u32>()}; | ||
| 416 | auto transfer_memory_handle{ctx.GetCopyHandle(0)}; | ||
| 417 | auto transfer_memory{ | ||
| 418 | system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( | ||
| 419 | transfer_memory_handle)}; | ||
| 344 | 420 | ||
| 345 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | 421 | LOG_DEBUG(Service_Audio, |
| 346 | sample_rate == 12000 || sample_rate == 8000, | 422 | "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " |
| 347 | "Invalid sample rate"); | 423 | "use_large_frame_size {}" |
| 348 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | 424 | "transfer_memory_size 0x{:X}", |
| 425 | params.sample_rate, params.channel_count, params.total_stream_count, | ||
| 426 | params.stereo_stream_count, params.use_large_frame_size, transfer_memory_size); | ||
| 349 | 427 | ||
| 350 | const int num_stereo_streams = channel_count == 2 ? 1 : 0; | 428 | auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; |
| 351 | const auto mapping_table = CreateMappingTable(channel_count); | ||
| 352 | 429 | ||
| 353 | int error = 0; | 430 | auto result = |
| 354 | OpusDecoderPtr decoder{ | 431 | decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); |
| 355 | opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, | ||
| 356 | num_stereo_streams, mapping_table.data(), &error)}; | ||
| 357 | if (error != OPUS_OK || decoder == nullptr) { | ||
| 358 | LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); | ||
| 359 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 360 | // TODO(ogniK): Use correct error code | ||
| 361 | rb.Push(ResultUnknown); | ||
| 362 | return; | ||
| 363 | } | ||
| 364 | 432 | ||
| 365 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 433 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 366 | rb.Push(ResultSuccess); | 434 | rb.Push(result); |
| 367 | rb.PushIpcInterface<IHardwareOpusDecoderManager>( | 435 | rb.PushIpcInterface(decoder); |
| 368 | system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); | ||
| 369 | } | 436 | } |
| 370 | 437 | ||
| 371 | void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) { | 438 | void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { |
| 439 | IPC::RequestParser rp{ctx}; | ||
| 440 | |||
| 441 | auto input{ctx.ReadBuffer()}; | ||
| 372 | OpusMultiStreamParametersEx params; | 442 | OpusMultiStreamParametersEx params; |
| 373 | std::memcpy(¶ms, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); | 443 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); |
| 374 | |||
| 375 | const auto& sample_rate = params.sample_rate; | ||
| 376 | const auto& channel_count = params.channel_count; | ||
| 377 | |||
| 378 | LOG_INFO( | ||
| 379 | Audio, | ||
| 380 | "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}", | ||
| 381 | sample_rate, channel_count, params.number_streams, params.number_stereo_streams); | ||
| 382 | |||
| 383 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | ||
| 384 | sample_rate == 12000 || sample_rate == 8000, | ||
| 385 | "Invalid sample rate"); | ||
| 386 | |||
| 387 | int error = 0; | ||
| 388 | OpusDecoderPtr decoder{opus_multistream_decoder_create( | ||
| 389 | sample_rate, static_cast<int>(channel_count), params.number_streams, | ||
| 390 | params.number_stereo_streams, params.channel_mappings.data(), &error)}; | ||
| 391 | if (error != OPUS_OK || decoder == nullptr) { | ||
| 392 | LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); | ||
| 393 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 394 | // TODO(ogniK): Use correct error code | ||
| 395 | rb.Push(ResultUnknown); | ||
| 396 | return; | ||
| 397 | } | ||
| 398 | 444 | ||
| 399 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 445 | u64 size{}; |
| 400 | rb.Push(ResultSuccess); | 446 | auto result = impl.GetWorkBufferSizeForMultiStreamEx(params, size); |
| 401 | rb.PushIpcInterface<IHardwareOpusDecoderManager>( | 447 | |
| 402 | system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); | 448 | LOG_DEBUG(Service_Audio, |
| 449 | "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " | ||
| 450 | "use_large_frame_size {} -- returned size 0x{:X}", | ||
| 451 | params.sample_rate, params.channel_count, params.total_stream_count, | ||
| 452 | params.stereo_stream_count, params.use_large_frame_size, size); | ||
| 453 | |||
| 454 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 455 | rb.Push(result); | ||
| 456 | rb.Push(size); | ||
| 457 | } | ||
| 458 | |||
| 459 | void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) { | ||
| 460 | IPC::RequestParser rp{ctx}; | ||
| 461 | auto params = rp.PopRaw<OpusParametersEx>(); | ||
| 462 | |||
| 463 | u64 size{}; | ||
| 464 | auto result = impl.GetWorkBufferSizeExEx(params, size); | ||
| 465 | |||
| 466 | LOG_DEBUG(Service_Audio, "size 0x{:X}", size); | ||
| 467 | |||
| 468 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 469 | rb.Push(result); | ||
| 470 | rb.Push(size); | ||
| 471 | } | ||
| 472 | |||
| 473 | void HwOpus::GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx) { | ||
| 474 | IPC::RequestParser rp{ctx}; | ||
| 475 | |||
| 476 | auto input{ctx.ReadBuffer()}; | ||
| 477 | OpusMultiStreamParametersEx params; | ||
| 478 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); | ||
| 479 | |||
| 480 | u64 size{}; | ||
| 481 | auto result = impl.GetWorkBufferSizeForMultiStreamExEx(params, size); | ||
| 482 | |||
| 483 | LOG_DEBUG(Service_Audio, "size 0x{:X}", size); | ||
| 484 | |||
| 485 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 486 | rb.Push(result); | ||
| 487 | rb.Push(size); | ||
| 403 | } | 488 | } |
| 404 | 489 | ||
| 405 | HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { | 490 | HwOpus::HwOpus(Core::System& system_) |
| 491 | : ServiceFramework{system_, "hwopus"}, system{system_}, impl{system} { | ||
| 406 | static const FunctionInfo functions[] = { | 492 | static const FunctionInfo functions[] = { |
| 407 | {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"}, | 493 | {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"}, |
| 408 | {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, | 494 | {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, |
| 409 | {2, nullptr, "OpenOpusDecoderForMultiStream"}, | 495 | {2, &HwOpus::OpenHardwareOpusDecoderForMultiStream, "OpenOpusDecoderForMultiStream"}, |
| 410 | {3, nullptr, "GetWorkBufferSizeForMultiStream"}, | 496 | {3, &HwOpus::GetWorkBufferSizeForMultiStream, "GetWorkBufferSizeForMultiStream"}, |
| 411 | {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, | 497 | {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, |
| 412 | {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, | 498 | {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, |
| 413 | {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx, | 499 | {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx, |
| 414 | "OpenHardwareOpusDecoderForMultiStreamEx"}, | 500 | "OpenHardwareOpusDecoderForMultiStreamEx"}, |
| 415 | {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, | 501 | {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, |
| 416 | {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"}, | 502 | {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"}, |
| 417 | {9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"}, | 503 | {9, &HwOpus::GetWorkBufferSizeForMultiStreamExEx, "GetWorkBufferSizeForMultiStreamExEx"}, |
| 418 | }; | 504 | }; |
| 419 | RegisterHandlers(functions); | 505 | RegisterHandlers(functions); |
| 420 | } | 506 | } |
diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h index 90867bf74..d3960065e 100644 --- a/src/core/hle/service/audio/hwopus.h +++ b/src/core/hle/service/audio/hwopus.h | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include "audio_core/opus/decoder_manager.h" | ||
| 6 | #include "core/hle/service/service.h" | 7 | #include "core/hle/service/service.h" |
| 7 | 8 | ||
| 8 | namespace Core { | 9 | namespace Core { |
| @@ -11,18 +12,6 @@ class System; | |||
| 11 | 12 | ||
| 12 | namespace Service::Audio { | 13 | namespace Service::Audio { |
| 13 | 14 | ||
| 14 | struct OpusMultiStreamParametersEx { | ||
| 15 | u32 sample_rate; | ||
| 16 | u32 channel_count; | ||
| 17 | u32 number_streams; | ||
| 18 | u32 number_stereo_streams; | ||
| 19 | u32 use_large_frame_size; | ||
| 20 | u32 padding; | ||
| 21 | std::array<u8, 0x100> channel_mappings; | ||
| 22 | }; | ||
| 23 | static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118, | ||
| 24 | "OpusMultiStreamParametersEx has incorrect size"); | ||
| 25 | |||
| 26 | class HwOpus final : public ServiceFramework<HwOpus> { | 15 | class HwOpus final : public ServiceFramework<HwOpus> { |
| 27 | public: | 16 | public: |
| 28 | explicit HwOpus(Core::System& system_); | 17 | explicit HwOpus(Core::System& system_); |
| @@ -30,12 +19,18 @@ public: | |||
| 30 | 19 | ||
| 31 | private: | 20 | private: |
| 32 | void OpenHardwareOpusDecoder(HLERequestContext& ctx); | 21 | void OpenHardwareOpusDecoder(HLERequestContext& ctx); |
| 33 | void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); | ||
| 34 | void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx); | ||
| 35 | void GetWorkBufferSize(HLERequestContext& ctx); | 22 | void GetWorkBufferSize(HLERequestContext& ctx); |
| 23 | void OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx); | ||
| 24 | void GetWorkBufferSizeForMultiStream(HLERequestContext& ctx); | ||
| 25 | void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); | ||
| 36 | void GetWorkBufferSizeEx(HLERequestContext& ctx); | 26 | void GetWorkBufferSizeEx(HLERequestContext& ctx); |
| 37 | void GetWorkBufferSizeExEx(HLERequestContext& ctx); | 27 | void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx); |
| 38 | void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); | 28 | void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); |
| 29 | void GetWorkBufferSizeExEx(HLERequestContext& ctx); | ||
| 30 | void GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx); | ||
| 31 | |||
| 32 | Core::System& system; | ||
| 33 | AudioCore::OpusDecoder::OpusDecoderManager impl; | ||
| 39 | }; | 34 | }; |
| 40 | 35 | ||
| 41 | } // namespace Service::Audio | 36 | } // namespace Service::Audio |
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 3b349b4c4..bc822f19e 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp | |||
| @@ -193,7 +193,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 193 | shared_memory->system_properties.use_minus.Assign(1); | 193 | shared_memory->system_properties.use_minus.Assign(1); |
| 194 | shared_memory->system_properties.is_charging_joy_dual.Assign( | 194 | shared_memory->system_properties.is_charging_joy_dual.Assign( |
| 195 | battery_level.dual.is_charging); | 195 | battery_level.dual.is_charging); |
| 196 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController; | 196 | shared_memory->applet_footer_type = AppletFooterUiType::SwitchProController; |
| 197 | shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); | 197 | shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); |
| 198 | break; | 198 | break; |
| 199 | case Core::HID::NpadStyleIndex::Handheld: | 199 | case Core::HID::NpadStyleIndex::Handheld: |
| @@ -216,8 +216,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 216 | shared_memory->system_properties.is_charging_joy_right.Assign( | 216 | shared_memory->system_properties.is_charging_joy_right.Assign( |
| 217 | battery_level.right.is_charging); | 217 | battery_level.right.is_charging); |
| 218 | shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; | 218 | shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; |
| 219 | shared_memory->applet_nfc_xcd.applet_footer.type = | 219 | shared_memory->applet_footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight; |
| 220 | AppletFooterUiType::HandheldJoyConLeftJoyConRight; | ||
| 221 | shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); | 220 | shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); |
| 222 | break; | 221 | break; |
| 223 | case Core::HID::NpadStyleIndex::JoyconDual: | 222 | case Core::HID::NpadStyleIndex::JoyconDual: |
| @@ -247,19 +246,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 247 | shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; | 246 | shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; |
| 248 | 247 | ||
| 249 | if (controller.is_dual_left_connected && controller.is_dual_right_connected) { | 248 | if (controller.is_dual_left_connected && controller.is_dual_right_connected) { |
| 250 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual; | 249 | shared_memory->applet_footer_type = AppletFooterUiType::JoyDual; |
| 251 | shared_memory->fullkey_color.fullkey = body_colors.left; | 250 | shared_memory->fullkey_color.fullkey = body_colors.left; |
| 252 | shared_memory->battery_level_dual = battery_level.left.battery_level; | 251 | shared_memory->battery_level_dual = battery_level.left.battery_level; |
| 253 | shared_memory->system_properties.is_charging_joy_dual.Assign( | 252 | shared_memory->system_properties.is_charging_joy_dual.Assign( |
| 254 | battery_level.left.is_charging); | 253 | battery_level.left.is_charging); |
| 255 | } else if (controller.is_dual_left_connected) { | 254 | } else if (controller.is_dual_left_connected) { |
| 256 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly; | 255 | shared_memory->applet_footer_type = AppletFooterUiType::JoyDualLeftOnly; |
| 257 | shared_memory->fullkey_color.fullkey = body_colors.left; | 256 | shared_memory->fullkey_color.fullkey = body_colors.left; |
| 258 | shared_memory->battery_level_dual = battery_level.left.battery_level; | 257 | shared_memory->battery_level_dual = battery_level.left.battery_level; |
| 259 | shared_memory->system_properties.is_charging_joy_dual.Assign( | 258 | shared_memory->system_properties.is_charging_joy_dual.Assign( |
| 260 | battery_level.left.is_charging); | 259 | battery_level.left.is_charging); |
| 261 | } else { | 260 | } else { |
| 262 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly; | 261 | shared_memory->applet_footer_type = AppletFooterUiType::JoyDualRightOnly; |
| 263 | shared_memory->fullkey_color.fullkey = body_colors.right; | 262 | shared_memory->fullkey_color.fullkey = body_colors.right; |
| 264 | shared_memory->battery_level_dual = battery_level.right.battery_level; | 263 | shared_memory->battery_level_dual = battery_level.right.battery_level; |
| 265 | shared_memory->system_properties.is_charging_joy_dual.Assign( | 264 | shared_memory->system_properties.is_charging_joy_dual.Assign( |
| @@ -278,7 +277,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 278 | shared_memory->system_properties.use_minus.Assign(1); | 277 | shared_memory->system_properties.use_minus.Assign(1); |
| 279 | shared_memory->system_properties.is_charging_joy_left.Assign( | 278 | shared_memory->system_properties.is_charging_joy_left.Assign( |
| 280 | battery_level.left.is_charging); | 279 | battery_level.left.is_charging); |
| 281 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal; | 280 | shared_memory->applet_footer_type = AppletFooterUiType::JoyLeftHorizontal; |
| 282 | shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); | 281 | shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); |
| 283 | break; | 282 | break; |
| 284 | case Core::HID::NpadStyleIndex::JoyconRight: | 283 | case Core::HID::NpadStyleIndex::JoyconRight: |
| @@ -293,7 +292,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 293 | shared_memory->system_properties.use_plus.Assign(1); | 292 | shared_memory->system_properties.use_plus.Assign(1); |
| 294 | shared_memory->system_properties.is_charging_joy_right.Assign( | 293 | shared_memory->system_properties.is_charging_joy_right.Assign( |
| 295 | battery_level.right.is_charging); | 294 | battery_level.right.is_charging); |
| 296 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal; | 295 | shared_memory->applet_footer_type = AppletFooterUiType::JoyRightHorizontal; |
| 297 | shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); | 296 | shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); |
| 298 | break; | 297 | break; |
| 299 | case Core::HID::NpadStyleIndex::GameCube: | 298 | case Core::HID::NpadStyleIndex::GameCube: |
| @@ -314,12 +313,12 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 314 | case Core::HID::NpadStyleIndex::SNES: | 313 | case Core::HID::NpadStyleIndex::SNES: |
| 315 | shared_memory->style_tag.lucia.Assign(1); | 314 | shared_memory->style_tag.lucia.Assign(1); |
| 316 | shared_memory->device_type.fullkey.Assign(1); | 315 | shared_memory->device_type.fullkey.Assign(1); |
| 317 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lucia; | 316 | shared_memory->applet_footer_type = AppletFooterUiType::Lucia; |
| 318 | break; | 317 | break; |
| 319 | case Core::HID::NpadStyleIndex::N64: | 318 | case Core::HID::NpadStyleIndex::N64: |
| 320 | shared_memory->style_tag.lagoon.Assign(1); | 319 | shared_memory->style_tag.lagoon.Assign(1); |
| 321 | shared_memory->device_type.fullkey.Assign(1); | 320 | shared_memory->device_type.fullkey.Assign(1); |
| 322 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lagon; | 321 | shared_memory->applet_footer_type = AppletFooterUiType::Lagon; |
| 323 | break; | 322 | break; |
| 324 | case Core::HID::NpadStyleIndex::SegaGenesis: | 323 | case Core::HID::NpadStyleIndex::SegaGenesis: |
| 325 | shared_memory->style_tag.lager.Assign(1); | 324 | shared_memory->style_tag.lager.Assign(1); |
| @@ -347,6 +346,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 347 | } | 346 | } |
| 348 | SignalStyleSetChangedEvent(npad_id); | 347 | SignalStyleSetChangedEvent(npad_id); |
| 349 | WriteEmptyEntry(controller.shared_memory); | 348 | WriteEmptyEntry(controller.shared_memory); |
| 349 | hid_core.SetLastActiveController(npad_id); | ||
| 350 | } | 350 | } |
| 351 | 351 | ||
| 352 | void Controller_NPad::OnInit() { | 352 | void Controller_NPad::OnInit() { |
| @@ -419,9 +419,17 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { | |||
| 419 | std::scoped_lock lock{mutex}; | 419 | std::scoped_lock lock{mutex}; |
| 420 | auto& controller = GetControllerFromNpadIdType(npad_id); | 420 | auto& controller = GetControllerFromNpadIdType(npad_id); |
| 421 | const auto controller_type = controller.device->GetNpadStyleIndex(); | 421 | const auto controller_type = controller.device->GetNpadStyleIndex(); |
| 422 | |||
| 423 | if (!controller.device->IsConnected() && controller.is_connected) { | ||
| 424 | DisconnectNpad(npad_id); | ||
| 425 | return; | ||
| 426 | } | ||
| 422 | if (!controller.device->IsConnected()) { | 427 | if (!controller.device->IsConnected()) { |
| 423 | return; | 428 | return; |
| 424 | } | 429 | } |
| 430 | if (controller.device->IsConnected() && !controller.is_connected) { | ||
| 431 | InitNewlyAddedController(npad_id); | ||
| 432 | } | ||
| 425 | 433 | ||
| 426 | // This function is unique to yuzu for the turbo buttons and motion to work properly | 434 | // This function is unique to yuzu for the turbo buttons and motion to work properly |
| 427 | controller.device->StatusUpdate(); | 435 | controller.device->StatusUpdate(); |
| @@ -468,6 +476,10 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { | |||
| 468 | pad_entry.npad_buttons.l.Assign(button_state.zl); | 476 | pad_entry.npad_buttons.l.Assign(button_state.zl); |
| 469 | pad_entry.npad_buttons.r.Assign(button_state.zr); | 477 | pad_entry.npad_buttons.r.Assign(button_state.zr); |
| 470 | } | 478 | } |
| 479 | |||
| 480 | if (pad_entry.npad_buttons.raw != Core::HID::NpadButton::None) { | ||
| 481 | hid_core.SetLastActiveController(npad_id); | ||
| 482 | } | ||
| 471 | } | 483 | } |
| 472 | 484 | ||
| 473 | void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { | 485 | void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { |
| @@ -736,14 +748,6 @@ void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) { | |||
| 736 | 748 | ||
| 737 | // Once SetSupportedStyleSet is called controllers are fully initialized | 749 | // Once SetSupportedStyleSet is called controllers are fully initialized |
| 738 | is_controller_initialized = true; | 750 | is_controller_initialized = true; |
| 739 | |||
| 740 | // Connect all active controllers | ||
| 741 | for (auto& controller : controller_data) { | ||
| 742 | const auto& device = controller.device; | ||
| 743 | if (device->IsConnected()) { | ||
| 744 | AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType()); | ||
| 745 | } | ||
| 746 | } | ||
| 747 | } | 751 | } |
| 748 | 752 | ||
| 749 | Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { | 753 | Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { |
| @@ -1116,7 +1120,7 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) { | |||
| 1116 | .left = {}, | 1120 | .left = {}, |
| 1117 | .right = {}, | 1121 | .right = {}, |
| 1118 | }; | 1122 | }; |
| 1119 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::None; | 1123 | shared_memory->applet_footer_type = AppletFooterUiType::None; |
| 1120 | 1124 | ||
| 1121 | controller.is_dual_left_connected = true; | 1125 | controller.is_dual_left_connected = true; |
| 1122 | controller.is_dual_right_connected = true; | 1126 | controller.is_dual_right_connected = true; |
| @@ -1508,6 +1512,31 @@ Core::HID::NpadButton Controller_NPad::GetAndResetPressState() { | |||
| 1508 | return static_cast<Core::HID::NpadButton>(press_state.exchange(0)); | 1512 | return static_cast<Core::HID::NpadButton>(press_state.exchange(0)); |
| 1509 | } | 1513 | } |
| 1510 | 1514 | ||
| 1515 | void Controller_NPad::ApplyNpadSystemCommonPolicy() { | ||
| 1516 | Core::HID::NpadStyleTag styletag{}; | ||
| 1517 | styletag.fullkey.Assign(1); | ||
| 1518 | styletag.handheld.Assign(1); | ||
| 1519 | styletag.joycon_dual.Assign(1); | ||
| 1520 | styletag.system_ext.Assign(1); | ||
| 1521 | styletag.system.Assign(1); | ||
| 1522 | SetSupportedStyleSet(styletag); | ||
| 1523 | |||
| 1524 | SetNpadHandheldActivationMode(NpadHandheldActivationMode::Dual); | ||
| 1525 | |||
| 1526 | supported_npad_id_types.clear(); | ||
| 1527 | supported_npad_id_types.resize(10); | ||
| 1528 | supported_npad_id_types[0] = Core::HID::NpadIdType::Player1; | ||
| 1529 | supported_npad_id_types[1] = Core::HID::NpadIdType::Player2; | ||
| 1530 | supported_npad_id_types[2] = Core::HID::NpadIdType::Player3; | ||
| 1531 | supported_npad_id_types[3] = Core::HID::NpadIdType::Player4; | ||
| 1532 | supported_npad_id_types[4] = Core::HID::NpadIdType::Player5; | ||
| 1533 | supported_npad_id_types[5] = Core::HID::NpadIdType::Player6; | ||
| 1534 | supported_npad_id_types[6] = Core::HID::NpadIdType::Player7; | ||
| 1535 | supported_npad_id_types[7] = Core::HID::NpadIdType::Player8; | ||
| 1536 | supported_npad_id_types[8] = Core::HID::NpadIdType::Other; | ||
| 1537 | supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld; | ||
| 1538 | } | ||
| 1539 | |||
| 1511 | bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const { | 1540 | bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const { |
| 1512 | if (controller == Core::HID::NpadStyleIndex::Handheld) { | 1541 | if (controller == Core::HID::NpadStyleIndex::Handheld) { |
| 1513 | const bool support_handheld = | 1542 | const bool support_handheld = |
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 776411261..949e58a4c 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h | |||
| @@ -190,6 +190,8 @@ public: | |||
| 190 | // Specifically for cheat engine and other features. | 190 | // Specifically for cheat engine and other features. |
| 191 | Core::HID::NpadButton GetAndResetPressState(); | 191 | Core::HID::NpadButton GetAndResetPressState(); |
| 192 | 192 | ||
| 193 | void ApplyNpadSystemCommonPolicy(); | ||
| 194 | |||
| 193 | static bool IsNpadIdValid(Core::HID::NpadIdType npad_id); | 195 | static bool IsNpadIdValid(Core::HID::NpadIdType npad_id); |
| 194 | static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle); | 196 | static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle); |
| 195 | static Result VerifyValidSixAxisSensorHandle( | 197 | static Result VerifyValidSixAxisSensorHandle( |
| @@ -360,7 +362,7 @@ private: | |||
| 360 | enum class AppletFooterUiType : u8 { | 362 | enum class AppletFooterUiType : u8 { |
| 361 | None = 0, | 363 | None = 0, |
| 362 | HandheldNone = 1, | 364 | HandheldNone = 1, |
| 363 | HandheldJoyConLeftOnly = 1, | 365 | HandheldJoyConLeftOnly = 2, |
| 364 | HandheldJoyConRightOnly = 3, | 366 | HandheldJoyConRightOnly = 3, |
| 365 | HandheldJoyConLeftJoyConRight = 4, | 367 | HandheldJoyConLeftJoyConRight = 4, |
| 366 | JoyDual = 5, | 368 | JoyDual = 5, |
| @@ -382,13 +384,6 @@ private: | |||
| 382 | Lagon = 21, | 384 | Lagon = 21, |
| 383 | }; | 385 | }; |
| 384 | 386 | ||
| 385 | struct AppletFooterUi { | ||
| 386 | AppletFooterUiAttributes attributes{}; | ||
| 387 | AppletFooterUiType type{AppletFooterUiType::None}; | ||
| 388 | INSERT_PADDING_BYTES(0x5B); // Reserved | ||
| 389 | }; | ||
| 390 | static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size"); | ||
| 391 | |||
| 392 | // This is nn::hid::NpadLarkType | 387 | // This is nn::hid::NpadLarkType |
| 393 | enum class NpadLarkType : u32 { | 388 | enum class NpadLarkType : u32 { |
| 394 | Invalid, | 389 | Invalid, |
| @@ -419,13 +414,6 @@ private: | |||
| 419 | U, | 414 | U, |
| 420 | }; | 415 | }; |
| 421 | 416 | ||
| 422 | struct AppletNfcXcd { | ||
| 423 | union { | ||
| 424 | AppletFooterUi applet_footer{}; | ||
| 425 | Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo; | ||
| 426 | }; | ||
| 427 | }; | ||
| 428 | |||
| 429 | // This is nn::hid::detail::NpadInternalState | 417 | // This is nn::hid::detail::NpadInternalState |
| 430 | struct NpadInternalState { | 418 | struct NpadInternalState { |
| 431 | Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; | 419 | Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; |
| @@ -452,7 +440,9 @@ private: | |||
| 452 | Core::HID::NpadBatteryLevel battery_level_dual{}; | 440 | Core::HID::NpadBatteryLevel battery_level_dual{}; |
| 453 | Core::HID::NpadBatteryLevel battery_level_left{}; | 441 | Core::HID::NpadBatteryLevel battery_level_left{}; |
| 454 | Core::HID::NpadBatteryLevel battery_level_right{}; | 442 | Core::HID::NpadBatteryLevel battery_level_right{}; |
| 455 | AppletNfcXcd applet_nfc_xcd{}; | 443 | AppletFooterUiAttributes applet_footer_attributes{}; |
| 444 | AppletFooterUiType applet_footer_type{AppletFooterUiType::None}; | ||
| 445 | INSERT_PADDING_BYTES(0x5B); // Reserved | ||
| 456 | INSERT_PADDING_BYTES(0x20); // Unknown | 446 | INSERT_PADDING_BYTES(0x20); // Unknown |
| 457 | Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{}; | 447 | Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{}; |
| 458 | NpadLarkType lark_type_l_and_main{}; | 448 | NpadLarkType lark_type_l_and_main{}; |
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index fd466db7b..4d70006c1 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp | |||
| @@ -231,8 +231,10 @@ std::shared_ptr<IAppletResource> Hid::GetAppletResource() { | |||
| 231 | return applet_resource; | 231 | return applet_resource; |
| 232 | } | 232 | } |
| 233 | 233 | ||
| 234 | Hid::Hid(Core::System& system_) | 234 | Hid::Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_) |
| 235 | : ServiceFramework{system_, "hid"}, service_context{system_, service_name} { | 235 | : ServiceFramework{system_, "hid"}, applet_resource{applet_resource_}, service_context{ |
| 236 | system_, | ||
| 237 | service_name} { | ||
| 236 | // clang-format off | 238 | // clang-format off |
| 237 | static const FunctionInfo functions[] = { | 239 | static const FunctionInfo functions[] = { |
| 238 | {0, &Hid::CreateAppletResource, "CreateAppletResource"}, | 240 | {0, &Hid::CreateAppletResource, "CreateAppletResource"}, |
| @@ -2543,8 +2545,9 @@ public: | |||
| 2543 | 2545 | ||
| 2544 | class HidSys final : public ServiceFramework<HidSys> { | 2546 | class HidSys final : public ServiceFramework<HidSys> { |
| 2545 | public: | 2547 | public: |
| 2546 | explicit HidSys(Core::System& system_) | 2548 | explicit HidSys(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_) |
| 2547 | : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"} { | 2549 | : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"}, |
| 2550 | applet_resource{applet_resource_} { | ||
| 2548 | // clang-format off | 2551 | // clang-format off |
| 2549 | static const FunctionInfo functions[] = { | 2552 | static const FunctionInfo functions[] = { |
| 2550 | {31, nullptr, "SendKeyboardLockKeyEvent"}, | 2553 | {31, nullptr, "SendKeyboardLockKeyEvent"}, |
| @@ -2756,9 +2759,12 @@ public: | |||
| 2756 | 2759 | ||
| 2757 | private: | 2760 | private: |
| 2758 | void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { | 2761 | void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { |
| 2759 | // We already do this for homebrew so we can just stub it out | ||
| 2760 | LOG_WARNING(Service_HID, "called"); | 2762 | LOG_WARNING(Service_HID, "called"); |
| 2761 | 2763 | ||
| 2764 | GetAppletResource() | ||
| 2765 | ->GetController<Controller_NPad>(HidController::NPad) | ||
| 2766 | .ApplyNpadSystemCommonPolicy(); | ||
| 2767 | |||
| 2762 | IPC::ResponseBuilder rb{ctx, 2}; | 2768 | IPC::ResponseBuilder rb{ctx, 2}; |
| 2763 | rb.Push(ResultSuccess); | 2769 | rb.Push(ResultSuccess); |
| 2764 | } | 2770 | } |
| @@ -2768,7 +2774,7 @@ private: | |||
| 2768 | 2774 | ||
| 2769 | IPC::ResponseBuilder rb{ctx, 3}; | 2775 | IPC::ResponseBuilder rb{ctx, 3}; |
| 2770 | rb.Push(ResultSuccess); | 2776 | rb.Push(ResultSuccess); |
| 2771 | rb.PushEnum(Core::HID::NpadIdType::Handheld); | 2777 | rb.PushEnum(system.HIDCore().GetLastActiveController()); |
| 2772 | } | 2778 | } |
| 2773 | 2779 | ||
| 2774 | void GetUniquePadsFromNpad(HLERequestContext& ctx) { | 2780 | void GetUniquePadsFromNpad(HLERequestContext& ctx) { |
| @@ -2821,17 +2827,28 @@ private: | |||
| 2821 | rb.PushRaw(touchscreen_config); | 2827 | rb.PushRaw(touchscreen_config); |
| 2822 | } | 2828 | } |
| 2823 | 2829 | ||
| 2830 | std::shared_ptr<IAppletResource> GetAppletResource() { | ||
| 2831 | if (applet_resource == nullptr) { | ||
| 2832 | applet_resource = std::make_shared<IAppletResource>(system, service_context); | ||
| 2833 | } | ||
| 2834 | |||
| 2835 | return applet_resource; | ||
| 2836 | } | ||
| 2837 | |||
| 2824 | Kernel::KEvent* joy_detach_event; | 2838 | Kernel::KEvent* joy_detach_event; |
| 2825 | KernelHelpers::ServiceContext service_context; | 2839 | KernelHelpers::ServiceContext service_context; |
| 2840 | std::shared_ptr<IAppletResource> applet_resource; | ||
| 2826 | }; | 2841 | }; |
| 2827 | 2842 | ||
| 2828 | void LoopProcess(Core::System& system) { | 2843 | void LoopProcess(Core::System& system) { |
| 2829 | auto server_manager = std::make_unique<ServerManager>(system); | 2844 | auto server_manager = std::make_unique<ServerManager>(system); |
| 2845 | std::shared_ptr<IAppletResource> applet_resource; | ||
| 2830 | 2846 | ||
| 2831 | server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system)); | 2847 | server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system, applet_resource)); |
| 2832 | server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system)); | 2848 | server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system)); |
| 2833 | server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system)); | 2849 | server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system)); |
| 2834 | server_manager->RegisterNamedService("hid:sys", std::make_shared<HidSys>(system)); | 2850 | server_manager->RegisterNamedService("hid:sys", |
| 2851 | std::make_shared<HidSys>(system, applet_resource)); | ||
| 2835 | 2852 | ||
| 2836 | server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system)); | 2853 | server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system)); |
| 2837 | server_manager->RegisterNamedService("irs:sys", | 2854 | server_manager->RegisterNamedService("irs:sys", |
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index f247b83c2..0ca43de93 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h | |||
| @@ -95,7 +95,7 @@ private: | |||
| 95 | 95 | ||
| 96 | class Hid final : public ServiceFramework<Hid> { | 96 | class Hid final : public ServiceFramework<Hid> { |
| 97 | public: | 97 | public: |
| 98 | explicit Hid(Core::System& system_); | 98 | explicit Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_); |
| 99 | ~Hid() override; | 99 | ~Hid() override; |
| 100 | 100 | ||
| 101 | std::shared_ptr<IAppletResource> GetAppletResource(); | 101 | std::shared_ptr<IAppletResource> GetAppletResource(); |
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index 3b83c5ed7..c28eed926 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp | |||
| @@ -8,6 +8,9 @@ | |||
| 8 | #include "core/hle/service/mii/mii.h" | 8 | #include "core/hle/service/mii/mii.h" |
| 9 | #include "core/hle/service/mii/mii_manager.h" | 9 | #include "core/hle/service/mii/mii_manager.h" |
| 10 | #include "core/hle/service/mii/mii_result.h" | 10 | #include "core/hle/service/mii/mii_result.h" |
| 11 | #include "core/hle/service/mii/types/char_info.h" | ||
| 12 | #include "core/hle/service/mii/types/store_data.h" | ||
| 13 | #include "core/hle/service/mii/types/ver3_store_data.h" | ||
| 11 | #include "core/hle/service/server_manager.h" | 14 | #include "core/hle/service/server_manager.h" |
| 12 | #include "core/hle/service/service.h" | 15 | #include "core/hle/service/service.h" |
| 13 | 16 | ||
| @@ -15,8 +18,10 @@ namespace Service::Mii { | |||
| 15 | 18 | ||
| 16 | class IDatabaseService final : public ServiceFramework<IDatabaseService> { | 19 | class IDatabaseService final : public ServiceFramework<IDatabaseService> { |
| 17 | public: | 20 | public: |
| 18 | explicit IDatabaseService(Core::System& system_, bool is_system_) | 21 | explicit IDatabaseService(Core::System& system_, std::shared_ptr<MiiManager> mii_manager, |
| 19 | : ServiceFramework{system_, "IDatabaseService"}, is_system{is_system_} { | 22 | bool is_system_) |
| 23 | : ServiceFramework{system_, "IDatabaseService"}, manager{mii_manager}, is_system{ | ||
| 24 | is_system_} { | ||
| 20 | // clang-format off | 25 | // clang-format off |
| 21 | static const FunctionInfo functions[] = { | 26 | static const FunctionInfo functions[] = { |
| 22 | {0, &IDatabaseService::IsUpdated, "IsUpdated"}, | 27 | {0, &IDatabaseService::IsUpdated, "IsUpdated"}, |
| @@ -27,29 +32,31 @@ public: | |||
| 27 | {5, &IDatabaseService::UpdateLatest, "UpdateLatest"}, | 32 | {5, &IDatabaseService::UpdateLatest, "UpdateLatest"}, |
| 28 | {6, &IDatabaseService::BuildRandom, "BuildRandom"}, | 33 | {6, &IDatabaseService::BuildRandom, "BuildRandom"}, |
| 29 | {7, &IDatabaseService::BuildDefault, "BuildDefault"}, | 34 | {7, &IDatabaseService::BuildDefault, "BuildDefault"}, |
| 30 | {8, nullptr, "Get2"}, | 35 | {8, &IDatabaseService::Get2, "Get2"}, |
| 31 | {9, nullptr, "Get3"}, | 36 | {9, &IDatabaseService::Get3, "Get3"}, |
| 32 | {10, nullptr, "UpdateLatest1"}, | 37 | {10, &IDatabaseService::UpdateLatest1, "UpdateLatest1"}, |
| 33 | {11, nullptr, "FindIndex"}, | 38 | {11, &IDatabaseService::FindIndex, "FindIndex"}, |
| 34 | {12, nullptr, "Move"}, | 39 | {12, &IDatabaseService::Move, "Move"}, |
| 35 | {13, nullptr, "AddOrReplace"}, | 40 | {13, &IDatabaseService::AddOrReplace, "AddOrReplace"}, |
| 36 | {14, nullptr, "Delete"}, | 41 | {14, &IDatabaseService::Delete, "Delete"}, |
| 37 | {15, nullptr, "DestroyFile"}, | 42 | {15, &IDatabaseService::DestroyFile, "DestroyFile"}, |
| 38 | {16, nullptr, "DeleteFile"}, | 43 | {16, &IDatabaseService::DeleteFile, "DeleteFile"}, |
| 39 | {17, nullptr, "Format"}, | 44 | {17, &IDatabaseService::Format, "Format"}, |
| 40 | {18, nullptr, "Import"}, | 45 | {18, nullptr, "Import"}, |
| 41 | {19, nullptr, "Export"}, | 46 | {19, nullptr, "Export"}, |
| 42 | {20, nullptr, "IsBrokenDatabaseWithClearFlag"}, | 47 | {20, &IDatabaseService::IsBrokenDatabaseWithClearFlag, "IsBrokenDatabaseWithClearFlag"}, |
| 43 | {21, &IDatabaseService::GetIndex, "GetIndex"}, | 48 | {21, &IDatabaseService::GetIndex, "GetIndex"}, |
| 44 | {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"}, | 49 | {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"}, |
| 45 | {23, &IDatabaseService::Convert, "Convert"}, | 50 | {23, &IDatabaseService::Convert, "Convert"}, |
| 46 | {24, nullptr, "ConvertCoreDataToCharInfo"}, | 51 | {24, &IDatabaseService::ConvertCoreDataToCharInfo, "ConvertCoreDataToCharInfo"}, |
| 47 | {25, nullptr, "ConvertCharInfoToCoreData"}, | 52 | {25, &IDatabaseService::ConvertCharInfoToCoreData, "ConvertCharInfoToCoreData"}, |
| 48 | {26, nullptr, "Append"}, | 53 | {26, &IDatabaseService::Append, "Append"}, |
| 49 | }; | 54 | }; |
| 50 | // clang-format on | 55 | // clang-format on |
| 51 | 56 | ||
| 52 | RegisterHandlers(functions); | 57 | RegisterHandlers(functions); |
| 58 | |||
| 59 | manager->Initialize(metadata); | ||
| 53 | } | 60 | } |
| 54 | 61 | ||
| 55 | private: | 62 | private: |
| @@ -59,7 +66,7 @@ private: | |||
| 59 | 66 | ||
| 60 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 67 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); |
| 61 | 68 | ||
| 62 | const bool is_updated = manager.IsUpdated(metadata, source_flag); | 69 | const bool is_updated = manager->IsUpdated(metadata, source_flag); |
| 63 | 70 | ||
| 64 | IPC::ResponseBuilder rb{ctx, 3}; | 71 | IPC::ResponseBuilder rb{ctx, 3}; |
| 65 | rb.Push(ResultSuccess); | 72 | rb.Push(ResultSuccess); |
| @@ -69,7 +76,7 @@ private: | |||
| 69 | void IsFullDatabase(HLERequestContext& ctx) { | 76 | void IsFullDatabase(HLERequestContext& ctx) { |
| 70 | LOG_DEBUG(Service_Mii, "called"); | 77 | LOG_DEBUG(Service_Mii, "called"); |
| 71 | 78 | ||
| 72 | const bool is_full_database = manager.IsFullDatabase(); | 79 | const bool is_full_database = manager->IsFullDatabase(); |
| 73 | 80 | ||
| 74 | IPC::ResponseBuilder rb{ctx, 3}; | 81 | IPC::ResponseBuilder rb{ctx, 3}; |
| 75 | rb.Push(ResultSuccess); | 82 | rb.Push(ResultSuccess); |
| @@ -80,9 +87,9 @@ private: | |||
| 80 | IPC::RequestParser rp{ctx}; | 87 | IPC::RequestParser rp{ctx}; |
| 81 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 88 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 82 | 89 | ||
| 83 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 90 | const u32 mii_count = manager->GetCount(metadata, source_flag); |
| 84 | 91 | ||
| 85 | const u32 mii_count = manager.GetCount(metadata, source_flag); | 92 | LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count); |
| 86 | 93 | ||
| 87 | IPC::ResponseBuilder rb{ctx, 3}; | 94 | IPC::ResponseBuilder rb{ctx, 3}; |
| 88 | rb.Push(ResultSuccess); | 95 | rb.Push(ResultSuccess); |
| @@ -94,16 +101,17 @@ private: | |||
| 94 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 101 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 95 | const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()}; | 102 | const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()}; |
| 96 | 103 | ||
| 97 | LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size); | ||
| 98 | |||
| 99 | u32 mii_count{}; | 104 | u32 mii_count{}; |
| 100 | std::vector<CharInfoElement> char_info_elements(output_size); | 105 | std::vector<CharInfoElement> char_info_elements(output_size); |
| 101 | Result 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); |
| 102 | 107 | ||
| 103 | if (mii_count != 0) { | 108 | if (mii_count != 0) { |
| 104 | ctx.WriteBuffer(char_info_elements); | 109 | ctx.WriteBuffer(char_info_elements); |
| 105 | } | 110 | } |
| 106 | 111 | ||
| 112 | LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, | ||
| 113 | output_size, mii_count); | ||
| 114 | |||
| 107 | IPC::ResponseBuilder rb{ctx, 3}; | 115 | IPC::ResponseBuilder rb{ctx, 3}; |
| 108 | rb.Push(result); | 116 | rb.Push(result); |
| 109 | rb.Push(mii_count); | 117 | rb.Push(mii_count); |
| @@ -114,16 +122,17 @@ private: | |||
| 114 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 122 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 115 | const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()}; | 123 | const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()}; |
| 116 | 124 | ||
| 117 | LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size); | ||
| 118 | |||
| 119 | u32 mii_count{}; | 125 | u32 mii_count{}; |
| 120 | std::vector<CharInfo> char_info(output_size); | 126 | std::vector<CharInfo> char_info(output_size); |
| 121 | Result result = manager.Get(metadata, char_info, mii_count, source_flag); | 127 | const auto result = manager->Get(metadata, char_info, mii_count, source_flag); |
| 122 | 128 | ||
| 123 | if (mii_count != 0) { | 129 | if (mii_count != 0) { |
| 124 | ctx.WriteBuffer(char_info); | 130 | ctx.WriteBuffer(char_info); |
| 125 | } | 131 | } |
| 126 | 132 | ||
| 133 | LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, | ||
| 134 | output_size, mii_count); | ||
| 135 | |||
| 127 | IPC::ResponseBuilder rb{ctx, 3}; | 136 | IPC::ResponseBuilder rb{ctx, 3}; |
| 128 | rb.Push(result); | 137 | rb.Push(result); |
| 129 | rb.Push(mii_count); | 138 | rb.Push(mii_count); |
| @@ -134,10 +143,10 @@ private: | |||
| 134 | const auto char_info{rp.PopRaw<CharInfo>()}; | 143 | const auto char_info{rp.PopRaw<CharInfo>()}; |
| 135 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 144 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 136 | 145 | ||
| 137 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 146 | LOG_INFO(Service_Mii, "called with source_flag={}", source_flag); |
| 138 | 147 | ||
| 139 | CharInfo new_char_info{}; | 148 | CharInfo new_char_info{}; |
| 140 | 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); |
| 141 | if (result.IsFailure()) { | 150 | if (result.IsFailure()) { |
| 142 | IPC::ResponseBuilder rb{ctx, 2}; | 151 | IPC::ResponseBuilder rb{ctx, 2}; |
| 143 | rb.Push(result); | 152 | rb.Push(result); |
| @@ -146,7 +155,7 @@ private: | |||
| 146 | 155 | ||
| 147 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 156 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 148 | rb.Push(ResultSuccess); | 157 | rb.Push(ResultSuccess); |
| 149 | rb.PushRaw<CharInfo>(new_char_info); | 158 | rb.PushRaw(new_char_info); |
| 150 | } | 159 | } |
| 151 | 160 | ||
| 152 | void BuildRandom(HLERequestContext& ctx) { | 161 | void BuildRandom(HLERequestContext& ctx) { |
| @@ -176,18 +185,18 @@ private: | |||
| 176 | } | 185 | } |
| 177 | 186 | ||
| 178 | CharInfo char_info{}; | 187 | CharInfo char_info{}; |
| 179 | manager.BuildRandom(char_info, age, gender, race); | 188 | manager->BuildRandom(char_info, age, gender, race); |
| 180 | 189 | ||
| 181 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 190 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 182 | rb.Push(ResultSuccess); | 191 | rb.Push(ResultSuccess); |
| 183 | rb.PushRaw<CharInfo>(char_info); | 192 | rb.PushRaw(char_info); |
| 184 | } | 193 | } |
| 185 | 194 | ||
| 186 | void BuildDefault(HLERequestContext& ctx) { | 195 | void BuildDefault(HLERequestContext& ctx) { |
| 187 | IPC::RequestParser rp{ctx}; | 196 | IPC::RequestParser rp{ctx}; |
| 188 | const auto index{rp.Pop<u32>()}; | 197 | const auto index{rp.Pop<u32>()}; |
| 189 | 198 | ||
| 190 | LOG_INFO(Service_Mii, "called with index={}", index); | 199 | LOG_DEBUG(Service_Mii, "called with index={}", index); |
| 191 | 200 | ||
| 192 | if (index > 5) { | 201 | if (index > 5) { |
| 193 | IPC::ResponseBuilder rb{ctx, 2}; | 202 | IPC::ResponseBuilder rb{ctx, 2}; |
| @@ -196,11 +205,243 @@ private: | |||
| 196 | } | 205 | } |
| 197 | 206 | ||
| 198 | CharInfo char_info{}; | 207 | CharInfo char_info{}; |
| 199 | manager.BuildDefault(char_info, index); | 208 | manager->BuildDefault(char_info, index); |
| 200 | 209 | ||
| 201 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 210 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 202 | rb.Push(ResultSuccess); | 211 | rb.Push(ResultSuccess); |
| 203 | rb.PushRaw<CharInfo>(char_info); | 212 | rb.PushRaw(char_info); |
| 213 | } | ||
| 214 | |||
| 215 | void Get2(HLERequestContext& ctx) { | ||
| 216 | IPC::RequestParser rp{ctx}; | ||
| 217 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | ||
| 218 | const auto output_size{ctx.GetWriteBufferNumElements<StoreDataElement>()}; | ||
| 219 | |||
| 220 | u32 mii_count{}; | ||
| 221 | std::vector<StoreDataElement> store_data_elements(output_size); | ||
| 222 | const auto result = manager->Get(metadata, store_data_elements, mii_count, source_flag); | ||
| 223 | |||
| 224 | if (mii_count != 0) { | ||
| 225 | ctx.WriteBuffer(store_data_elements); | ||
| 226 | } | ||
| 227 | |||
| 228 | LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, | ||
| 229 | output_size, mii_count); | ||
| 230 | |||
| 231 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 232 | rb.Push(result); | ||
| 233 | rb.Push(mii_count); | ||
| 234 | } | ||
| 235 | |||
| 236 | void Get3(HLERequestContext& ctx) { | ||
| 237 | IPC::RequestParser rp{ctx}; | ||
| 238 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | ||
| 239 | const auto output_size{ctx.GetWriteBufferNumElements<StoreData>()}; | ||
| 240 | |||
| 241 | u32 mii_count{}; | ||
| 242 | std::vector<StoreData> store_data(output_size); | ||
| 243 | const auto result = manager->Get(metadata, store_data, mii_count, source_flag); | ||
| 244 | |||
| 245 | if (mii_count != 0) { | ||
| 246 | ctx.WriteBuffer(store_data); | ||
| 247 | } | ||
| 248 | |||
| 249 | LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, | ||
| 250 | output_size, mii_count); | ||
| 251 | |||
| 252 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 253 | rb.Push(result); | ||
| 254 | rb.Push(mii_count); | ||
| 255 | } | ||
| 256 | |||
| 257 | void UpdateLatest1(HLERequestContext& ctx) { | ||
| 258 | IPC::RequestParser rp{ctx}; | ||
| 259 | const auto store_data{rp.PopRaw<StoreData>()}; | ||
| 260 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | ||
| 261 | |||
| 262 | LOG_INFO(Service_Mii, "called with source_flag={}", source_flag); | ||
| 263 | |||
| 264 | Result result = ResultSuccess; | ||
| 265 | if (!is_system) { | ||
| 266 | result = ResultPermissionDenied; | ||
| 267 | } | ||
| 268 | |||
| 269 | StoreData new_store_data{}; | ||
| 270 | if (result.IsSuccess()) { | ||
| 271 | result = manager->UpdateLatest(metadata, new_store_data, store_data, source_flag); | ||
| 272 | } | ||
| 273 | |||
| 274 | if (result.IsFailure()) { | ||
| 275 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 276 | rb.Push(result); | ||
| 277 | return; | ||
| 278 | } | ||
| 279 | |||
| 280 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(StoreData) / sizeof(u32)}; | ||
| 281 | rb.Push(ResultSuccess); | ||
| 282 | rb.PushRaw<StoreData>(new_store_data); | ||
| 283 | } | ||
| 284 | |||
| 285 | void FindIndex(HLERequestContext& ctx) { | ||
| 286 | IPC::RequestParser rp{ctx}; | ||
| 287 | const auto create_id{rp.PopRaw<Common::UUID>()}; | ||
| 288 | const auto is_special{rp.PopRaw<bool>()}; | ||
| 289 | |||
| 290 | LOG_INFO(Service_Mii, "called with create_id={}, is_special={}", | ||
| 291 | create_id.FormattedString(), is_special); | ||
| 292 | |||
| 293 | const s32 index = manager->FindIndex(create_id, is_special); | ||
| 294 | |||
| 295 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 296 | rb.Push(ResultSuccess); | ||
| 297 | rb.Push(index); | ||
| 298 | } | ||
| 299 | |||
| 300 | void Move(HLERequestContext& ctx) { | ||
| 301 | IPC::RequestParser rp{ctx}; | ||
| 302 | const auto create_id{rp.PopRaw<Common::UUID>()}; | ||
| 303 | const auto new_index{rp.PopRaw<s32>()}; | ||
| 304 | |||
| 305 | LOG_INFO(Service_Mii, "called with create_id={}, new_index={}", create_id.FormattedString(), | ||
| 306 | new_index); | ||
| 307 | |||
| 308 | Result result = ResultSuccess; | ||
| 309 | if (!is_system) { | ||
| 310 | result = ResultPermissionDenied; | ||
| 311 | } | ||
| 312 | |||
| 313 | if (result.IsSuccess()) { | ||
| 314 | const u32 count = manager->GetCount(metadata, SourceFlag::Database); | ||
| 315 | if (new_index < 0 || new_index >= static_cast<s32>(count)) { | ||
| 316 | result = ResultInvalidArgument; | ||
| 317 | } | ||
| 318 | } | ||
| 319 | |||
| 320 | if (result.IsSuccess()) { | ||
| 321 | result = manager->Move(metadata, new_index, create_id); | ||
| 322 | } | ||
| 323 | |||
| 324 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 325 | rb.Push(result); | ||
| 326 | } | ||
| 327 | |||
| 328 | void AddOrReplace(HLERequestContext& ctx) { | ||
| 329 | IPC::RequestParser rp{ctx}; | ||
| 330 | const auto store_data{rp.PopRaw<StoreData>()}; | ||
| 331 | |||
| 332 | LOG_INFO(Service_Mii, "called"); | ||
| 333 | |||
| 334 | Result result = ResultSuccess; | ||
| 335 | |||
| 336 | if (!is_system) { | ||
| 337 | result = ResultPermissionDenied; | ||
| 338 | } | ||
| 339 | |||
| 340 | if (result.IsSuccess()) { | ||
| 341 | result = manager->AddOrReplace(metadata, store_data); | ||
| 342 | } | ||
| 343 | |||
| 344 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 345 | rb.Push(result); | ||
| 346 | } | ||
| 347 | |||
| 348 | void Delete(HLERequestContext& ctx) { | ||
| 349 | IPC::RequestParser rp{ctx}; | ||
| 350 | const auto create_id{rp.PopRaw<Common::UUID>()}; | ||
| 351 | |||
| 352 | LOG_INFO(Service_Mii, "called, create_id={}", create_id.FormattedString()); | ||
| 353 | |||
| 354 | Result result = ResultSuccess; | ||
| 355 | |||
| 356 | if (!is_system) { | ||
| 357 | result = ResultPermissionDenied; | ||
| 358 | } | ||
| 359 | |||
| 360 | if (result.IsSuccess()) { | ||
| 361 | result = manager->Delete(metadata, create_id); | ||
| 362 | } | ||
| 363 | |||
| 364 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 365 | rb.Push(result); | ||
| 366 | } | ||
| 367 | |||
| 368 | void DestroyFile(HLERequestContext& ctx) { | ||
| 369 | // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); | ||
| 370 | const bool is_db_test_mode_enabled = false; | ||
| 371 | |||
| 372 | LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled); | ||
| 373 | |||
| 374 | Result result = ResultSuccess; | ||
| 375 | |||
| 376 | if (!is_db_test_mode_enabled) { | ||
| 377 | result = ResultTestModeOnly; | ||
| 378 | } | ||
| 379 | |||
| 380 | if (result.IsSuccess()) { | ||
| 381 | result = manager->DestroyFile(metadata); | ||
| 382 | } | ||
| 383 | |||
| 384 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 385 | rb.Push(result); | ||
| 386 | } | ||
| 387 | |||
| 388 | void DeleteFile(HLERequestContext& ctx) { | ||
| 389 | // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); | ||
| 390 | const bool is_db_test_mode_enabled = false; | ||
| 391 | |||
| 392 | LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled); | ||
| 393 | |||
| 394 | Result result = ResultSuccess; | ||
| 395 | |||
| 396 | if (!is_db_test_mode_enabled) { | ||
| 397 | result = ResultTestModeOnly; | ||
| 398 | } | ||
| 399 | |||
| 400 | if (result.IsSuccess()) { | ||
| 401 | result = manager->DeleteFile(); | ||
| 402 | } | ||
| 403 | |||
| 404 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 405 | rb.Push(result); | ||
| 406 | } | ||
| 407 | |||
| 408 | void Format(HLERequestContext& ctx) { | ||
| 409 | // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); | ||
| 410 | const bool is_db_test_mode_enabled = false; | ||
| 411 | |||
| 412 | LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled); | ||
| 413 | |||
| 414 | Result result = ResultSuccess; | ||
| 415 | |||
| 416 | if (!is_db_test_mode_enabled) { | ||
| 417 | result = ResultTestModeOnly; | ||
| 418 | } | ||
| 419 | |||
| 420 | if (result.IsSuccess()) { | ||
| 421 | result = manager->Format(metadata); | ||
| 422 | } | ||
| 423 | |||
| 424 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 425 | rb.Push(result); | ||
| 426 | } | ||
| 427 | |||
| 428 | void IsBrokenDatabaseWithClearFlag(HLERequestContext& ctx) { | ||
| 429 | LOG_DEBUG(Service_Mii, "called"); | ||
| 430 | |||
| 431 | bool is_broken_with_clear_flag = false; | ||
| 432 | Result result = ResultSuccess; | ||
| 433 | |||
| 434 | if (!is_system) { | ||
| 435 | result = ResultPermissionDenied; | ||
| 436 | } | ||
| 437 | |||
| 438 | if (result.IsSuccess()) { | ||
| 439 | is_broken_with_clear_flag = manager->IsBrokenWithClearFlag(metadata); | ||
| 440 | } | ||
| 441 | |||
| 442 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 443 | rb.Push(result); | ||
| 444 | rb.Push<u8>(is_broken_with_clear_flag); | ||
| 204 | } | 445 | } |
| 205 | 446 | ||
| 206 | void GetIndex(HLERequestContext& ctx) { | 447 | void GetIndex(HLERequestContext& ctx) { |
| @@ -210,7 +451,7 @@ private: | |||
| 210 | LOG_DEBUG(Service_Mii, "called"); | 451 | LOG_DEBUG(Service_Mii, "called"); |
| 211 | 452 | ||
| 212 | s32 index{}; | 453 | s32 index{}; |
| 213 | const auto result = manager.GetIndex(metadata, info, index); | 454 | const auto result = manager->GetIndex(metadata, info, index); |
| 214 | 455 | ||
| 215 | IPC::ResponseBuilder rb{ctx, 3}; | 456 | IPC::ResponseBuilder rb{ctx, 3}; |
| 216 | rb.Push(result); | 457 | rb.Push(result); |
| @@ -223,7 +464,7 @@ private: | |||
| 223 | 464 | ||
| 224 | LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version); | 465 | LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version); |
| 225 | 466 | ||
| 226 | manager.SetInterfaceVersion(metadata, interface_version); | 467 | manager->SetInterfaceVersion(metadata, interface_version); |
| 227 | 468 | ||
| 228 | IPC::ResponseBuilder rb{ctx, 2}; | 469 | IPC::ResponseBuilder rb{ctx, 2}; |
| 229 | rb.Push(ResultSuccess); | 470 | rb.Push(ResultSuccess); |
| @@ -236,51 +477,96 @@ private: | |||
| 236 | LOG_INFO(Service_Mii, "called"); | 477 | LOG_INFO(Service_Mii, "called"); |
| 237 | 478 | ||
| 238 | CharInfo char_info{}; | 479 | CharInfo char_info{}; |
| 239 | manager.ConvertV3ToCharInfo(char_info, mii_v3); | 480 | const auto result = manager->ConvertV3ToCharInfo(char_info, mii_v3); |
| 240 | 481 | ||
| 241 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 482 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 242 | rb.Push(ResultSuccess); | 483 | rb.Push(result); |
| 243 | rb.PushRaw<CharInfo>(char_info); | 484 | rb.PushRaw<CharInfo>(char_info); |
| 244 | } | 485 | } |
| 245 | 486 | ||
| 246 | MiiManager manager{}; | 487 | void ConvertCoreDataToCharInfo(HLERequestContext& ctx) { |
| 247 | DatabaseSessionMetadata metadata{}; | 488 | IPC::RequestParser rp{ctx}; |
| 248 | bool is_system{}; | 489 | const auto core_data{rp.PopRaw<CoreData>()}; |
| 249 | }; | ||
| 250 | 490 | ||
| 251 | class MiiDBModule final : public ServiceFramework<MiiDBModule> { | 491 | LOG_INFO(Service_Mii, "called"); |
| 252 | public: | ||
| 253 | explicit MiiDBModule(Core::System& system_, const char* name_, bool is_system_) | ||
| 254 | : ServiceFramework{system_, name_}, is_system{is_system_} { | ||
| 255 | // clang-format off | ||
| 256 | static const FunctionInfo functions[] = { | ||
| 257 | {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, | ||
| 258 | }; | ||
| 259 | // clang-format on | ||
| 260 | 492 | ||
| 261 | RegisterHandlers(functions); | 493 | CharInfo char_info{}; |
| 494 | const auto result = manager->ConvertCoreDataToCharInfo(char_info, core_data); | ||
| 495 | |||
| 496 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | ||
| 497 | rb.Push(result); | ||
| 498 | rb.PushRaw<CharInfo>(char_info); | ||
| 262 | } | 499 | } |
| 263 | 500 | ||
| 264 | private: | 501 | void ConvertCharInfoToCoreData(HLERequestContext& ctx) { |
| 265 | void GetDatabaseService(HLERequestContext& ctx) { | 502 | IPC::RequestParser rp{ctx}; |
| 266 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 503 | const auto char_info{rp.PopRaw<CharInfo>()}; |
| 267 | rb.Push(ResultSuccess); | ||
| 268 | rb.PushIpcInterface<IDatabaseService>(system, is_system); | ||
| 269 | 504 | ||
| 270 | LOG_DEBUG(Service_Mii, "called"); | 505 | LOG_INFO(Service_Mii, "called"); |
| 506 | |||
| 507 | CoreData core_data{}; | ||
| 508 | const auto result = manager->ConvertCharInfoToCoreData(core_data, char_info); | ||
| 509 | |||
| 510 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)}; | ||
| 511 | rb.Push(result); | ||
| 512 | rb.PushRaw<CoreData>(core_data); | ||
| 513 | } | ||
| 514 | |||
| 515 | void Append(HLERequestContext& ctx) { | ||
| 516 | IPC::RequestParser rp{ctx}; | ||
| 517 | const auto char_info{rp.PopRaw<CharInfo>()}; | ||
| 518 | |||
| 519 | LOG_INFO(Service_Mii, "called"); | ||
| 520 | |||
| 521 | const auto result = manager->Append(metadata, char_info); | ||
| 522 | |||
| 523 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 524 | rb.Push(result); | ||
| 271 | } | 525 | } |
| 272 | 526 | ||
| 527 | std::shared_ptr<MiiManager> manager = nullptr; | ||
| 528 | DatabaseSessionMetadata metadata{}; | ||
| 273 | bool is_system{}; | 529 | bool is_system{}; |
| 274 | }; | 530 | }; |
| 275 | 531 | ||
| 532 | MiiDBModule::MiiDBModule(Core::System& system_, const char* name_, | ||
| 533 | std::shared_ptr<MiiManager> mii_manager, bool is_system_) | ||
| 534 | : ServiceFramework{system_, name_}, manager{mii_manager}, is_system{is_system_} { | ||
| 535 | // clang-format off | ||
| 536 | static const FunctionInfo functions[] = { | ||
| 537 | {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, | ||
| 538 | }; | ||
| 539 | // clang-format on | ||
| 540 | |||
| 541 | RegisterHandlers(functions); | ||
| 542 | |||
| 543 | if (manager == nullptr) { | ||
| 544 | manager = std::make_shared<MiiManager>(); | ||
| 545 | } | ||
| 546 | } | ||
| 547 | |||
| 548 | MiiDBModule::~MiiDBModule() = default; | ||
| 549 | |||
| 550 | void MiiDBModule::GetDatabaseService(HLERequestContext& ctx) { | ||
| 551 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 552 | rb.Push(ResultSuccess); | ||
| 553 | rb.PushIpcInterface<IDatabaseService>(system, manager, is_system); | ||
| 554 | |||
| 555 | LOG_DEBUG(Service_Mii, "called"); | ||
| 556 | } | ||
| 557 | |||
| 558 | std::shared_ptr<MiiManager> MiiDBModule::GetMiiManager() { | ||
| 559 | return manager; | ||
| 560 | } | ||
| 561 | |||
| 276 | class MiiImg final : public ServiceFramework<MiiImg> { | 562 | class MiiImg final : public ServiceFramework<MiiImg> { |
| 277 | public: | 563 | public: |
| 278 | explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} { | 564 | explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} { |
| 279 | // clang-format off | 565 | // clang-format off |
| 280 | static const FunctionInfo functions[] = { | 566 | static const FunctionInfo functions[] = { |
| 281 | {0, nullptr, "Initialize"}, | 567 | {0, &MiiImg::Initialize, "Initialize"}, |
| 282 | {10, nullptr, "Reload"}, | 568 | {10, nullptr, "Reload"}, |
| 283 | {11, nullptr, "GetCount"}, | 569 | {11, &MiiImg::GetCount, "GetCount"}, |
| 284 | {12, nullptr, "IsEmpty"}, | 570 | {12, nullptr, "IsEmpty"}, |
| 285 | {13, nullptr, "IsFull"}, | 571 | {13, nullptr, "IsFull"}, |
| 286 | {14, nullptr, "GetAttribute"}, | 572 | {14, nullptr, "GetAttribute"}, |
| @@ -297,15 +583,32 @@ public: | |||
| 297 | 583 | ||
| 298 | RegisterHandlers(functions); | 584 | RegisterHandlers(functions); |
| 299 | } | 585 | } |
| 586 | |||
| 587 | private: | ||
| 588 | void Initialize(HLERequestContext& ctx) { | ||
| 589 | LOG_INFO(Service_Mii, "called"); | ||
| 590 | |||
| 591 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 592 | rb.Push(ResultSuccess); | ||
| 593 | } | ||
| 594 | |||
| 595 | void GetCount(HLERequestContext& ctx) { | ||
| 596 | LOG_DEBUG(Service_Mii, "called"); | ||
| 597 | |||
| 598 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 599 | rb.Push(ResultSuccess); | ||
| 600 | rb.Push(0); | ||
| 601 | } | ||
| 300 | }; | 602 | }; |
| 301 | 603 | ||
| 302 | void LoopProcess(Core::System& system) { | 604 | void LoopProcess(Core::System& system) { |
| 303 | auto server_manager = std::make_unique<ServerManager>(system); | 605 | auto server_manager = std::make_unique<ServerManager>(system); |
| 606 | std::shared_ptr<MiiManager> manager = nullptr; | ||
| 304 | 607 | ||
| 305 | server_manager->RegisterNamedService("mii:e", | 608 | server_manager->RegisterNamedService( |
| 306 | std::make_shared<MiiDBModule>(system, "mii:e", true)); | 609 | "mii:e", std::make_shared<MiiDBModule>(system, "mii:e", manager, true)); |
| 307 | server_manager->RegisterNamedService("mii:u", | 610 | server_manager->RegisterNamedService( |
| 308 | std::make_shared<MiiDBModule>(system, "mii:u", false)); | 611 | "mii:u", std::make_shared<MiiDBModule>(system, "mii:u", manager, false)); |
| 309 | server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); | 612 | server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); |
| 310 | ServerManager::RunServer(std::move(server_manager)); | 613 | ServerManager::RunServer(std::move(server_manager)); |
| 311 | } | 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.cpp b/src/core/hle/service/mii/mii_database.cpp new file mode 100644 index 000000000..3803e58e2 --- /dev/null +++ b/src/core/hle/service/mii/mii_database.cpp | |||
| @@ -0,0 +1,142 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/hle/service/mii/mii_database.h" | ||
| 5 | #include "core/hle/service/mii/mii_result.h" | ||
| 6 | #include "core/hle/service/mii/mii_util.h" | ||
| 7 | |||
| 8 | namespace Service::Mii { | ||
| 9 | |||
| 10 | u8 NintendoFigurineDatabase::GetDatabaseLength() const { | ||
| 11 | return database_length; | ||
| 12 | } | ||
| 13 | |||
| 14 | bool NintendoFigurineDatabase::IsFull() const { | ||
| 15 | return database_length >= MaxDatabaseLength; | ||
| 16 | } | ||
| 17 | |||
| 18 | StoreData NintendoFigurineDatabase::Get(std::size_t index) const { | ||
| 19 | StoreData store_data = miis.at(index); | ||
| 20 | |||
| 21 | // This hack is to make external database dumps compatible | ||
| 22 | store_data.SetDeviceChecksum(); | ||
| 23 | |||
| 24 | return store_data; | ||
| 25 | } | ||
| 26 | |||
| 27 | u32 NintendoFigurineDatabase::GetCount(const DatabaseSessionMetadata& metadata) const { | ||
| 28 | if (magic == MiiMagic) { | ||
| 29 | return GetDatabaseLength(); | ||
| 30 | } | ||
| 31 | |||
| 32 | u32 mii_count{}; | ||
| 33 | for (std::size_t index = 0; index < mii_count; ++index) { | ||
| 34 | const auto& store_data = Get(index); | ||
| 35 | if (!store_data.IsSpecial()) { | ||
| 36 | mii_count++; | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | return mii_count; | ||
| 41 | } | ||
| 42 | |||
| 43 | bool NintendoFigurineDatabase::GetIndexByCreatorId(u32& out_index, | ||
| 44 | const Common::UUID& create_id) const { | ||
| 45 | for (std::size_t index = 0; index < database_length; ++index) { | ||
| 46 | if (miis[index].GetCreateId() == create_id) { | ||
| 47 | out_index = static_cast<u32>(index); | ||
| 48 | return true; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | return false; | ||
| 53 | } | ||
| 54 | |||
| 55 | Result NintendoFigurineDatabase::Move(u32 current_index, u32 new_index) { | ||
| 56 | if (current_index == new_index) { | ||
| 57 | return ResultNotUpdated; | ||
| 58 | } | ||
| 59 | |||
| 60 | const StoreData store_data = miis[current_index]; | ||
| 61 | |||
| 62 | if (new_index > current_index) { | ||
| 63 | // Shift left | ||
| 64 | const u32 index_diff = new_index - current_index; | ||
| 65 | for (std::size_t i = 0; i < index_diff; i++) { | ||
| 66 | miis[current_index + i] = miis[current_index + i + 1]; | ||
| 67 | } | ||
| 68 | } else { | ||
| 69 | // Shift right | ||
| 70 | const u32 index_diff = current_index - new_index; | ||
| 71 | for (std::size_t i = 0; i < index_diff; i++) { | ||
| 72 | miis[current_index - i] = miis[current_index - i - 1]; | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | miis[new_index] = store_data; | ||
| 77 | crc = GenerateDatabaseCrc(); | ||
| 78 | return ResultSuccess; | ||
| 79 | } | ||
| 80 | |||
| 81 | void NintendoFigurineDatabase::Replace(u32 index, const StoreData& store_data) { | ||
| 82 | miis[index] = store_data; | ||
| 83 | crc = GenerateDatabaseCrc(); | ||
| 84 | } | ||
| 85 | |||
| 86 | void NintendoFigurineDatabase::Add(const StoreData& store_data) { | ||
| 87 | miis[database_length] = store_data; | ||
| 88 | database_length++; | ||
| 89 | crc = GenerateDatabaseCrc(); | ||
| 90 | } | ||
| 91 | |||
| 92 | void NintendoFigurineDatabase::Delete(u32 index) { | ||
| 93 | // Shift left | ||
| 94 | const s32 new_database_size = database_length - 1; | ||
| 95 | if (static_cast<s32>(index) < new_database_size) { | ||
| 96 | for (std::size_t i = index; i < static_cast<std::size_t>(new_database_size); i++) { | ||
| 97 | miis[i] = miis[i + 1]; | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 101 | database_length = static_cast<u8>(new_database_size); | ||
| 102 | crc = GenerateDatabaseCrc(); | ||
| 103 | } | ||
| 104 | |||
| 105 | void NintendoFigurineDatabase::CleanDatabase() { | ||
| 106 | miis = {}; | ||
| 107 | version = 1; | ||
| 108 | magic = DatabaseMagic; | ||
| 109 | database_length = 0; | ||
| 110 | crc = GenerateDatabaseCrc(); | ||
| 111 | } | ||
| 112 | |||
| 113 | void NintendoFigurineDatabase::CorruptCrc() { | ||
| 114 | crc = GenerateDatabaseCrc(); | ||
| 115 | crc = ~crc; | ||
| 116 | } | ||
| 117 | |||
| 118 | Result NintendoFigurineDatabase::CheckIntegrity() { | ||
| 119 | if (magic != DatabaseMagic) { | ||
| 120 | return ResultInvalidDatabaseSignature; | ||
| 121 | } | ||
| 122 | |||
| 123 | if (version != 1) { | ||
| 124 | return ResultInvalidDatabaseVersion; | ||
| 125 | } | ||
| 126 | |||
| 127 | if (crc != GenerateDatabaseCrc()) { | ||
| 128 | return ResultInvalidDatabaseChecksum; | ||
| 129 | } | ||
| 130 | |||
| 131 | if (database_length >= MaxDatabaseLength) { | ||
| 132 | return ResultInvalidDatabaseLength; | ||
| 133 | } | ||
| 134 | |||
| 135 | return ResultSuccess; | ||
| 136 | } | ||
| 137 | |||
| 138 | u16 NintendoFigurineDatabase::GenerateDatabaseCrc() { | ||
| 139 | return MiiUtil::CalculateCrc16(&magic, sizeof(NintendoFigurineDatabase) - sizeof(crc)); | ||
| 140 | } | ||
| 141 | |||
| 142 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/mii_database.h b/src/core/hle/service/mii/mii_database.h new file mode 100644 index 000000000..3bd240f93 --- /dev/null +++ b/src/core/hle/service/mii/mii_database.h | |||
| @@ -0,0 +1,66 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/result.h" | ||
| 7 | #include "core/hle/service/mii/types/store_data.h" | ||
| 8 | |||
| 9 | namespace Service::Mii { | ||
| 10 | |||
| 11 | constexpr std::size_t MaxDatabaseLength{100}; | ||
| 12 | constexpr u32 MiiMagic{0xa523b78f}; | ||
| 13 | constexpr u32 DatabaseMagic{0x4244464e}; // NFDB | ||
| 14 | |||
| 15 | class NintendoFigurineDatabase { | ||
| 16 | public: | ||
| 17 | /// Returns the total mii count. | ||
| 18 | u8 GetDatabaseLength() const; | ||
| 19 | |||
| 20 | /// Returns true if database is full. | ||
| 21 | bool IsFull() const; | ||
| 22 | |||
| 23 | /// Returns the mii of the specified index. | ||
| 24 | StoreData Get(std::size_t index) const; | ||
| 25 | |||
| 26 | /// Returns the total mii count. Ignoring special mii. | ||
| 27 | u32 GetCount(const DatabaseSessionMetadata& metadata) const; | ||
| 28 | |||
| 29 | /// Returns the index of a mii. If the mii isn't found returns false. | ||
| 30 | bool GetIndexByCreatorId(u32& out_index, const Common::UUID& create_id) const; | ||
| 31 | |||
| 32 | /// Moves the location of a specific mii. | ||
| 33 | Result Move(u32 current_index, u32 new_index); | ||
| 34 | |||
| 35 | /// Replaces mii with new data. | ||
| 36 | void Replace(u32 index, const StoreData& store_data); | ||
| 37 | |||
| 38 | /// Adds a new mii to the end of the database. | ||
| 39 | void Add(const StoreData& store_data); | ||
| 40 | |||
| 41 | /// Removes mii from database and shifts left the remainding data. | ||
| 42 | void Delete(u32 index); | ||
| 43 | |||
| 44 | /// Deletes all contents with a fresh database | ||
| 45 | void CleanDatabase(); | ||
| 46 | |||
| 47 | /// Intentionally sets a bad checksum | ||
| 48 | void CorruptCrc(); | ||
| 49 | |||
| 50 | /// Returns success if database is valid otherwise returns the corresponding error code. | ||
| 51 | Result CheckIntegrity(); | ||
| 52 | |||
| 53 | private: | ||
| 54 | /// Returns the checksum of the database | ||
| 55 | u16 GenerateDatabaseCrc(); | ||
| 56 | |||
| 57 | u32 magic{}; // 'NFDB' | ||
| 58 | std::array<StoreData, MaxDatabaseLength> miis{}; | ||
| 59 | u8 version{}; | ||
| 60 | u8 database_length{}; | ||
| 61 | u16 crc{}; | ||
| 62 | }; | ||
| 63 | static_assert(sizeof(NintendoFigurineDatabase) == 0x1A98, | ||
| 64 | "NintendoFigurineDatabase has incorrect size."); | ||
| 65 | |||
| 66 | }; // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/mii_database_manager.cpp b/src/core/hle/service/mii/mii_database_manager.cpp new file mode 100644 index 000000000..0080b6705 --- /dev/null +++ b/src/core/hle/service/mii/mii_database_manager.cpp | |||
| @@ -0,0 +1,420 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/assert.h" | ||
| 5 | #include "common/fs/file.h" | ||
| 6 | #include "common/fs/fs.h" | ||
| 7 | #include "common/fs/path_util.h" | ||
| 8 | #include "common/logging/log.h" | ||
| 9 | #include "common/string_util.h" | ||
| 10 | |||
| 11 | #include "core/hle/service/mii/mii_database_manager.h" | ||
| 12 | #include "core/hle/service/mii/mii_result.h" | ||
| 13 | #include "core/hle/service/mii/mii_util.h" | ||
| 14 | #include "core/hle/service/mii/types/char_info.h" | ||
| 15 | #include "core/hle/service/mii/types/store_data.h" | ||
| 16 | |||
| 17 | namespace Service::Mii { | ||
| 18 | const char* DbFileName = "MiiDatabase.dat"; | ||
| 19 | |||
| 20 | DatabaseManager::DatabaseManager() {} | ||
| 21 | |||
| 22 | Result DatabaseManager::MountSaveData() { | ||
| 23 | if (!is_save_data_mounted) { | ||
| 24 | system_save_dir = | ||
| 25 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000030"; | ||
| 26 | if (is_test_db) { | ||
| 27 | system_save_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / | ||
| 28 | "system/save/8000000000000031"; | ||
| 29 | } | ||
| 30 | |||
| 31 | // mount point should be "mii:" | ||
| 32 | |||
| 33 | if (!Common::FS::CreateDirs(system_save_dir)) { | ||
| 34 | return ResultUnknown; | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | is_save_data_mounted = true; | ||
| 39 | return ResultSuccess; | ||
| 40 | } | ||
| 41 | |||
| 42 | Result DatabaseManager::Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken) { | ||
| 43 | is_database_broken = false; | ||
| 44 | if (!is_save_data_mounted) { | ||
| 45 | return ResultInvalidArgument; | ||
| 46 | } | ||
| 47 | |||
| 48 | database.CleanDatabase(); | ||
| 49 | update_counter++; | ||
| 50 | metadata.update_counter = update_counter; | ||
| 51 | |||
| 52 | const Common::FS::IOFile db_file{system_save_dir / DbFileName, Common::FS::FileAccessMode::Read, | ||
| 53 | Common::FS::FileType::BinaryFile}; | ||
| 54 | |||
| 55 | if (!db_file.IsOpen()) { | ||
| 56 | return SaveDatabase(); | ||
| 57 | } | ||
| 58 | |||
| 59 | if (Common::FS::GetSize(system_save_dir / DbFileName) != sizeof(NintendoFigurineDatabase)) { | ||
| 60 | is_database_broken = true; | ||
| 61 | } | ||
| 62 | |||
| 63 | if (db_file.Read(database) != 1) { | ||
| 64 | is_database_broken = true; | ||
| 65 | } | ||
| 66 | |||
| 67 | if (is_database_broken) { | ||
| 68 | // Dragons happen here for simplicity just clean the database | ||
| 69 | LOG_ERROR(Service_Mii, "Mii database is corrupted"); | ||
| 70 | database.CleanDatabase(); | ||
| 71 | return ResultUnknown; | ||
| 72 | } | ||
| 73 | |||
| 74 | const auto result = database.CheckIntegrity(); | ||
| 75 | |||
| 76 | if (result.IsError()) { | ||
| 77 | LOG_ERROR(Service_Mii, "Mii database is corrupted 0x{:0x}", result.raw); | ||
| 78 | database.CleanDatabase(); | ||
| 79 | return ResultSuccess; | ||
| 80 | } | ||
| 81 | |||
| 82 | LOG_INFO(Service_Mii, "Successfully loaded mii database. size={}", | ||
| 83 | database.GetDatabaseLength()); | ||
| 84 | return ResultSuccess; | ||
| 85 | } | ||
| 86 | |||
| 87 | bool DatabaseManager::IsFullDatabase() const { | ||
| 88 | return database.GetDatabaseLength() == MaxDatabaseLength; | ||
| 89 | } | ||
| 90 | |||
| 91 | bool DatabaseManager::IsModified() const { | ||
| 92 | return is_moddified; | ||
| 93 | } | ||
| 94 | |||
| 95 | u64 DatabaseManager::GetUpdateCounter() const { | ||
| 96 | return update_counter; | ||
| 97 | } | ||
| 98 | |||
| 99 | u32 DatabaseManager::GetCount(const DatabaseSessionMetadata& metadata) const { | ||
| 100 | const u32 database_size = database.GetDatabaseLength(); | ||
| 101 | if (metadata.magic == MiiMagic) { | ||
| 102 | return database_size; | ||
| 103 | } | ||
| 104 | |||
| 105 | // Special mii can't be used. Skip those. | ||
| 106 | |||
| 107 | u32 mii_count{}; | ||
| 108 | for (std::size_t index = 0; index < database_size; ++index) { | ||
| 109 | const auto& store_data = database.Get(index); | ||
| 110 | if (store_data.IsSpecial()) { | ||
| 111 | continue; | ||
| 112 | } | ||
| 113 | mii_count++; | ||
| 114 | } | ||
| 115 | |||
| 116 | return mii_count; | ||
| 117 | } | ||
| 118 | |||
| 119 | void DatabaseManager::Get(StoreData& out_store_data, std::size_t index, | ||
| 120 | const DatabaseSessionMetadata& metadata) const { | ||
| 121 | if (metadata.magic == MiiMagic) { | ||
| 122 | out_store_data = database.Get(index); | ||
| 123 | return; | ||
| 124 | } | ||
| 125 | |||
| 126 | // The index refeers to the mii index without special mii. | ||
| 127 | // Search on the database until we find it | ||
| 128 | |||
| 129 | u32 virtual_index = 0; | ||
| 130 | const u32 database_size = database.GetDatabaseLength(); | ||
| 131 | for (std::size_t i = 0; i < database_size; ++i) { | ||
| 132 | const auto& store_data = database.Get(i); | ||
| 133 | if (store_data.IsSpecial()) { | ||
| 134 | continue; | ||
| 135 | } | ||
| 136 | if (virtual_index == index) { | ||
| 137 | out_store_data = store_data; | ||
| 138 | return; | ||
| 139 | } | ||
| 140 | virtual_index++; | ||
| 141 | } | ||
| 142 | |||
| 143 | // This function doesn't fail. It returns the first mii instead | ||
| 144 | out_store_data = database.Get(0); | ||
| 145 | } | ||
| 146 | |||
| 147 | Result DatabaseManager::FindIndex(s32& out_index, const Common::UUID& create_id, | ||
| 148 | bool is_special) const { | ||
| 149 | u32 index{}; | ||
| 150 | const bool is_found = database.GetIndexByCreatorId(index, create_id); | ||
| 151 | |||
| 152 | if (!is_found) { | ||
| 153 | return ResultNotFound; | ||
| 154 | } | ||
| 155 | |||
| 156 | if (is_special) { | ||
| 157 | out_index = index; | ||
| 158 | return ResultSuccess; | ||
| 159 | } | ||
| 160 | |||
| 161 | if (database.Get(index).IsSpecial()) { | ||
| 162 | return ResultNotFound; | ||
| 163 | } | ||
| 164 | |||
| 165 | out_index = 0; | ||
| 166 | |||
| 167 | if (index < 1) { | ||
| 168 | return ResultSuccess; | ||
| 169 | } | ||
| 170 | |||
| 171 | for (std::size_t i = 0; i < index; ++i) { | ||
| 172 | if (database.Get(i).IsSpecial()) { | ||
| 173 | continue; | ||
| 174 | } | ||
| 175 | out_index++; | ||
| 176 | } | ||
| 177 | return ResultSuccess; | ||
| 178 | } | ||
| 179 | |||
| 180 | Result DatabaseManager::FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index, | ||
| 181 | const Common::UUID& create_id) const { | ||
| 182 | u32 index{}; | ||
| 183 | const bool is_found = database.GetIndexByCreatorId(index, create_id); | ||
| 184 | |||
| 185 | if (!is_found) { | ||
| 186 | return ResultNotFound; | ||
| 187 | } | ||
| 188 | |||
| 189 | if (metadata.magic == MiiMagic) { | ||
| 190 | out_index = index; | ||
| 191 | return ResultSuccess; | ||
| 192 | } | ||
| 193 | |||
| 194 | if (database.Get(index).IsSpecial()) { | ||
| 195 | return ResultNotFound; | ||
| 196 | } | ||
| 197 | |||
| 198 | out_index = 0; | ||
| 199 | |||
| 200 | if (index < 1) { | ||
| 201 | return ResultSuccess; | ||
| 202 | } | ||
| 203 | |||
| 204 | // The index refeers to the mii index without special mii. | ||
| 205 | // Search on the database until we find it | ||
| 206 | |||
| 207 | for (std::size_t i = 0; i <= index; ++i) { | ||
| 208 | const auto& store_data = database.Get(i); | ||
| 209 | if (store_data.IsSpecial()) { | ||
| 210 | continue; | ||
| 211 | } | ||
| 212 | out_index++; | ||
| 213 | } | ||
| 214 | return ResultSuccess; | ||
| 215 | } | ||
| 216 | |||
| 217 | Result DatabaseManager::FindMoveIndex(u32& out_index, u32 new_index, | ||
| 218 | const Common::UUID& create_id) const { | ||
| 219 | const auto database_size = database.GetDatabaseLength(); | ||
| 220 | |||
| 221 | if (database_size >= 1) { | ||
| 222 | u32 virtual_index{}; | ||
| 223 | for (std::size_t i = 0; i < database_size; ++i) { | ||
| 224 | const StoreData& store_data = database.Get(i); | ||
| 225 | if (store_data.IsSpecial()) { | ||
| 226 | continue; | ||
| 227 | } | ||
| 228 | if (virtual_index == new_index) { | ||
| 229 | const bool is_found = database.GetIndexByCreatorId(out_index, create_id); | ||
| 230 | if (!is_found) { | ||
| 231 | return ResultNotFound; | ||
| 232 | } | ||
| 233 | if (store_data.IsSpecial()) { | ||
| 234 | return ResultInvalidOperation; | ||
| 235 | } | ||
| 236 | return ResultSuccess; | ||
| 237 | } | ||
| 238 | virtual_index++; | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | const bool is_found = database.GetIndexByCreatorId(out_index, create_id); | ||
| 243 | if (!is_found) { | ||
| 244 | return ResultNotFound; | ||
| 245 | } | ||
| 246 | const StoreData& store_data = database.Get(out_index); | ||
| 247 | if (store_data.IsSpecial()) { | ||
| 248 | return ResultInvalidOperation; | ||
| 249 | } | ||
| 250 | return ResultSuccess; | ||
| 251 | } | ||
| 252 | |||
| 253 | Result DatabaseManager::Move(DatabaseSessionMetadata& metadata, u32 new_index, | ||
| 254 | const Common::UUID& create_id) { | ||
| 255 | u32 current_index{}; | ||
| 256 | if (metadata.magic == MiiMagic) { | ||
| 257 | const bool is_found = database.GetIndexByCreatorId(current_index, create_id); | ||
| 258 | if (!is_found) { | ||
| 259 | return ResultNotFound; | ||
| 260 | } | ||
| 261 | } else { | ||
| 262 | const auto result = FindMoveIndex(current_index, new_index, create_id); | ||
| 263 | if (result.IsError()) { | ||
| 264 | return result; | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | const auto result = database.Move(current_index, new_index); | ||
| 269 | if (result.IsFailure()) { | ||
| 270 | return result; | ||
| 271 | } | ||
| 272 | |||
| 273 | is_moddified = true; | ||
| 274 | update_counter++; | ||
| 275 | metadata.update_counter = update_counter; | ||
| 276 | return ResultSuccess; | ||
| 277 | } | ||
| 278 | |||
| 279 | Result DatabaseManager::AddOrReplace(DatabaseSessionMetadata& metadata, | ||
| 280 | const StoreData& store_data) { | ||
| 281 | if (store_data.IsValid() != ValidationResult::NoErrors) { | ||
| 282 | return ResultInvalidStoreData; | ||
| 283 | } | ||
| 284 | if (metadata.magic != MiiMagic && store_data.IsSpecial()) { | ||
| 285 | return ResultInvalidOperation; | ||
| 286 | } | ||
| 287 | |||
| 288 | u32 index{}; | ||
| 289 | const bool is_found = database.GetIndexByCreatorId(index, store_data.GetCreateId()); | ||
| 290 | if (is_found) { | ||
| 291 | const StoreData& old_store_data = database.Get(index); | ||
| 292 | |||
| 293 | if (store_data.IsSpecial() != old_store_data.IsSpecial()) { | ||
| 294 | return ResultInvalidOperation; | ||
| 295 | } | ||
| 296 | |||
| 297 | database.Replace(index, store_data); | ||
| 298 | } else { | ||
| 299 | if (database.IsFull()) { | ||
| 300 | return ResultDatabaseFull; | ||
| 301 | } | ||
| 302 | |||
| 303 | database.Add(store_data); | ||
| 304 | } | ||
| 305 | |||
| 306 | is_moddified = true; | ||
| 307 | update_counter++; | ||
| 308 | metadata.update_counter = update_counter; | ||
| 309 | return ResultSuccess; | ||
| 310 | } | ||
| 311 | |||
| 312 | Result DatabaseManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) { | ||
| 313 | u32 index{}; | ||
| 314 | const bool is_found = database.GetIndexByCreatorId(index, create_id); | ||
| 315 | if (!is_found) { | ||
| 316 | return ResultNotFound; | ||
| 317 | } | ||
| 318 | |||
| 319 | if (metadata.magic != MiiMagic) { | ||
| 320 | const auto& store_data = database.Get(index); | ||
| 321 | if (store_data.IsSpecial()) { | ||
| 322 | return ResultInvalidOperation; | ||
| 323 | } | ||
| 324 | } | ||
| 325 | |||
| 326 | database.Delete(index); | ||
| 327 | |||
| 328 | is_moddified = true; | ||
| 329 | update_counter++; | ||
| 330 | metadata.update_counter = update_counter; | ||
| 331 | return ResultSuccess; | ||
| 332 | } | ||
| 333 | |||
| 334 | Result DatabaseManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) { | ||
| 335 | if (char_info.Verify() != ValidationResult::NoErrors) { | ||
| 336 | return ResultInvalidCharInfo2; | ||
| 337 | } | ||
| 338 | if (char_info.GetType() == 1) { | ||
| 339 | return ResultInvalidCharInfoType; | ||
| 340 | } | ||
| 341 | |||
| 342 | u32 index{}; | ||
| 343 | StoreData store_data{}; | ||
| 344 | |||
| 345 | // Loop until the mii we created is not on the database | ||
| 346 | do { | ||
| 347 | store_data.BuildWithCharInfo(char_info); | ||
| 348 | } while (database.GetIndexByCreatorId(index, store_data.GetCreateId())); | ||
| 349 | |||
| 350 | const Result result = store_data.Restore(); | ||
| 351 | |||
| 352 | if (result.IsSuccess() || result == ResultNotUpdated) { | ||
| 353 | return AddOrReplace(metadata, store_data); | ||
| 354 | } | ||
| 355 | |||
| 356 | return result; | ||
| 357 | } | ||
| 358 | |||
| 359 | Result DatabaseManager::DestroyFile(DatabaseSessionMetadata& metadata) { | ||
| 360 | database.CorruptCrc(); | ||
| 361 | |||
| 362 | is_moddified = true; | ||
| 363 | update_counter++; | ||
| 364 | metadata.update_counter = update_counter; | ||
| 365 | |||
| 366 | const auto result = SaveDatabase(); | ||
| 367 | database.CleanDatabase(); | ||
| 368 | |||
| 369 | return result; | ||
| 370 | } | ||
| 371 | |||
| 372 | Result DatabaseManager::DeleteFile() { | ||
| 373 | const bool result = Common::FS::RemoveFile(system_save_dir / DbFileName); | ||
| 374 | // TODO: Return proper FS error here | ||
| 375 | return result ? ResultSuccess : ResultUnknown; | ||
| 376 | } | ||
| 377 | |||
| 378 | void DatabaseManager::Format(DatabaseSessionMetadata& metadata) { | ||
| 379 | database.CleanDatabase(); | ||
| 380 | is_moddified = true; | ||
| 381 | update_counter++; | ||
| 382 | metadata.update_counter = update_counter; | ||
| 383 | } | ||
| 384 | |||
| 385 | Result DatabaseManager::SaveDatabase() { | ||
| 386 | // TODO: Replace unknown error codes with proper FS error codes when available | ||
| 387 | |||
| 388 | if (!Common::FS::Exists(system_save_dir / DbFileName)) { | ||
| 389 | if (!Common::FS::NewFile(system_save_dir / DbFileName)) { | ||
| 390 | LOG_ERROR(Service_Mii, "Failed to create mii database"); | ||
| 391 | return ResultUnknown; | ||
| 392 | } | ||
| 393 | } | ||
| 394 | |||
| 395 | const auto file_size = Common::FS::GetSize(system_save_dir / DbFileName); | ||
| 396 | if (file_size != 0 && file_size != sizeof(NintendoFigurineDatabase)) { | ||
| 397 | if (!Common::FS::RemoveFile(system_save_dir / DbFileName)) { | ||
| 398 | LOG_ERROR(Service_Mii, "Failed to delete mii database"); | ||
| 399 | return ResultUnknown; | ||
| 400 | } | ||
| 401 | if (!Common::FS::NewFile(system_save_dir / DbFileName)) { | ||
| 402 | LOG_ERROR(Service_Mii, "Failed to create mii database"); | ||
| 403 | return ResultUnknown; | ||
| 404 | } | ||
| 405 | } | ||
| 406 | |||
| 407 | const Common::FS::IOFile db_file{system_save_dir / DbFileName, | ||
| 408 | Common::FS::FileAccessMode::ReadWrite, | ||
| 409 | Common::FS::FileType::BinaryFile}; | ||
| 410 | |||
| 411 | if (db_file.Write(database) != 1) { | ||
| 412 | LOG_ERROR(Service_Mii, "Failed to save mii database"); | ||
| 413 | return ResultUnknown; | ||
| 414 | } | ||
| 415 | |||
| 416 | is_moddified = false; | ||
| 417 | return ResultSuccess; | ||
| 418 | } | ||
| 419 | |||
| 420 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/mii_database_manager.h b/src/core/hle/service/mii/mii_database_manager.h new file mode 100644 index 000000000..52c32be82 --- /dev/null +++ b/src/core/hle/service/mii/mii_database_manager.h | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/fs/fs.h" | ||
| 7 | #include "core/hle/result.h" | ||
| 8 | #include "core/hle/service/mii/mii_database.h" | ||
| 9 | |||
| 10 | namespace Service::Mii { | ||
| 11 | class CharInfo; | ||
| 12 | class StoreData; | ||
| 13 | |||
| 14 | class DatabaseManager { | ||
| 15 | public: | ||
| 16 | DatabaseManager(); | ||
| 17 | Result MountSaveData(); | ||
| 18 | Result Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken); | ||
| 19 | |||
| 20 | bool IsFullDatabase() const; | ||
| 21 | bool IsModified() const; | ||
| 22 | u64 GetUpdateCounter() const; | ||
| 23 | |||
| 24 | void Get(StoreData& out_store_data, std::size_t index, | ||
| 25 | const DatabaseSessionMetadata& metadata) const; | ||
| 26 | u32 GetCount(const DatabaseSessionMetadata& metadata) const; | ||
| 27 | |||
| 28 | Result FindIndex(s32& out_index, const Common::UUID& create_id, bool is_special) const; | ||
| 29 | Result FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index, | ||
| 30 | const Common::UUID& create_id) const; | ||
| 31 | Result FindMoveIndex(u32& out_index, u32 new_index, const Common::UUID& create_id) const; | ||
| 32 | |||
| 33 | Result Move(DatabaseSessionMetadata& metadata, u32 current_index, | ||
| 34 | const Common::UUID& create_id); | ||
| 35 | Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& out_store_data); | ||
| 36 | Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id); | ||
| 37 | Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info); | ||
| 38 | |||
| 39 | Result DestroyFile(DatabaseSessionMetadata& metadata); | ||
| 40 | Result DeleteFile(); | ||
| 41 | void Format(DatabaseSessionMetadata& metadata); | ||
| 42 | |||
| 43 | Result SaveDatabase(); | ||
| 44 | |||
| 45 | private: | ||
| 46 | // This is the global value of | ||
| 47 | // nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); | ||
| 48 | bool is_test_db{}; | ||
| 49 | |||
| 50 | bool is_moddified{}; | ||
| 51 | bool is_save_data_mounted{}; | ||
| 52 | u64 update_counter{}; | ||
| 53 | NintendoFigurineDatabase database{}; | ||
| 54 | |||
| 55 | std::filesystem::path system_save_dir{}; | ||
| 56 | }; | ||
| 57 | |||
| 58 | }; // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 292d63777..dcfd6b2e2 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp | |||
| @@ -1,38 +1,63 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <cstring> | ||
| 5 | #include <random> | ||
| 6 | |||
| 7 | #include "common/assert.h" | ||
| 8 | #include "common/logging/log.h" | 4 | #include "common/logging/log.h" |
| 9 | #include "common/string_util.h" | 5 | #include "core/hle/service/mii/mii_database_manager.h" |
| 10 | |||
| 11 | #include "core/hle/service/acc/profile_manager.h" | ||
| 12 | #include "core/hle/service/mii/mii_manager.h" | 6 | #include "core/hle/service/mii/mii_manager.h" |
| 13 | #include "core/hle/service/mii/mii_result.h" | 7 | #include "core/hle/service/mii/mii_result.h" |
| 14 | #include "core/hle/service/mii/mii_util.h" | 8 | #include "core/hle/service/mii/mii_util.h" |
| 9 | #include "core/hle/service/mii/types/char_info.h" | ||
| 15 | #include "core/hle/service/mii/types/core_data.h" | 10 | #include "core/hle/service/mii/types/core_data.h" |
| 16 | #include "core/hle/service/mii/types/raw_data.h" | 11 | #include "core/hle/service/mii/types/raw_data.h" |
| 12 | #include "core/hle/service/mii/types/store_data.h" | ||
| 13 | #include "core/hle/service/mii/types/ver3_store_data.h" | ||
| 17 | 14 | ||
| 18 | namespace Service::Mii { | 15 | namespace Service::Mii { |
| 19 | constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; | 16 | constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; |
| 20 | 17 | ||
| 21 | MiiManager::MiiManager() {} | 18 | MiiManager::MiiManager() {} |
| 22 | 19 | ||
| 20 | Result MiiManager::Initialize(DatabaseSessionMetadata& metadata) { | ||
| 21 | database_manager.MountSaveData(); | ||
| 22 | database_manager.Initialize(metadata, is_broken_with_clear_flag); | ||
| 23 | return ResultSuccess; | ||
| 24 | } | ||
| 25 | |||
| 26 | void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const { | ||
| 27 | StoreData store_data{}; | ||
| 28 | store_data.BuildDefault(index); | ||
| 29 | out_char_info.SetFromStoreData(store_data); | ||
| 30 | } | ||
| 31 | |||
| 32 | void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const { | ||
| 33 | StoreData store_data{}; | ||
| 34 | store_data.BuildBase(gender); | ||
| 35 | out_char_info.SetFromStoreData(store_data); | ||
| 36 | } | ||
| 37 | |||
| 38 | void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const { | ||
| 39 | StoreData store_data{}; | ||
| 40 | store_data.BuildRandom(age, gender, race); | ||
| 41 | out_char_info.SetFromStoreData(store_data); | ||
| 42 | } | ||
| 43 | |||
| 44 | bool MiiManager::IsFullDatabase() const { | ||
| 45 | return database_manager.IsFullDatabase(); | ||
| 46 | } | ||
| 47 | |||
| 48 | void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const { | ||
| 49 | metadata.interface_version = version; | ||
| 50 | } | ||
| 51 | |||
| 23 | bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { | 52 | bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { |
| 24 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | 53 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { |
| 25 | return false; | 54 | return false; |
| 26 | } | 55 | } |
| 27 | 56 | ||
| 28 | const auto metadata_update_counter = metadata.update_counter; | 57 | const u64 metadata_update_counter = metadata.update_counter; |
| 29 | metadata.update_counter = update_counter; | 58 | const u64 database_update_counter = database_manager.GetUpdateCounter(); |
| 30 | return metadata_update_counter != update_counter; | 59 | metadata.update_counter = database_update_counter; |
| 31 | } | 60 | return metadata_update_counter != database_update_counter; |
| 32 | |||
| 33 | bool MiiManager::IsFullDatabase() const { | ||
| 34 | // TODO(bunnei): We don't implement the Mii database, so it cannot be full | ||
| 35 | return false; | ||
| 36 | } | 61 | } |
| 37 | 62 | ||
| 38 | u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { | 63 | u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { |
| @@ -41,72 +66,343 @@ u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag sou | |||
| 41 | mii_count += DefaultMiiCount; | 66 | mii_count += DefaultMiiCount; |
| 42 | } | 67 | } |
| 43 | if ((source_flag & SourceFlag::Database) != SourceFlag::None) { | 68 | if ((source_flag & SourceFlag::Database) != SourceFlag::None) { |
| 44 | // TODO(bunnei): We don't implement the Mii database, but when we do, update this | 69 | mii_count += database_manager.GetCount(metadata); |
| 45 | } | 70 | } |
| 46 | return mii_count; | 71 | return mii_count; |
| 47 | } | 72 | } |
| 48 | 73 | ||
| 49 | Result MiiManager::UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info, | 74 | Result MiiManager::Move(DatabaseSessionMetadata& metadata, u32 index, |
| 50 | const CharInfo& char_info, SourceFlag source_flag) { | 75 | const Common::UUID& create_id) { |
| 51 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | 76 | const auto result = database_manager.Move(metadata, index, create_id); |
| 77 | |||
| 78 | if (result.IsFailure()) { | ||
| 79 | return result; | ||
| 80 | } | ||
| 81 | |||
| 82 | if (!database_manager.IsModified()) { | ||
| 83 | return ResultNotUpdated; | ||
| 84 | } | ||
| 85 | |||
| 86 | return database_manager.SaveDatabase(); | ||
| 87 | } | ||
| 88 | |||
| 89 | Result MiiManager::AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data) { | ||
| 90 | const auto result = database_manager.AddOrReplace(metadata, store_data); | ||
| 91 | |||
| 92 | if (result.IsFailure()) { | ||
| 93 | return result; | ||
| 94 | } | ||
| 95 | |||
| 96 | if (!database_manager.IsModified()) { | ||
| 97 | return ResultNotUpdated; | ||
| 98 | } | ||
| 99 | |||
| 100 | return database_manager.SaveDatabase(); | ||
| 101 | } | ||
| 102 | |||
| 103 | Result MiiManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) { | ||
| 104 | const auto result = database_manager.Delete(metadata, create_id); | ||
| 105 | |||
| 106 | if (result.IsFailure()) { | ||
| 107 | return result; | ||
| 108 | } | ||
| 109 | |||
| 110 | if (!database_manager.IsModified()) { | ||
| 111 | return ResultNotUpdated; | ||
| 112 | } | ||
| 113 | |||
| 114 | return database_manager.SaveDatabase(); | ||
| 115 | } | ||
| 116 | |||
| 117 | s32 MiiManager::FindIndex(const Common::UUID& create_id, bool is_special) const { | ||
| 118 | s32 index{}; | ||
| 119 | const auto result = database_manager.FindIndex(index, create_id, is_special); | ||
| 120 | if (result.IsError()) { | ||
| 121 | index = -1; | ||
| 122 | } | ||
| 123 | return index; | ||
| 124 | } | ||
| 125 | |||
| 126 | Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, | ||
| 127 | s32& out_index) const { | ||
| 128 | if (char_info.Verify() != ValidationResult::NoErrors) { | ||
| 129 | return ResultInvalidCharInfo; | ||
| 130 | } | ||
| 131 | |||
| 132 | s32 index{}; | ||
| 133 | const bool is_special = metadata.magic == MiiMagic; | ||
| 134 | const auto result = database_manager.FindIndex(index, char_info.GetCreateId(), is_special); | ||
| 135 | |||
| 136 | if (result.IsError()) { | ||
| 137 | index = -1; | ||
| 138 | } | ||
| 139 | |||
| 140 | if (index == -1) { | ||
| 52 | return ResultNotFound; | 141 | return ResultNotFound; |
| 53 | } | 142 | } |
| 54 | 143 | ||
| 55 | // TODO(bunnei): We don't implement the Mii database, so we can't have an entry | 144 | out_index = index; |
| 56 | return ResultNotFound; | 145 | return ResultSuccess; |
| 57 | } | 146 | } |
| 58 | 147 | ||
| 59 | void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const { | 148 | Result MiiManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) { |
| 60 | StoreData store_data{}; | 149 | const auto result = database_manager.Append(metadata, char_info); |
| 61 | store_data.BuildDefault(index); | 150 | |
| 62 | out_char_info.SetFromStoreData(store_data); | 151 | if (result.IsError()) { |
| 152 | return ResultNotFound; | ||
| 153 | } | ||
| 154 | |||
| 155 | if (!database_manager.IsModified()) { | ||
| 156 | return ResultNotUpdated; | ||
| 157 | } | ||
| 158 | |||
| 159 | return database_manager.SaveDatabase(); | ||
| 63 | } | 160 | } |
| 64 | 161 | ||
| 65 | void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const { | 162 | bool MiiManager::IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata) { |
| 163 | const bool is_broken = is_broken_with_clear_flag; | ||
| 164 | if (is_broken_with_clear_flag) { | ||
| 165 | is_broken_with_clear_flag = false; | ||
| 166 | database_manager.Format(metadata); | ||
| 167 | database_manager.SaveDatabase(); | ||
| 168 | } | ||
| 169 | return is_broken; | ||
| 170 | } | ||
| 171 | |||
| 172 | Result MiiManager::DestroyFile(DatabaseSessionMetadata& metadata) { | ||
| 173 | is_broken_with_clear_flag = true; | ||
| 174 | return database_manager.DestroyFile(metadata); | ||
| 175 | } | ||
| 176 | |||
| 177 | Result MiiManager::DeleteFile() { | ||
| 178 | return database_manager.DeleteFile(); | ||
| 179 | } | ||
| 180 | |||
| 181 | Result MiiManager::Format(DatabaseSessionMetadata& metadata) { | ||
| 182 | database_manager.Format(metadata); | ||
| 183 | |||
| 184 | if (!database_manager.IsModified()) { | ||
| 185 | return ResultNotUpdated; | ||
| 186 | } | ||
| 187 | return database_manager.SaveDatabase(); | ||
| 188 | } | ||
| 189 | |||
| 190 | Result MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const { | ||
| 191 | if (!mii_v3.IsValid()) { | ||
| 192 | return ResultInvalidCharInfo; | ||
| 193 | } | ||
| 194 | |||
| 66 | StoreData store_data{}; | 195 | StoreData store_data{}; |
| 67 | store_data.BuildBase(gender); | 196 | mii_v3.BuildToStoreData(store_data); |
| 197 | const auto name = store_data.GetNickname(); | ||
| 198 | if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) { | ||
| 199 | store_data.SetInvalidName(); | ||
| 200 | } | ||
| 201 | |||
| 68 | out_char_info.SetFromStoreData(store_data); | 202 | out_char_info.SetFromStoreData(store_data); |
| 203 | return ResultSuccess; | ||
| 69 | } | 204 | } |
| 70 | 205 | ||
| 71 | void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const { | 206 | Result MiiManager::ConvertCoreDataToCharInfo(CharInfo& out_char_info, |
| 207 | const CoreData& core_data) const { | ||
| 208 | if (core_data.IsValid() != ValidationResult::NoErrors) { | ||
| 209 | return ResultInvalidCharInfo; | ||
| 210 | } | ||
| 211 | |||
| 72 | StoreData store_data{}; | 212 | StoreData store_data{}; |
| 73 | store_data.BuildRandom(age, gender, race); | 213 | store_data.BuildWithCoreData(core_data); |
| 214 | const auto name = store_data.GetNickname(); | ||
| 215 | if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) { | ||
| 216 | store_data.SetInvalidName(); | ||
| 217 | } | ||
| 218 | |||
| 74 | out_char_info.SetFromStoreData(store_data); | 219 | out_char_info.SetFromStoreData(store_data); |
| 220 | return ResultSuccess; | ||
| 221 | } | ||
| 222 | |||
| 223 | Result MiiManager::ConvertCharInfoToCoreData(CoreData& out_core_data, | ||
| 224 | const CharInfo& char_info) const { | ||
| 225 | if (char_info.Verify() != ValidationResult::NoErrors) { | ||
| 226 | return ResultInvalidCharInfo; | ||
| 227 | } | ||
| 228 | |||
| 229 | out_core_data.BuildFromCharInfo(char_info); | ||
| 230 | const auto name = out_core_data.GetNickname(); | ||
| 231 | if (!MiiUtil::IsFontRegionValid(out_core_data.GetFontRegion(), name.data)) { | ||
| 232 | out_core_data.SetNickname(out_core_data.GetInvalidNickname()); | ||
| 233 | } | ||
| 234 | |||
| 235 | return ResultSuccess; | ||
| 75 | } | 236 | } |
| 76 | 237 | ||
| 77 | void MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const { | 238 | Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info, |
| 239 | const CharInfo& char_info, SourceFlag source_flag) const { | ||
| 240 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | ||
| 241 | return ResultNotFound; | ||
| 242 | } | ||
| 243 | |||
| 244 | if (metadata.IsInterfaceVersionSupported(1)) { | ||
| 245 | if (char_info.Verify() != ValidationResult::NoErrors) { | ||
| 246 | return ResultInvalidCharInfo; | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | u32 index{}; | ||
| 251 | Result result = database_manager.FindIndex(metadata, index, char_info.GetCreateId()); | ||
| 252 | |||
| 253 | if (result.IsError()) { | ||
| 254 | return result; | ||
| 255 | } | ||
| 256 | |||
| 78 | StoreData store_data{}; | 257 | StoreData store_data{}; |
| 79 | mii_v3.BuildToStoreData(store_data); | 258 | database_manager.Get(store_data, index, metadata); |
| 259 | |||
| 260 | if (store_data.GetType() != char_info.GetType()) { | ||
| 261 | return ResultNotFound; | ||
| 262 | } | ||
| 263 | |||
| 80 | out_char_info.SetFromStoreData(store_data); | 264 | out_char_info.SetFromStoreData(store_data); |
| 265 | |||
| 266 | if (char_info == out_char_info) { | ||
| 267 | return ResultNotUpdated; | ||
| 268 | } | ||
| 269 | |||
| 270 | return ResultSuccess; | ||
| 271 | } | ||
| 272 | |||
| 273 | Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data, | ||
| 274 | const StoreData& store_data, SourceFlag source_flag) const { | ||
| 275 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | ||
| 276 | return ResultNotFound; | ||
| 277 | } | ||
| 278 | |||
| 279 | if (metadata.IsInterfaceVersionSupported(1)) { | ||
| 280 | if (store_data.IsValid() != ValidationResult::NoErrors) { | ||
| 281 | return ResultInvalidCharInfo; | ||
| 282 | } | ||
| 283 | } | ||
| 284 | |||
| 285 | u32 index{}; | ||
| 286 | Result result = database_manager.FindIndex(metadata, index, store_data.GetCreateId()); | ||
| 287 | |||
| 288 | if (result.IsError()) { | ||
| 289 | return result; | ||
| 290 | } | ||
| 291 | |||
| 292 | database_manager.Get(out_store_data, index, metadata); | ||
| 293 | |||
| 294 | if (out_store_data.GetType() != store_data.GetType()) { | ||
| 295 | return ResultNotFound; | ||
| 296 | } | ||
| 297 | |||
| 298 | if (store_data == out_store_data) { | ||
| 299 | return ResultNotUpdated; | ||
| 300 | } | ||
| 301 | |||
| 302 | return ResultSuccess; | ||
| 81 | } | 303 | } |
| 82 | 304 | ||
| 83 | Result MiiManager::Get(const DatabaseSessionMetadata& metadata, | 305 | Result MiiManager::Get(const DatabaseSessionMetadata& metadata, |
| 84 | std::span<CharInfoElement> out_elements, u32& out_count, | 306 | std::span<CharInfoElement> out_elements, u32& out_count, |
| 85 | SourceFlag source_flag) { | 307 | SourceFlag source_flag) const { |
| 86 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | 308 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { |
| 87 | return BuildDefault(out_elements, out_count, source_flag); | 309 | return BuildDefault(out_elements, out_count, source_flag); |
| 88 | } | 310 | } |
| 89 | 311 | ||
| 90 | // TODO(bunnei): We don't implement the Mii database, so we can't have an entry | 312 | const auto mii_count = database_manager.GetCount(metadata); |
| 313 | |||
| 314 | for (std::size_t index = 0; index < mii_count; ++index) { | ||
| 315 | if (out_elements.size() <= static_cast<std::size_t>(out_count)) { | ||
| 316 | return ResultInvalidArgumentSize; | ||
| 317 | } | ||
| 318 | |||
| 319 | StoreData store_data{}; | ||
| 320 | database_manager.Get(store_data, index, metadata); | ||
| 321 | |||
| 322 | out_elements[out_count].source = Source::Database; | ||
| 323 | out_elements[out_count].char_info.SetFromStoreData(store_data); | ||
| 324 | out_count++; | ||
| 325 | } | ||
| 91 | 326 | ||
| 92 | // Include default Mii at the end of the list | 327 | // Include default Mii at the end of the list |
| 93 | return BuildDefault(out_elements, out_count, source_flag); | 328 | return BuildDefault(out_elements, out_count, source_flag); |
| 94 | } | 329 | } |
| 95 | 330 | ||
| 96 | Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, | 331 | Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, |
| 97 | u32& out_count, SourceFlag source_flag) { | 332 | u32& out_count, SourceFlag source_flag) const { |
| 98 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | 333 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { |
| 99 | return BuildDefault(out_char_info, out_count, source_flag); | 334 | return BuildDefault(out_char_info, out_count, source_flag); |
| 100 | } | 335 | } |
| 101 | 336 | ||
| 102 | // TODO(bunnei): We don't implement the Mii database, so we can't have an entry | 337 | const auto mii_count = database_manager.GetCount(metadata); |
| 338 | |||
| 339 | for (std::size_t index = 0; index < mii_count; ++index) { | ||
| 340 | if (out_char_info.size() <= static_cast<std::size_t>(out_count)) { | ||
| 341 | return ResultInvalidArgumentSize; | ||
| 342 | } | ||
| 343 | |||
| 344 | StoreData store_data{}; | ||
| 345 | database_manager.Get(store_data, index, metadata); | ||
| 346 | |||
| 347 | out_char_info[out_count].SetFromStoreData(store_data); | ||
| 348 | out_count++; | ||
| 349 | } | ||
| 103 | 350 | ||
| 104 | // Include default Mii at the end of the list | 351 | // Include default Mii at the end of the list |
| 105 | return BuildDefault(out_char_info, out_count, source_flag); | 352 | return BuildDefault(out_char_info, out_count, source_flag); |
| 106 | } | 353 | } |
| 107 | 354 | ||
| 355 | Result MiiManager::Get(const DatabaseSessionMetadata& metadata, | ||
| 356 | std::span<StoreDataElement> out_elements, u32& out_count, | ||
| 357 | SourceFlag source_flag) const { | ||
| 358 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | ||
| 359 | return BuildDefault(out_elements, out_count, source_flag); | ||
| 360 | } | ||
| 361 | |||
| 362 | const auto mii_count = database_manager.GetCount(metadata); | ||
| 363 | |||
| 364 | for (std::size_t index = 0; index < mii_count; ++index) { | ||
| 365 | if (out_elements.size() <= static_cast<std::size_t>(out_count)) { | ||
| 366 | return ResultInvalidArgumentSize; | ||
| 367 | } | ||
| 368 | |||
| 369 | StoreData store_data{}; | ||
| 370 | database_manager.Get(store_data, index, metadata); | ||
| 371 | |||
| 372 | out_elements[out_count].store_data = store_data; | ||
| 373 | out_elements[out_count].source = Source::Database; | ||
| 374 | out_count++; | ||
| 375 | } | ||
| 376 | |||
| 377 | // Include default Mii at the end of the list | ||
| 378 | return BuildDefault(out_elements, out_count, source_flag); | ||
| 379 | } | ||
| 380 | |||
| 381 | Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data, | ||
| 382 | u32& out_count, SourceFlag source_flag) const { | ||
| 383 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | ||
| 384 | return BuildDefault(out_store_data, out_count, source_flag); | ||
| 385 | } | ||
| 386 | |||
| 387 | const auto mii_count = database_manager.GetCount(metadata); | ||
| 388 | |||
| 389 | for (std::size_t index = 0; index < mii_count; ++index) { | ||
| 390 | if (out_store_data.size() <= static_cast<std::size_t>(out_count)) { | ||
| 391 | return ResultInvalidArgumentSize; | ||
| 392 | } | ||
| 393 | |||
| 394 | StoreData store_data{}; | ||
| 395 | database_manager.Get(store_data, index, metadata); | ||
| 396 | |||
| 397 | out_store_data[out_count] = store_data; | ||
| 398 | out_count++; | ||
| 399 | } | ||
| 400 | |||
| 401 | // Include default Mii at the end of the list | ||
| 402 | return BuildDefault(out_store_data, out_count, source_flag); | ||
| 403 | } | ||
| 108 | Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, | 404 | Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, |
| 109 | SourceFlag source_flag) { | 405 | SourceFlag source_flag) const { |
| 110 | if ((source_flag & SourceFlag::Default) == SourceFlag::None) { | 406 | if ((source_flag & SourceFlag::Default) == SourceFlag::None) { |
| 111 | return ResultSuccess; | 407 | return ResultSuccess; |
| 112 | } | 408 | } |
| @@ -129,7 +425,7 @@ Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& ou | |||
| 129 | } | 425 | } |
| 130 | 426 | ||
| 131 | Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, | 427 | Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, |
| 132 | SourceFlag source_flag) { | 428 | SourceFlag source_flag) const { |
| 133 | if ((source_flag & SourceFlag::Default) == SourceFlag::None) { | 429 | if ((source_flag & SourceFlag::Default) == SourceFlag::None) { |
| 134 | return ResultSuccess; | 430 | return ResultSuccess; |
| 135 | } | 431 | } |
| @@ -150,23 +446,41 @@ Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_coun | |||
| 150 | return ResultSuccess; | 446 | return ResultSuccess; |
| 151 | } | 447 | } |
| 152 | 448 | ||
| 153 | Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, | 449 | Result MiiManager::BuildDefault(std::span<StoreDataElement> out_elements, u32& out_count, |
| 154 | s32& out_index) { | 450 | SourceFlag source_flag) const { |
| 155 | 451 | if ((source_flag & SourceFlag::Default) == SourceFlag::None) { | |
| 156 | if (char_info.Verify() != ValidationResult::NoErrors) { | 452 | return ResultSuccess; |
| 157 | return ResultInvalidCharInfo; | ||
| 158 | } | 453 | } |
| 159 | 454 | ||
| 160 | constexpr u32 INVALID_INDEX{0xFFFFFFFF}; | 455 | for (std::size_t index = 0; index < DefaultMiiCount; ++index) { |
| 456 | if (out_elements.size() <= static_cast<std::size_t>(out_count)) { | ||
| 457 | return ResultInvalidArgumentSize; | ||
| 458 | } | ||
| 161 | 459 | ||
| 162 | out_index = INVALID_INDEX; | 460 | out_elements[out_count].store_data.BuildDefault(static_cast<u32>(index)); |
| 461 | out_elements[out_count].source = Source::Default; | ||
| 462 | out_count++; | ||
| 463 | } | ||
| 163 | 464 | ||
| 164 | // TODO(bunnei): We don't implement the Mii database, so we can't have an index | 465 | return ResultSuccess; |
| 165 | return ResultNotFound; | ||
| 166 | } | 466 | } |
| 167 | 467 | ||
| 168 | void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) { | 468 | Result MiiManager::BuildDefault(std::span<StoreData> out_char_info, u32& out_count, |
| 169 | metadata.interface_version = version; | 469 | SourceFlag source_flag) const { |
| 470 | if ((source_flag & SourceFlag::Default) == SourceFlag::None) { | ||
| 471 | return ResultSuccess; | ||
| 472 | } | ||
| 473 | |||
| 474 | for (std::size_t index = 0; index < DefaultMiiCount; ++index) { | ||
| 475 | if (out_char_info.size() <= static_cast<std::size_t>(out_count)) { | ||
| 476 | return ResultInvalidArgumentSize; | ||
| 477 | } | ||
| 478 | |||
| 479 | out_char_info[out_count].BuildDefault(static_cast<u32>(index)); | ||
| 480 | out_count++; | ||
| 481 | } | ||
| 482 | |||
| 483 | return ResultSuccess; | ||
| 170 | } | 484 | } |
| 171 | 485 | ||
| 172 | } // namespace Service::Mii | 486 | } // namespace Service::Mii |
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h index a2e7a6d73..48d8e8bb7 100644 --- a/src/core/hle/service/mii/mii_manager.h +++ b/src/core/hle/service/mii/mii_manager.h | |||
| @@ -3,47 +3,85 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <vector> | 6 | #include <span> |
| 7 | 7 | ||
| 8 | #include "core/hle/result.h" | 8 | #include "core/hle/result.h" |
| 9 | #include "core/hle/service/mii/mii_database_manager.h" | ||
| 9 | #include "core/hle/service/mii/mii_types.h" | 10 | #include "core/hle/service/mii/mii_types.h" |
| 10 | #include "core/hle/service/mii/types/char_info.h" | ||
| 11 | #include "core/hle/service/mii/types/store_data.h" | ||
| 12 | #include "core/hle/service/mii/types/ver3_store_data.h" | ||
| 13 | 11 | ||
| 14 | namespace Service::Mii { | 12 | namespace Service::Mii { |
| 13 | class CharInfo; | ||
| 14 | class CoreData; | ||
| 15 | class StoreData; | ||
| 16 | class Ver3StoreData; | ||
| 15 | 17 | ||
| 16 | // The Mii manager is responsible for loading and storing the Miis to the database in NAND along | 18 | struct CharInfoElement; |
| 17 | // with providing an easy interface for HLE emulation of the mii service. | 19 | struct StoreDataElement; |
| 20 | |||
| 21 | // The Mii manager is responsible for handling mii operations along with providing an easy interface | ||
| 22 | // for HLE emulation of the mii service. | ||
| 18 | class MiiManager { | 23 | class MiiManager { |
| 19 | public: | 24 | public: |
| 20 | MiiManager(); | 25 | MiiManager(); |
| 26 | Result Initialize(DatabaseSessionMetadata& metadata); | ||
| 21 | 27 | ||
| 22 | bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; | 28 | // Auto generated mii |
| 29 | void BuildDefault(CharInfo& out_char_info, u32 index) const; | ||
| 30 | void BuildBase(CharInfo& out_char_info, Gender gender) const; | ||
| 31 | void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const; | ||
| 23 | 32 | ||
| 33 | // Database operations | ||
| 24 | bool IsFullDatabase() const; | 34 | bool IsFullDatabase() const; |
| 35 | void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const; | ||
| 36 | bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; | ||
| 25 | u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; | 37 | u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; |
| 26 | Result UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info, | 38 | Result Move(DatabaseSessionMetadata& metadata, u32 index, const Common::UUID& create_id); |
| 27 | const CharInfo& char_info, SourceFlag source_flag); | 39 | Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data); |
| 40 | Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id); | ||
| 41 | s32 FindIndex(const Common::UUID& create_id, bool is_special) const; | ||
| 42 | Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, | ||
| 43 | s32& out_index) const; | ||
| 44 | Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info); | ||
| 45 | |||
| 46 | // Test database operations | ||
| 47 | bool IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata); | ||
| 48 | Result DestroyFile(DatabaseSessionMetadata& metadata); | ||
| 49 | Result DeleteFile(); | ||
| 50 | Result Format(DatabaseSessionMetadata& metadata); | ||
| 51 | |||
| 52 | // Mii conversions | ||
| 53 | Result ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const; | ||
| 54 | Result ConvertCoreDataToCharInfo(CharInfo& out_char_info, const CoreData& core_data) const; | ||
| 55 | Result ConvertCharInfoToCoreData(CoreData& out_core_data, const CharInfo& char_info) const; | ||
| 56 | Result UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info, | ||
| 57 | const CharInfo& char_info, SourceFlag source_flag) const; | ||
| 58 | Result UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data, | ||
| 59 | const StoreData& store_data, SourceFlag source_flag) const; | ||
| 60 | |||
| 61 | // Overloaded getters | ||
| 28 | Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements, | 62 | Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements, |
| 29 | u32& out_count, SourceFlag source_flag); | 63 | u32& out_count, SourceFlag source_flag) const; |
| 30 | Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, | 64 | Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, |
| 31 | u32& out_count, SourceFlag source_flag); | 65 | u32& out_count, SourceFlag source_flag) const; |
| 32 | void BuildDefault(CharInfo& out_char_info, u32 index) const; | 66 | Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreDataElement> out_elements, |
| 33 | void BuildBase(CharInfo& out_char_info, Gender gender) const; | 67 | u32& out_count, SourceFlag source_flag) const; |
| 34 | void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const; | 68 | Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data, |
| 35 | void ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const; | 69 | u32& out_count, SourceFlag source_flag) const; |
| 36 | std::vector<CharInfoElement> GetDefault(SourceFlag source_flag); | ||
| 37 | Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, | ||
| 38 | s32& out_index); | ||
| 39 | void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version); | ||
| 40 | 70 | ||
| 41 | private: | 71 | private: |
| 42 | Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, | 72 | Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, |
| 43 | SourceFlag source_flag); | 73 | SourceFlag source_flag) const; |
| 44 | Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, SourceFlag source_flag); | 74 | Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, |
| 75 | SourceFlag source_flag) const; | ||
| 76 | Result BuildDefault(std::span<StoreDataElement> out_char_info, u32& out_count, | ||
| 77 | SourceFlag source_flag) const; | ||
| 78 | Result BuildDefault(std::span<StoreData> out_char_info, u32& out_count, | ||
| 79 | SourceFlag source_flag) const; | ||
| 80 | |||
| 81 | DatabaseManager database_manager{}; | ||
| 45 | 82 | ||
| 46 | u64 update_counter{}; | 83 | // This should be a global value |
| 84 | bool is_broken_with_clear_flag{}; | ||
| 47 | }; | 85 | }; |
| 48 | 86 | ||
| 49 | }; // namespace Service::Mii | 87 | }; // namespace Service::Mii |
diff --git a/src/core/hle/service/mii/mii_result.h b/src/core/hle/service/mii/mii_result.h index 021cb76da..e2c36e556 100644 --- a/src/core/hle/service/mii/mii_result.h +++ b/src/core/hle/service/mii/mii_result.h | |||
| @@ -1,4 +1,4 @@ | |||
| 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-2.0-or-later |
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| @@ -13,8 +13,15 @@ constexpr Result ResultNotUpdated{ErrorModule::Mii, 3}; | |||
| 13 | constexpr Result ResultNotFound{ErrorModule::Mii, 4}; | 13 | constexpr Result ResultNotFound{ErrorModule::Mii, 4}; |
| 14 | constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5}; | 14 | constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5}; |
| 15 | constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100}; | 15 | constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100}; |
| 16 | constexpr Result ResultInvalidDatabaseChecksum{ErrorModule::Mii, 101}; | ||
| 17 | constexpr Result ResultInvalidDatabaseSignature{ErrorModule::Mii, 103}; | ||
| 18 | constexpr Result ResultInvalidDatabaseVersion{ErrorModule::Mii, 104}; | ||
| 19 | constexpr Result ResultInvalidDatabaseLength{ErrorModule::Mii, 105}; | ||
| 20 | constexpr Result ResultInvalidCharInfo2{ErrorModule::Mii, 107}; | ||
| 16 | constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109}; | 21 | constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109}; |
| 17 | constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202}; | 22 | constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202}; |
| 18 | constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203}; | 23 | constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203}; |
| 24 | constexpr Result ResultTestModeOnly{ErrorModule::Mii, 204}; | ||
| 25 | constexpr Result ResultInvalidCharInfoType{ErrorModule::Mii, 205}; | ||
| 19 | 26 | ||
| 20 | }; // namespace Service::Mii | 27 | }; // namespace Service::Mii |
diff --git a/src/core/hle/service/mii/mii_types.h b/src/core/hle/service/mii/mii_types.h index 95476f745..08c6029df 100644 --- a/src/core/hle/service/mii/mii_types.h +++ b/src/core/hle/service/mii/mii_types.h | |||
| @@ -1,4 +1,4 @@ | |||
| 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-2.0-or-later |
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| @@ -13,6 +13,7 @@ | |||
| 13 | 13 | ||
| 14 | namespace Service::Mii { | 14 | namespace Service::Mii { |
| 15 | 15 | ||
| 16 | constexpr std::size_t MaxNameSize = 10; | ||
| 16 | constexpr u8 MaxHeight = 127; | 17 | constexpr u8 MaxHeight = 127; |
| 17 | constexpr u8 MaxBuild = 127; | 18 | constexpr u8 MaxBuild = 127; |
| 18 | constexpr u8 MaxType = 1; | 19 | constexpr u8 MaxType = 1; |
| @@ -26,14 +27,14 @@ constexpr u8 MaxEyebrowScale = 8; | |||
| 26 | constexpr u8 MaxEyebrowAspect = 6; | 27 | constexpr u8 MaxEyebrowAspect = 6; |
| 27 | constexpr u8 MaxEyebrowRotate = 11; | 28 | constexpr u8 MaxEyebrowRotate = 11; |
| 28 | constexpr u8 MaxEyebrowX = 12; | 29 | constexpr u8 MaxEyebrowX = 12; |
| 29 | constexpr u8 MaxEyebrowY = 18; | 30 | constexpr u8 MaxEyebrowY = 15; |
| 30 | constexpr u8 MaxNoseScale = 8; | 31 | constexpr u8 MaxNoseScale = 8; |
| 31 | constexpr u8 MaxNoseY = 18; | 32 | constexpr u8 MaxNoseY = 18; |
| 32 | constexpr u8 MaxMouthScale = 8; | 33 | constexpr u8 MaxMouthScale = 8; |
| 33 | constexpr u8 MaxMoutAspect = 6; | 34 | constexpr u8 MaxMoutAspect = 6; |
| 34 | constexpr u8 MaxMouthY = 18; | 35 | constexpr u8 MaxMouthY = 18; |
| 35 | constexpr u8 MaxMustacheScale = 8; | 36 | constexpr u8 MaxMustacheScale = 8; |
| 36 | constexpr u8 MasMustacheY = 16; | 37 | constexpr u8 MaxMustacheY = 16; |
| 37 | constexpr u8 MaxGlassScale = 7; | 38 | constexpr u8 MaxGlassScale = 7; |
| 38 | constexpr u8 MaxGlassY = 20; | 39 | constexpr u8 MaxGlassY = 20; |
| 39 | constexpr u8 MaxMoleScale = 8; | 40 | constexpr u8 MaxMoleScale = 8; |
| @@ -599,24 +600,21 @@ enum class ValidationResult : u32 { | |||
| 599 | InvalidRegionMove = 0x31, | 600 | InvalidRegionMove = 0x31, |
| 600 | InvalidCreateId = 0x32, | 601 | InvalidCreateId = 0x32, |
| 601 | InvalidName = 0x33, | 602 | InvalidName = 0x33, |
| 603 | InvalidChecksum = 0x34, | ||
| 602 | InvalidType = 0x35, | 604 | InvalidType = 0x35, |
| 603 | }; | 605 | }; |
| 604 | 606 | ||
| 605 | struct Nickname { | 607 | struct Nickname { |
| 606 | static constexpr std::size_t MaxNameSize = 10; | 608 | std::array<char16_t, MaxNameSize> data{}; |
| 607 | std::array<char16_t, MaxNameSize> data; | ||
| 608 | 609 | ||
| 609 | // Checks for null, non-zero terminated or dirty strings | 610 | // Checks for null or dirty strings |
| 610 | bool IsValid() const { | 611 | bool IsValid() const { |
| 611 | if (data[0] == 0) { | 612 | if (data[0] == 0) { |
| 612 | return false; | 613 | return false; |
| 613 | } | 614 | } |
| 614 | 615 | ||
| 615 | if (data[MaxNameSize] != 0) { | ||
| 616 | return false; | ||
| 617 | } | ||
| 618 | std::size_t index = 1; | 616 | std::size_t index = 1; |
| 619 | while (data[index] != 0) { | 617 | while (index < MaxNameSize && data[index] != 0) { |
| 620 | index++; | 618 | index++; |
| 621 | } | 619 | } |
| 622 | while (index < MaxNameSize && data[index] == 0) { | 620 | while (index < MaxNameSize && data[index] == 0) { |
diff --git a/src/core/hle/service/mii/mii_util.h b/src/core/hle/service/mii/mii_util.h index ddb544c23..3534fa31d 100644 --- a/src/core/hle/service/mii/mii_util.h +++ b/src/core/hle/service/mii/mii_util.h | |||
| @@ -28,6 +28,32 @@ public: | |||
| 28 | return Common::swap16(static_cast<u16>(crc)); | 28 | return Common::swap16(static_cast<u16>(crc)); |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | static u16 CalculateDeviceCrc16(const Common::UUID& uuid, std::size_t data_size) { | ||
| 32 | constexpr u16 magic{0x1021}; | ||
| 33 | s32 crc{}; | ||
| 34 | |||
| 35 | for (std::size_t i = 0; i < uuid.uuid.size(); i++) { | ||
| 36 | for (std::size_t j = 0; j < 8; j++) { | ||
| 37 | crc <<= 1; | ||
| 38 | if ((crc & 0x10000) != 0) { | ||
| 39 | crc = crc ^ magic; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | crc ^= uuid.uuid[i]; | ||
| 43 | } | ||
| 44 | |||
| 45 | // As much as this looks wrong this is what N's does | ||
| 46 | |||
| 47 | for (std::size_t i = 0; i < data_size * 8; i++) { | ||
| 48 | crc <<= 1; | ||
| 49 | if ((crc & 0x10000) != 0) { | ||
| 50 | crc = crc ^ magic; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | return Common::swap16(static_cast<u16>(crc)); | ||
| 55 | } | ||
| 56 | |||
| 31 | static Common::UUID MakeCreateId() { | 57 | static Common::UUID MakeCreateId() { |
| 32 | return Common::UUID::MakeRandomRFC4122V4(); | 58 | return Common::UUID::MakeRandomRFC4122V4(); |
| 33 | } | 59 | } |
diff --git a/src/core/hle/service/mii/types/char_info.cpp b/src/core/hle/service/mii/types/char_info.cpp index bb948c628..e90124af4 100644 --- a/src/core/hle/service/mii/types/char_info.cpp +++ b/src/core/hle/service/mii/types/char_info.cpp | |||
| @@ -37,7 +37,7 @@ void CharInfo::SetFromStoreData(const StoreData& store_data) { | |||
| 37 | eyebrow_aspect = store_data.GetEyebrowAspect(); | 37 | eyebrow_aspect = store_data.GetEyebrowAspect(); |
| 38 | eyebrow_rotate = store_data.GetEyebrowRotate(); | 38 | eyebrow_rotate = store_data.GetEyebrowRotate(); |
| 39 | eyebrow_x = store_data.GetEyebrowX(); | 39 | eyebrow_x = store_data.GetEyebrowX(); |
| 40 | eyebrow_y = store_data.GetEyebrowY(); | 40 | eyebrow_y = store_data.GetEyebrowY() + 3; |
| 41 | nose_type = store_data.GetNoseType(); | 41 | nose_type = store_data.GetNoseType(); |
| 42 | nose_scale = store_data.GetNoseScale(); | 42 | nose_scale = store_data.GetNoseScale(); |
| 43 | nose_y = store_data.GetNoseY(); | 43 | nose_y = store_data.GetNoseY(); |
| @@ -150,7 +150,7 @@ ValidationResult CharInfo::Verify() const { | |||
| 150 | if (eyebrow_x > MaxEyebrowX) { | 150 | if (eyebrow_x > MaxEyebrowX) { |
| 151 | return ValidationResult::InvalidEyebrowX; | 151 | return ValidationResult::InvalidEyebrowX; |
| 152 | } | 152 | } |
| 153 | if (eyebrow_y > MaxEyebrowY) { | 153 | if (eyebrow_y - 3 > MaxEyebrowY) { |
| 154 | return ValidationResult::InvalidEyebrowY; | 154 | return ValidationResult::InvalidEyebrowY; |
| 155 | } | 155 | } |
| 156 | if (nose_type > NoseType::Max) { | 156 | if (nose_type > NoseType::Max) { |
| @@ -189,7 +189,7 @@ ValidationResult CharInfo::Verify() const { | |||
| 189 | if (mustache_scale > MaxMustacheScale) { | 189 | if (mustache_scale > MaxMustacheScale) { |
| 190 | return ValidationResult::InvalidMustacheScale; | 190 | return ValidationResult::InvalidMustacheScale; |
| 191 | } | 191 | } |
| 192 | if (mustache_y > MasMustacheY) { | 192 | if (mustache_y > MaxMustacheY) { |
| 193 | return ValidationResult::InvalidMustacheY; | 193 | return ValidationResult::InvalidMustacheY; |
| 194 | } | 194 | } |
| 195 | if (glass_type > GlassType::Max) { | 195 | if (glass_type > GlassType::Max) { |
diff --git a/src/core/hle/service/mii/types/char_info.h b/src/core/hle/service/mii/types/char_info.h index d069b221f..d0c457fd5 100644 --- a/src/core/hle/service/mii/types/char_info.h +++ b/src/core/hle/service/mii/types/char_info.h | |||
| @@ -70,59 +70,59 @@ public: | |||
| 70 | bool operator==(const CharInfo& info); | 70 | bool operator==(const CharInfo& info); |
| 71 | 71 | ||
| 72 | private: | 72 | private: |
| 73 | Common::UUID create_id; | 73 | Common::UUID create_id{}; |
| 74 | Nickname name; | 74 | Nickname name{}; |
| 75 | u16 null_terminator; | 75 | u16 null_terminator{}; |
| 76 | FontRegion font_region; | 76 | FontRegion font_region{}; |
| 77 | FavoriteColor favorite_color; | 77 | FavoriteColor favorite_color{}; |
| 78 | Gender gender; | 78 | Gender gender{}; |
| 79 | u8 height; | 79 | u8 height{}; |
| 80 | u8 build; | 80 | u8 build{}; |
| 81 | u8 type; | 81 | u8 type{}; |
| 82 | u8 region_move; | 82 | u8 region_move{}; |
| 83 | FacelineType faceline_type; | 83 | FacelineType faceline_type{}; |
| 84 | FacelineColor faceline_color; | 84 | FacelineColor faceline_color{}; |
| 85 | FacelineWrinkle faceline_wrinkle; | 85 | FacelineWrinkle faceline_wrinkle{}; |
| 86 | FacelineMake faceline_make; | 86 | FacelineMake faceline_make{}; |
| 87 | HairType hair_type; | 87 | HairType hair_type{}; |
| 88 | CommonColor hair_color; | 88 | CommonColor hair_color{}; |
| 89 | HairFlip hair_flip; | 89 | HairFlip hair_flip{}; |
| 90 | EyeType eye_type; | 90 | EyeType eye_type{}; |
| 91 | CommonColor eye_color; | 91 | CommonColor eye_color{}; |
| 92 | u8 eye_scale; | 92 | u8 eye_scale{}; |
| 93 | u8 eye_aspect; | 93 | u8 eye_aspect{}; |
| 94 | u8 eye_rotate; | 94 | u8 eye_rotate{}; |
| 95 | u8 eye_x; | 95 | u8 eye_x{}; |
| 96 | u8 eye_y; | 96 | u8 eye_y{}; |
| 97 | EyebrowType eyebrow_type; | 97 | EyebrowType eyebrow_type{}; |
| 98 | CommonColor eyebrow_color; | 98 | CommonColor eyebrow_color{}; |
| 99 | u8 eyebrow_scale; | 99 | u8 eyebrow_scale{}; |
| 100 | u8 eyebrow_aspect; | 100 | u8 eyebrow_aspect{}; |
| 101 | u8 eyebrow_rotate; | 101 | u8 eyebrow_rotate{}; |
| 102 | u8 eyebrow_x; | 102 | u8 eyebrow_x{}; |
| 103 | u8 eyebrow_y; | 103 | u8 eyebrow_y{}; |
| 104 | NoseType nose_type; | 104 | NoseType nose_type{}; |
| 105 | u8 nose_scale; | 105 | u8 nose_scale{}; |
| 106 | u8 nose_y; | 106 | u8 nose_y{}; |
| 107 | MouthType mouth_type; | 107 | MouthType mouth_type{}; |
| 108 | CommonColor mouth_color; | 108 | CommonColor mouth_color{}; |
| 109 | u8 mouth_scale; | 109 | u8 mouth_scale{}; |
| 110 | u8 mouth_aspect; | 110 | u8 mouth_aspect{}; |
| 111 | u8 mouth_y; | 111 | u8 mouth_y{}; |
| 112 | CommonColor beard_color; | 112 | CommonColor beard_color{}; |
| 113 | BeardType beard_type; | 113 | BeardType beard_type{}; |
| 114 | MustacheType mustache_type; | 114 | MustacheType mustache_type{}; |
| 115 | u8 mustache_scale; | 115 | u8 mustache_scale{}; |
| 116 | u8 mustache_y; | 116 | u8 mustache_y{}; |
| 117 | GlassType glass_type; | 117 | GlassType glass_type{}; |
| 118 | CommonColor glass_color; | 118 | CommonColor glass_color{}; |
| 119 | u8 glass_scale; | 119 | u8 glass_scale{}; |
| 120 | u8 glass_y; | 120 | u8 glass_y{}; |
| 121 | MoleType mole_type; | 121 | MoleType mole_type{}; |
| 122 | u8 mole_scale; | 122 | u8 mole_scale{}; |
| 123 | u8 mole_x; | 123 | u8 mole_x{}; |
| 124 | u8 mole_y; | 124 | u8 mole_y{}; |
| 125 | u8 padding; | 125 | u8 padding{}; |
| 126 | }; | 126 | }; |
| 127 | static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); | 127 | static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); |
| 128 | static_assert(std::has_unique_object_representations_v<CharInfo>, | 128 | static_assert(std::has_unique_object_representations_v<CharInfo>, |
diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp index 659288b51..970c748ca 100644 --- a/src/core/hle/service/mii/types/core_data.cpp +++ b/src/core/hle/service/mii/types/core_data.cpp | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "common/assert.h" | 4 | #include "common/assert.h" |
| 5 | #include "core/hle/service/mii/mii_util.h" | 5 | #include "core/hle/service/mii/mii_util.h" |
| 6 | #include "core/hle/service/mii/types/char_info.h" | ||
| 6 | #include "core/hle/service/mii/types/core_data.h" | 7 | #include "core/hle/service/mii/types/core_data.h" |
| 7 | #include "core/hle/service/mii/types/raw_data.h" | 8 | #include "core/hle/service/mii/types/raw_data.h" |
| 8 | 9 | ||
| @@ -112,7 +113,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) { | |||
| 112 | .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); | 113 | .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); |
| 113 | 114 | ||
| 114 | const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; | 115 | const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; |
| 115 | const auto eyebrow_y{race == Race::Asian ? 9 : 10}; | 116 | const auto eyebrow_y{race == Race::Asian ? 6 : 7}; |
| 116 | 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}; |
| 117 | const auto eyebrow_rotate{ | 118 | const auto eyebrow_rotate{ |
| 118 | 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())]}; |
| @@ -170,7 +171,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) { | |||
| 170 | u8 glasses_type{}; | 171 | u8 glasses_type{}; |
| 171 | while (glasses_type_start < glasses_type_info.values[glasses_type]) { | 172 | while (glasses_type_start < glasses_type_info.values[glasses_type]) { |
| 172 | if (++glasses_type >= glasses_type_info.values_count) { | 173 | if (++glasses_type >= glasses_type_info.values_count) { |
| 173 | ASSERT(false); | 174 | glasses_type = 0; |
| 174 | break; | 175 | break; |
| 175 | } | 176 | } |
| 176 | } | 177 | } |
| @@ -178,6 +179,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) { | |||
| 178 | SetGlassType(static_cast<GlassType>(glasses_type)); | 179 | SetGlassType(static_cast<GlassType>(glasses_type)); |
| 179 | SetGlassColor(RawData::GetGlassColorFromVer3(0)); | 180 | SetGlassColor(RawData::GetGlassColorFromVer3(0)); |
| 180 | SetGlassScale(4); | 181 | SetGlassScale(4); |
| 182 | SetGlassY(static_cast<u8>(axis_y + 10)); | ||
| 181 | 183 | ||
| 182 | SetMoleType(MoleType::None); | 184 | SetMoleType(MoleType::None); |
| 183 | SetMoleScale(4); | 185 | SetMoleScale(4); |
| @@ -185,9 +187,211 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) { | |||
| 185 | SetMoleY(20); | 187 | SetMoleY(20); |
| 186 | } | 188 | } |
| 187 | 189 | ||
| 188 | u32 CoreData::IsValid() const { | 190 | void CoreData::BuildFromCharInfo(const CharInfo& char_info) { |
| 189 | // TODO: Complete this | 191 | name = char_info.GetNickname(); |
| 190 | return 0; | 192 | SetFontRegion(char_info.GetFontRegion()); |
| 193 | SetFavoriteColor(char_info.GetFavoriteColor()); | ||
| 194 | SetGender(char_info.GetGender()); | ||
| 195 | SetHeight(char_info.GetHeight()); | ||
| 196 | SetBuild(char_info.GetBuild()); | ||
| 197 | SetType(char_info.GetType()); | ||
| 198 | SetRegionMove(char_info.GetRegionMove()); | ||
| 199 | SetFacelineType(char_info.GetFacelineType()); | ||
| 200 | SetFacelineColor(char_info.GetFacelineColor()); | ||
| 201 | SetFacelineWrinkle(char_info.GetFacelineWrinkle()); | ||
| 202 | SetFacelineMake(char_info.GetFacelineMake()); | ||
| 203 | SetHairType(char_info.GetHairType()); | ||
| 204 | SetHairColor(char_info.GetHairColor()); | ||
| 205 | SetHairFlip(char_info.GetHairFlip()); | ||
| 206 | SetEyeType(char_info.GetEyeType()); | ||
| 207 | SetEyeColor(char_info.GetEyeColor()); | ||
| 208 | SetEyeScale(char_info.GetEyeScale()); | ||
| 209 | SetEyeAspect(char_info.GetEyeAspect()); | ||
| 210 | SetEyeRotate(char_info.GetEyeRotate()); | ||
| 211 | SetEyeX(char_info.GetEyeX()); | ||
| 212 | SetEyeY(char_info.GetEyeY()); | ||
| 213 | SetEyebrowType(char_info.GetEyebrowType()); | ||
| 214 | SetEyebrowColor(char_info.GetEyebrowColor()); | ||
| 215 | SetEyebrowScale(char_info.GetEyebrowScale()); | ||
| 216 | SetEyebrowAspect(char_info.GetEyebrowAspect()); | ||
| 217 | SetEyebrowRotate(char_info.GetEyebrowRotate()); | ||
| 218 | SetEyebrowX(char_info.GetEyebrowX()); | ||
| 219 | SetEyebrowY(char_info.GetEyebrowY() - 3); | ||
| 220 | SetNoseType(char_info.GetNoseType()); | ||
| 221 | SetNoseScale(char_info.GetNoseScale()); | ||
| 222 | SetNoseY(char_info.GetNoseY()); | ||
| 223 | SetMouthType(char_info.GetMouthType()); | ||
| 224 | SetMouthColor(char_info.GetMouthColor()); | ||
| 225 | SetMouthScale(char_info.GetMouthScale()); | ||
| 226 | SetMouthAspect(char_info.GetMouthAspect()); | ||
| 227 | SetMouthY(char_info.GetMouthY()); | ||
| 228 | SetBeardColor(char_info.GetBeardColor()); | ||
| 229 | SetBeardType(char_info.GetBeardType()); | ||
| 230 | SetMustacheType(char_info.GetMustacheType()); | ||
| 231 | SetMustacheScale(char_info.GetMustacheScale()); | ||
| 232 | SetMustacheY(char_info.GetMustacheY()); | ||
| 233 | SetGlassType(char_info.GetGlassType()); | ||
| 234 | SetGlassColor(char_info.GetGlassColor()); | ||
| 235 | SetGlassScale(char_info.GetGlassScale()); | ||
| 236 | SetGlassY(char_info.GetGlassY()); | ||
| 237 | SetMoleType(char_info.GetMoleType()); | ||
| 238 | SetMoleScale(char_info.GetMoleScale()); | ||
| 239 | SetMoleX(char_info.GetMoleX()); | ||
| 240 | SetMoleY(char_info.GetMoleY()); | ||
| 241 | } | ||
| 242 | |||
| 243 | ValidationResult CoreData::IsValid() const { | ||
| 244 | if (!name.IsValid()) { | ||
| 245 | return ValidationResult::InvalidName; | ||
| 246 | } | ||
| 247 | if (GetFontRegion() > FontRegion::Max) { | ||
| 248 | return ValidationResult::InvalidFont; | ||
| 249 | } | ||
| 250 | if (GetFavoriteColor() > FavoriteColor::Max) { | ||
| 251 | return ValidationResult::InvalidColor; | ||
| 252 | } | ||
| 253 | if (GetGender() > Gender::Max) { | ||
| 254 | return ValidationResult::InvalidGender; | ||
| 255 | } | ||
| 256 | if (GetHeight() > MaxHeight) { | ||
| 257 | return ValidationResult::InvalidHeight; | ||
| 258 | } | ||
| 259 | if (GetBuild() > MaxBuild) { | ||
| 260 | return ValidationResult::InvalidBuild; | ||
| 261 | } | ||
| 262 | if (GetType() > MaxType) { | ||
| 263 | return ValidationResult::InvalidType; | ||
| 264 | } | ||
| 265 | if (GetRegionMove() > MaxRegionMove) { | ||
| 266 | return ValidationResult::InvalidRegionMove; | ||
| 267 | } | ||
| 268 | if (GetFacelineType() > FacelineType::Max) { | ||
| 269 | return ValidationResult::InvalidFacelineType; | ||
| 270 | } | ||
| 271 | if (GetFacelineColor() > FacelineColor::Max) { | ||
| 272 | return ValidationResult::InvalidFacelineColor; | ||
| 273 | } | ||
| 274 | if (GetFacelineWrinkle() > FacelineWrinkle::Max) { | ||
| 275 | return ValidationResult::InvalidFacelineWrinkle; | ||
| 276 | } | ||
| 277 | if (GetFacelineMake() > FacelineMake::Max) { | ||
| 278 | return ValidationResult::InvalidFacelineMake; | ||
| 279 | } | ||
| 280 | if (GetHairType() > HairType::Max) { | ||
| 281 | return ValidationResult::InvalidHairType; | ||
| 282 | } | ||
| 283 | if (GetHairColor() > CommonColor::Max) { | ||
| 284 | return ValidationResult::InvalidHairColor; | ||
| 285 | } | ||
| 286 | if (GetHairFlip() > HairFlip::Max) { | ||
| 287 | return ValidationResult::InvalidHairFlip; | ||
| 288 | } | ||
| 289 | if (GetEyeType() > EyeType::Max) { | ||
| 290 | return ValidationResult::InvalidEyeType; | ||
| 291 | } | ||
| 292 | if (GetEyeColor() > CommonColor::Max) { | ||
| 293 | return ValidationResult::InvalidEyeColor; | ||
| 294 | } | ||
| 295 | if (GetEyeScale() > MaxEyeScale) { | ||
| 296 | return ValidationResult::InvalidEyeScale; | ||
| 297 | } | ||
| 298 | if (GetEyeAspect() > MaxEyeAspect) { | ||
| 299 | return ValidationResult::InvalidEyeAspect; | ||
| 300 | } | ||
| 301 | if (GetEyeRotate() > MaxEyeRotate) { | ||
| 302 | return ValidationResult::InvalidEyeRotate; | ||
| 303 | } | ||
| 304 | if (GetEyeX() > MaxEyeX) { | ||
| 305 | return ValidationResult::InvalidEyeX; | ||
| 306 | } | ||
| 307 | if (GetEyeY() > MaxEyeY) { | ||
| 308 | return ValidationResult::InvalidEyeY; | ||
| 309 | } | ||
| 310 | if (GetEyebrowType() > EyebrowType::Max) { | ||
| 311 | return ValidationResult::InvalidEyebrowType; | ||
| 312 | } | ||
| 313 | if (GetEyebrowColor() > CommonColor::Max) { | ||
| 314 | return ValidationResult::InvalidEyebrowColor; | ||
| 315 | } | ||
| 316 | if (GetEyebrowScale() > MaxEyebrowScale) { | ||
| 317 | return ValidationResult::InvalidEyebrowScale; | ||
| 318 | } | ||
| 319 | if (GetEyebrowAspect() > MaxEyebrowAspect) { | ||
| 320 | return ValidationResult::InvalidEyebrowAspect; | ||
| 321 | } | ||
| 322 | if (GetEyebrowRotate() > MaxEyebrowRotate) { | ||
| 323 | return ValidationResult::InvalidEyebrowRotate; | ||
| 324 | } | ||
| 325 | if (GetEyebrowX() > MaxEyebrowX) { | ||
| 326 | return ValidationResult::InvalidEyebrowX; | ||
| 327 | } | ||
| 328 | if (GetEyebrowY() > MaxEyebrowY) { | ||
| 329 | return ValidationResult::InvalidEyebrowY; | ||
| 330 | } | ||
| 331 | if (GetNoseType() > NoseType::Max) { | ||
| 332 | return ValidationResult::InvalidNoseType; | ||
| 333 | } | ||
| 334 | if (GetNoseScale() > MaxNoseScale) { | ||
| 335 | return ValidationResult::InvalidNoseScale; | ||
| 336 | } | ||
| 337 | if (GetNoseY() > MaxNoseY) { | ||
| 338 | return ValidationResult::InvalidNoseY; | ||
| 339 | } | ||
| 340 | if (GetMouthType() > MouthType::Max) { | ||
| 341 | return ValidationResult::InvalidMouthType; | ||
| 342 | } | ||
| 343 | if (GetMouthColor() > CommonColor::Max) { | ||
| 344 | return ValidationResult::InvalidMouthColor; | ||
| 345 | } | ||
| 346 | if (GetMouthScale() > MaxMouthScale) { | ||
| 347 | return ValidationResult::InvalidMouthScale; | ||
| 348 | } | ||
| 349 | if (GetMouthAspect() > MaxMoutAspect) { | ||
| 350 | return ValidationResult::InvalidMouthAspect; | ||
| 351 | } | ||
| 352 | if (GetMouthY() > MaxMouthY) { | ||
| 353 | return ValidationResult::InvalidMouthY; | ||
| 354 | } | ||
| 355 | if (GetBeardColor() > CommonColor::Max) { | ||
| 356 | return ValidationResult::InvalidBeardColor; | ||
| 357 | } | ||
| 358 | if (GetBeardType() > BeardType::Max) { | ||
| 359 | return ValidationResult::InvalidBeardType; | ||
| 360 | } | ||
| 361 | if (GetMustacheType() > MustacheType::Max) { | ||
| 362 | return ValidationResult::InvalidMustacheType; | ||
| 363 | } | ||
| 364 | if (GetMustacheScale() > MaxMustacheScale) { | ||
| 365 | return ValidationResult::InvalidMustacheScale; | ||
| 366 | } | ||
| 367 | if (GetMustacheY() > MaxMustacheY) { | ||
| 368 | return ValidationResult::InvalidMustacheY; | ||
| 369 | } | ||
| 370 | if (GetGlassType() > GlassType::Max) { | ||
| 371 | return ValidationResult::InvalidGlassType; | ||
| 372 | } | ||
| 373 | if (GetGlassColor() > CommonColor::Max) { | ||
| 374 | return ValidationResult::InvalidGlassColor; | ||
| 375 | } | ||
| 376 | if (GetGlassScale() > MaxGlassScale) { | ||
| 377 | return ValidationResult::InvalidGlassScale; | ||
| 378 | } | ||
| 379 | if (GetGlassY() > MaxGlassY) { | ||
| 380 | return ValidationResult::InvalidGlassY; | ||
| 381 | } | ||
| 382 | if (GetMoleType() > MoleType::Max) { | ||
| 383 | return ValidationResult::InvalidMoleType; | ||
| 384 | } | ||
| 385 | if (GetMoleScale() > MaxMoleScale) { | ||
| 386 | return ValidationResult::InvalidMoleScale; | ||
| 387 | } | ||
| 388 | if (GetMoleX() > MaxMoleX) { | ||
| 389 | return ValidationResult::InvalidMoleX; | ||
| 390 | } | ||
| 391 | if (GetMoleY() > MaxMoleY) { | ||
| 392 | return ValidationResult::InvalidMoleY; | ||
| 393 | } | ||
| 394 | return ValidationResult::NoErrors; | ||
| 191 | } | 395 | } |
| 192 | 396 | ||
| 193 | void CoreData::SetFontRegion(FontRegion value) { | 397 | void CoreData::SetFontRegion(FontRegion value) { |
| @@ -314,8 +518,8 @@ void CoreData::SetNoseY(u8 value) { | |||
| 314 | data.nose_y.Assign(value); | 518 | data.nose_y.Assign(value); |
| 315 | } | 519 | } |
| 316 | 520 | ||
| 317 | void CoreData::SetMouthType(u8 value) { | 521 | void CoreData::SetMouthType(MouthType value) { |
| 318 | data.mouth_type.Assign(value); | 522 | data.mouth_type.Assign(static_cast<u32>(value)); |
| 319 | } | 523 | } |
| 320 | 524 | ||
| 321 | void CoreData::SetMouthColor(CommonColor value) { | 525 | void CoreData::SetMouthColor(CommonColor value) { |
diff --git a/src/core/hle/service/mii/types/core_data.h b/src/core/hle/service/mii/types/core_data.h index cebcd2ee4..8897e4f3b 100644 --- a/src/core/hle/service/mii/types/core_data.h +++ b/src/core/hle/service/mii/types/core_data.h | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | #include "core/hle/service/mii/mii_types.h" | 6 | #include "core/hle/service/mii/mii_types.h" |
| 7 | 7 | ||
| 8 | namespace Service::Mii { | 8 | namespace Service::Mii { |
| 9 | class CharInfo; | ||
| 9 | 10 | ||
| 10 | struct StoreDataBitFields { | 11 | struct StoreDataBitFields { |
| 11 | union { | 12 | union { |
| @@ -100,8 +101,9 @@ class CoreData { | |||
| 100 | public: | 101 | public: |
| 101 | void SetDefault(); | 102 | void SetDefault(); |
| 102 | void BuildRandom(Age age, Gender gender, Race race); | 103 | void BuildRandom(Age age, Gender gender, Race race); |
| 104 | void BuildFromCharInfo(const CharInfo& char_info); | ||
| 103 | 105 | ||
| 104 | u32 IsValid() const; | 106 | ValidationResult IsValid() const; |
| 105 | 107 | ||
| 106 | void SetFontRegion(FontRegion value); | 108 | void SetFontRegion(FontRegion value); |
| 107 | void SetFavoriteColor(FavoriteColor value); | 109 | void SetFavoriteColor(FavoriteColor value); |
| @@ -134,7 +136,7 @@ public: | |||
| 134 | void SetNoseType(NoseType value); | 136 | void SetNoseType(NoseType value); |
| 135 | void SetNoseScale(u8 value); | 137 | void SetNoseScale(u8 value); |
| 136 | void SetNoseY(u8 value); | 138 | void SetNoseY(u8 value); |
| 137 | void SetMouthType(u8 value); | 139 | void SetMouthType(MouthType value); |
| 138 | void SetMouthColor(CommonColor value); | 140 | void SetMouthColor(CommonColor value); |
| 139 | void SetMouthScale(u8 value); | 141 | void SetMouthScale(u8 value); |
| 140 | void SetMouthAspect(u8 value); | 142 | void SetMouthAspect(u8 value); |
| @@ -212,5 +214,6 @@ private: | |||
| 212 | Nickname name{}; | 214 | Nickname name{}; |
| 213 | }; | 215 | }; |
| 214 | static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size."); | 216 | static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size."); |
| 217 | static_assert(std::is_trivially_copyable_v<CoreData>, "CoreData type must be trivially copyable."); | ||
| 215 | 218 | ||
| 216 | }; // namespace Service::Mii | 219 | }; // namespace Service::Mii |
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/mii/types/store_data.cpp b/src/core/hle/service/mii/types/store_data.cpp index 8fce636c7..127221fdb 100644 --- a/src/core/hle/service/mii/types/store_data.cpp +++ b/src/core/hle/service/mii/types/store_data.cpp | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "core/hle/service/mii/mii_result.h" | ||
| 4 | #include "core/hle/service/mii/mii_util.h" | 5 | #include "core/hle/service/mii/mii_util.h" |
| 5 | #include "core/hle/service/mii/types/raw_data.h" | 6 | #include "core/hle/service/mii/types/raw_data.h" |
| 6 | #include "core/hle/service/mii/types/store_data.h" | 7 | #include "core/hle/service/mii/types/store_data.h" |
| @@ -35,13 +36,13 @@ void StoreData::BuildDefault(u32 mii_index) { | |||
| 35 | core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); | 36 | core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); |
| 36 | core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); | 37 | core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); |
| 37 | core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); | 38 | core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); |
| 38 | core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y)); | 39 | core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3)); |
| 39 | 40 | ||
| 40 | core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); | 41 | core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); |
| 41 | core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); | 42 | core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); |
| 42 | core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); | 43 | core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); |
| 43 | 44 | ||
| 44 | core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type)); | 45 | core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type)); |
| 45 | core_data.SetMouthColor( | 46 | core_data.SetMouthColor( |
| 46 | RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); | 47 | RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); |
| 47 | core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); | 48 | core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); |
| @@ -75,10 +76,8 @@ void StoreData::BuildDefault(u32 mii_index) { | |||
| 75 | core_data.SetType(static_cast<u8>(default_mii.type)); | 76 | core_data.SetType(static_cast<u8>(default_mii.type)); |
| 76 | core_data.SetNickname(default_mii.nickname); | 77 | core_data.SetNickname(default_mii.nickname); |
| 77 | 78 | ||
| 78 | const auto device_id = MiiUtil::GetDeviceId(); | ||
| 79 | create_id = MiiUtil::MakeCreateId(); | 79 | create_id = MiiUtil::MakeCreateId(); |
| 80 | device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); | 80 | SetChecksum(); |
| 81 | data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); | ||
| 82 | } | 81 | } |
| 83 | 82 | ||
| 84 | void StoreData::BuildBase(Gender gender) { | 83 | void StoreData::BuildBase(Gender gender) { |
| @@ -109,13 +108,13 @@ void StoreData::BuildBase(Gender gender) { | |||
| 109 | core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); | 108 | core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); |
| 110 | core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); | 109 | core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); |
| 111 | core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); | 110 | core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); |
| 112 | core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y)); | 111 | core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3)); |
| 113 | 112 | ||
| 114 | core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); | 113 | core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); |
| 115 | core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); | 114 | core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); |
| 116 | core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); | 115 | core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); |
| 117 | 116 | ||
| 118 | core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type)); | 117 | core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type)); |
| 119 | core_data.SetMouthColor( | 118 | core_data.SetMouthColor( |
| 120 | RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); | 119 | RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); |
| 121 | core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); | 120 | core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); |
| @@ -149,37 +148,51 @@ void StoreData::BuildBase(Gender gender) { | |||
| 149 | core_data.SetType(static_cast<u8>(default_mii.type)); | 148 | core_data.SetType(static_cast<u8>(default_mii.type)); |
| 150 | core_data.SetNickname(default_mii.nickname); | 149 | core_data.SetNickname(default_mii.nickname); |
| 151 | 150 | ||
| 152 | const auto device_id = MiiUtil::GetDeviceId(); | ||
| 153 | create_id = MiiUtil::MakeCreateId(); | 151 | create_id = MiiUtil::MakeCreateId(); |
| 154 | device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); | 152 | SetChecksum(); |
| 155 | data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); | ||
| 156 | } | 153 | } |
| 157 | 154 | ||
| 158 | void StoreData::BuildRandom(Age age, Gender gender, Race race) { | 155 | void StoreData::BuildRandom(Age age, Gender gender, Race race) { |
| 159 | core_data.BuildRandom(age, gender, race); | 156 | core_data.BuildRandom(age, gender, race); |
| 160 | const auto device_id = MiiUtil::GetDeviceId(); | ||
| 161 | create_id = MiiUtil::MakeCreateId(); | 157 | create_id = MiiUtil::MakeCreateId(); |
| 162 | device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); | 158 | SetChecksum(); |
| 163 | data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); | ||
| 164 | } | 159 | } |
| 165 | 160 | ||
| 166 | void StoreData::SetInvalidName() { | 161 | void StoreData::BuildWithCharInfo(const CharInfo& char_info) { |
| 167 | const auto& invalid_name = core_data.GetInvalidNickname(); | 162 | core_data.BuildFromCharInfo(char_info); |
| 163 | create_id = MiiUtil::MakeCreateId(); | ||
| 164 | SetChecksum(); | ||
| 165 | } | ||
| 166 | |||
| 167 | void StoreData::BuildWithCoreData(const CoreData& in_core_data) { | ||
| 168 | core_data = in_core_data; | ||
| 169 | create_id = MiiUtil::MakeCreateId(); | ||
| 170 | SetChecksum(); | ||
| 171 | } | ||
| 172 | |||
| 173 | Result StoreData::Restore() { | ||
| 174 | // TODO: Implement this | ||
| 175 | return ResultNotUpdated; | ||
| 176 | } | ||
| 177 | |||
| 178 | ValidationResult StoreData::IsValid() const { | ||
| 179 | if (core_data.IsValid() != ValidationResult::NoErrors) { | ||
| 180 | return core_data.IsValid(); | ||
| 181 | } | ||
| 182 | if (data_crc != MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID))) { | ||
| 183 | return ValidationResult::InvalidChecksum; | ||
| 184 | } | ||
| 168 | const auto device_id = MiiUtil::GetDeviceId(); | 185 | const auto device_id = MiiUtil::GetDeviceId(); |
| 169 | core_data.SetNickname(invalid_name); | 186 | if (device_crc != MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData))) { |
| 170 | device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); | 187 | return ValidationResult::InvalidChecksum; |
| 171 | data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); | 188 | } |
| 189 | return ValidationResult::NoErrors; | ||
| 172 | } | 190 | } |
| 173 | 191 | ||
| 174 | bool StoreData::IsSpecial() const { | 192 | bool StoreData::IsSpecial() const { |
| 175 | return GetType() == 1; | 193 | return GetType() == 1; |
| 176 | } | 194 | } |
| 177 | 195 | ||
| 178 | u32 StoreData::IsValid() const { | ||
| 179 | // TODO: complete this | ||
| 180 | return 0; | ||
| 181 | } | ||
| 182 | |||
| 183 | void StoreData::SetFontRegion(FontRegion value) { | 196 | void StoreData::SetFontRegion(FontRegion value) { |
| 184 | core_data.SetFontRegion(value); | 197 | core_data.SetFontRegion(value); |
| 185 | } | 198 | } |
| @@ -304,7 +317,7 @@ void StoreData::SetNoseY(u8 value) { | |||
| 304 | core_data.SetNoseY(value); | 317 | core_data.SetNoseY(value); |
| 305 | } | 318 | } |
| 306 | 319 | ||
| 307 | void StoreData::SetMouthType(u8 value) { | 320 | void StoreData::SetMouthType(MouthType value) { |
| 308 | core_data.SetMouthType(value); | 321 | core_data.SetMouthType(value); |
| 309 | } | 322 | } |
| 310 | 323 | ||
| @@ -380,6 +393,26 @@ void StoreData::SetNickname(Nickname value) { | |||
| 380 | core_data.SetNickname(value); | 393 | core_data.SetNickname(value); |
| 381 | } | 394 | } |
| 382 | 395 | ||
| 396 | void StoreData::SetInvalidName() { | ||
| 397 | const auto& invalid_name = core_data.GetInvalidNickname(); | ||
| 398 | core_data.SetNickname(invalid_name); | ||
| 399 | SetChecksum(); | ||
| 400 | } | ||
| 401 | |||
| 402 | void StoreData::SetChecksum() { | ||
| 403 | SetDataChecksum(); | ||
| 404 | SetDeviceChecksum(); | ||
| 405 | } | ||
| 406 | |||
| 407 | void StoreData::SetDataChecksum() { | ||
| 408 | data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID)); | ||
| 409 | } | ||
| 410 | |||
| 411 | void StoreData::SetDeviceChecksum() { | ||
| 412 | const auto device_id = MiiUtil::GetDeviceId(); | ||
| 413 | device_crc = MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData)); | ||
| 414 | } | ||
| 415 | |||
| 383 | Common::UUID StoreData::GetCreateId() const { | 416 | Common::UUID StoreData::GetCreateId() const { |
| 384 | return create_id; | 417 | return create_id; |
| 385 | } | 418 | } |
| @@ -585,7 +618,7 @@ Nickname StoreData::GetNickname() const { | |||
| 585 | } | 618 | } |
| 586 | 619 | ||
| 587 | bool StoreData::operator==(const StoreData& data) { | 620 | bool StoreData::operator==(const StoreData& data) { |
| 588 | bool is_identical = data.core_data.IsValid() == 0; | 621 | bool is_identical = data.core_data.IsValid() == ValidationResult::NoErrors; |
| 589 | is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data; | 622 | is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data; |
| 590 | is_identical &= GetCreateId() == data.GetCreateId(); | 623 | is_identical &= GetCreateId() == data.GetCreateId(); |
| 591 | is_identical &= GetFontRegion() == data.GetFontRegion(); | 624 | is_identical &= GetFontRegion() == data.GetFontRegion(); |
diff --git a/src/core/hle/service/mii/types/store_data.h b/src/core/hle/service/mii/types/store_data.h index 224c32cf8..ed5dfb949 100644 --- a/src/core/hle/service/mii/types/store_data.h +++ b/src/core/hle/service/mii/types/store_data.h | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include "core/hle/result.h" | ||
| 6 | #include "core/hle/service/mii/mii_types.h" | 7 | #include "core/hle/service/mii/mii_types.h" |
| 7 | #include "core/hle/service/mii/types/core_data.h" | 8 | #include "core/hle/service/mii/types/core_data.h" |
| 8 | 9 | ||
| @@ -10,17 +11,16 @@ namespace Service::Mii { | |||
| 10 | 11 | ||
| 11 | class StoreData { | 12 | class StoreData { |
| 12 | public: | 13 | public: |
| 13 | // nn::mii::detail::StoreDataRaw::BuildDefault | ||
| 14 | void BuildDefault(u32 mii_index); | 14 | void BuildDefault(u32 mii_index); |
| 15 | // nn::mii::detail::StoreDataRaw::BuildDefault | ||
| 16 | |||
| 17 | void BuildBase(Gender gender); | 15 | void BuildBase(Gender gender); |
| 18 | // nn::mii::detail::StoreDataRaw::BuildRandom | ||
| 19 | void BuildRandom(Age age, Gender gender, Race race); | 16 | void BuildRandom(Age age, Gender gender, Race race); |
| 17 | void BuildWithCharInfo(const CharInfo& char_info); | ||
| 18 | void BuildWithCoreData(const CoreData& in_core_data); | ||
| 19 | Result Restore(); | ||
| 20 | 20 | ||
| 21 | bool IsSpecial() const; | 21 | ValidationResult IsValid() const; |
| 22 | 22 | ||
| 23 | u32 IsValid() const; | 23 | bool IsSpecial() const; |
| 24 | 24 | ||
| 25 | void SetFontRegion(FontRegion value); | 25 | void SetFontRegion(FontRegion value); |
| 26 | void SetFavoriteColor(FavoriteColor value); | 26 | void SetFavoriteColor(FavoriteColor value); |
| @@ -53,7 +53,7 @@ public: | |||
| 53 | void SetNoseType(NoseType value); | 53 | void SetNoseType(NoseType value); |
| 54 | void SetNoseScale(u8 value); | 54 | void SetNoseScale(u8 value); |
| 55 | void SetNoseY(u8 value); | 55 | void SetNoseY(u8 value); |
| 56 | void SetMouthType(u8 value); | 56 | void SetMouthType(MouthType value); |
| 57 | void SetMouthColor(CommonColor value); | 57 | void SetMouthColor(CommonColor value); |
| 58 | void SetMouthScale(u8 value); | 58 | void SetMouthScale(u8 value); |
| 59 | void SetMouthAspect(u8 value); | 59 | void SetMouthAspect(u8 value); |
| @@ -73,6 +73,9 @@ public: | |||
| 73 | void SetMoleY(u8 value); | 73 | void SetMoleY(u8 value); |
| 74 | void SetNickname(Nickname nickname); | 74 | void SetNickname(Nickname nickname); |
| 75 | void SetInvalidName(); | 75 | void SetInvalidName(); |
| 76 | void SetChecksum(); | ||
| 77 | void SetDataChecksum(); | ||
| 78 | void SetDeviceChecksum(); | ||
| 76 | 79 | ||
| 77 | Common::UUID GetCreateId() const; | 80 | Common::UUID GetCreateId() const; |
| 78 | FontRegion GetFontRegion() const; | 81 | FontRegion GetFontRegion() const; |
| @@ -135,6 +138,8 @@ private: | |||
| 135 | u16 device_crc{}; | 138 | u16 device_crc{}; |
| 136 | }; | 139 | }; |
| 137 | static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size."); | 140 | static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size."); |
| 141 | static_assert(std::is_trivially_copyable_v<StoreData>, | ||
| 142 | "StoreData type must be trivially copyable."); | ||
| 138 | 143 | ||
| 139 | struct StoreDataElement { | 144 | struct StoreDataElement { |
| 140 | StoreData store_data{}; | 145 | StoreData store_data{}; |
diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp index 1c28e0b1b..a019cc9f7 100644 --- a/src/core/hle/service/mii/types/ver3_store_data.cpp +++ b/src/core/hle/service/mii/types/ver3_store_data.cpp | |||
| @@ -22,12 +22,6 @@ void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) { | |||
| 22 | void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const { | 22 | void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const { |
| 23 | out_store_data.BuildBase(Gender::Male); | 23 | out_store_data.BuildBase(Gender::Male); |
| 24 | 24 | ||
| 25 | if (!IsValid()) { | ||
| 26 | return; | ||
| 27 | } | ||
| 28 | |||
| 29 | // TODO: We are ignoring a bunch of data from the mii_v3 | ||
| 30 | |||
| 31 | out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value())); | 25 | out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value())); |
| 32 | out_store_data.SetFavoriteColor( | 26 | out_store_data.SetFavoriteColor( |
| 33 | static_cast<FavoriteColor>(mii_information.favorite_color.Value())); | 27 | static_cast<FavoriteColor>(mii_information.favorite_color.Value())); |
| @@ -36,65 +30,71 @@ void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const { | |||
| 36 | 30 | ||
| 37 | out_store_data.SetNickname(mii_name); | 31 | out_store_data.SetNickname(mii_name); |
| 38 | out_store_data.SetFontRegion( | 32 | out_store_data.SetFontRegion( |
| 39 | static_cast<FontRegion>(static_cast<u8>(region_information.font_region))); | 33 | static_cast<FontRegion>(static_cast<u8>(region_information.font_region.Value()))); |
| 40 | 34 | ||
| 41 | out_store_data.SetFacelineType( | 35 | out_store_data.SetFacelineType( |
| 42 | static_cast<FacelineType>(appearance_bits1.faceline_type.Value())); | 36 | static_cast<FacelineType>(appearance_bits1.faceline_type.Value())); |
| 43 | out_store_data.SetFacelineColor( | 37 | out_store_data.SetFacelineColor( |
| 44 | static_cast<FacelineColor>(appearance_bits1.faceline_color.Value())); | 38 | RawData::GetFacelineColorFromVer3(appearance_bits1.faceline_color.Value())); |
| 45 | out_store_data.SetFacelineWrinkle( | 39 | out_store_data.SetFacelineWrinkle( |
| 46 | static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value())); | 40 | static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value())); |
| 47 | out_store_data.SetFacelineMake( | 41 | out_store_data.SetFacelineMake( |
| 48 | static_cast<FacelineMake>(appearance_bits2.faceline_make.Value())); | 42 | static_cast<FacelineMake>(appearance_bits2.faceline_make.Value())); |
| 49 | 43 | ||
| 50 | out_store_data.SetHairType(static_cast<HairType>(hair_type)); | 44 | out_store_data.SetHairType(static_cast<HairType>(hair_type)); |
| 51 | out_store_data.SetHairColor(static_cast<CommonColor>(appearance_bits3.hair_color.Value())); | 45 | out_store_data.SetHairColor(RawData::GetHairColorFromVer3(appearance_bits3.hair_color.Value())); |
| 52 | out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value())); | 46 | out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value())); |
| 53 | 47 | ||
| 54 | out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value())); | 48 | out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value())); |
| 55 | out_store_data.SetEyeColor(static_cast<CommonColor>(appearance_bits4.eye_color.Value())); | 49 | out_store_data.SetEyeColor(RawData::GetEyeColorFromVer3(appearance_bits4.eye_color.Value())); |
| 56 | out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale)); | 50 | out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale.Value())); |
| 57 | out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect)); | 51 | out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect.Value())); |
| 58 | out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate)); | 52 | out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate.Value())); |
| 59 | out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x)); | 53 | out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x.Value())); |
| 60 | out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y)); | 54 | out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y.Value())); |
| 61 | 55 | ||
| 62 | out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value())); | 56 | out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value())); |
| 63 | out_store_data.SetEyebrowColor( | 57 | out_store_data.SetEyebrowColor( |
| 64 | static_cast<CommonColor>(appearance_bits5.eyebrow_color.Value())); | 58 | RawData::GetHairColorFromVer3(appearance_bits5.eyebrow_color.Value())); |
| 65 | out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale)); | 59 | out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale.Value())); |
| 66 | out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect)); | 60 | out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect.Value())); |
| 67 | out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate)); | 61 | out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate.Value())); |
| 68 | out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x)); | 62 | out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x.Value())); |
| 69 | out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y)); | 63 | out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y.Value() - 3)); |
| 70 | 64 | ||
| 71 | out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value())); | 65 | out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value())); |
| 72 | out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale)); | 66 | out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale.Value())); |
| 73 | out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y)); | 67 | out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y.Value())); |
| 74 | 68 | ||
| 75 | out_store_data.SetMouthType(static_cast<u8>(appearance_bits7.mouth_type)); | 69 | out_store_data.SetMouthType(static_cast<MouthType>(appearance_bits7.mouth_type.Value())); |
| 76 | out_store_data.SetMouthColor(static_cast<CommonColor>(appearance_bits7.mouth_color.Value())); | 70 | out_store_data.SetMouthColor( |
| 77 | out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale)); | 71 | RawData::GetMouthColorFromVer3(appearance_bits7.mouth_color.Value())); |
| 78 | out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect)); | 72 | out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale.Value())); |
| 79 | out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y)); | 73 | out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect.Value())); |
| 74 | out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y.Value())); | ||
| 80 | 75 | ||
| 81 | out_store_data.SetMustacheType( | 76 | out_store_data.SetMustacheType( |
| 82 | static_cast<MustacheType>(appearance_bits8.mustache_type.Value())); | 77 | static_cast<MustacheType>(appearance_bits8.mustache_type.Value())); |
| 83 | out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale)); | 78 | out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale.Value())); |
| 84 | out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y)); | 79 | out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y.Value())); |
| 85 | 80 | ||
| 86 | out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value())); | 81 | out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value())); |
| 87 | out_store_data.SetBeardColor(static_cast<CommonColor>(appearance_bits9.beard_color.Value())); | 82 | out_store_data.SetBeardColor( |
| 83 | RawData::GetHairColorFromVer3(appearance_bits9.beard_color.Value())); | ||
| 88 | 84 | ||
| 85 | // Glass type is compatible as it is. It doesn't need a table | ||
| 89 | out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value())); | 86 | out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value())); |
| 90 | out_store_data.SetGlassColor(static_cast<CommonColor>(appearance_bits10.glass_color.Value())); | 87 | out_store_data.SetGlassColor( |
| 91 | out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale)); | 88 | RawData::GetGlassColorFromVer3(appearance_bits10.glass_color.Value())); |
| 92 | out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y)); | 89 | out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale.Value())); |
| 90 | out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y.Value())); | ||
| 93 | 91 | ||
| 94 | out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value())); | 92 | out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value())); |
| 95 | out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale)); | 93 | out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale.Value())); |
| 96 | out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x)); | 94 | out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x.Value())); |
| 97 | out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y)); | 95 | out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y.Value())); |
| 96 | |||
| 97 | out_store_data.SetChecksum(); | ||
| 98 | } | 98 | } |
| 99 | 99 | ||
| 100 | void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) { | 100 | void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) { |
| @@ -220,7 +220,7 @@ u32 Ver3StoreData::IsValid() const { | |||
| 220 | 220 | ||
| 221 | is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max)); | 221 | is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max)); |
| 222 | is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale); | 222 | is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale); |
| 223 | is_valid = is_valid && (appearance_bits9.mustache_y <= MasMustacheY); | 223 | is_valid = is_valid && (appearance_bits9.mustache_y <= MaxMustacheY); |
| 224 | 224 | ||
| 225 | is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max)); | 225 | is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max)); |
| 226 | is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor); | 226 | is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor); |
| @@ -228,7 +228,7 @@ u32 Ver3StoreData::IsValid() const { | |||
| 228 | is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType); | 228 | is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType); |
| 229 | is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2); | 229 | is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2); |
| 230 | is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale); | 230 | is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale); |
| 231 | is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassScale); | 231 | is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassY); |
| 232 | 232 | ||
| 233 | is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max)); | 233 | is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max)); |
| 234 | is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale); | 234 | is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale); |
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index 5dda12343..68c407f81 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp | |||
| @@ -439,6 +439,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target | |||
| 439 | 439 | ||
| 440 | device_state = DeviceState::TagMounted; | 440 | device_state = DeviceState::TagMounted; |
| 441 | mount_target = mount_target_; | 441 | mount_target = mount_target_; |
| 442 | |||
| 442 | return ResultSuccess; | 443 | return ResultSuccess; |
| 443 | } | 444 | } |
| 444 | 445 | ||
| @@ -716,12 +717,13 @@ Result NfcDevice::GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info | |||
| 716 | return ResultRegistrationIsNotInitialized; | 717 | return ResultRegistrationIsNotInitialized; |
| 717 | } | 718 | } |
| 718 | 719 | ||
| 719 | Service::Mii::MiiManager manager; | 720 | Mii::StoreData store_data{}; |
| 720 | const auto& settings = tag_data.settings; | 721 | const auto& settings = tag_data.settings; |
| 722 | tag_data.owner_mii.BuildToStoreData(store_data); | ||
| 721 | 723 | ||
| 722 | // TODO: Validate and complete this data | 724 | // TODO: Validate and complete this data |
| 723 | register_info = { | 725 | register_info = { |
| 724 | .mii_store_data = {}, | 726 | .mii_store_data = store_data, |
| 725 | .creation_date = settings.init_date.GetWriteDate(), | 727 | .creation_date = settings.init_date.GetWriteDate(), |
| 726 | .amiibo_name = GetAmiiboName(settings), | 728 | .amiibo_name = GetAmiiboName(settings), |
| 727 | .font_region = settings.settings.font_region, | 729 | .font_region = settings.settings.font_region, |
| @@ -874,17 +876,19 @@ Result NfcDevice::RestoreAmiibo() { | |||
| 874 | } | 876 | } |
| 875 | 877 | ||
| 876 | Result NfcDevice::Format() { | 878 | Result NfcDevice::Format() { |
| 877 | auto result1 = DeleteApplicationArea(); | 879 | Result result = ResultSuccess; |
| 878 | auto result2 = DeleteRegisterInfo(); | ||
| 879 | 880 | ||
| 880 | if (result1.IsError()) { | 881 | if (device_state == DeviceState::TagFound) { |
| 881 | return result1; | 882 | result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All); |
| 882 | } | 883 | } |
| 883 | 884 | ||
| 884 | if (result2.IsError()) { | 885 | if (result.IsError()) { |
| 885 | return result2; | 886 | return result; |
| 886 | } | 887 | } |
| 887 | 888 | ||
| 889 | DeleteApplicationArea(); | ||
| 890 | DeleteRegisterInfo(); | ||
| 891 | |||
| 888 | return Flush(); | 892 | return Flush(); |
| 889 | } | 893 | } |
| 890 | 894 | ||
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/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index d8509c1dd..85849d5f3 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp | |||
| @@ -170,7 +170,7 @@ void BSD::Socket(HLERequestContext& ctx) { | |||
| 170 | } | 170 | } |
| 171 | 171 | ||
| 172 | void BSD::Select(HLERequestContext& ctx) { | 172 | void BSD::Select(HLERequestContext& ctx) { |
| 173 | LOG_WARNING(Service, "(STUBBED) called"); | 173 | LOG_DEBUG(Service, "(STUBBED) called"); |
| 174 | 174 | ||
| 175 | IPC::ResponseBuilder rb{ctx, 4}; | 175 | IPC::ResponseBuilder rb{ctx, 4}; |
| 176 | 176 | ||
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index f4eaf3331..5a42dea48 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp | |||
| @@ -18,7 +18,7 @@ namespace Loader { | |||
| 18 | 18 | ||
| 19 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, | 19 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, |
| 20 | bool override_update_) | 20 | bool override_update_) |
| 21 | : AppLoader(std::move(file_)), override_update(override_update_) { | 21 | : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) { |
| 22 | const auto file_dir = file->GetContainingDirectory(); | 22 | const auto file_dir = file->GetContainingDirectory(); |
| 23 | 23 | ||
| 24 | // Title ID | 24 | // Title ID |
| @@ -69,9 +69,9 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys | |||
| 69 | } | 69 | } |
| 70 | 70 | ||
| 71 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( | 71 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( |
| 72 | FileSys::VirtualDir directory, bool override_update_) | 72 | FileSys::VirtualDir directory, bool override_update_, bool is_hbl_) |
| 73 | : AppLoader(directory->GetFile("main")), dir(std::move(directory)), | 73 | : AppLoader(directory->GetFile("main")), dir(std::move(directory)), |
| 74 | override_update(override_update_) {} | 74 | override_update(override_update_), is_hbl(is_hbl_) {} |
| 75 | 75 | ||
| 76 | FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { | 76 | FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { |
| 77 | if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) { | 77 | if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) { |
| @@ -147,7 +147,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect | |||
| 147 | } | 147 | } |
| 148 | 148 | ||
| 149 | // Setup the process code layout | 149 | // Setup the process code layout |
| 150 | if (process.LoadFromMetadata(metadata, code_size).IsError()) { | 150 | if (process.LoadFromMetadata(metadata, code_size, is_hbl).IsError()) { |
| 151 | return {ResultStatus::ErrorUnableToParseKernelMetadata, {}}; | 151 | return {ResultStatus::ErrorUnableToParseKernelMetadata, {}}; |
| 152 | } | 152 | } |
| 153 | 153 | ||
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index f7702225e..1e9f765c9 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h | |||
| @@ -27,7 +27,8 @@ public: | |||
| 27 | 27 | ||
| 28 | // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' | 28 | // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' |
| 29 | explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, | 29 | explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, |
| 30 | bool override_update_ = false); | 30 | bool override_update_ = false, |
| 31 | bool is_hbl_ = false); | ||
| 31 | 32 | ||
| 32 | /** | 33 | /** |
| 33 | * Identifies whether or not the given file is a deconstructed ROM directory. | 34 | * Identifies whether or not the given file is a deconstructed ROM directory. |
| @@ -62,6 +63,7 @@ private: | |||
| 62 | std::string name; | 63 | std::string name; |
| 63 | u64 title_id{}; | 64 | u64 title_id{}; |
| 64 | bool override_update; | 65 | bool override_update; |
| 66 | bool is_hbl; | ||
| 65 | 67 | ||
| 66 | Modules modules; | 68 | Modules modules; |
| 67 | }; | 69 | }; |
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp index d722459c6..bf56a08b4 100644 --- a/src/core/loader/kip.cpp +++ b/src/core/loader/kip.cpp | |||
| @@ -90,7 +90,8 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process, | |||
| 90 | codeset.DataSegment().size += kip->GetBSSSize(); | 90 | codeset.DataSegment().size += kip->GetBSSSize(); |
| 91 | 91 | ||
| 92 | // Setup the process code layout | 92 | // Setup the process code layout |
| 93 | if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) | 93 | if (process |
| 94 | .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false) | ||
| 94 | .IsError()) { | 95 | .IsError()) { |
| 95 | return {ResultStatus::ErrorNotInitialized, {}}; | 96 | return {ResultStatus::ErrorNotInitialized, {}}; |
| 96 | } | 97 | } |
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index d7562b4bc..69f1a54ed 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp | |||
| @@ -196,7 +196,8 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data) | |||
| 196 | program_image.resize(static_cast<u32>(program_image.size()) + bss_size); | 196 | program_image.resize(static_cast<u32>(program_image.size()) + bss_size); |
| 197 | 197 | ||
| 198 | // Setup the process code layout | 198 | // Setup the process code layout |
| 199 | if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) | 199 | if (process |
| 200 | .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false) | ||
| 200 | .IsError()) { | 201 | .IsError()) { |
| 201 | return false; | 202 | return false; |
| 202 | } | 203 | } |
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 549822506..1350da8dc 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp | |||
| @@ -127,13 +127,14 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: | |||
| 127 | } | 127 | } |
| 128 | 128 | ||
| 129 | // Apply patches if necessary | 129 | // Apply patches if necessary |
| 130 | if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { | 130 | const auto name = nso_file.GetName(); |
| 131 | if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) { | ||
| 131 | std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); | 132 | std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); |
| 132 | std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); | 133 | std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); |
| 133 | std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(), | 134 | std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(), |
| 134 | program_image.size()); | 135 | program_image.size()); |
| 135 | 136 | ||
| 136 | pi_header = pm->PatchNSO(pi_header, nso_file.GetName()); | 137 | pi_header = pm->PatchNSO(pi_header, name); |
| 137 | 138 | ||
| 138 | std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); | 139 | std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); |
| 139 | } | 140 | } |
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index fe2af1ae6..f4ab75b77 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp | |||
| @@ -30,7 +30,8 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_, | |||
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | if (nsp->IsExtractedType()) { | 32 | if (nsp->IsExtractedType()) { |
| 33 | secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); | 33 | secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>( |
| 34 | nsp->GetExeFS(), false, file->GetName() == "hbl.nsp"); | ||
| 34 | } else { | 35 | } else { |
| 35 | const auto control_nca = | 36 | const auto control_nca = |
| 36 | nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); | 37 | nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); |
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp index 7b52f61a7..a06e99166 100644 --- a/src/core/memory/cheat_engine.cpp +++ b/src/core/memory/cheat_engine.cpp | |||
| @@ -154,7 +154,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const { | |||
| 154 | return {}; | 154 | return {}; |
| 155 | } | 155 | } |
| 156 | 156 | ||
| 157 | const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10)); | 157 | const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10)); |
| 158 | out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = | 158 | out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = |
| 159 | value; | 159 | value; |
| 160 | 160 | ||
diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp new file mode 100644 index 000000000..947fa6cb3 --- /dev/null +++ b/src/core/tools/renderdoc.cpp | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <renderdoc_app.h> | ||
| 5 | |||
| 6 | #include "common/assert.h" | ||
| 7 | #include "common/dynamic_library.h" | ||
| 8 | #include "core/tools/renderdoc.h" | ||
| 9 | |||
| 10 | #ifdef _WIN32 | ||
| 11 | #include <windows.h> | ||
| 12 | #else | ||
| 13 | #include <dlfcn.h> | ||
| 14 | #endif | ||
| 15 | |||
| 16 | namespace Tools { | ||
| 17 | |||
| 18 | RenderdocAPI::RenderdocAPI() { | ||
| 19 | #ifdef WIN32 | ||
| 20 | if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) { | ||
| 21 | const auto RENDERDOC_GetAPI = | ||
| 22 | reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(mod, "RENDERDOC_GetAPI")); | ||
| 23 | const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api); | ||
| 24 | ASSERT(ret == 1); | ||
| 25 | } | ||
| 26 | #else | ||
| 27 | #ifdef ANDROID | ||
| 28 | static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so"; | ||
| 29 | #else | ||
| 30 | static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so"; | ||
| 31 | #endif | ||
| 32 | if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) { | ||
| 33 | const auto RENDERDOC_GetAPI = | ||
| 34 | reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI")); | ||
| 35 | const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api); | ||
| 36 | ASSERT(ret == 1); | ||
| 37 | } | ||
| 38 | #endif | ||
| 39 | } | ||
| 40 | |||
| 41 | RenderdocAPI::~RenderdocAPI() = default; | ||
| 42 | |||
| 43 | void RenderdocAPI::ToggleCapture() { | ||
| 44 | if (!rdoc_api) [[unlikely]] { | ||
| 45 | return; | ||
| 46 | } | ||
| 47 | if (!is_capturing) { | ||
| 48 | rdoc_api->StartFrameCapture(NULL, NULL); | ||
| 49 | } else { | ||
| 50 | rdoc_api->EndFrameCapture(NULL, NULL); | ||
| 51 | } | ||
| 52 | is_capturing = !is_capturing; | ||
| 53 | } | ||
| 54 | |||
| 55 | } // namespace Tools | ||
diff --git a/src/core/tools/renderdoc.h b/src/core/tools/renderdoc.h new file mode 100644 index 000000000..0e5e43da5 --- /dev/null +++ b/src/core/tools/renderdoc.h | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | struct RENDERDOC_API_1_6_0; | ||
| 7 | |||
| 8 | namespace Tools { | ||
| 9 | |||
| 10 | class RenderdocAPI { | ||
| 11 | public: | ||
| 12 | explicit RenderdocAPI(); | ||
| 13 | ~RenderdocAPI(); | ||
| 14 | |||
| 15 | void ToggleCapture(); | ||
| 16 | |||
| 17 | private: | ||
| 18 | RENDERDOC_API_1_6_0* rdoc_api{}; | ||
| 19 | bool is_capturing{false}; | ||
| 20 | }; | ||
| 21 | |||
| 22 | } // namespace Tools | ||
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_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 4f83a88e1..a1ec1a100 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | |||
| @@ -294,10 +294,11 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device | |||
| 294 | texture_cache{texture_cache_}, shader_notify{shader_notify_}, | 294 | texture_cache{texture_cache_}, shader_notify{shader_notify_}, |
| 295 | use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()}, | 295 | use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()}, |
| 296 | use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()}, | 296 | use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()}, |
| 297 | workers(device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY | 297 | #ifdef ANDROID |
| 298 | ? 1 | 298 | workers(1, "VkPipelineBuilder"), |
| 299 | : (std::max(std::thread::hardware_concurrency(), 2U) - 1), | 299 | #else |
| 300 | "VkPipelineBuilder"), | 300 | workers(std::max(std::thread::hardware_concurrency(), 2U) - 1, "VkPipelineBuilder"), |
| 301 | #endif | ||
| 301 | serialization_thread(1, "VkPipelineSerialization") { | 302 | serialization_thread(1, "VkPipelineSerialization") { |
| 302 | const auto& float_control{device.FloatControlProperties()}; | 303 | const auto& float_control{device.FloatControlProperties()}; |
| 303 | const VkDriverId driver_id{device.GetDriverID()}; | 304 | const VkDriverId driver_id{device.GetDriverID()}; |
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/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index 11ced6c38..56307d030 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp | |||
| @@ -140,6 +140,8 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, | |||
| 140 | return PixelFormat::D32_FLOAT; | 140 | return PixelFormat::D32_FLOAT; |
| 141 | case Hash(TextureFormat::Z16, UNORM): | 141 | case Hash(TextureFormat::Z16, UNORM): |
| 142 | return PixelFormat::D16_UNORM; | 142 | return PixelFormat::D16_UNORM; |
| 143 | case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR): | ||
| 144 | return PixelFormat::D16_UNORM; | ||
| 143 | case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR): | 145 | case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR): |
| 144 | return PixelFormat::S8_UINT_D24_UNORM; | 146 | return PixelFormat::S8_UINT_D24_UNORM; |
| 145 | case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR): | 147 | case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR): |
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/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index cbeb8f168..b22fda746 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp | |||
| @@ -59,6 +59,8 @@ void ConfigureDebug::SetConfiguration() { | |||
| 59 | ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); | 59 | ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); |
| 60 | ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); | 60 | ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); |
| 61 | ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue()); | 61 | ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue()); |
| 62 | ui->enable_renderdoc_hotkey->setEnabled(runtime_lock); | ||
| 63 | ui->enable_renderdoc_hotkey->setChecked(Settings::values.enable_renderdoc_hotkey.GetValue()); | ||
| 62 | ui->enable_graphics_debugging->setEnabled(runtime_lock); | 64 | ui->enable_graphics_debugging->setEnabled(runtime_lock); |
| 63 | ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue()); | 65 | ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue()); |
| 64 | ui->enable_shader_feedback->setEnabled(runtime_lock); | 66 | ui->enable_shader_feedback->setEnabled(runtime_lock); |
| @@ -111,6 +113,7 @@ void ConfigureDebug::ApplyConfiguration() { | |||
| 111 | Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); | 113 | Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); |
| 112 | Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked(); | 114 | Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked(); |
| 113 | Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); | 115 | Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); |
| 116 | Settings::values.enable_renderdoc_hotkey = ui->enable_renderdoc_hotkey->isChecked(); | ||
| 114 | Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); | 117 | Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); |
| 115 | Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); | 118 | Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); |
| 116 | Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); | 119 | Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); |
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 97c7d9022..66b8b7459 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui | |||
| @@ -18,8 +18,8 @@ | |||
| 18 | <rect> | 18 | <rect> |
| 19 | <x>0</x> | 19 | <x>0</x> |
| 20 | <y>0</y> | 20 | <y>0</y> |
| 21 | <width>829</width> | 21 | <width>842</width> |
| 22 | <height>758</height> | 22 | <height>741</height> |
| 23 | </rect> | 23 | </rect> |
| 24 | </property> | 24 | </property> |
| 25 | <layout class="QVBoxLayout" name="verticalLayout_1"> | 25 | <layout class="QVBoxLayout" name="verticalLayout_1"> |
| @@ -260,7 +260,7 @@ | |||
| 260 | <string>Graphics</string> | 260 | <string>Graphics</string> |
| 261 | </property> | 261 | </property> |
| 262 | <layout class="QGridLayout" name="gridLayout_2"> | 262 | <layout class="QGridLayout" name="gridLayout_2"> |
| 263 | <item row="3" column="0"> | 263 | <item row="4" column="0"> |
| 264 | <widget class="QCheckBox" name="disable_loop_safety_checks"> | 264 | <widget class="QCheckBox" name="disable_loop_safety_checks"> |
| 265 | <property name="toolTip"> | 265 | <property name="toolTip"> |
| 266 | <string>When checked, it executes shaders without loop logic changes</string> | 266 | <string>When checked, it executes shaders without loop logic changes</string> |
| @@ -270,33 +270,53 @@ | |||
| 270 | </property> | 270 | </property> |
| 271 | </widget> | 271 | </widget> |
| 272 | </item> | 272 | </item> |
| 273 | <item row="4" column="0"> | 273 | <item row="8" column="0"> |
| 274 | <widget class="QCheckBox" name="dump_shaders"> | 274 | <widget class="QCheckBox" name="disable_macro_hle"> |
| 275 | <property name="enabled"> | 275 | <property name="enabled"> |
| 276 | <bool>true</bool> | 276 | <bool>true</bool> |
| 277 | </property> | 277 | </property> |
| 278 | <property name="toolTip"> | 278 | <property name="toolTip"> |
| 279 | <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string> | 279 | <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string> |
| 280 | </property> | 280 | </property> |
| 281 | <property name="text"> | 281 | <property name="text"> |
| 282 | <string>Dump Game Shaders</string> | 282 | <string>Disable Macro HLE</string> |
| 283 | </property> | 283 | </property> |
| 284 | </widget> | 284 | </widget> |
| 285 | </item> | 285 | </item> |
| 286 | <item row="7" column="0"> | 286 | <item row="7" column="0"> |
| 287 | <widget class="QCheckBox" name="disable_macro_hle"> | 287 | <widget class="QCheckBox" name="dump_macros"> |
| 288 | <property name="enabled"> | 288 | <property name="enabled"> |
| 289 | <bool>true</bool> | 289 | <bool>true</bool> |
| 290 | </property> | 290 | </property> |
| 291 | <property name="toolTip"> | 291 | <property name="toolTip"> |
| 292 | <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string> | 292 | <string>When checked, it will dump all the macro programs of the GPU</string> |
| 293 | </property> | 293 | </property> |
| 294 | <property name="text"> | 294 | <property name="text"> |
| 295 | <string>Disable Macro HLE</string> | 295 | <string>Dump Maxwell Macros</string> |
| 296 | </property> | 296 | </property> |
| 297 | </widget> | 297 | </widget> |
| 298 | </item> | 298 | </item> |
| 299 | <item row="5" column="0"> | 299 | <item row="3" column="0"> |
| 300 | <widget class="QCheckBox" name="enable_nsight_aftermath"> | ||
| 301 | <property name="toolTip"> | ||
| 302 | <string>When checked, it enables Nsight Aftermath crash dumps</string> | ||
| 303 | </property> | ||
| 304 | <property name="text"> | ||
| 305 | <string>Enable Nsight Aftermath</string> | ||
| 306 | </property> | ||
| 307 | </widget> | ||
| 308 | </item> | ||
| 309 | <item row="2" column="0"> | ||
| 310 | <widget class="QCheckBox" name="enable_shader_feedback"> | ||
| 311 | <property name="toolTip"> | ||
| 312 | <string>When checked, yuzu will log statistics about the compiled pipeline cache</string> | ||
| 313 | </property> | ||
| 314 | <property name="text"> | ||
| 315 | <string>Enable Shader Feedback</string> | ||
| 316 | </property> | ||
| 317 | </widget> | ||
| 318 | </item> | ||
| 319 | <item row="6" column="0"> | ||
| 300 | <widget class="QCheckBox" name="disable_macro_jit"> | 320 | <widget class="QCheckBox" name="disable_macro_jit"> |
| 301 | <property name="enabled"> | 321 | <property name="enabled"> |
| 302 | <bool>true</bool> | 322 | <bool>true</bool> |
| @@ -309,6 +329,22 @@ | |||
| 309 | </property> | 329 | </property> |
| 310 | </widget> | 330 | </widget> |
| 311 | </item> | 331 | </item> |
| 332 | <item row="9" column="0"> | ||
| 333 | <spacer name="verticalSpacer_5"> | ||
| 334 | <property name="orientation"> | ||
| 335 | <enum>Qt::Vertical</enum> | ||
| 336 | </property> | ||
| 337 | <property name="sizeType"> | ||
| 338 | <enum>QSizePolicy::Preferred</enum> | ||
| 339 | </property> | ||
| 340 | <property name="sizeHint" stdset="0"> | ||
| 341 | <size> | ||
| 342 | <width>20</width> | ||
| 343 | <height>0</height> | ||
| 344 | </size> | ||
| 345 | </property> | ||
| 346 | </spacer> | ||
| 347 | </item> | ||
| 312 | <item row="0" column="0"> | 348 | <item row="0" column="0"> |
| 313 | <widget class="QCheckBox" name="enable_graphics_debugging"> | 349 | <widget class="QCheckBox" name="enable_graphics_debugging"> |
| 314 | <property name="enabled"> | 350 | <property name="enabled"> |
| @@ -322,55 +358,26 @@ | |||
| 322 | </property> | 358 | </property> |
| 323 | </widget> | 359 | </widget> |
| 324 | </item> | 360 | </item> |
| 325 | <item row="6" column="0"> | 361 | <item row="5" column="0"> |
| 326 | <widget class="QCheckBox" name="dump_macros"> | 362 | <widget class="QCheckBox" name="dump_shaders"> |
| 327 | <property name="enabled"> | 363 | <property name="enabled"> |
| 328 | <bool>true</bool> | 364 | <bool>true</bool> |
| 329 | </property> | 365 | </property> |
| 330 | <property name="toolTip"> | 366 | <property name="toolTip"> |
| 331 | <string>When checked, it will dump all the macro programs of the GPU</string> | 367 | <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string> |
| 332 | </property> | 368 | </property> |
| 333 | <property name="text"> | 369 | <property name="text"> |
| 334 | <string>Dump Maxwell Macros</string> | 370 | <string>Dump Game Shaders</string> |
| 335 | </property> | 371 | </property> |
| 336 | </widget> | 372 | </widget> |
| 337 | </item> | 373 | </item> |
| 338 | <item row="1" column="0"> | 374 | <item row="1" column="0"> |
| 339 | <widget class="QCheckBox" name="enable_shader_feedback"> | 375 | <widget class="QCheckBox" name="enable_renderdoc_hotkey"> |
| 340 | <property name="toolTip"> | ||
| 341 | <string>When checked, yuzu will log statistics about the compiled pipeline cache</string> | ||
| 342 | </property> | ||
| 343 | <property name="text"> | ||
| 344 | <string>Enable Shader Feedback</string> | ||
| 345 | </property> | ||
| 346 | </widget> | ||
| 347 | </item> | ||
| 348 | <item row="2" column="0"> | ||
| 349 | <widget class="QCheckBox" name="enable_nsight_aftermath"> | ||
| 350 | <property name="toolTip"> | ||
| 351 | <string>When checked, it enables Nsight Aftermath crash dumps</string> | ||
| 352 | </property> | ||
| 353 | <property name="text"> | 376 | <property name="text"> |
| 354 | <string>Enable Nsight Aftermath</string> | 377 | <string>Enable Renderdoc Hotkey</string> |
| 355 | </property> | 378 | </property> |
| 356 | </widget> | 379 | </widget> |
| 357 | </item> | 380 | </item> |
| 358 | <item row="8" column="0"> | ||
| 359 | <spacer name="verticalSpacer_5"> | ||
| 360 | <property name="orientation"> | ||
| 361 | <enum>Qt::Vertical</enum> | ||
| 362 | </property> | ||
| 363 | <property name="sizeType"> | ||
| 364 | <enum>QSizePolicy::Preferred</enum> | ||
| 365 | </property> | ||
| 366 | <property name="sizeHint" stdset="0"> | ||
| 367 | <size> | ||
| 368 | <width>20</width> | ||
| 369 | <height>0</height> | ||
| 370 | </size> | ||
| 371 | </property> | ||
| 372 | </spacer> | ||
| 373 | </item> | ||
| 374 | </layout> | 381 | </layout> |
| 375 | </widget> | 382 | </widget> |
| 376 | </item> | 383 | </item> |
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index 34ab01617..a9fde9f4f 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #include "yuzu/configuration/configure_ui.h" | 4 | #include "yuzu/configuration/configure_ui.h" |
| 5 | 5 | ||
| 6 | #include <array> | 6 | #include <array> |
| 7 | #include <cstdlib> | ||
| 7 | #include <set> | 8 | #include <set> |
| 8 | #include <stdexcept> | 9 | #include <stdexcept> |
| 9 | #include <string> | 10 | #include <string> |
| @@ -94,11 +95,7 @@ static void PopulateResolutionComboBox(QComboBox* screenshot_height, QWidget* pa | |||
| 94 | } | 95 | } |
| 95 | 96 | ||
| 96 | static u32 ScreenshotDimensionToInt(const QString& height) { | 97 | static u32 ScreenshotDimensionToInt(const QString& height) { |
| 97 | try { | 98 | return std::strtoul(height.toUtf8(), nullptr, 0); |
| 98 | return std::stoi(height.toStdString()); | ||
| 99 | } catch (std::invalid_argument&) { | ||
| 100 | return 0; | ||
| 101 | } | ||
| 102 | } | 99 | } |
| 103 | 100 | ||
| 104 | ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) | 101 | ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) |
diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp index d63093985..ea8d7add4 100644 --- a/src/yuzu/configuration/shared_widget.cpp +++ b/src/yuzu/configuration/shared_widget.cpp | |||
| @@ -63,7 +63,7 @@ static QString DefaultSuffix(QWidget* parent, Settings::BasicSetting& setting) { | |||
| 63 | return tr("%", context.c_str()); | 63 | return tr("%", context.c_str()); |
| 64 | } | 64 | } |
| 65 | 65 | ||
| 66 | return QStringLiteral(""); | 66 | return default_suffix; |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) { | 69 | QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) { |
| @@ -71,7 +71,7 @@ QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* paren | |||
| 71 | 71 | ||
| 72 | QStyle* style = parent->style(); | 72 | QStyle* style = parent->style(); |
| 73 | QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton)); | 73 | QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton)); |
| 74 | QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(""), parent); | 74 | QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(), parent); |
| 75 | restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count)); | 75 | restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count)); |
| 76 | restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); | 76 | restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); |
| 77 | 77 | ||
| @@ -151,7 +151,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer, | |||
| 151 | return -1; | 151 | return -1; |
| 152 | }; | 152 | }; |
| 153 | 153 | ||
| 154 | const u32 setting_value = std::stoi(setting.ToString()); | 154 | const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0); |
| 155 | combobox->setCurrentIndex(find_index(setting_value)); | 155 | combobox->setCurrentIndex(find_index(setting_value)); |
| 156 | 156 | ||
| 157 | serializer = [this, enumeration]() { | 157 | serializer = [this, enumeration]() { |
| @@ -160,7 +160,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer, | |||
| 160 | }; | 160 | }; |
| 161 | 161 | ||
| 162 | restore_func = [this, find_index]() { | 162 | restore_func = [this, find_index]() { |
| 163 | const u32 global_value = std::stoi(RelevantDefault(setting)); | 163 | const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0); |
| 164 | combobox->setCurrentIndex(find_index(global_value)); | 164 | combobox->setCurrentIndex(find_index(global_value)); |
| 165 | }; | 165 | }; |
| 166 | 166 | ||
| @@ -209,7 +209,7 @@ QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer, | |||
| 209 | } | 209 | } |
| 210 | }; | 210 | }; |
| 211 | 211 | ||
| 212 | const u32 setting_value = std::stoi(setting.ToString()); | 212 | const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0); |
| 213 | set_index(setting_value); | 213 | set_index(setting_value); |
| 214 | 214 | ||
| 215 | serializer = [get_selected]() { | 215 | serializer = [get_selected]() { |
| @@ -218,7 +218,7 @@ QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer, | |||
| 218 | }; | 218 | }; |
| 219 | 219 | ||
| 220 | restore_func = [this, set_index]() { | 220 | restore_func = [this, set_index]() { |
| 221 | const u32 global_value = std::stoi(RelevantDefault(setting)); | 221 | const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0); |
| 222 | set_index(global_value); | 222 | set_index(global_value); |
| 223 | }; | 223 | }; |
| 224 | 224 | ||
| @@ -255,6 +255,59 @@ QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer, | |||
| 255 | return line_edit; | 255 | return line_edit; |
| 256 | } | 256 | } |
| 257 | 257 | ||
| 258 | static void CreateIntSlider(Settings::BasicSetting& setting, bool reversed, float multiplier, | ||
| 259 | QLabel* feedback, const QString& use_format, QSlider* slider, | ||
| 260 | std::function<std::string()>& serializer, | ||
| 261 | std::function<void()>& restore_func) { | ||
| 262 | const int max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0); | ||
| 263 | |||
| 264 | const auto update_feedback = [=](int value) { | ||
| 265 | int present = (reversed ? max_val - value : value) * multiplier + 0.5f; | ||
| 266 | feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>())); | ||
| 267 | }; | ||
| 268 | |||
| 269 | QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback); | ||
| 270 | update_feedback(std::strtol(setting.ToString().c_str(), nullptr, 0)); | ||
| 271 | |||
| 272 | slider->setMinimum(std::strtol(setting.MinVal().c_str(), nullptr, 0)); | ||
| 273 | slider->setMaximum(max_val); | ||
| 274 | slider->setValue(std::strtol(setting.ToString().c_str(), nullptr, 0)); | ||
| 275 | |||
| 276 | serializer = [slider]() { return std::to_string(slider->value()); }; | ||
| 277 | restore_func = [slider, &setting]() { | ||
| 278 | slider->setValue(std::strtol(RelevantDefault(setting).c_str(), nullptr, 0)); | ||
| 279 | }; | ||
| 280 | } | ||
| 281 | |||
| 282 | static void CreateFloatSlider(Settings::BasicSetting& setting, bool reversed, float multiplier, | ||
| 283 | QLabel* feedback, const QString& use_format, QSlider* slider, | ||
| 284 | std::function<std::string()>& serializer, | ||
| 285 | std::function<void()>& restore_func) { | ||
| 286 | const float max_val = std::strtof(setting.MaxVal().c_str(), nullptr); | ||
| 287 | const float min_val = std::strtof(setting.MinVal().c_str(), nullptr); | ||
| 288 | const float use_multiplier = | ||
| 289 | multiplier == default_multiplier ? default_float_multiplier : multiplier; | ||
| 290 | |||
| 291 | const auto update_feedback = [=](float value) { | ||
| 292 | int present = (reversed ? max_val - value : value) + 0.5f; | ||
| 293 | feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>())); | ||
| 294 | }; | ||
| 295 | |||
| 296 | QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback); | ||
| 297 | update_feedback(std::strtof(setting.ToString().c_str(), nullptr)); | ||
| 298 | |||
| 299 | slider->setMinimum(min_val * use_multiplier); | ||
| 300 | slider->setMaximum(max_val * use_multiplier); | ||
| 301 | slider->setValue(std::strtof(setting.ToString().c_str(), nullptr) * use_multiplier); | ||
| 302 | |||
| 303 | serializer = [slider, use_multiplier]() { | ||
| 304 | return std::to_string(slider->value() / use_multiplier); | ||
| 305 | }; | ||
| 306 | restore_func = [slider, &setting, use_multiplier]() { | ||
| 307 | slider->setValue(std::strtof(RelevantDefault(setting).c_str(), nullptr) * use_multiplier); | ||
| 308 | }; | ||
| 309 | } | ||
| 310 | |||
| 258 | QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix, | 311 | QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix, |
| 259 | std::function<std::string()>& serializer, | 312 | std::function<std::string()>& serializer, |
| 260 | std::function<void()>& restore_func, | 313 | std::function<void()>& restore_func, |
| @@ -278,27 +331,19 @@ QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& gi | |||
| 278 | 331 | ||
| 279 | layout->setContentsMargins(0, 0, 0, 0); | 332 | layout->setContentsMargins(0, 0, 0, 0); |
| 280 | 333 | ||
| 281 | int max_val = std::stoi(setting.MaxVal()); | 334 | QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix; |
| 282 | |||
| 283 | QString suffix = | ||
| 284 | given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix; | ||
| 285 | 335 | ||
| 286 | const QString use_format = QStringLiteral("%1").append(suffix); | 336 | const QString use_format = QStringLiteral("%1").append(suffix); |
| 287 | 337 | ||
| 288 | QObject::connect(slider, &QAbstractSlider::valueChanged, [=](int value) { | 338 | if (setting.IsIntegral()) { |
| 289 | int present = (reversed ? max_val - value : value) * multiplier + 0.5f; | 339 | CreateIntSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer, |
| 290 | feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>())); | 340 | restore_func); |
| 291 | }); | 341 | } else { |
| 292 | 342 | CreateFloatSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer, | |
| 293 | slider->setMinimum(std::stoi(setting.MinVal())); | 343 | restore_func); |
| 294 | slider->setMaximum(max_val); | 344 | } |
| 295 | slider->setValue(std::stoi(setting.ToString())); | ||
| 296 | 345 | ||
| 297 | slider->setInvertedAppearance(reversed); | 346 | slider->setInvertedAppearance(reversed); |
| 298 | slider->setInvertedControls(reversed); | ||
| 299 | |||
| 300 | serializer = [this]() { return std::to_string(slider->value()); }; | ||
| 301 | restore_func = [this]() { slider->setValue(std::stoi(RelevantDefault(setting))); }; | ||
| 302 | 347 | ||
| 303 | if (!Settings::IsConfiguringGlobal()) { | 348 | if (!Settings::IsConfiguringGlobal()) { |
| 304 | QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); }); | 349 | QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); }); |
| @@ -311,14 +356,11 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix, | |||
| 311 | std::function<std::string()>& serializer, | 356 | std::function<std::string()>& serializer, |
| 312 | std::function<void()>& restore_func, | 357 | std::function<void()>& restore_func, |
| 313 | const std::function<void()>& touch) { | 358 | const std::function<void()>& touch) { |
| 314 | const int min_val = | 359 | const auto min_val = std::strtol(setting.MinVal().c_str(), nullptr, 0); |
| 315 | setting.Ranged() ? std::stoi(setting.MinVal()) : std::numeric_limits<int>::min(); | 360 | const auto max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0); |
| 316 | const int max_val = | 361 | const auto default_val = std::strtol(setting.ToString().c_str(), nullptr, 0); |
| 317 | setting.Ranged() ? std::stoi(setting.MaxVal()) : std::numeric_limits<int>::max(); | ||
| 318 | const int default_val = std::stoi(setting.ToString()); | ||
| 319 | 362 | ||
| 320 | QString suffix = | 363 | QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix; |
| 321 | given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix; | ||
| 322 | 364 | ||
| 323 | spinbox = new QSpinBox(this); | 365 | spinbox = new QSpinBox(this); |
| 324 | spinbox->setRange(min_val, max_val); | 366 | spinbox->setRange(min_val, max_val); |
| @@ -329,13 +371,13 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix, | |||
| 329 | serializer = [this]() { return std::to_string(spinbox->value()); }; | 371 | serializer = [this]() { return std::to_string(spinbox->value()); }; |
| 330 | 372 | ||
| 331 | restore_func = [this]() { | 373 | restore_func = [this]() { |
| 332 | auto value{std::stol(RelevantDefault(setting))}; | 374 | auto value{std::strtol(RelevantDefault(setting).c_str(), nullptr, 0)}; |
| 333 | spinbox->setValue(value); | 375 | spinbox->setValue(value); |
| 334 | }; | 376 | }; |
| 335 | 377 | ||
| 336 | if (!Settings::IsConfiguringGlobal()) { | 378 | if (!Settings::IsConfiguringGlobal()) { |
| 337 | QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() { | 379 | QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() { |
| 338 | if (spinbox->value() != std::stoi(setting.ToStringGlobal())) { | 380 | if (spinbox->value() != std::strtol(setting.ToStringGlobal().c_str(), nullptr, 0)) { |
| 339 | touch(); | 381 | touch(); |
| 340 | } | 382 | } |
| 341 | }); | 383 | }); |
| @@ -344,6 +386,42 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix, | |||
| 344 | return spinbox; | 386 | return spinbox; |
| 345 | } | 387 | } |
| 346 | 388 | ||
| 389 | QWidget* Widget::CreateDoubleSpinBox(const QString& given_suffix, | ||
| 390 | std::function<std::string()>& serializer, | ||
| 391 | std::function<void()>& restore_func, | ||
| 392 | const std::function<void()>& touch) { | ||
| 393 | const auto min_val = std::strtod(setting.MinVal().c_str(), nullptr); | ||
| 394 | const auto max_val = std::strtod(setting.MaxVal().c_str(), nullptr); | ||
| 395 | const auto default_val = std::strtod(setting.ToString().c_str(), nullptr); | ||
| 396 | |||
| 397 | QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix; | ||
| 398 | |||
| 399 | double_spinbox = new QDoubleSpinBox(this); | ||
| 400 | double_spinbox->setRange(min_val, max_val); | ||
| 401 | double_spinbox->setValue(default_val); | ||
| 402 | double_spinbox->setSuffix(suffix); | ||
| 403 | double_spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); | ||
| 404 | |||
| 405 | serializer = [this]() { return fmt::format("{:f}", double_spinbox->value()); }; | ||
| 406 | |||
| 407 | restore_func = [this]() { | ||
| 408 | auto value{std::strtod(RelevantDefault(setting).c_str(), nullptr)}; | ||
| 409 | double_spinbox->setValue(value); | ||
| 410 | }; | ||
| 411 | |||
| 412 | if (!Settings::IsConfiguringGlobal()) { | ||
| 413 | QObject::connect(double_spinbox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), | ||
| 414 | [this, touch]() { | ||
| 415 | if (double_spinbox->value() != | ||
| 416 | std::strtod(setting.ToStringGlobal().c_str(), nullptr)) { | ||
| 417 | touch(); | ||
| 418 | } | ||
| 419 | }); | ||
| 420 | } | ||
| 421 | |||
| 422 | return double_spinbox; | ||
| 423 | } | ||
| 424 | |||
| 347 | QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer, | 425 | QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer, |
| 348 | std::function<void()>& restore_func, | 426 | std::function<void()>& restore_func, |
| 349 | const std::function<void()>& touch) { | 427 | const std::function<void()>& touch) { |
| @@ -353,7 +431,8 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer, | |||
| 353 | } | 431 | } |
| 354 | 432 | ||
| 355 | auto to_hex = [=](const std::string& input) { | 433 | auto to_hex = [=](const std::string& input) { |
| 356 | return QString::fromStdString(fmt::format("{:08x}", std::stoul(input))); | 434 | return QString::fromStdString( |
| 435 | fmt::format("{:08x}", std::strtoul(input.c_str(), nullptr, 0))); | ||
| 357 | }; | 436 | }; |
| 358 | 437 | ||
| 359 | QRegularExpressionValidator* regex = new QRegularExpressionValidator( | 438 | QRegularExpressionValidator* regex = new QRegularExpressionValidator( |
| @@ -366,7 +445,7 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer, | |||
| 366 | line_edit->setValidator(regex); | 445 | line_edit->setValidator(regex); |
| 367 | 446 | ||
| 368 | auto hex_to_dec = [this]() -> std::string { | 447 | auto hex_to_dec = [this]() -> std::string { |
| 369 | return std::to_string(std::stoul(line_edit->text().toStdString(), nullptr, 16)); | 448 | return std::to_string(std::strtoul(line_edit->text().toStdString().c_str(), nullptr, 16)); |
| 370 | }; | 449 | }; |
| 371 | 450 | ||
| 372 | serializer = [hex_to_dec]() { return hex_to_dec(); }; | 451 | serializer = [hex_to_dec]() { return hex_to_dec(); }; |
| @@ -386,7 +465,8 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict, | |||
| 386 | std::function<void()>& restore_func, | 465 | std::function<void()>& restore_func, |
| 387 | const std::function<void()>& touch) { | 466 | const std::function<void()>& touch) { |
| 388 | const long long current_time = QDateTime::currentSecsSinceEpoch(); | 467 | const long long current_time = QDateTime::currentSecsSinceEpoch(); |
| 389 | const s64 the_time = disabled ? current_time : std::stoll(setting.ToString()); | 468 | const s64 the_time = |
| 469 | disabled ? current_time : std::strtoll(setting.ToString().c_str(), nullptr, 0); | ||
| 390 | const auto default_val = QDateTime::fromSecsSinceEpoch(the_time); | 470 | const auto default_val = QDateTime::fromSecsSinceEpoch(the_time); |
| 391 | 471 | ||
| 392 | date_time_edit = new QDateTimeEdit(this); | 472 | date_time_edit = new QDateTimeEdit(this); |
| @@ -399,7 +479,7 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict, | |||
| 399 | auto get_clear_val = [this, restrict, current_time]() { | 479 | auto get_clear_val = [this, restrict, current_time]() { |
| 400 | return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() { | 480 | return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() { |
| 401 | if (restrict && checkbox->checkState() == Qt::Checked) { | 481 | if (restrict && checkbox->checkState() == Qt::Checked) { |
| 402 | return std::stoll(RelevantDefault(setting)); | 482 | return std::strtoll(RelevantDefault(setting).c_str(), nullptr, 0); |
| 403 | } | 483 | } |
| 404 | return current_time; | 484 | return current_time; |
| 405 | }()); | 485 | }()); |
| @@ -506,8 +586,7 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu | |||
| 506 | } else { | 586 | } else { |
| 507 | data_component = CreateCombobox(serializer, restore_func, touch); | 587 | data_component = CreateCombobox(serializer, restore_func, touch); |
| 508 | } | 588 | } |
| 509 | } else if (type == typeid(u32) || type == typeid(int) || type == typeid(u16) || | 589 | } else if (setting.IsIntegral()) { |
| 510 | type == typeid(s64) || type == typeid(u8)) { | ||
| 511 | switch (request) { | 590 | switch (request) { |
| 512 | case RequestType::Slider: | 591 | case RequestType::Slider: |
| 513 | case RequestType::ReverseSlider: | 592 | case RequestType::ReverseSlider: |
| @@ -534,6 +613,20 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu | |||
| 534 | default: | 613 | default: |
| 535 | UNIMPLEMENTED(); | 614 | UNIMPLEMENTED(); |
| 536 | } | 615 | } |
| 616 | } else if (setting.IsFloatingPoint()) { | ||
| 617 | switch (request) { | ||
| 618 | case RequestType::Default: | ||
| 619 | case RequestType::SpinBox: | ||
| 620 | data_component = CreateDoubleSpinBox(suffix, serializer, restore_func, touch); | ||
| 621 | break; | ||
| 622 | case RequestType::Slider: | ||
| 623 | case RequestType::ReverseSlider: | ||
| 624 | data_component = CreateSlider(request == RequestType::ReverseSlider, multiplier, suffix, | ||
| 625 | serializer, restore_func, touch); | ||
| 626 | break; | ||
| 627 | default: | ||
| 628 | UNIMPLEMENTED(); | ||
| 629 | } | ||
| 537 | } else if (type == typeid(std::string)) { | 630 | } else if (type == typeid(std::string)) { |
| 538 | switch (request) { | 631 | switch (request) { |
| 539 | case RequestType::Default: | 632 | case RequestType::Default: |
| @@ -638,10 +731,10 @@ Widget::Widget(Settings::BasicSetting* setting_, const TranslationMap& translati | |||
| 638 | return std::pair{translations.at(id).first, translations.at(id).second}; | 731 | return std::pair{translations.at(id).first, translations.at(id).second}; |
| 639 | } | 732 | } |
| 640 | LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label); | 733 | LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label); |
| 641 | return std::pair{QString::fromStdString(setting_label), QStringLiteral("")}; | 734 | return std::pair{QString::fromStdString(setting_label), QStringLiteral()}; |
| 642 | }(); | 735 | }(); |
| 643 | 736 | ||
| 644 | if (label == QStringLiteral("")) { | 737 | if (label == QStringLiteral()) { |
| 645 | LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...", | 738 | LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...", |
| 646 | setting.GetLabel()); | 739 | setting.GetLabel()); |
| 647 | return; | 740 | return; |
diff --git a/src/yuzu/configuration/shared_widget.h b/src/yuzu/configuration/shared_widget.h index 5303dd898..226284cf3 100644 --- a/src/yuzu/configuration/shared_widget.h +++ b/src/yuzu/configuration/shared_widget.h | |||
| @@ -22,6 +22,7 @@ class QObject; | |||
| 22 | class QPushButton; | 22 | class QPushButton; |
| 23 | class QSlider; | 23 | class QSlider; |
| 24 | class QSpinBox; | 24 | class QSpinBox; |
| 25 | class QDoubleSpinBox; | ||
| 25 | class QRadioButton; | 26 | class QRadioButton; |
| 26 | 27 | ||
| 27 | namespace Settings { | 28 | namespace Settings { |
| @@ -43,6 +44,10 @@ enum class RequestType { | |||
| 43 | MaxEnum, | 44 | MaxEnum, |
| 44 | }; | 45 | }; |
| 45 | 46 | ||
| 47 | constexpr float default_multiplier{1.f}; | ||
| 48 | constexpr float default_float_multiplier{100.f}; | ||
| 49 | static const QString default_suffix = QStringLiteral(); | ||
| 50 | |||
| 46 | class Widget : public QWidget { | 51 | class Widget : public QWidget { |
| 47 | Q_OBJECT | 52 | Q_OBJECT |
| 48 | 53 | ||
| @@ -66,8 +71,9 @@ public: | |||
| 66 | const ComboboxTranslationMap& combobox_translations, QWidget* parent, | 71 | const ComboboxTranslationMap& combobox_translations, QWidget* parent, |
| 67 | bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_, | 72 | bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_, |
| 68 | RequestType request = RequestType::Default, bool managed = true, | 73 | RequestType request = RequestType::Default, bool managed = true, |
| 69 | float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, | 74 | float multiplier = default_multiplier, |
| 70 | const QString& suffix = QStringLiteral("")); | 75 | Settings::BasicSetting* other_setting = nullptr, |
| 76 | const QString& suffix = default_suffix); | ||
| 71 | virtual ~Widget(); | 77 | virtual ~Widget(); |
| 72 | 78 | ||
| 73 | /** | 79 | /** |
| @@ -89,6 +95,7 @@ public: | |||
| 89 | QPushButton* restore_button{}; ///< Restore button for custom configurations | 95 | QPushButton* restore_button{}; ///< Restore button for custom configurations |
| 90 | QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit | 96 | QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit |
| 91 | QSpinBox* spinbox{}; | 97 | QSpinBox* spinbox{}; |
| 98 | QDoubleSpinBox* double_spinbox{}; | ||
| 92 | QCheckBox* checkbox{}; | 99 | QCheckBox* checkbox{}; |
| 93 | QSlider* slider{}; | 100 | QSlider* slider{}; |
| 94 | QComboBox* combobox{}; | 101 | QComboBox* combobox{}; |
| @@ -126,6 +133,9 @@ private: | |||
| 126 | const std::function<void()>& touch); | 133 | const std::function<void()>& touch); |
| 127 | QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer, | 134 | QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer, |
| 128 | std::function<void()>& restore_func, const std::function<void()>& touch); | 135 | std::function<void()>& restore_func, const std::function<void()>& touch); |
| 136 | QWidget* CreateDoubleSpinBox(const QString& suffix, std::function<std::string()>& serializer, | ||
| 137 | std::function<void()>& restore_func, | ||
| 138 | const std::function<void()>& touch); | ||
| 129 | 139 | ||
| 130 | QWidget* parent; | 140 | QWidget* parent; |
| 131 | const TranslationMap& translations; | 141 | const TranslationMap& translations; |
| @@ -145,14 +155,15 @@ public: | |||
| 145 | Widget* BuildWidget(Settings::BasicSetting* setting, | 155 | Widget* BuildWidget(Settings::BasicSetting* setting, |
| 146 | std::vector<std::function<void(bool)>>& apply_funcs, | 156 | std::vector<std::function<void(bool)>>& apply_funcs, |
| 147 | RequestType request = RequestType::Default, bool managed = true, | 157 | RequestType request = RequestType::Default, bool managed = true, |
| 148 | float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, | 158 | float multiplier = default_multiplier, |
| 149 | const QString& suffix = QStringLiteral("")) const; | 159 | Settings::BasicSetting* other_setting = nullptr, |
| 160 | const QString& suffix = default_suffix) const; | ||
| 150 | 161 | ||
| 151 | Widget* BuildWidget(Settings::BasicSetting* setting, | 162 | Widget* BuildWidget(Settings::BasicSetting* setting, |
| 152 | std::vector<std::function<void(bool)>>& apply_funcs, | 163 | std::vector<std::function<void(bool)>>& apply_funcs, |
| 153 | Settings::BasicSetting* other_setting, | 164 | Settings::BasicSetting* other_setting, |
| 154 | RequestType request = RequestType::Default, | 165 | RequestType request = RequestType::Default, |
| 155 | const QString& suffix = QStringLiteral("")) const; | 166 | const QString& suffix = default_suffix) const; |
| 156 | 167 | ||
| 157 | const ComboboxTranslationMap& ComboboxTranslations() const; | 168 | const ComboboxTranslationMap& ComboboxTranslations() const; |
| 158 | 169 | ||
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h index 848239c35..56eee8d82 100644 --- a/src/yuzu/hotkeys.h +++ b/src/yuzu/hotkeys.h | |||
| @@ -4,10 +4,12 @@ | |||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <map> | 6 | #include <map> |
| 7 | #include <QKeySequence> | ||
| 8 | #include <QString> | ||
| 9 | #include <QWidget> | ||
| 7 | #include "core/hid/hid_types.h" | 10 | #include "core/hid/hid_types.h" |
| 8 | 11 | ||
| 9 | class QDialog; | 12 | class QDialog; |
| 10 | class QKeySequence; | ||
| 11 | class QSettings; | 13 | class QSettings; |
| 12 | class QShortcut; | 14 | class QShortcut; |
| 13 | class ControllerShortcut; | 15 | class ControllerShortcut; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 97d216638..adb7b332f 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include <memory> | 9 | #include <memory> |
| 10 | #include <thread> | 10 | #include <thread> |
| 11 | #include "core/loader/nca.h" | 11 | #include "core/loader/nca.h" |
| 12 | #include "core/tools/renderdoc.h" | ||
| 12 | #ifdef __APPLE__ | 13 | #ifdef __APPLE__ |
| 13 | #include <unistd.h> // for chdir | 14 | #include <unistd.h> // for chdir |
| 14 | #endif | 15 | #endif |
| @@ -1348,6 +1349,11 @@ void GMainWindow::InitializeHotkeys() { | |||
| 1348 | connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { | 1349 | connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { |
| 1349 | Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); | 1350 | Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); |
| 1350 | }); | 1351 | }); |
| 1352 | connect_shortcut(QStringLiteral("Toggle Renderdoc Capture"), [this] { | ||
| 1353 | if (Settings::values.enable_renderdoc_hotkey) { | ||
| 1354 | system->GetRenderdocAPI().ToggleCapture(); | ||
| 1355 | } | ||
| 1356 | }); | ||
| 1351 | connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { | 1357 | connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { |
| 1352 | if (Settings::values.mouse_enabled) { | 1358 | if (Settings::values.mouse_enabled) { |
| 1353 | Settings::values.mouse_panning = false; | 1359 | Settings::values.mouse_panning = false; |
| @@ -1545,6 +1551,7 @@ void GMainWindow::ConnectMenuEvents() { | |||
| 1545 | // Tools | 1551 | // Tools |
| 1546 | connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, | 1552 | connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, |
| 1547 | ReinitializeKeyBehavior::Warning)); | 1553 | ReinitializeKeyBehavior::Warning)); |
| 1554 | connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit); | ||
| 1548 | connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot); | 1555 | connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot); |
| 1549 | 1556 | ||
| 1550 | // TAS | 1557 | // TAS |
| @@ -1584,6 +1591,8 @@ void GMainWindow::UpdateMenuState() { | |||
| 1584 | } | 1591 | } |
| 1585 | 1592 | ||
| 1586 | multiplayer_state->UpdateNotificationStatus(); | 1593 | multiplayer_state->UpdateNotificationStatus(); |
| 1594 | |||
| 1595 | ui->action_Load_Mii_Edit->setEnabled(CheckFirmwarePresence()); | ||
| 1587 | } | 1596 | } |
| 1588 | 1597 | ||
| 1589 | void GMainWindow::OnDisplayTitleBars(bool show) { | 1598 | void GMainWindow::OnDisplayTitleBars(bool show) { |
| @@ -3104,10 +3113,9 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 3104 | QFuture<InstallResult> future; | 3113 | QFuture<InstallResult> future; |
| 3105 | InstallResult result; | 3114 | InstallResult result; |
| 3106 | 3115 | ||
| 3107 | if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || | 3116 | if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { |
| 3108 | file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { | ||
| 3109 | 3117 | ||
| 3110 | future = QtConcurrent::run([this, &file] { return InstallNSPXCI(file); }); | 3118 | future = QtConcurrent::run([this, &file] { return InstallNSP(file); }); |
| 3111 | 3119 | ||
| 3112 | while (!future.isFinished()) { | 3120 | while (!future.isFinished()) { |
| 3113 | QCoreApplication::processEvents(); | 3121 | QCoreApplication::processEvents(); |
| @@ -3166,7 +3174,7 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 3166 | ui->action_Install_File_NAND->setEnabled(true); | 3174 | ui->action_Install_File_NAND->setEnabled(true); |
| 3167 | } | 3175 | } |
| 3168 | 3176 | ||
| 3169 | InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { | 3177 | InstallResult GMainWindow::InstallNSP(const QString& filename) { |
| 3170 | const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, | 3178 | const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, |
| 3171 | const FileSys::VirtualFile& dest, std::size_t block_size) { | 3179 | const FileSys::VirtualFile& dest, std::size_t block_size) { |
| 3172 | if (src == nullptr || dest == nullptr) { | 3180 | if (src == nullptr || dest == nullptr) { |
| @@ -3200,9 +3208,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { | |||
| 3200 | return InstallResult::Failure; | 3208 | return InstallResult::Failure; |
| 3201 | } | 3209 | } |
| 3202 | } else { | 3210 | } else { |
| 3203 | const auto xci = std::make_shared<FileSys::XCI>( | 3211 | return InstallResult::Failure; |
| 3204 | vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); | ||
| 3205 | nsp = xci->GetSecurePartitionNSP(); | ||
| 3206 | } | 3212 | } |
| 3207 | 3213 | ||
| 3208 | if (nsp->GetStatus() != Loader::ResultStatus::Success) { | 3214 | if (nsp->GetStatus() != Loader::ResultStatus::Success) { |
| @@ -4128,6 +4134,27 @@ void GMainWindow::OnToggleStatusBar() { | |||
| 4128 | statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); | 4134 | statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); |
| 4129 | } | 4135 | } |
| 4130 | 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 | |||
| 4131 | void GMainWindow::OnCaptureScreenshot() { | 4158 | void GMainWindow::OnCaptureScreenshot() { |
| 4132 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { | 4159 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { |
| 4133 | return; | 4160 | return; |
| @@ -4534,6 +4561,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { | |||
| 4534 | if (behavior == ReinitializeKeyBehavior::Warning) { | 4561 | if (behavior == ReinitializeKeyBehavior::Warning) { |
| 4535 | game_list->PopulateAsync(UISettings::values.game_dirs); | 4562 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 4536 | } | 4563 | } |
| 4564 | |||
| 4565 | UpdateMenuState(); | ||
| 4537 | } | 4566 | } |
| 4538 | 4567 | ||
| 4539 | bool GMainWindow::CheckSystemArchiveDecryption() { | 4568 | bool GMainWindow::CheckSystemArchiveDecryption() { |
| @@ -4555,6 +4584,22 @@ bool GMainWindow::CheckSystemArchiveDecryption() { | |||
| 4555 | return mii_nca->GetRomFS().get() != nullptr; | 4584 | return mii_nca->GetRomFS().get() != nullptr; |
| 4556 | } | 4585 | } |
| 4557 | 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 | |||
| 4558 | bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, | 4603 | bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, |
| 4559 | u64* selected_title_id, u8* selected_content_record_type) { | 4604 | u64* selected_title_id, u8* selected_content_record_type) { |
| 4560 | 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> |
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index c42d98709..0d25ff400 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp | |||
| @@ -259,7 +259,7 @@ void Config::ReadValues() { | |||
| 259 | std::stringstream ss(title_list); | 259 | std::stringstream ss(title_list); |
| 260 | std::string line; | 260 | std::string line; |
| 261 | while (std::getline(ss, line, '|')) { | 261 | while (std::getline(ss, line, '|')) { |
| 262 | const auto title_id = std::stoul(line, nullptr, 16); | 262 | const auto title_id = std::strtoul(line.c_str(), nullptr, 16); |
| 263 | const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); | 263 | const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); |
| 264 | 264 | ||
| 265 | std::stringstream inner_ss(disabled_list); | 265 | std::stringstream inner_ss(disabled_list); |
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 55d0938f7..087cfaa26 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp | |||
| @@ -264,8 +264,9 @@ int main(int argc, char** argv) { | |||
| 264 | nickname = match[1]; | 264 | nickname = match[1]; |
| 265 | password = match[2]; | 265 | password = match[2]; |
| 266 | address = match[3]; | 266 | address = match[3]; |
| 267 | if (!match[4].str().empty()) | 267 | if (!match[4].str().empty()) { |
| 268 | port = static_cast<u16>(std::stoi(match[4])); | 268 | port = static_cast<u16>(std::strtoul(match[4].str().c_str(), nullptr, 0)); |
| 269 | } | ||
| 269 | std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); | 270 | std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); |
| 270 | if (!std::regex_match(nickname, nickname_re)) { | 271 | if (!std::regex_match(nickname, nickname_re)) { |
| 271 | std::cout | 272 | std::cout |