Compare commits

...

75 Commits

Author SHA1 Message Date
1e6eba32ae Android #82 2023-09-26 00:58:17 +00:00
a19f62e636 Merge pull request #11583 from t895/overlay-fix-2
android: Use measured size of view for input overlay bounds
2023-09-25 10:27:09 -04:00
854457a392 Merge pull request #11225 from FernandoS27/no-laxatives-in-santas-cookies
Y.F.C: Rework the Query Cache.
2023-09-25 09:18:29 -04:00
0d7d3d938c android: Use measured size of view for input overlay bounds
Even after updating the androidx window library, this did not fix the issue for all devices. This ensures that the measured size of the overlay will be used instead of a potentially larger one seen by androidx.
2023-09-24 22:18:38 -04:00
37a4a6751a Merge pull request #11569 from german77/lle_applet
service: am: Add support for LLE Mii Edit Applet
2023-09-24 10:50:38 -04:00
93a1cd75fe Merge pull request #11562 from GPUCode/srgb-madness
vk_texture_cache: Limit srgb block to transcoding only
2023-09-24 10:50:28 -04:00
b356909212 Merge pull request #11165 from Morph1984/ds_blit
vulkan_device: Return true if either depth/stencil format supports blit
2023-09-24 10:50:04 -04:00
bb28f4a0c4 service: mii: Limit checks to string size 2023-09-23 20:14:37 -06:00
0993c71335 service: hid: Set last connected controller as active 2023-09-23 20:14:37 -06:00
6e1b113c89 service: am: Stub to exit applet cleanly 2023-09-23 20:14:37 -06:00
c46f54b091 service: am: Implement stuff needed for Mii Edit 2023-09-23 20:14:33 -06:00
3983ce9b5c service: fsp: Implement CreateSaveDataFileSystemBySystemSaveDataId and OpenSaveDataFileSystemBySystemSaveDataId 2023-09-23 20:13:36 -06:00
d2cd08e3e1 service: ns: Implement GetSharedFontInOrderOfPriorityForSystem 2023-09-23 20:13:36 -06:00
bb4ae5ee53 yuzu: Add button to boot mii edit from firmware 2023-09-23 20:13:36 -06:00
57d8cd6c40 Query Cache: Fix Prefix Sums 2023-09-23 23:05:30 +02:00
bf0d6b8806 Query Cache: Fix behavior in Normal Accuracy 2023-09-23 23:05:30 +02:00
a07c88e686 Query Cache: Simplify Prefix Sum compute shader 2023-09-23 23:05:30 +02:00
c8237d5c31 Query Cache: Implement host side sample counting. 2023-09-23 23:05:30 +02:00
2fea1b8407 Query Cache: Fix guest side sample counting 2023-09-23 23:05:30 +02:00
282ae8fa51 Query Cache: address issues 2023-09-23 23:05:30 +02:00
aa6587d854 QueryCache: Implement dependant queries. 2023-09-23 23:05:29 +02:00
57401589c2 Macro HLE: Add DrawIndirectByteCount 2023-09-23 23:05:29 +02:00
f1a2e36711 Query Cachge: Fully rework Vulkan's query cache 2023-09-23 23:05:29 +02:00
bdc01254a9 Query Cache: Setup Base rework 2023-09-23 23:05:29 +02:00
ace91dd0c0 Merge pull request #11567 from liamwhite/fixing-my-error
emit_spirv: fix incorrect use of descriptor index in image atomics
2023-09-23 13:00:31 +02:00
2921a24268 Merge pull request #11573 from t895/import-fix
android: Use smaller read buffer size for exporting user data
2023-09-22 22:02:06 -04:00
5269a46399 android: Use smaller read buffer size for exporting user data
The File.readBytes() extension attempts to load an entire file into a byte array. This would cause crashes when loading huge files into memory.
2023-09-22 16:51:48 -04:00
33e2dce715 Merge pull request #11572 from t895/import-heuristic
android: Adjust valid user data check
2023-09-22 14:53:39 -04:00
f3bc7354b1 android: Adjust valid user data check 2023-09-22 12:05:44 -04:00
bd5ae33153 Merge pull request #11561 from german77/hle_applet
am: mii_edit: Implement DB operations
2023-09-22 09:56:14 -04:00
16f1592e50 Merge pull request #11557 from GPUCode/brr-format
renderer_vulkan: Correct component order for A4B4G4R4_UNORM
2023-09-22 09:56:04 -04:00
fda08cbbb0 Merge pull request #11563 from Kelebek1/dma_regs
Fix DMA engine register offsets
2023-09-22 09:55:54 -04:00
1e24d02434 emit_spirv: fix incorrect use of descriptor index in image atomics 2023-09-22 00:39:09 -04:00
a57ca3fb66 am: mii_edit: Implement DB operations 2023-09-21 18:21:39 -06:00
c619199bb4 Merge pull request #11564 from t895/overlay-inset-fix
android: Update androidx window library to 1.2.0-beta03
2023-09-21 19:15:36 -04:00
703bf7cfce android: Update androidx window library to 1.2.0-beta03
Fixes an issue with the input overlay on certain devices where the controls would appear offscreen.
2023-09-21 17:36:14 -04:00
4f69be8169 Fix DMA engine register offsets 2023-09-21 20:21:00 +01:00
b6ad7e263b vk_texture_cache: Limit srgb block to transcoding only 2023-09-21 21:46:35 +03:00
9e9cb28471 Merge pull request #11555 from yuzu-emu/revert-11551-allow-save-imports-always
Revert "android: Allow save imports always"
2023-09-21 09:21:19 -04:00
2ffea42ec8 Merge pull request #11553 from rkfg/pfs-fix
pfs: Fix reading filenames past the buffer end
2023-09-21 09:21:08 -04:00
4a59dc2947 renderer_vulkan: Correct component order for A4B4G4R4_UNORM 2023-09-21 15:33:44 +03:00
c644c1a90a Revert "android: Allow save imports always" 2023-09-21 02:57:28 -04:00
753bc3a448 pfs: Fix reading filenames past the buffer end 2023-09-21 05:12:05 +03:00
c708643972 Merge pull request #11551 from t895/allow-save-imports-always
android: Allow save imports always
2023-09-20 18:32:31 -04:00
a85325f56a android: Remove unused strings related to the save manager 2023-09-20 15:01:03 -04:00
bdb4fd208f android: Allow importing saves even if no saves are found
Exporting still won't be allowed on an empty save directory.
2023-09-20 15:00:34 -04:00
1fae4a01a8 Merge pull request #11543 from t895/import-export-user-data
android: Add import/export buttons for user data
2023-09-20 10:17:24 -04:00
1e740df9b8 android: Add import/export buttons for user data 2023-09-19 15:54:47 -04:00
df56ecc318 Merge pull request #11542 from t895/touch-offset-fix
android: Screen orientation and aspect ratio fixes
2023-09-19 09:25:09 -04:00
49cb89e324 Merge pull request #11526 from german77/mii_service_v2
service: mii: Update implementation Part2 - Mii database support
2023-09-19 09:24:49 -04:00
da8cbbf958 Merge pull request #11536 from abouvier/renderdoc
cmake: prefer system renderdoc header
2023-09-19 09:24:36 -04:00
55087ab08a Merge pull request #11538 from cathyjf/renderdoc-check-correct-win32-symbol
renderdoc: Check for `_WIN32` symbol rather than `WIN32`
2023-09-19 09:24:23 -04:00
d7e8926ff6 Merge pull request #11540 from liamwhite/aoc-ec
aoc: stub purchase info calls
2023-09-19 09:24:08 -04:00
7dd3d1b8ad android: Ignore validation layers library in git 2023-09-19 00:31:43 -04:00
fd09784231 android: Don't pause emulation when entering PiP 2023-09-19 00:31:43 -04:00
3b612cff28 android: Fix showing input overlay in PiP 2023-09-19 00:31:43 -04:00
32d65fc8de android: Properly update emulation surface
Previously the emulation surface wasn't being updated during configuration changes and only during specific view events. This would break input and the screen dimensions after each orientation/aspect ratio change. Now a new surface is provided every time and the display dimensions are updated as needed.
2023-09-19 00:31:43 -04:00
3ff29de4a1 aoc: stub purchase info calls 2023-09-18 16:20:53 -04:00
9fef6560f0 renderdoc: Check for _WIN32 symbol rather than WIN32 2023-09-18 12:09:51 -07:00
dca36ebb87 service: mii: Address review comments 2023-09-18 11:08:04 -06:00
f93f31f4ae cmake: prefer system renderdoc header 2023-09-18 18:35:20 +02:00
974380fe10 Merge pull request #11258 from Squall-Leonhart/Z16_Assert_Fix
Fix a logged assert in the format lookup table for Z16
2023-09-18 09:31:05 -04:00
d6cf54dd2f Merge pull request #11520 from Kelebek1/estimated_time
Do not consider voice commands in time estimation, fix adpcm estimate
2023-09-18 09:30:56 -04:00
5d7571114e Do not consider voice commands in time estimation, fix adpcm estimate 2023-09-18 10:38:14 +01:00
2fb71aecb0 service: mii: Implement the rest of the service 2023-09-17 16:06:25 -06:00
9a878de33f service: mii: Implement database manager 2023-09-17 16:06:25 -06:00
a50b50f8b4 service: mii: Implement figurine database 2023-09-17 16:06:25 -06:00
bd409c3416 service: mii: Add device crc16 2023-09-17 16:06:25 -06:00
2f22b53732 service: nfc: Fully Implement GetRegisterInfoPrivate 2023-09-17 16:06:25 -06:00
1e8616bd01 service: mii: Complete structs and fix mistakes 2023-09-17 16:06:23 -06:00
e3c546a1ed android: Export PiP receiver on API 33 and later
Could cause crashes on API 33+ devices
2023-09-17 17:33:33 -04:00
e3dd78e414 Needed to make this an extra case so it didnt also start asserting in BOTW.
Thanks Liam
2023-08-11 08:45:15 +10:00
b9ab44ed0e Fix an assert in the format lookup table fir Z16
Came across this while looking into Asterix and Obelix XXL glitching
2023-08-11 08:18:54 +10:00
d31676935e vulkan_device: Test depth stencil blit support by format 2023-07-31 19:14:20 -04:00
26658c2e93 vulkan_device: Return true if either depth/stencil format supports blit
On devices that don't support D24S8 but supports D32S8, this should still return true if D32S8 supports src and dst blit
2023-07-26 20:21:37 -04:00
113 changed files with 6456 additions and 898 deletions

View File

@ -333,6 +333,7 @@ find_package(LLVM 17 MODULE COMPONENTS Demangle)
find_package(lz4 REQUIRED)
find_package(nlohmann_json 3.8 REQUIRED)
find_package(Opus 1.3 MODULE)
find_package(RenderDoc MODULE)
find_package(VulkanMemoryAllocator CONFIG)
find_package(ZLIB 1.2 REQUIRED)
find_package(zstd 1.5 REQUIRED)

View File

@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: 2023 Alexandre Bouvier <contact@amb.tf>
#
# SPDX-License-Identifier: GPL-3.0-or-later
find_path(RenderDoc_INCLUDE_DIR renderdoc_app.h)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(RenderDoc
REQUIRED_VARS RenderDoc_INCLUDE_DIR
)
if (RenderDoc_FOUND AND NOT TARGET RenderDoc::API)
add_library(RenderDoc::API INTERFACE IMPORTED)
set_target_properties(RenderDoc::API PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${RenderDoc_INCLUDE_DIR}"
)
endif()
mark_as_advanced(RenderDoc_INCLUDE_DIR)

View File

@ -1,3 +1,11 @@
| Pull Request | Commit | Title | Author | Merged? |
|----|----|----|----|----|
End of merge log. You can find the original README.md below the break.
-----
<!--
SPDX-FileCopyrightText: 2018 yuzu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later

View File

@ -174,8 +174,11 @@ target_include_directories(stb PUBLIC ./stb)
add_library(bc_decoder bc_decoder/bc_decoder.cpp)
target_include_directories(bc_decoder PUBLIC ./bc_decoder)
add_library(renderdoc INTERFACE)
target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc)
if (NOT TARGET RenderDoc::API)
add_library(renderdoc INTERFACE)
target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc)
add_library(RenderDoc::API ALIAS renderdoc)
endif()
if (ANDROID)
if (ARCHITECTURE_arm64)

View File

@ -63,3 +63,6 @@ fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Autogenerated library for vulkan validation layers
libVkLayer_khronos_validation.so

View File

@ -214,7 +214,7 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
implementation("io.coil-kt:coil:2.2.2")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.window:window:1.1.0")
implementation("androidx.window:window:1.2.0-beta03")
implementation("org.ini4j:ini4j:0.5.4")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")

View File

@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.activities
import android.annotation.SuppressLint
import android.app.Activity
import android.app.PendingIntent
import android.app.PictureInPictureParams
@ -397,6 +398,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
}
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
@ -409,7 +411,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
addAction(actionMute)
addAction(actionUnmute)
}.also {
registerReceiver(pictureInPictureReceiver, it)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(pictureInPictureReceiver, it, RECEIVER_EXPORTED)
} else {
registerReceiver(pictureInPictureReceiver, it)
}
}
} else {
try {

View File

@ -49,6 +49,7 @@ class HomeSettingAdapter(
holder.option.onClick.invoke()
} else {
MessageDialogFragment.newInstance(
activity,
titleId = holder.option.disabledTitleId,
descriptionId = holder.option.disabledMessageId
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)

View File

@ -26,6 +26,7 @@ import org.yuzu.yuzu_emu.BuildConfig
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentAboutBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
class AboutFragment : Fragment() {
private var _binding: FragmentAboutBinding? = null
@ -92,6 +93,12 @@ class AboutFragment : Fragment() {
}
}
val mainActivity = requireActivity() as MainActivity
binding.buttonExport.setOnClickListener { mainActivity.exportUserData.launch("export.zip") }
binding.buttonImport.setOnClickListener {
mainActivity.importUserData.launch(arrayOf("application/zip"))
}
binding.buttonDiscord.setOnClickListener { openLink(getString(R.string.support_link)) }
binding.buttonWebsite.setOnClickListener { openLink(getString(R.string.website_link)) }
binding.buttonGithub.setOnClickListener { openLink(getString(R.string.github_link)) }

View File

@ -15,7 +15,6 @@ import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Rational
import android.view.*
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
@ -287,13 +286,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
updateScreenLayout()
if (emulationActivity?.isInPictureInPictureMode == true) {
if (binding.drawerLayout.isOpen) {
binding.drawerLayout.close()
}
if (EmulationMenuSettings.showOverlay) {
binding.surfaceInputOverlay.post {
binding.surfaceInputOverlay.visibility = View.VISIBLE
binding.surfaceInputOverlay.visibility = View.INVISIBLE
}
}
} else {
@ -328,7 +328,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
override fun onPause() {
if (emulationState.isRunning) {
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
emulationState.pause()
}
super.onPause()
@ -394,16 +394,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
private fun updateScreenLayout() {
binding.surfaceEmulation.setAspectRatio(
when (IntSetting.RENDERER_ASPECT_RATIO.int) {
0 -> Rational(16, 9)
1 -> Rational(4, 3)
2 -> Rational(21, 9)
3 -> Rational(16, 10)
4 -> null // Stretch
else -> Rational(16, 9)
}
)
binding.surfaceEmulation.setAspectRatio(null)
emulationActivity?.buildPictureInPictureParams()
updateOrientation()
}
@ -693,7 +684,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private class EmulationState(private val gamePath: String) {
private var state: State
private var surface: Surface? = null
private var runWhenSurfaceIsValid = false
init {
// Starting state is stopped.
@ -751,8 +741,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
// If the surface is set, run now. Otherwise, wait for it to get set.
if (surface != null) {
runWithValidSurface()
} else {
runWhenSurfaceIsValid = true
}
}
@ -760,7 +748,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@Synchronized
fun newSurface(surface: Surface?) {
this.surface = surface
if (runWhenSurfaceIsValid) {
if (this.surface != null) {
runWithValidSurface()
}
}
@ -788,10 +776,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
private fun runWithValidSurface() {
runWhenSurfaceIsValid = false
NativeLibrary.surfaceChanged(surface)
when (state) {
State.STOPPED -> {
NativeLibrary.surfaceChanged(surface)
val emulationThread = Thread({
Log.debug("[EmulationFragment] Starting emulation thread.")
NativeLibrary.run(gamePath)
@ -801,7 +788,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
State.PAUSED -> {
Log.debug("[EmulationFragment] Resuming emulation.")
NativeLibrary.surfaceChanged(surface)
NativeLibrary.unpauseEmulation()
}

View File

@ -187,6 +187,7 @@ class ImportExportSavesFragment : DialogFragment() {
withContext(Dispatchers.Main) {
if (!validZip) {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.save_file_invalid_zip_structure,
descriptionId = R.string.save_file_invalid_zip_structure_description
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)

View File

@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -18,6 +19,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel
@ -28,19 +30,27 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val titleId = requireArguments().getInt(TITLE)
val cancellable = requireArguments().getBoolean(CANCELLABLE)
binding = DialogProgressBarBinding.inflate(layoutInflater)
binding.progressBar.isIndeterminate = true
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(titleId)
.setView(binding.root)
.create()
dialog.setCanceledOnTouchOutside(false)
if (cancellable) {
dialog.setNegativeButton(android.R.string.cancel) { _: DialogInterface, _: Int ->
taskViewModel.setCancelled(true)
}
}
val alertDialog = dialog.create()
alertDialog.setCanceledOnTouchOutside(false)
if (!taskViewModel.isRunning.value) {
taskViewModel.runTask()
}
return dialog
return alertDialog
}
override fun onCreateView(
@ -53,21 +63,35 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
taskViewModel.isComplete.collect {
if (it) {
dismiss()
when (val result = taskViewModel.result.value) {
is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG)
.show()
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
taskViewModel.isComplete.collect {
if (it) {
dismiss()
when (val result = taskViewModel.result.value) {
is String -> Toast.makeText(
requireContext(),
result,
Toast.LENGTH_LONG
).show()
is MessageDialogFragment -> result.show(
requireActivity().supportFragmentManager,
MessageDialogFragment.TAG
)
is MessageDialogFragment -> result.show(
requireActivity().supportFragmentManager,
MessageDialogFragment.TAG
)
}
taskViewModel.clear()
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
taskViewModel.cancelled.collect {
if (it) {
dialog?.setTitle(R.string.cancelling)
}
taskViewModel.clear()
}
}
}
@ -78,16 +102,19 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
const val TAG = "IndeterminateProgressDialogFragment"
private const val TITLE = "Title"
private const val CANCELLABLE = "Cancellable"
fun newInstance(
activity: AppCompatActivity,
titleId: Int,
cancellable: Boolean = false,
task: () -> Any
): IndeterminateProgressDialogFragment {
val dialog = IndeterminateProgressDialogFragment()
val args = Bundle()
ViewModelProvider(activity)[TaskViewModel::class.java].task = task
args.putInt(TITLE, titleId)
args.putBoolean(CANCELLABLE, cancellable)
dialog.arguments = args
return dialog
}

View File

@ -4,14 +4,21 @@
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.model.MessageDialogViewModel
class MessageDialogFragment : DialogFragment() {
private val messageDialogViewModel: MessageDialogViewModel by activityViewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val titleId = requireArguments().getInt(TITLE_ID)
val titleString = requireArguments().getString(TITLE_STRING)!!
@ -37,6 +44,12 @@ class MessageDialogFragment : DialogFragment() {
return dialog.show()
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
messageDialogViewModel.dismissAction.invoke()
messageDialogViewModel.clear()
}
private fun openLink(link: String) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
startActivity(intent)
@ -52,11 +65,13 @@ class MessageDialogFragment : DialogFragment() {
private const val HELP_LINK = "Link"
fun newInstance(
activity: FragmentActivity,
titleId: Int = 0,
titleString: String = "",
descriptionId: Int = 0,
descriptionString: String = "",
helpLinkId: Int = 0
helpLinkId: Int = 0,
dismissAction: () -> Unit = {}
): MessageDialogFragment {
val dialog = MessageDialogFragment()
val bundle = Bundle()
@ -67,6 +82,8 @@ class MessageDialogFragment : DialogFragment() {
putString(DESCRIPTION_STRING, descriptionString)
putInt(HELP_LINK, helpLinkId)
}
ViewModelProvider(activity)[MessageDialogViewModel::class.java].dismissAction =
dismissAction
dialog.arguments = bundle
return dialog
}

View File

@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.model
import androidx.lifecycle.ViewModel
class MessageDialogViewModel : ViewModel() {
var dismissAction: () -> Unit = {}
fun clear() {
dismissAction = {}
}
}

View File

@ -20,12 +20,20 @@ class TaskViewModel : ViewModel() {
val isRunning: StateFlow<Boolean> get() = _isRunning
private val _isRunning = MutableStateFlow(false)
val cancelled: StateFlow<Boolean> get() = _cancelled
private val _cancelled = MutableStateFlow(false)
lateinit var task: () -> Any
fun clear() {
_result.value = Any()
_isComplete.value = false
_isRunning.value = false
_cancelled.value = false
}
fun setCancelled(value: Boolean) {
_cancelled.value = value
}
fun runTask() {

View File

@ -352,7 +352,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
}
private fun addOverlayControls(layout: String) {
val windowSize = getSafeScreenSize(context)
val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) {
overlayButtons.add(
initializeOverlayButton(
@ -593,7 +593,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
}
private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) {
val windowSize = getSafeScreenSize(context)
val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
val min = windowSize.first
val max = windowSize.second
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
@ -968,14 +968,17 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
* @return A pair of points, the first being the top left corner of the safe area,
* the second being the bottom right corner of the safe area
*/
private fun getSafeScreenSize(context: Context): Pair<Point, Point> {
private fun getSafeScreenSize(
context: Context,
screenSize: Pair<Int, Int>
): Pair<Point, Point> {
// Get screen size
val windowMetrics = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(context as Activity)
var maxY = windowMetrics.bounds.height().toFloat()
var maxX = windowMetrics.bounds.width().toFloat()
var minY = 0
var maxX = screenSize.first.toFloat()
var maxY = screenSize.second.toFloat()
var minX = 0
var minY = 0
// If we have API access, calculate the safe area to draw the overlay
var cutoutLeft = 0

View File

@ -46,13 +46,22 @@ import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.*
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
class MainActivity : AppCompatActivity(), ThemeProvider {
private lateinit var binding: ActivityMainBinding
private val homeViewModel: HomeViewModel by viewModels()
private val gamesViewModel: GamesViewModel by viewModels()
private val taskViewModel: TaskViewModel by viewModels()
override var themeId: Int = 0
@ -307,6 +316,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
fun processKey(result: Uri): Boolean {
if (FileUtil.getExtension(result) != "keys") {
MessageDialogFragment.newInstance(
this,
titleId = R.string.reading_keys_failure,
descriptionId = R.string.install_prod_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
@ -336,6 +346,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return true
} else {
MessageDialogFragment.newInstance(
this,
titleId = R.string.invalid_keys_error,
descriptionId = R.string.install_keys_failure_description,
helpLinkId = R.string.dumping_keys_quickstart_link
@ -376,6 +387,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
MessageDialogFragment.newInstance(
this,
titleId = R.string.firmware_installed_failure,
descriptionId = R.string.firmware_installed_failure_description
)
@ -395,7 +407,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
IndeterminateProgressDialogFragment.newInstance(
this,
R.string.firmware_installing,
task
task = task
).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
@ -407,6 +419,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (FileUtil.getExtension(result) != "bin") {
MessageDialogFragment.newInstance(
this,
titleId = R.string.reading_keys_failure,
descriptionId = R.string.install_amiibo_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
@ -434,6 +447,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
).show()
} else {
MessageDialogFragment.newInstance(
this,
titleId = R.string.invalid_keys_error,
descriptionId = R.string.install_keys_failure_description,
helpLinkId = R.string.dumping_keys_quickstart_link
@ -583,12 +597,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
installResult.append(separator)
}
return@newInstance MessageDialogFragment.newInstance(
this,
titleId = R.string.install_game_content_failure,
descriptionString = installResult.toString().trim(),
helpLinkId = R.string.install_game_content_help_link
)
} else {
return@newInstance MessageDialogFragment.newInstance(
this,
titleId = R.string.install_game_content_success,
descriptionString = installResult.toString().trim()
)
@ -596,4 +612,119 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
}
val exportUserData = registerForActivityResult(
ActivityResultContracts.CreateDocument("application/zip")
) { result ->
if (result == null) {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
this,
R.string.exporting_user_data,
true
) {
val zos = ZipOutputStream(
BufferedOutputStream(contentResolver.openOutputStream(result))
)
zos.use { stream ->
File(DirectoryInitialization.userDirectory!!).walkTopDown().forEach { file ->
if (taskViewModel.cancelled.value) {
return@newInstance R.string.user_data_export_cancelled
}
if (!file.isDirectory) {
val newPath = file.path.substring(
DirectoryInitialization.userDirectory!!.length,
file.path.length
)
stream.putNextEntry(ZipEntry(newPath))
val buffer = ByteArray(8096)
var read: Int
FileInputStream(file).use { fis ->
while (fis.read(buffer).also { read = it } != -1) {
stream.write(buffer, 0, read)
}
}
stream.closeEntry()
}
}
}
return@newInstance getString(R.string.user_data_export_success)
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
val importUserData =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
this,
R.string.importing_user_data
) {
val checkStream =
ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
var isYuzuBackup = false
checkStream.use { stream ->
var ze: ZipEntry? = null
while (stream.nextEntry?.also { ze = it } != null) {
val itemName = ze!!.name.trim()
if (itemName == "/config/config.ini" || itemName == "config/config.ini") {
isYuzuBackup = true
return@use
}
}
}
if (!isYuzuBackup) {
return@newInstance getString(R.string.invalid_yuzu_backup)
}
File(DirectoryInitialization.userDirectory!!).deleteRecursively()
val zis =
ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
val userDirectory = File(DirectoryInitialization.userDirectory!!)
val canonicalPath = userDirectory.canonicalPath + '/'
zis.use { stream ->
var ze: ZipEntry? = stream.nextEntry
while (ze != null) {
val newFile = File(userDirectory, ze!!.name)
val destinationDirectory =
if (ze!!.isDirectory) newFile else newFile.parentFile
if (!newFile.canonicalPath.startsWith(canonicalPath)) {
throw SecurityException(
"Zip file attempted path traversal! ${ze!!.name}"
)
}
if (!destinationDirectory.isDirectory && !destinationDirectory.mkdirs()) {
throw IOException("Failed to create directory $destinationDirectory")
}
if (!ze!!.isDirectory) {
val buffer = ByteArray(8096)
var read: Int
BufferedOutputStream(FileOutputStream(newFile)).use { bos ->
while (zis.read(buffer).also { read = it } != -1) {
bos.write(buffer, 0, read)
}
}
}
ze = stream.nextEntry
}
}
// Reinitialize relevant data
NativeLibrary.initializeEmulation()
gamesViewModel.reloadGames(false)
return@newInstance getString(R.string.user_data_import_success)
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
}

View File

@ -11,6 +11,12 @@
#include "jni/emu_window/emu_window.h"
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
m_window_width = ANativeWindow_getWidth(surface);
m_window_height = ANativeWindow_getHeight(surface);
// Ensures that we emulate with the correct aspect ratio.
UpdateCurrentFramebufferLayout(m_window_width, m_window_height);
window_info.render_surface = reinterpret_cast<void*>(surface);
}
@ -62,14 +68,8 @@ EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsyste
return;
}
m_window_width = ANativeWindow_getWidth(surface);
m_window_height = ANativeWindow_getHeight(surface);
// Ensures that we emulate with the correct aspect ratio.
UpdateCurrentFramebufferLayout(m_window_width, m_window_height);
OnSurfaceChanged(surface);
window_info.type = Core::Frontend::WindowSystemType::Android;
window_info.render_surface = reinterpret_cast<void*>(surface);
m_input_subsystem->Initialize();
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
</vector>

View File

@ -1,24 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
<com.google.android.material.progressindicator.LinearProgressIndicator xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="24dp"
app:trackCornerRadius="4dp" />
<TextView
android:id="@+id/progress_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:layout_marginBottom="24dp"
android:gravity="end" />
</LinearLayout>
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="24dp"
app:trackCornerRadius="4dp" />

View File

@ -176,6 +176,67 @@
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="16dp"
android:paddingHorizontal="16dp"
android:orientation="vertical"
android:layout_weight="1">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:textAlignment="viewStart"
android:text="@string/user_data" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp"
android:textAlignment="viewStart"
android:text="@string/user_data_description" />
</LinearLayout>
<Button
android:id="@+id/button_import"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:contentDescription="@string/string_import"
android:tooltipText="@string/string_import"
app:icon="@drawable/ic_import" />
<Button
android:id="@+id/button_export"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="24dp"
android:layout_gravity="center_vertical"
android:contentDescription="@string/export"
android:tooltipText="@string/export"
app:icon="@drawable/ic_export" />
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -128,6 +128,15 @@
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
<string name="licenses_description">Projects that make yuzu for Android possible</string>
<string name="build">Build</string>
<string name="user_data">User data</string>
<string name="user_data_description">Import/export all app data.\n\nWhen importing user data, all existing user data will be deleted!</string>
<string name="exporting_user_data">Exporting user data…</string>
<string name="importing_user_data">Importing user data…</string>
<string name="import_user_data">Import user data</string>
<string name="invalid_yuzu_backup">Invalid yuzu backup</string>
<string name="user_data_export_success">User data exported successfully</string>
<string name="user_data_import_success">User data imported successfully</string>
<string name="user_data_export_cancelled">Export cancelled</string>
<string name="support_link">https://discord.gg/u77vRWY</string>
<string name="website_link">https://yuzu-emu.org/</string>
<string name="github_link">https://github.com/yuzu-emu</string>
@ -215,6 +224,9 @@
<string name="auto">Auto</string>
<string name="submit">Submit</string>
<string name="string_null">Null</string>
<string name="string_import">Import</string>
<string name="export">Export</string>
<string name="cancelling">Cancelling</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string>

View File

@ -27,12 +27,12 @@ u32 CommandProcessingTimeEstimatorVersion1::Estimate(
u32 CommandProcessingTimeEstimatorVersion1::Estimate(
const AdpcmDataSourceVersion1Command& command) const {
return static_cast<u32>(command.pitch * 0.25f * 1.2f);
return static_cast<u32>(command.pitch * 0.46f * 1.2f);
}
u32 CommandProcessingTimeEstimatorVersion1::Estimate(
const AdpcmDataSourceVersion2Command& command) const {
return static_cast<u32>(command.pitch * 0.25f * 1.2f);
return static_cast<u32>(command.pitch * 0.46f * 1.2f);
}
u32 CommandProcessingTimeEstimatorVersion1::Estimate(

View File

@ -684,11 +684,11 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
sink_context, splitter_context, perf_manager};
voice_context.SortInfo();
command_generator.GenerateVoiceCommands();
const auto start_estimated_time{drop_voice_param *
static_cast<f32>(command_buffer.estimated_process_time)};
command_generator.GenerateVoiceCommands();
command_generator.GenerateSubMixCommands();
command_generator.GenerateFinalMixCommands();
command_generator.GenerateSinkCommands();
@ -708,11 +708,13 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
const auto end_estimated_time{drop_voice_param *
static_cast<f32>(command_buffer.estimated_process_time)};
const auto dsp_time_limit{((time_limit_percent / 100.0f) * 2'880'000.0f) *
(static_cast<f32>(render_time_limit_percent) / 100.0f)};
const auto estimated_time{start_estimated_time - end_estimated_time};
const auto time_limit{static_cast<u32>(
estimated_time + (((time_limit_percent / 100.0f) * 2'880'000.0) *
(static_cast<f32>(render_time_limit_percent) / 100.0f)))};
const auto time_limit{static_cast<u32>(std::max(dsp_time_limit + estimated_time, 0.0f))};
num_voices_dropped =
DropVoices(command_buffer, static_cast<u32>(start_estimated_time), time_limit);
}

View File

@ -130,13 +130,17 @@ void LogSettings() {
log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
}
void UpdateGPUAccuracy() {
values.current_gpu_accuracy = values.gpu_accuracy.GetValue();
}
bool IsGPULevelExtreme() {
return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme;
return values.current_gpu_accuracy == GpuAccuracy::Extreme;
}
bool IsGPULevelHigh() {
return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme ||
values.gpu_accuracy.GetValue() == GpuAccuracy::High;
return values.current_gpu_accuracy == GpuAccuracy::Extreme ||
values.current_gpu_accuracy == GpuAccuracy::High;
}
bool IsFastmemEnabled() {

View File

@ -307,6 +307,7 @@ struct Values {
Specialization::Default,
true,
true};
GpuAccuracy current_gpu_accuracy{GpuAccuracy::High};
SwitchableSetting<AnisotropyMode, true> max_anisotropy{
linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16,
"max_anisotropy", Category::RendererAdvanced};
@ -522,6 +523,7 @@ struct Values {
extern Values values;
void UpdateGPUAccuracy();
bool IsGPULevelExtreme();
bool IsGPULevelHigh();

View File

@ -596,6 +596,10 @@ add_library(core STATIC
hle/service/mii/types/ver3_store_data.h
hle/service/mii/mii.cpp
hle/service/mii/mii.h
hle/service/mii/mii_database.cpp
hle/service/mii/mii_database.h
hle/service/mii/mii_database_manager.cpp
hle/service/mii/mii_database_manager.h
hle/service/mii/mii_manager.cpp
hle/service/mii/mii_manager.h
hle/service/mii/mii_result.h
@ -890,7 +894,7 @@ endif()
create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls renderdoc)
target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls RenderDoc::API)
if (MINGW)
target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
endif()

View File

@ -47,6 +47,7 @@ PartitionFilesystem::PartitionFilesystem(VirtualFile file) {
// Actually read in now...
std::vector<u8> file_data = file->ReadBytes(metadata_size);
const std::size_t total_size = file_data.size();
file_data.push_back(0);
if (total_size != metadata_size) {
status = Loader::ResultStatus::ErrorIncorrectPFSFileSize;

View File

@ -19,6 +19,7 @@
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applet_mii_edit_types.h"
#include "core/hle/service/am/applets/applet_profile_select.h"
#include "core/hle/service/am/applets/applet_web_browser.h"
#include "core/hle/service/am/applets/applets.h"
@ -190,7 +191,7 @@ IDisplayController::IDisplayController(Core::System& system_)
{5, nullptr, "GetLastForegroundCaptureImageEx"},
{6, nullptr, "GetLastApplicationCaptureImageEx"},
{7, nullptr, "GetCallerAppletCaptureImageEx"},
{8, nullptr, "TakeScreenShotOfOwnLayer"},
{8, &IDisplayController::TakeScreenShotOfOwnLayer, "TakeScreenShotOfOwnLayer"},
{9, nullptr, "CopyBetweenCaptureBuffers"},
{10, nullptr, "AcquireLastApplicationCaptureBuffer"},
{11, nullptr, "ReleaseLastApplicationCaptureBuffer"},
@ -218,6 +219,13 @@ IDisplayController::IDisplayController(Core::System& system_)
IDisplayController::~IDisplayController() = default;
void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
IDebugFunctions::IDebugFunctions(Core::System& system_)
: ServiceFramework{system_, "IDebugFunctions"} {
// clang-format off
@ -724,7 +732,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
{110, nullptr, "OpenMyGpuErrorHandler"},
{120, nullptr, "GetAppletLaunchedHistory"},
{200, nullptr, "GetOperationModeSystemInfo"},
{300, nullptr, "GetSettingsPlatformRegion"},
{300, &ICommonStateGetter::GetSettingsPlatformRegion, "GetSettingsPlatformRegion"},
{400, nullptr, "ActivateMigrationService"},
{401, nullptr, "DeactivateMigrationService"},
{500, nullptr, "DisableSleepTillShutdown"},
@ -736,6 +744,10 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
// clang-format on
RegisterHandlers(functions);
// Configure applets to be in foreground state
msg_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
}
ICommonStateGetter::~ICommonStateGetter() = default;
@ -867,6 +879,14 @@ void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext&
rb.Push(ResultSuccess);
}
void ICommonStateGetter::GetSettingsPlatformRegion(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.PushEnum(SysPlatformRegion::Global);
}
void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(
HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
@ -1324,18 +1344,19 @@ void ILibraryAppletCreator::CreateHandleStorage(HLERequestContext& ctx) {
ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
: ServiceFramework{system_, "ILibraryAppletSelfAccessor"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "PopInData"},
{1, nullptr, "PushOutData"},
{0, &ILibraryAppletSelfAccessor::PopInData, "PopInData"},
{1, &ILibraryAppletSelfAccessor::PushOutData, "PushOutData"},
{2, nullptr, "PopInteractiveInData"},
{3, nullptr, "PushInteractiveOutData"},
{5, nullptr, "GetPopInDataEvent"},
{6, nullptr, "GetPopInteractiveInDataEvent"},
{10, nullptr, "ExitProcessAndReturn"},
{11, nullptr, "GetLibraryAppletInfo"},
{10, &ILibraryAppletSelfAccessor::ExitProcessAndReturn, "ExitProcessAndReturn"},
{11, &ILibraryAppletSelfAccessor::GetLibraryAppletInfo, "GetLibraryAppletInfo"},
{12, nullptr, "GetMainAppletIdentityInfo"},
{13, nullptr, "CanUseApplicationCore"},
{14, nullptr, "GetCallerAppletIdentityInfo"},
{14, &ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo, "GetCallerAppletIdentityInfo"},
{15, nullptr, "GetMainAppletApplicationControlProperty"},
{16, nullptr, "GetMainAppletStorageId"},
{17, nullptr, "GetCallerAppletIdentityInfoStack"},
@ -1361,10 +1382,142 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
{140, nullptr, "SetApplicationMemoryReservation"},
{150, nullptr, "ShouldSetGpuTimeSliceManually"},
};
// clang-format on
RegisterHandlers(functions);
PushInShowMiiEditData();
}
ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default;
void ILibraryAppletSelfAccessor::PopInData(HLERequestContext& ctx) {
LOG_INFO(Service_AM, "called");
if (queue_data.empty()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultNoDataInChannel);
return;
}
auto data = queue_data.front();
queue_data.pop_front();
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IStorage>(system, std::move(data));
}
void ILibraryAppletSelfAccessor::PushOutData(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void ILibraryAppletSelfAccessor::ExitProcessAndReturn(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
system.Exit();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void ILibraryAppletSelfAccessor::GetLibraryAppletInfo(HLERequestContext& ctx) {
struct LibraryAppletInfo {
Applets::AppletId applet_id;
Applets::LibraryAppletMode library_applet_mode;
};
LOG_WARNING(Service_AM, "(STUBBED) called");
const LibraryAppletInfo applet_info{
.applet_id = Applets::AppletId::MiiEdit,
.library_applet_mode = Applets::LibraryAppletMode::AllForeground,
};
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
rb.PushRaw(applet_info);
}
void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext& ctx) {
struct AppletIdentityInfo {
Applets::AppletId applet_id;
INSERT_PADDING_BYTES(0x4);
u64 application_id;
};
LOG_WARNING(Service_AM, "(STUBBED) called");
const AppletIdentityInfo applet_info{
.applet_id = Applets::AppletId::QLaunch,
.application_id = 0x0100000000001000ull,
};
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
rb.PushRaw(applet_info);
}
void ILibraryAppletSelfAccessor::PushInShowMiiEditData() {
struct MiiEditV3 {
Applets::MiiEditAppletInputCommon common;
Applets::MiiEditAppletInputV3 input;
};
static_assert(sizeof(MiiEditV3) == 0x100, "MiiEditV3 has incorrect size.");
MiiEditV3 mii_arguments{
.common =
{
.version = Applets::MiiEditAppletVersion::Version3,
.applet_mode = Applets::MiiEditAppletMode::ShowMiiEdit,
},
.input{},
};
std::vector<u8> argument_data(sizeof(mii_arguments));
std::memcpy(argument_data.data(), &mii_arguments, sizeof(mii_arguments));
queue_data.emplace_back(std::move(argument_data));
}
IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_)
: ServiceFramework{system_, "IAppletCommonFunctions"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "SetTerminateResult"},
{10, nullptr, "ReadThemeStorage"},
{11, nullptr, "WriteThemeStorage"},
{20, nullptr, "PushToAppletBoundChannel"},
{21, nullptr, "TryPopFromAppletBoundChannel"},
{40, nullptr, "GetDisplayLogicalResolution"},
{42, nullptr, "SetDisplayMagnification"},
{50, nullptr, "SetHomeButtonDoubleClickEnabled"},
{51, nullptr, "GetHomeButtonDoubleClickEnabled"},
{52, nullptr, "IsHomeButtonShortPressedBlocked"},
{60, nullptr, "IsVrModeCurtainRequired"},
{61, nullptr, "IsSleepRequiredByHighTemperature"},
{62, nullptr, "IsSleepRequiredByLowBattery"},
{70, &IAppletCommonFunctions::SetCpuBoostRequestPriority, "SetCpuBoostRequestPriority"},
{80, nullptr, "SetHandlingCaptureButtonShortPressedMessageEnabledForApplet"},
{81, nullptr, "SetHandlingCaptureButtonLongPressedMessageEnabledForApplet"},
{90, nullptr, "OpenNamedChannelAsParent"},
{91, nullptr, "OpenNamedChannelAsChild"},
{100, nullptr, "SetApplicationCoreUsageMode"},
};
// clang-format on
RegisterHandlers(functions);
}
IAppletCommonFunctions::~IAppletCommonFunctions() = default;
void IAppletCommonFunctions::SetCpuBoostRequestPriority(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
IApplicationFunctions::IApplicationFunctions(Core::System& system_)
: ServiceFramework{system_, "IApplicationFunctions"}, service_context{system,
@ -2049,8 +2202,8 @@ IProcessWindingController::IProcessWindingController(Core::System& system_)
: ServiceFramework{system_, "IProcessWindingController"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetLaunchReason"},
{11, nullptr, "OpenCallingLibraryApplet"},
{0, &IProcessWindingController::GetLaunchReason, "GetLaunchReason"},
{11, &IProcessWindingController::OpenCallingLibraryApplet, "OpenCallingLibraryApplet"},
{21, nullptr, "PushContext"},
{22, nullptr, "PopContext"},
{23, nullptr, "CancelWindingReservation"},
@ -2064,4 +2217,46 @@ IProcessWindingController::IProcessWindingController(Core::System& system_)
}
IProcessWindingController::~IProcessWindingController() = default;
void IProcessWindingController::GetLaunchReason(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
struct AppletProcessLaunchReason {
u8 flag;
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(AppletProcessLaunchReason) == 0x4,
"AppletProcessLaunchReason is an invalid size");
AppletProcessLaunchReason reason{
.flag = 0,
};
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.PushRaw(reason);
}
void IProcessWindingController::OpenCallingLibraryApplet(HLERequestContext& ctx) {
const auto applet_id = Applets::AppletId::MiiEdit;
const auto applet_mode = Applets::LibraryAppletMode::AllForeground;
LOG_WARNING(Service_AM, "(STUBBED) called with applet_id={:08X}, applet_mode={:08X}", applet_id,
applet_mode);
const auto& applet_manager{system.GetAppletManager()};
const auto applet = applet_manager.GetApplet(applet_id, applet_mode);
if (applet == nullptr) {
LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultUnknown);
return;
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<ILibraryAppletAccessor>(system, applet);
}
} // namespace Service::AM

View File

@ -120,6 +120,9 @@ class IDisplayController final : public ServiceFramework<IDisplayController> {
public:
explicit IDisplayController(Core::System& system_);
~IDisplayController() override;
private:
void TakeScreenShotOfOwnLayer(HLERequestContext& ctx);
};
class IDebugFunctions final : public ServiceFramework<IDebugFunctions> {
@ -212,6 +215,11 @@ private:
CaptureButtonLongPressing,
};
enum class SysPlatformRegion : s32 {
Global = 1,
Terra = 2,
};
void GetEventHandle(HLERequestContext& ctx);
void ReceiveMessage(HLERequestContext& ctx);
void GetCurrentFocusState(HLERequestContext& ctx);
@ -227,6 +235,7 @@ private:
void GetDefaultDisplayResolution(HLERequestContext& ctx);
void SetCpuBoostMode(HLERequestContext& ctx);
void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx);
void GetSettingsPlatformRegion(HLERequestContext& ctx);
void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx);
std::shared_ptr<AppletMessageQueue> msg_queue;
@ -294,6 +303,26 @@ class ILibraryAppletSelfAccessor final : public ServiceFramework<ILibraryAppletS
public:
explicit ILibraryAppletSelfAccessor(Core::System& system_);
~ILibraryAppletSelfAccessor() override;
private:
void PopInData(HLERequestContext& ctx);
void PushOutData(HLERequestContext& ctx);
void GetLibraryAppletInfo(HLERequestContext& ctx);
void ExitProcessAndReturn(HLERequestContext& ctx);
void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
void PushInShowMiiEditData();
std::deque<std::vector<u8>> queue_data;
};
class IAppletCommonFunctions final : public ServiceFramework<IAppletCommonFunctions> {
public:
explicit IAppletCommonFunctions(Core::System& system_);
~IAppletCommonFunctions() override;
private:
void SetCpuBoostRequestPriority(HLERequestContext& ctx);
};
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
@ -378,6 +407,10 @@ class IProcessWindingController final : public ServiceFramework<IProcessWindingC
public:
explicit IProcessWindingController(Core::System& system_);
~IProcessWindingController() override;
private:
void GetLaunchReason(HLERequestContext& ctx);
void OpenCallingLibraryApplet(HLERequestContext& ctx);
};
void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system);

View File

@ -27,7 +27,7 @@ public:
{10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"},
{11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
{20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"},
{21, nullptr, "GetAppletCommonFunctions"},
{21, &ILibraryAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"},
{22, nullptr, "GetHomeMenuFunctions"},
{23, nullptr, "GetGlobalStateController"},
{1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
@ -86,14 +86,6 @@ private:
rb.PushIpcInterface<IProcessWindingController>(system);
}
void GetDebugFunctions(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IDebugFunctions>(system);
}
void GetLibraryAppletCreator(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
@ -110,6 +102,22 @@ private:
rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system);
}
void GetAppletCommonFunctions(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IAppletCommonFunctions>(system);
}
void GetDebugFunctions(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IDebugFunctions>(system);
}
Nvnflinger::Nvnflinger& nvnflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
};
@ -133,7 +141,7 @@ public:
{20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
{21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
{22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"},
{23, nullptr, "GetAppletCommonFunctions"},
{23, &ISystemAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"},
{1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
};
// clang-format on
@ -182,14 +190,6 @@ private:
rb.PushIpcInterface<IDisplayController>(system);
}
void GetDebugFunctions(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IDebugFunctions>(system);
}
void GetLibraryAppletCreator(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
@ -222,6 +222,22 @@ private:
rb.PushIpcInterface<IApplicationCreator>(system);
}
void GetAppletCommonFunctions(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IAppletCommonFunctions>(system);
}
void GetDebugFunctions(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IDebugFunctions>(system);
}
Nvnflinger::Nvnflinger& nvnflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
};

View File

@ -7,7 +7,9 @@
#include "core/frontend/applets/mii_edit.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applet_mii_edit.h"
#include "core/hle/service/mii/mii.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/sm/sm.h"
namespace Service::AM::Applets {
@ -56,6 +58,12 @@ void MiiEdit::Initialize() {
sizeof(MiiEditAppletInputV4));
break;
}
manager = system.ServiceManager().GetService<Mii::MiiDBModule>("mii:e")->GetMiiManager();
if (manager == nullptr) {
manager = std::make_shared<Mii::MiiManager>();
}
manager->Initialize(metadata);
}
bool MiiEdit::TransactionComplete() const {
@ -78,22 +86,46 @@ void MiiEdit::Execute() {
// This is a default stub for each of the MiiEdit applet modes.
switch (applet_input_common.applet_mode) {
case MiiEditAppletMode::ShowMiiEdit:
case MiiEditAppletMode::AppendMii:
case MiiEditAppletMode::AppendMiiImage:
case MiiEditAppletMode::UpdateMiiImage:
MiiEditOutput(MiiEditResult::Success, 0);
break;
case MiiEditAppletMode::CreateMii:
case MiiEditAppletMode::EditMii: {
Mii::CharInfo char_info{};
case MiiEditAppletMode::AppendMii: {
Mii::StoreData store_data{};
store_data.BuildBase(Mii::Gender::Male);
char_info.SetFromStoreData(store_data);
store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
store_data.SetNickname({u'y', u'u', u'z', u'u'});
store_data.SetChecksum();
const auto result = manager->AddOrReplace(metadata, store_data);
if (result.IsError()) {
MiiEditOutput(MiiEditResult::Cancel, 0);
break;
}
s32 index = manager->FindIndex(store_data.GetCreateId(), false);
if (index == -1) {
MiiEditOutput(MiiEditResult::Cancel, 0);
break;
}
MiiEditOutput(MiiEditResult::Success, index);
break;
}
case MiiEditAppletMode::CreateMii: {
Mii::CharInfo char_info{};
manager->BuildRandom(char_info, Mii::Age::All, Mii::Gender::All, Mii::Race::All);
const MiiEditCharInfo edit_char_info{
.mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii
? applet_input_v4.char_info.mii_info
: char_info},
.mii_info{char_info},
};
MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
break;
}
case MiiEditAppletMode::EditMii: {
const MiiEditCharInfo edit_char_info{
.mii_info{applet_input_v4.char_info.mii_info},
};
MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);

View File

@ -11,6 +11,11 @@ namespace Core {
class System;
} // namespace Core
namespace Service::Mii {
struct DatabaseSessionMetadata;
class MiiManager;
} // namespace Service::Mii
namespace Service::AM::Applets {
class MiiEdit final : public Applet {
@ -40,6 +45,8 @@ private:
MiiEditAppletInputV4 applet_input_v4{};
bool is_complete{false};
std::shared_ptr<Mii::MiiManager> manager = nullptr;
Mii::DatabaseSessionMetadata metadata{};
};
} // namespace Service::AM::Applets

View File

@ -22,6 +22,8 @@
namespace Service::AOC {
constexpr Result ResultNoPurchasedProductInfoAvailable{ErrorModule::NIMShop, 400};
static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
return FileSys::GetBaseTitleID(title_id) == base;
}
@ -54,8 +56,8 @@ public:
{0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"},
{1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"},
{2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"},
{3, nullptr, "PopPurchasedProductInfo"},
{4, nullptr, "PopPurchasedProductInfoWithUid"},
{3, &IPurchaseEventManager::PopPurchasedProductInfo, "PopPurchasedProductInfo"},
{4, &IPurchaseEventManager::PopPurchasedProductInfoWithUid, "PopPurchasedProductInfoWithUid"},
};
// clang-format on
@ -101,6 +103,20 @@ private:
rb.PushCopyObjects(purchased_event->GetReadableEvent());
}
void PopPurchasedProductInfo(HLERequestContext& ctx) {
LOG_DEBUG(Service_AOC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultNoPurchasedProductInfoAvailable);
}
void PopPurchasedProductInfoWithUid(HLERequestContext& ctx) {
LOG_DEBUG(Service_AOC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultNoPurchasedProductInfoAvailable);
}
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* purchased_event;

View File

@ -698,7 +698,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
{19, nullptr, "FormatSdCardFileSystem"},
{21, nullptr, "DeleteSaveDataFileSystem"},
{22, &FSP_SRV::CreateSaveDataFileSystem, "CreateSaveDataFileSystem"},
{23, nullptr, "CreateSaveDataFileSystemBySystemSaveDataId"},
{23, &FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId, "CreateSaveDataFileSystemBySystemSaveDataId"},
{24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"},
{25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"},
{26, nullptr, "FormatSdCardDryRun"},
@ -712,7 +712,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
{35, nullptr, "CreateSaveDataFileSystemByHashSalt"},
{36, nullptr, "OpenHostFileSystemWithOption"},
{51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"},
{52, nullptr, "OpenSaveDataFileSystemBySystemSaveDataId"},
{52, &FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId, "OpenSaveDataFileSystemBySystemSaveDataId"},
{53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"},
{57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"},
{58, nullptr, "ReadSaveDataFileSystemExtraData"},
@ -870,6 +870,21 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
void FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto save_struct = rp.PopRaw<FileSys::SaveDataAttribute>();
[[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo());
FileSys::VirtualDir save_data_dir{};
fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandSystem, save_struct);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
@ -916,6 +931,11 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
}
void FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) {
LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
OpenSaveDataFileSystem(ctx);
}
void FSP_SRV::OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx) {
LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
OpenSaveDataFileSystem(ctx);

View File

@ -39,7 +39,9 @@ private:
void OpenFileSystemWithPatch(HLERequestContext& ctx);
void OpenSdCardFileSystem(HLERequestContext& ctx);
void CreateSaveDataFileSystem(HLERequestContext& ctx);
void CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx);
void OpenSaveDataFileSystem(HLERequestContext& ctx);
void OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx);
void OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx);
void OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx);
void OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx);

View File

@ -346,6 +346,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
}
SignalStyleSetChangedEvent(npad_id);
WriteEmptyEntry(controller.shared_memory);
hid_core.SetLastActiveController(npad_id);
}
void Controller_NPad::OnInit() {

View File

@ -8,6 +8,9 @@
#include "core/hle/service/mii/mii.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/mii/mii_result.h"
#include "core/hle/service/mii/types/char_info.h"
#include "core/hle/service/mii/types/store_data.h"
#include "core/hle/service/mii/types/ver3_store_data.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
@ -15,8 +18,10 @@ namespace Service::Mii {
class IDatabaseService final : public ServiceFramework<IDatabaseService> {
public:
explicit IDatabaseService(Core::System& system_, bool is_system_)
: ServiceFramework{system_, "IDatabaseService"}, is_system{is_system_} {
explicit IDatabaseService(Core::System& system_, std::shared_ptr<MiiManager> mii_manager,
bool is_system_)
: ServiceFramework{system_, "IDatabaseService"}, manager{mii_manager}, is_system{
is_system_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDatabaseService::IsUpdated, "IsUpdated"},
@ -27,29 +32,31 @@ public:
{5, &IDatabaseService::UpdateLatest, "UpdateLatest"},
{6, &IDatabaseService::BuildRandom, "BuildRandom"},
{7, &IDatabaseService::BuildDefault, "BuildDefault"},
{8, nullptr, "Get2"},
{9, nullptr, "Get3"},
{10, nullptr, "UpdateLatest1"},
{11, nullptr, "FindIndex"},
{12, nullptr, "Move"},
{13, nullptr, "AddOrReplace"},
{14, nullptr, "Delete"},
{15, nullptr, "DestroyFile"},
{16, nullptr, "DeleteFile"},
{17, nullptr, "Format"},
{8, &IDatabaseService::Get2, "Get2"},
{9, &IDatabaseService::Get3, "Get3"},
{10, &IDatabaseService::UpdateLatest1, "UpdateLatest1"},
{11, &IDatabaseService::FindIndex, "FindIndex"},
{12, &IDatabaseService::Move, "Move"},
{13, &IDatabaseService::AddOrReplace, "AddOrReplace"},
{14, &IDatabaseService::Delete, "Delete"},
{15, &IDatabaseService::DestroyFile, "DestroyFile"},
{16, &IDatabaseService::DeleteFile, "DeleteFile"},
{17, &IDatabaseService::Format, "Format"},
{18, nullptr, "Import"},
{19, nullptr, "Export"},
{20, nullptr, "IsBrokenDatabaseWithClearFlag"},
{20, &IDatabaseService::IsBrokenDatabaseWithClearFlag, "IsBrokenDatabaseWithClearFlag"},
{21, &IDatabaseService::GetIndex, "GetIndex"},
{22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"},
{23, &IDatabaseService::Convert, "Convert"},
{24, nullptr, "ConvertCoreDataToCharInfo"},
{25, nullptr, "ConvertCharInfoToCoreData"},
{26, nullptr, "Append"},
{24, &IDatabaseService::ConvertCoreDataToCharInfo, "ConvertCoreDataToCharInfo"},
{25, &IDatabaseService::ConvertCharInfoToCoreData, "ConvertCharInfoToCoreData"},
{26, &IDatabaseService::Append, "Append"},
};
// clang-format on
RegisterHandlers(functions);
manager->Initialize(metadata);
}
private:
@ -59,7 +66,7 @@ private:
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
const bool is_updated = manager.IsUpdated(metadata, source_flag);
const bool is_updated = manager->IsUpdated(metadata, source_flag);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@ -69,7 +76,7 @@ private:
void IsFullDatabase(HLERequestContext& ctx) {
LOG_DEBUG(Service_Mii, "called");
const bool is_full_database = manager.IsFullDatabase();
const bool is_full_database = manager->IsFullDatabase();
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@ -80,9 +87,9 @@ private:
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
const u32 mii_count = manager->GetCount(metadata, source_flag);
const u32 mii_count = manager.GetCount(metadata, source_flag);
LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@ -94,16 +101,17 @@ private:
const auto source_flag{rp.PopRaw<SourceFlag>()};
const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
u32 mii_count{};
std::vector<CharInfoElement> char_info_elements(output_size);
Result result = manager.Get(metadata, char_info_elements, mii_count, source_flag);
const auto result = manager->Get(metadata, char_info_elements, mii_count, source_flag);
if (mii_count != 0) {
ctx.WriteBuffer(char_info_elements);
}
LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
output_size, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push(mii_count);
@ -114,16 +122,17 @@ private:
const auto source_flag{rp.PopRaw<SourceFlag>()};
const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
u32 mii_count{};
std::vector<CharInfo> char_info(output_size);
Result result = manager.Get(metadata, char_info, mii_count, source_flag);
const auto result = manager->Get(metadata, char_info, mii_count, source_flag);
if (mii_count != 0) {
ctx.WriteBuffer(char_info);
}
LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
output_size, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push(mii_count);
@ -134,10 +143,10 @@ private:
const auto char_info{rp.PopRaw<CharInfo>()};
const auto source_flag{rp.PopRaw<SourceFlag>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
LOG_INFO(Service_Mii, "called with source_flag={}", source_flag);
CharInfo new_char_info{};
const auto result = manager.UpdateLatest(metadata, new_char_info, char_info, source_flag);
const auto result = manager->UpdateLatest(metadata, new_char_info, char_info, source_flag);
if (result.IsFailure()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
@ -146,7 +155,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<CharInfo>(new_char_info);
rb.PushRaw(new_char_info);
}
void BuildRandom(HLERequestContext& ctx) {
@ -176,18 +185,18 @@ private:
}
CharInfo char_info{};
manager.BuildRandom(char_info, age, gender, race);
manager->BuildRandom(char_info, age, gender, race);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<CharInfo>(char_info);
rb.PushRaw(char_info);
}
void BuildDefault(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto index{rp.Pop<u32>()};
LOG_INFO(Service_Mii, "called with index={}", index);
LOG_DEBUG(Service_Mii, "called with index={}", index);
if (index > 5) {
IPC::ResponseBuilder rb{ctx, 2};
@ -196,11 +205,243 @@ private:
}
CharInfo char_info{};
manager.BuildDefault(char_info, index);
manager->BuildDefault(char_info, index);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<CharInfo>(char_info);
rb.PushRaw(char_info);
}
void Get2(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
const auto output_size{ctx.GetWriteBufferNumElements<StoreDataElement>()};
u32 mii_count{};
std::vector<StoreDataElement> store_data_elements(output_size);
const auto result = manager->Get(metadata, store_data_elements, mii_count, source_flag);
if (mii_count != 0) {
ctx.WriteBuffer(store_data_elements);
}
LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
output_size, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push(mii_count);
}
void Get3(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
const auto output_size{ctx.GetWriteBufferNumElements<StoreData>()};
u32 mii_count{};
std::vector<StoreData> store_data(output_size);
const auto result = manager->Get(metadata, store_data, mii_count, source_flag);
if (mii_count != 0) {
ctx.WriteBuffer(store_data);
}
LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
output_size, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push(mii_count);
}
void UpdateLatest1(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto store_data{rp.PopRaw<StoreData>()};
const auto source_flag{rp.PopRaw<SourceFlag>()};
LOG_INFO(Service_Mii, "called with source_flag={}", source_flag);
Result result = ResultSuccess;
if (!is_system) {
result = ResultPermissionDenied;
}
StoreData new_store_data{};
if (result.IsSuccess()) {
result = manager->UpdateLatest(metadata, new_store_data, store_data, source_flag);
}
if (result.IsFailure()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
}
IPC::ResponseBuilder rb{ctx, 2 + sizeof(StoreData) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<StoreData>(new_store_data);
}
void FindIndex(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto create_id{rp.PopRaw<Common::UUID>()};
const auto is_special{rp.PopRaw<bool>()};
LOG_INFO(Service_Mii, "called with create_id={}, is_special={}",
create_id.FormattedString(), is_special);
const s32 index = manager->FindIndex(create_id, is_special);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(index);
}
void Move(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto create_id{rp.PopRaw<Common::UUID>()};
const auto new_index{rp.PopRaw<s32>()};
LOG_INFO(Service_Mii, "called with create_id={}, new_index={}", create_id.FormattedString(),
new_index);
Result result = ResultSuccess;
if (!is_system) {
result = ResultPermissionDenied;
}
if (result.IsSuccess()) {
const u32 count = manager->GetCount(metadata, SourceFlag::Database);
if (new_index < 0 || new_index >= static_cast<s32>(count)) {
result = ResultInvalidArgument;
}
}
if (result.IsSuccess()) {
result = manager->Move(metadata, new_index, create_id);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void AddOrReplace(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto store_data{rp.PopRaw<StoreData>()};
LOG_INFO(Service_Mii, "called");
Result result = ResultSuccess;
if (!is_system) {
result = ResultPermissionDenied;
}
if (result.IsSuccess()) {
result = manager->AddOrReplace(metadata, store_data);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Delete(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto create_id{rp.PopRaw<Common::UUID>()};
LOG_INFO(Service_Mii, "called, create_id={}", create_id.FormattedString());
Result result = ResultSuccess;
if (!is_system) {
result = ResultPermissionDenied;
}
if (result.IsSuccess()) {
result = manager->Delete(metadata, create_id);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void DestroyFile(HLERequestContext& ctx) {
// This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
const bool is_db_test_mode_enabled = false;
LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
Result result = ResultSuccess;
if (!is_db_test_mode_enabled) {
result = ResultTestModeOnly;
}
if (result.IsSuccess()) {
result = manager->DestroyFile(metadata);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void DeleteFile(HLERequestContext& ctx) {
// This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
const bool is_db_test_mode_enabled = false;
LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
Result result = ResultSuccess;
if (!is_db_test_mode_enabled) {
result = ResultTestModeOnly;
}
if (result.IsSuccess()) {
result = manager->DeleteFile();
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Format(HLERequestContext& ctx) {
// This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
const bool is_db_test_mode_enabled = false;
LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
Result result = ResultSuccess;
if (!is_db_test_mode_enabled) {
result = ResultTestModeOnly;
}
if (result.IsSuccess()) {
result = manager->Format(metadata);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void IsBrokenDatabaseWithClearFlag(HLERequestContext& ctx) {
LOG_DEBUG(Service_Mii, "called");
bool is_broken_with_clear_flag = false;
Result result = ResultSuccess;
if (!is_system) {
result = ResultPermissionDenied;
}
if (result.IsSuccess()) {
is_broken_with_clear_flag = manager->IsBrokenWithClearFlag(metadata);
}
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push<u8>(is_broken_with_clear_flag);
}
void GetIndex(HLERequestContext& ctx) {
@ -210,7 +451,7 @@ private:
LOG_DEBUG(Service_Mii, "called");
s32 index{};
const auto result = manager.GetIndex(metadata, info, index);
const auto result = manager->GetIndex(metadata, info, index);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
@ -223,7 +464,7 @@ private:
LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version);
manager.SetInterfaceVersion(metadata, interface_version);
manager->SetInterfaceVersion(metadata, interface_version);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@ -236,51 +477,96 @@ private:
LOG_INFO(Service_Mii, "called");
CharInfo char_info{};
manager.ConvertV3ToCharInfo(char_info, mii_v3);
const auto result = manager->ConvertV3ToCharInfo(char_info, mii_v3);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.Push(result);
rb.PushRaw<CharInfo>(char_info);
}
MiiManager manager{};
void ConvertCoreDataToCharInfo(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto core_data{rp.PopRaw<CoreData>()};
LOG_INFO(Service_Mii, "called");
CharInfo char_info{};
const auto result = manager->ConvertCoreDataToCharInfo(char_info, core_data);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(result);
rb.PushRaw<CharInfo>(char_info);
}
void ConvertCharInfoToCoreData(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto char_info{rp.PopRaw<CharInfo>()};
LOG_INFO(Service_Mii, "called");
CoreData core_data{};
const auto result = manager->ConvertCharInfoToCoreData(core_data, char_info);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)};
rb.Push(result);
rb.PushRaw<CoreData>(core_data);
}
void Append(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto char_info{rp.PopRaw<CharInfo>()};
LOG_INFO(Service_Mii, "called");
const auto result = manager->Append(metadata, char_info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
std::shared_ptr<MiiManager> manager = nullptr;
DatabaseSessionMetadata metadata{};
bool is_system{};
};
class MiiDBModule final : public ServiceFramework<MiiDBModule> {
public:
explicit MiiDBModule(Core::System& system_, const char* name_, bool is_system_)
: ServiceFramework{system_, name_}, is_system{is_system_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
};
// clang-format on
MiiDBModule::MiiDBModule(Core::System& system_, const char* name_,
std::shared_ptr<MiiManager> mii_manager, bool is_system_)
: ServiceFramework{system_, name_}, manager{mii_manager}, is_system{is_system_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
};
// clang-format on
RegisterHandlers(functions);
RegisterHandlers(functions);
if (manager == nullptr) {
manager = std::make_shared<MiiManager>();
}
}
private:
void GetDatabaseService(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IDatabaseService>(system, is_system);
MiiDBModule::~MiiDBModule() = default;
LOG_DEBUG(Service_Mii, "called");
}
void MiiDBModule::GetDatabaseService(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IDatabaseService>(system, manager, is_system);
bool is_system{};
};
LOG_DEBUG(Service_Mii, "called");
}
std::shared_ptr<MiiManager> MiiDBModule::GetMiiManager() {
return manager;
}
class MiiImg final : public ServiceFramework<MiiImg> {
public:
explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
{0, &MiiImg::Initialize, "Initialize"},
{10, nullptr, "Reload"},
{11, nullptr, "GetCount"},
{11, &MiiImg::GetCount, "GetCount"},
{12, nullptr, "IsEmpty"},
{13, nullptr, "IsFull"},
{14, nullptr, "GetAttribute"},
@ -297,15 +583,32 @@ public:
RegisterHandlers(functions);
}
private:
void Initialize(HLERequestContext& ctx) {
LOG_INFO(Service_Mii, "called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void GetCount(HLERequestContext& ctx) {
LOG_DEBUG(Service_Mii, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(0);
}
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
std::shared_ptr<MiiManager> manager = nullptr;
server_manager->RegisterNamedService("mii:e",
std::make_shared<MiiDBModule>(system, "mii:e", true));
server_manager->RegisterNamedService("mii:u",
std::make_shared<MiiDBModule>(system, "mii:u", false));
server_manager->RegisterNamedService(
"mii:e", std::make_shared<MiiDBModule>(system, "mii:e", manager, true));
server_manager->RegisterNamedService(
"mii:u", std::make_shared<MiiDBModule>(system, "mii:u", manager, false));
server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system));
ServerManager::RunServer(std::move(server_manager));
}

View File

@ -3,11 +3,29 @@
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::Mii {
class MiiManager;
class MiiDBModule final : public ServiceFramework<MiiDBModule> {
public:
explicit MiiDBModule(Core::System& system_, const char* name_,
std::shared_ptr<MiiManager> mii_manager, bool is_system_);
~MiiDBModule() override;
std::shared_ptr<MiiManager> GetMiiManager();
private:
void GetDatabaseService(HLERequestContext& ctx);
std::shared_ptr<MiiManager> manager = nullptr;
bool is_system{};
};
void LoopProcess(Core::System& system);

View File

@ -0,0 +1,142 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/mii/mii_database.h"
#include "core/hle/service/mii/mii_result.h"
#include "core/hle/service/mii/mii_util.h"
namespace Service::Mii {
u8 NintendoFigurineDatabase::GetDatabaseLength() const {
return database_length;
}
bool NintendoFigurineDatabase::IsFull() const {
return database_length >= MaxDatabaseLength;
}
StoreData NintendoFigurineDatabase::Get(std::size_t index) const {
StoreData store_data = miis.at(index);
// This hack is to make external database dumps compatible
store_data.SetDeviceChecksum();
return store_data;
}
u32 NintendoFigurineDatabase::GetCount(const DatabaseSessionMetadata& metadata) const {
if (magic == MiiMagic) {
return GetDatabaseLength();
}
u32 mii_count{};
for (std::size_t index = 0; index < mii_count; ++index) {
const auto& store_data = Get(index);
if (!store_data.IsSpecial()) {
mii_count++;
}
}
return mii_count;
}
bool NintendoFigurineDatabase::GetIndexByCreatorId(u32& out_index,
const Common::UUID& create_id) const {
for (std::size_t index = 0; index < database_length; ++index) {
if (miis[index].GetCreateId() == create_id) {
out_index = static_cast<u32>(index);
return true;
}
}
return false;
}
Result NintendoFigurineDatabase::Move(u32 current_index, u32 new_index) {
if (current_index == new_index) {
return ResultNotUpdated;
}
const StoreData store_data = miis[current_index];
if (new_index > current_index) {
// Shift left
const u32 index_diff = new_index - current_index;
for (std::size_t i = 0; i < index_diff; i++) {
miis[current_index + i] = miis[current_index + i + 1];
}
} else {
// Shift right
const u32 index_diff = current_index - new_index;
for (std::size_t i = 0; i < index_diff; i++) {
miis[current_index - i] = miis[current_index - i - 1];
}
}
miis[new_index] = store_data;
crc = GenerateDatabaseCrc();
return ResultSuccess;
}
void NintendoFigurineDatabase::Replace(u32 index, const StoreData& store_data) {
miis[index] = store_data;
crc = GenerateDatabaseCrc();
}
void NintendoFigurineDatabase::Add(const StoreData& store_data) {
miis[database_length] = store_data;
database_length++;
crc = GenerateDatabaseCrc();
}
void NintendoFigurineDatabase::Delete(u32 index) {
// Shift left
const s32 new_database_size = database_length - 1;
if (static_cast<s32>(index) < new_database_size) {
for (std::size_t i = index; i < static_cast<std::size_t>(new_database_size); i++) {
miis[i] = miis[i + 1];
}
}
database_length = static_cast<u8>(new_database_size);
crc = GenerateDatabaseCrc();
}
void NintendoFigurineDatabase::CleanDatabase() {
miis = {};
version = 1;
magic = DatabaseMagic;
database_length = 0;
crc = GenerateDatabaseCrc();
}
void NintendoFigurineDatabase::CorruptCrc() {
crc = GenerateDatabaseCrc();
crc = ~crc;
}
Result NintendoFigurineDatabase::CheckIntegrity() {
if (magic != DatabaseMagic) {
return ResultInvalidDatabaseSignature;
}
if (version != 1) {
return ResultInvalidDatabaseVersion;
}
if (crc != GenerateDatabaseCrc()) {
return ResultInvalidDatabaseChecksum;
}
if (database_length >= MaxDatabaseLength) {
return ResultInvalidDatabaseLength;
}
return ResultSuccess;
}
u16 NintendoFigurineDatabase::GenerateDatabaseCrc() {
return MiiUtil::CalculateCrc16(&magic, sizeof(NintendoFigurineDatabase) - sizeof(crc));
}
} // namespace Service::Mii

View File

@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/result.h"
#include "core/hle/service/mii/types/store_data.h"
namespace Service::Mii {
constexpr std::size_t MaxDatabaseLength{100};
constexpr u32 MiiMagic{0xa523b78f};
constexpr u32 DatabaseMagic{0x4244464e}; // NFDB
class NintendoFigurineDatabase {
public:
/// Returns the total mii count.
u8 GetDatabaseLength() const;
/// Returns true if database is full.
bool IsFull() const;
/// Returns the mii of the specified index.
StoreData Get(std::size_t index) const;
/// Returns the total mii count. Ignoring special mii.
u32 GetCount(const DatabaseSessionMetadata& metadata) const;
/// Returns the index of a mii. If the mii isn't found returns false.
bool GetIndexByCreatorId(u32& out_index, const Common::UUID& create_id) const;
/// Moves the location of a specific mii.
Result Move(u32 current_index, u32 new_index);
/// Replaces mii with new data.
void Replace(u32 index, const StoreData& store_data);
/// Adds a new mii to the end of the database.
void Add(const StoreData& store_data);
/// Removes mii from database and shifts left the remainding data.
void Delete(u32 index);
/// Deletes all contents with a fresh database
void CleanDatabase();
/// Intentionally sets a bad checksum
void CorruptCrc();
/// Returns success if database is valid otherwise returns the corresponding error code.
Result CheckIntegrity();
private:
/// Returns the checksum of the database
u16 GenerateDatabaseCrc();
u32 magic{}; // 'NFDB'
std::array<StoreData, MaxDatabaseLength> miis{};
u8 version{};
u8 database_length{};
u16 crc{};
};
static_assert(sizeof(NintendoFigurineDatabase) == 0x1A98,
"NintendoFigurineDatabase has incorrect size.");
}; // namespace Service::Mii

View File

@ -0,0 +1,420 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/hle/service/mii/mii_database_manager.h"
#include "core/hle/service/mii/mii_result.h"
#include "core/hle/service/mii/mii_util.h"
#include "core/hle/service/mii/types/char_info.h"
#include "core/hle/service/mii/types/store_data.h"
namespace Service::Mii {
const char* DbFileName = "MiiDatabase.dat";
DatabaseManager::DatabaseManager() {}
Result DatabaseManager::MountSaveData() {
if (!is_save_data_mounted) {
system_save_dir =
Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000030";
if (is_test_db) {
system_save_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) /
"system/save/8000000000000031";
}
// mount point should be "mii:"
if (!Common::FS::CreateDirs(system_save_dir)) {
return ResultUnknown;
}
}
is_save_data_mounted = true;
return ResultSuccess;
}
Result DatabaseManager::Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken) {
is_database_broken = false;
if (!is_save_data_mounted) {
return ResultInvalidArgument;
}
database.CleanDatabase();
update_counter++;
metadata.update_counter = update_counter;
const Common::FS::IOFile db_file{system_save_dir / DbFileName, Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
if (!db_file.IsOpen()) {
return SaveDatabase();
}
if (Common::FS::GetSize(system_save_dir / DbFileName) != sizeof(NintendoFigurineDatabase)) {
is_database_broken = true;
}
if (db_file.Read(database) != 1) {
is_database_broken = true;
}
if (is_database_broken) {
// Dragons happen here for simplicity just clean the database
LOG_ERROR(Service_Mii, "Mii database is corrupted");
database.CleanDatabase();
return ResultUnknown;
}
const auto result = database.CheckIntegrity();
if (result.IsError()) {
LOG_ERROR(Service_Mii, "Mii database is corrupted 0x{:0x}", result.raw);
database.CleanDatabase();
return ResultSuccess;
}
LOG_INFO(Service_Mii, "Successfully loaded mii database. size={}",
database.GetDatabaseLength());
return ResultSuccess;
}
bool DatabaseManager::IsFullDatabase() const {
return database.GetDatabaseLength() == MaxDatabaseLength;
}
bool DatabaseManager::IsModified() const {
return is_moddified;
}
u64 DatabaseManager::GetUpdateCounter() const {
return update_counter;
}
u32 DatabaseManager::GetCount(const DatabaseSessionMetadata& metadata) const {
const u32 database_size = database.GetDatabaseLength();
if (metadata.magic == MiiMagic) {
return database_size;
}
// Special mii can't be used. Skip those.
u32 mii_count{};
for (std::size_t index = 0; index < database_size; ++index) {
const auto& store_data = database.Get(index);
if (store_data.IsSpecial()) {
continue;
}
mii_count++;
}
return mii_count;
}
void DatabaseManager::Get(StoreData& out_store_data, std::size_t index,
const DatabaseSessionMetadata& metadata) const {
if (metadata.magic == MiiMagic) {
out_store_data = database.Get(index);
return;
}
// The index refeers to the mii index without special mii.
// Search on the database until we find it
u32 virtual_index = 0;
const u32 database_size = database.GetDatabaseLength();
for (std::size_t i = 0; i < database_size; ++i) {
const auto& store_data = database.Get(i);
if (store_data.IsSpecial()) {
continue;
}
if (virtual_index == index) {
out_store_data = store_data;
return;
}
virtual_index++;
}
// This function doesn't fail. It returns the first mii instead
out_store_data = database.Get(0);
}
Result DatabaseManager::FindIndex(s32& out_index, const Common::UUID& create_id,
bool is_special) const {
u32 index{};
const bool is_found = database.GetIndexByCreatorId(index, create_id);
if (!is_found) {
return ResultNotFound;
}
if (is_special) {
out_index = index;
return ResultSuccess;
}
if (database.Get(index).IsSpecial()) {
return ResultNotFound;
}
out_index = 0;
if (index < 1) {
return ResultSuccess;
}
for (std::size_t i = 0; i <= index; ++i) {
if (database.Get(i).IsSpecial()) {
continue;
}
out_index++;
}
return ResultSuccess;
}
Result DatabaseManager::FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
const Common::UUID& create_id) const {
u32 index{};
const bool is_found = database.GetIndexByCreatorId(index, create_id);
if (!is_found) {
return ResultNotFound;
}
if (metadata.magic == MiiMagic) {
out_index = index;
return ResultSuccess;
}
if (database.Get(index).IsSpecial()) {
return ResultNotFound;
}
out_index = 0;
if (index < 1) {
return ResultSuccess;
}
// The index refeers to the mii index without special mii.
// Search on the database until we find it
for (std::size_t i = 0; i <= index; ++i) {
const auto& store_data = database.Get(i);
if (store_data.IsSpecial()) {
continue;
}
out_index++;
}
return ResultSuccess;
}
Result DatabaseManager::FindMoveIndex(u32& out_index, u32 new_index,
const Common::UUID& create_id) const {
const auto database_size = database.GetDatabaseLength();
if (database_size >= 1) {
u32 virtual_index{};
for (std::size_t i = 0; i < database_size; ++i) {
const StoreData& store_data = database.Get(i);
if (store_data.IsSpecial()) {
continue;
}
if (virtual_index == new_index) {
const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
if (!is_found) {
return ResultNotFound;
}
if (store_data.IsSpecial()) {
return ResultInvalidOperation;
}
return ResultSuccess;
}
virtual_index++;
}
}
const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
if (!is_found) {
return ResultNotFound;
}
const StoreData& store_data = database.Get(out_index);
if (store_data.IsSpecial()) {
return ResultInvalidOperation;
}
return ResultSuccess;
}
Result DatabaseManager::Move(DatabaseSessionMetadata& metadata, u32 new_index,
const Common::UUID& create_id) {
u32 current_index{};
if (metadata.magic == MiiMagic) {
const bool is_found = database.GetIndexByCreatorId(current_index, create_id);
if (!is_found) {
return ResultNotFound;
}
} else {
const auto result = FindMoveIndex(current_index, new_index, create_id);
if (result.IsError()) {
return result;
}
}
const auto result = database.Move(current_index, new_index);
if (result.IsFailure()) {
return result;
}
is_moddified = true;
update_counter++;
metadata.update_counter = update_counter;
return ResultSuccess;
}
Result DatabaseManager::AddOrReplace(DatabaseSessionMetadata& metadata,
const StoreData& store_data) {
if (store_data.IsValid() != ValidationResult::NoErrors) {
return ResultInvalidStoreData;
}
if (metadata.magic != MiiMagic && store_data.IsSpecial()) {
return ResultInvalidOperation;
}
u32 index{};
const bool is_found = database.GetIndexByCreatorId(index, store_data.GetCreateId());
if (is_found) {
const StoreData& old_store_data = database.Get(index);
if (store_data.IsSpecial() != old_store_data.IsSpecial()) {
return ResultInvalidOperation;
}
database.Replace(index, store_data);
} else {
if (database.IsFull()) {
return ResultDatabaseFull;
}
database.Add(store_data);
}
is_moddified = true;
update_counter++;
metadata.update_counter = update_counter;
return ResultSuccess;
}
Result DatabaseManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) {
u32 index{};
const bool is_found = database.GetIndexByCreatorId(index, create_id);
if (!is_found) {
return ResultNotFound;
}
if (metadata.magic != MiiMagic) {
const auto& store_data = database.Get(index);
if (store_data.IsSpecial()) {
return ResultInvalidOperation;
}
}
database.Delete(index);
is_moddified = true;
update_counter++;
metadata.update_counter = update_counter;
return ResultSuccess;
}
Result DatabaseManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) {
if (char_info.Verify() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo2;
}
if (char_info.GetType() == 1) {
return ResultInvalidCharInfoType;
}
u32 index{};
StoreData store_data{};
// Loop until the mii we created is not on the database
do {
store_data.BuildWithCharInfo(char_info);
} while (database.GetIndexByCreatorId(index, store_data.GetCreateId()));
const Result result = store_data.Restore();
if (result.IsSuccess() || result == ResultNotUpdated) {
return AddOrReplace(metadata, store_data);
}
return result;
}
Result DatabaseManager::DestroyFile(DatabaseSessionMetadata& metadata) {
database.CorruptCrc();
is_moddified = true;
update_counter++;
metadata.update_counter = update_counter;
const auto result = SaveDatabase();
database.CleanDatabase();
return result;
}
Result DatabaseManager::DeleteFile() {
const bool result = Common::FS::RemoveFile(system_save_dir / DbFileName);
// TODO: Return proper FS error here
return result ? ResultSuccess : ResultUnknown;
}
void DatabaseManager::Format(DatabaseSessionMetadata& metadata) {
database.CleanDatabase();
is_moddified = true;
update_counter++;
metadata.update_counter = update_counter;
}
Result DatabaseManager::SaveDatabase() {
// TODO: Replace unknown error codes with proper FS error codes when available
if (!Common::FS::Exists(system_save_dir / DbFileName)) {
if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
LOG_ERROR(Service_Mii, "Failed to create mii database");
return ResultUnknown;
}
}
const auto file_size = Common::FS::GetSize(system_save_dir / DbFileName);
if (file_size != 0 && file_size != sizeof(NintendoFigurineDatabase)) {
if (!Common::FS::RemoveFile(system_save_dir / DbFileName)) {
LOG_ERROR(Service_Mii, "Failed to delete mii database");
return ResultUnknown;
}
if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
LOG_ERROR(Service_Mii, "Failed to create mii database");
return ResultUnknown;
}
}
const Common::FS::IOFile db_file{system_save_dir / DbFileName,
Common::FS::FileAccessMode::ReadWrite,
Common::FS::FileType::BinaryFile};
if (db_file.Write(database) != 1) {
LOG_ERROR(Service_Mii, "Failed to save mii database");
return ResultUnknown;
}
is_moddified = false;
return ResultSuccess;
}
} // namespace Service::Mii

View File

@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/fs/fs.h"
#include "core/hle/result.h"
#include "core/hle/service/mii/mii_database.h"
namespace Service::Mii {
class CharInfo;
class StoreData;
class DatabaseManager {
public:
DatabaseManager();
Result MountSaveData();
Result Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken);
bool IsFullDatabase() const;
bool IsModified() const;
u64 GetUpdateCounter() const;
void Get(StoreData& out_store_data, std::size_t index,
const DatabaseSessionMetadata& metadata) const;
u32 GetCount(const DatabaseSessionMetadata& metadata) const;
Result FindIndex(s32& out_index, const Common::UUID& create_id, bool is_special) const;
Result FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
const Common::UUID& create_id) const;
Result FindMoveIndex(u32& out_index, u32 new_index, const Common::UUID& create_id) const;
Result Move(DatabaseSessionMetadata& metadata, u32 current_index,
const Common::UUID& create_id);
Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& out_store_data);
Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id);
Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info);
Result DestroyFile(DatabaseSessionMetadata& metadata);
Result DeleteFile();
void Format(DatabaseSessionMetadata& metadata);
Result SaveDatabase();
private:
// This is the global value of
// nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
bool is_test_db{};
bool is_moddified{};
bool is_save_data_mounted{};
u64 update_counter{};
NintendoFigurineDatabase database{};
std::filesystem::path system_save_dir{};
};
}; // namespace Service::Mii

View File

@ -1,59 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <random>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/mii/mii_database_manager.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/mii/mii_result.h"
#include "core/hle/service/mii/mii_util.h"
#include "core/hle/service/mii/types/char_info.h"
#include "core/hle/service/mii/types/core_data.h"
#include "core/hle/service/mii/types/raw_data.h"
#include "core/hle/service/mii/types/store_data.h"
#include "core/hle/service/mii/types/ver3_store_data.h"
namespace Service::Mii {
constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
MiiManager::MiiManager() {}
bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return false;
}
const auto metadata_update_counter = metadata.update_counter;
metadata.update_counter = update_counter;
return metadata_update_counter != update_counter;
}
bool MiiManager::IsFullDatabase() const {
// TODO(bunnei): We don't implement the Mii database, so it cannot be full
return false;
}
u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
u32 mii_count{};
if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
mii_count += DefaultMiiCount;
}
if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
// TODO(bunnei): We don't implement the Mii database, but when we do, update this
}
return mii_count;
}
Result MiiManager::UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
const CharInfo& char_info, SourceFlag source_flag) {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return ResultNotFound;
}
// TODO(bunnei): We don't implement the Mii database, so we can't have an entry
return ResultNotFound;
Result MiiManager::Initialize(DatabaseSessionMetadata& metadata) {
database_manager.MountSaveData();
database_manager.Initialize(metadata, is_broken_with_clear_flag);
return ResultSuccess;
}
void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const {
@ -74,39 +41,368 @@ void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Ra
out_char_info.SetFromStoreData(store_data);
}
void MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const {
bool MiiManager::IsFullDatabase() const {
return database_manager.IsFullDatabase();
}
void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const {
metadata.interface_version = version;
}
bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return false;
}
const u64 metadata_update_counter = metadata.update_counter;
const u64 database_update_counter = database_manager.GetUpdateCounter();
metadata.update_counter = database_update_counter;
return metadata_update_counter != database_update_counter;
}
u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
u32 mii_count{};
if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
mii_count += DefaultMiiCount;
}
if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
mii_count += database_manager.GetCount(metadata);
}
return mii_count;
}
Result MiiManager::Move(DatabaseSessionMetadata& metadata, u32 index,
const Common::UUID& create_id) {
const auto result = database_manager.Move(metadata, index, create_id);
if (result.IsFailure()) {
return result;
}
if (!database_manager.IsModified()) {
return ResultNotUpdated;
}
return database_manager.SaveDatabase();
}
Result MiiManager::AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data) {
const auto result = database_manager.AddOrReplace(metadata, store_data);
if (result.IsFailure()) {
return result;
}
if (!database_manager.IsModified()) {
return ResultNotUpdated;
}
return database_manager.SaveDatabase();
}
Result MiiManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) {
const auto result = database_manager.Delete(metadata, create_id);
if (result.IsFailure()) {
return result;
}
if (!database_manager.IsModified()) {
return ResultNotUpdated;
}
return database_manager.SaveDatabase();
}
s32 MiiManager::FindIndex(const Common::UUID& create_id, bool is_special) const {
s32 index{};
const auto result = database_manager.FindIndex(index, create_id, is_special);
if (result.IsError()) {
index = -1;
}
return index;
}
Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
s32& out_index) const {
if (char_info.Verify() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo;
}
s32 index{};
const bool is_special = metadata.magic == MiiMagic;
const auto result = database_manager.FindIndex(index, char_info.GetCreateId(), is_special);
if (result.IsError()) {
index = -1;
}
if (index == -1) {
return ResultNotFound;
}
out_index = index;
return ResultSuccess;
}
Result MiiManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) {
const auto result = database_manager.Append(metadata, char_info);
if (result.IsError()) {
return ResultNotFound;
}
if (!database_manager.IsModified()) {
return ResultNotUpdated;
}
return database_manager.SaveDatabase();
}
bool MiiManager::IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata) {
const bool is_broken = is_broken_with_clear_flag;
if (is_broken_with_clear_flag) {
is_broken_with_clear_flag = false;
database_manager.Format(metadata);
database_manager.SaveDatabase();
}
return is_broken;
}
Result MiiManager::DestroyFile(DatabaseSessionMetadata& metadata) {
is_broken_with_clear_flag = true;
return database_manager.DestroyFile(metadata);
}
Result MiiManager::DeleteFile() {
return database_manager.DeleteFile();
}
Result MiiManager::Format(DatabaseSessionMetadata& metadata) {
database_manager.Format(metadata);
if (!database_manager.IsModified()) {
return ResultNotUpdated;
}
return database_manager.SaveDatabase();
}
Result MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const {
if (!mii_v3.IsValid()) {
return ResultInvalidCharInfo;
}
StoreData store_data{};
mii_v3.BuildToStoreData(store_data);
const auto name = store_data.GetNickname();
if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) {
store_data.SetInvalidName();
}
out_char_info.SetFromStoreData(store_data);
return ResultSuccess;
}
Result MiiManager::ConvertCoreDataToCharInfo(CharInfo& out_char_info,
const CoreData& core_data) const {
if (core_data.IsValid() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo;
}
StoreData store_data{};
store_data.BuildWithCoreData(core_data);
const auto name = store_data.GetNickname();
if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) {
store_data.SetInvalidName();
}
out_char_info.SetFromStoreData(store_data);
return ResultSuccess;
}
Result MiiManager::ConvertCharInfoToCoreData(CoreData& out_core_data,
const CharInfo& char_info) const {
if (char_info.Verify() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo;
}
out_core_data.BuildFromCharInfo(char_info);
const auto name = out_core_data.GetNickname();
if (!MiiUtil::IsFontRegionValid(out_core_data.GetFontRegion(), name.data)) {
out_core_data.SetNickname(out_core_data.GetInvalidNickname());
}
return ResultSuccess;
}
Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
const CharInfo& char_info, SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return ResultNotFound;
}
if (metadata.IsInterfaceVersionSupported(1)) {
if (char_info.Verify() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo;
}
}
u32 index{};
Result result = database_manager.FindIndex(metadata, index, char_info.GetCreateId());
if (result.IsError()) {
return result;
}
StoreData store_data{};
database_manager.Get(store_data, index, metadata);
if (store_data.GetType() != char_info.GetType()) {
return ResultNotFound;
}
out_char_info.SetFromStoreData(store_data);
if (char_info == out_char_info) {
return ResultNotUpdated;
}
return ResultSuccess;
}
Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data,
const StoreData& store_data, SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return ResultNotFound;
}
if (metadata.IsInterfaceVersionSupported(1)) {
if (store_data.IsValid() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo;
}
}
u32 index{};
Result result = database_manager.FindIndex(metadata, index, store_data.GetCreateId());
if (result.IsError()) {
return result;
}
database_manager.Get(out_store_data, index, metadata);
if (out_store_data.GetType() != store_data.GetType()) {
return ResultNotFound;
}
if (store_data == out_store_data) {
return ResultNotUpdated;
}
return ResultSuccess;
}
Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
std::span<CharInfoElement> out_elements, u32& out_count,
SourceFlag source_flag) {
SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return BuildDefault(out_elements, out_count, source_flag);
}
// TODO(bunnei): We don't implement the Mii database, so we can't have an entry
const auto mii_count = database_manager.GetCount(metadata);
for (std::size_t index = 0; index < mii_count; ++index) {
if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
StoreData store_data{};
database_manager.Get(store_data, index, metadata);
out_elements[out_count].source = Source::Database;
out_elements[out_count].char_info.SetFromStoreData(store_data);
out_count++;
}
// Include default Mii at the end of the list
return BuildDefault(out_elements, out_count, source_flag);
}
Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
u32& out_count, SourceFlag source_flag) {
u32& out_count, SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return BuildDefault(out_char_info, out_count, source_flag);
}
// TODO(bunnei): We don't implement the Mii database, so we can't have an entry
const auto mii_count = database_manager.GetCount(metadata);
for (std::size_t index = 0; index < mii_count; ++index) {
if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
StoreData store_data{};
database_manager.Get(store_data, index, metadata);
out_char_info[out_count].SetFromStoreData(store_data);
out_count++;
}
// Include default Mii at the end of the list
return BuildDefault(out_char_info, out_count, source_flag);
}
Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
std::span<StoreDataElement> out_elements, u32& out_count,
SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return BuildDefault(out_elements, out_count, source_flag);
}
const auto mii_count = database_manager.GetCount(metadata);
for (std::size_t index = 0; index < mii_count; ++index) {
if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
StoreData store_data{};
database_manager.Get(store_data, index, metadata);
out_elements[out_count].store_data = store_data;
out_elements[out_count].source = Source::Database;
out_count++;
}
// Include default Mii at the end of the list
return BuildDefault(out_elements, out_count, source_flag);
}
Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data,
u32& out_count, SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return BuildDefault(out_store_data, out_count, source_flag);
}
const auto mii_count = database_manager.GetCount(metadata);
for (std::size_t index = 0; index < mii_count; ++index) {
if (out_store_data.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
StoreData store_data{};
database_manager.Get(store_data, index, metadata);
out_store_data[out_count] = store_data;
out_count++;
}
// Include default Mii at the end of the list
return BuildDefault(out_store_data, out_count, source_flag);
}
Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
SourceFlag source_flag) {
SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
return ResultSuccess;
}
@ -129,7 +425,7 @@ Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& ou
}
Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
SourceFlag source_flag) {
SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
return ResultSuccess;
}
@ -150,23 +446,41 @@ Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_coun
return ResultSuccess;
}
Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
s32& out_index) {
if (char_info.Verify() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo;
Result MiiManager::BuildDefault(std::span<StoreDataElement> out_elements, u32& out_count,
SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
return ResultSuccess;
}
constexpr u32 INVALID_INDEX{0xFFFFFFFF};
for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
out_index = INVALID_INDEX;
out_elements[out_count].store_data.BuildDefault(static_cast<u32>(index));
out_elements[out_count].source = Source::Default;
out_count++;
}
// TODO(bunnei): We don't implement the Mii database, so we can't have an index
return ResultNotFound;
return ResultSuccess;
}
void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) {
metadata.interface_version = version;
Result MiiManager::BuildDefault(std::span<StoreData> out_char_info, u32& out_count,
SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
return ResultSuccess;
}
for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
out_char_info[out_count].BuildDefault(static_cast<u32>(index));
out_count++;
}
return ResultSuccess;
}
} // namespace Service::Mii

View File

@ -3,47 +3,85 @@
#pragma once
#include <vector>
#include <span>
#include "core/hle/result.h"
#include "core/hle/service/mii/mii_database_manager.h"
#include "core/hle/service/mii/mii_types.h"
#include "core/hle/service/mii/types/char_info.h"
#include "core/hle/service/mii/types/store_data.h"
#include "core/hle/service/mii/types/ver3_store_data.h"
namespace Service::Mii {
class CharInfo;
class CoreData;
class StoreData;
class Ver3StoreData;
// The Mii manager is responsible for loading and storing the Miis to the database in NAND along
// with providing an easy interface for HLE emulation of the mii service.
struct CharInfoElement;
struct StoreDataElement;
// The Mii manager is responsible for handling mii operations along with providing an easy interface
// for HLE emulation of the mii service.
class MiiManager {
public:
MiiManager();
Result Initialize(DatabaseSessionMetadata& metadata);
bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
bool IsFullDatabase() const;
u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
Result UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
const CharInfo& char_info, SourceFlag source_flag);
Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements,
u32& out_count, SourceFlag source_flag);
Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
u32& out_count, SourceFlag source_flag);
// Auto generated mii
void BuildDefault(CharInfo& out_char_info, u32 index) const;
void BuildBase(CharInfo& out_char_info, Gender gender) const;
void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const;
void ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const;
std::vector<CharInfoElement> GetDefault(SourceFlag source_flag);
// Database operations
bool IsFullDatabase() const;
void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const;
bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
Result Move(DatabaseSessionMetadata& metadata, u32 index, const Common::UUID& create_id);
Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data);
Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id);
s32 FindIndex(const Common::UUID& create_id, bool is_special) const;
Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
s32& out_index);
void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version);
s32& out_index) const;
Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info);
// Test database operations
bool IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata);
Result DestroyFile(DatabaseSessionMetadata& metadata);
Result DeleteFile();
Result Format(DatabaseSessionMetadata& metadata);
// Mii conversions
Result ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const;
Result ConvertCoreDataToCharInfo(CharInfo& out_char_info, const CoreData& core_data) const;
Result ConvertCharInfoToCoreData(CoreData& out_core_data, const CharInfo& char_info) const;
Result UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
const CharInfo& char_info, SourceFlag source_flag) const;
Result UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data,
const StoreData& store_data, SourceFlag source_flag) const;
// Overloaded getters
Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements,
u32& out_count, SourceFlag source_flag) const;
Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
u32& out_count, SourceFlag source_flag) const;
Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreDataElement> out_elements,
u32& out_count, SourceFlag source_flag) const;
Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data,
u32& out_count, SourceFlag source_flag) const;
private:
Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
SourceFlag source_flag);
Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, SourceFlag source_flag);
SourceFlag source_flag) const;
Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
SourceFlag source_flag) const;
Result BuildDefault(std::span<StoreDataElement> out_char_info, u32& out_count,
SourceFlag source_flag) const;
Result BuildDefault(std::span<StoreData> out_char_info, u32& out_count,
SourceFlag source_flag) const;
u64 update_counter{};
DatabaseManager database_manager{};
// This should be a global value
bool is_broken_with_clear_flag{};
};
}; // namespace Service::Mii

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -13,8 +13,15 @@ constexpr Result ResultNotUpdated{ErrorModule::Mii, 3};
constexpr Result ResultNotFound{ErrorModule::Mii, 4};
constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5};
constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100};
constexpr Result ResultInvalidDatabaseChecksum{ErrorModule::Mii, 101};
constexpr Result ResultInvalidDatabaseSignature{ErrorModule::Mii, 103};
constexpr Result ResultInvalidDatabaseVersion{ErrorModule::Mii, 104};
constexpr Result ResultInvalidDatabaseLength{ErrorModule::Mii, 105};
constexpr Result ResultInvalidCharInfo2{ErrorModule::Mii, 107};
constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109};
constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202};
constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203};
constexpr Result ResultTestModeOnly{ErrorModule::Mii, 204};
constexpr Result ResultInvalidCharInfoType{ErrorModule::Mii, 205};
}; // namespace Service::Mii

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -13,6 +13,7 @@
namespace Service::Mii {
constexpr std::size_t MaxNameSize = 10;
constexpr u8 MaxHeight = 127;
constexpr u8 MaxBuild = 127;
constexpr u8 MaxType = 1;
@ -26,14 +27,14 @@ constexpr u8 MaxEyebrowScale = 8;
constexpr u8 MaxEyebrowAspect = 6;
constexpr u8 MaxEyebrowRotate = 11;
constexpr u8 MaxEyebrowX = 12;
constexpr u8 MaxEyebrowY = 18;
constexpr u8 MaxEyebrowY = 15;
constexpr u8 MaxNoseScale = 8;
constexpr u8 MaxNoseY = 18;
constexpr u8 MaxMouthScale = 8;
constexpr u8 MaxMoutAspect = 6;
constexpr u8 MaxMouthY = 18;
constexpr u8 MaxMustacheScale = 8;
constexpr u8 MasMustacheY = 16;
constexpr u8 MaxMustacheY = 16;
constexpr u8 MaxGlassScale = 7;
constexpr u8 MaxGlassY = 20;
constexpr u8 MaxMoleScale = 8;
@ -599,12 +600,12 @@ enum class ValidationResult : u32 {
InvalidRegionMove = 0x31,
InvalidCreateId = 0x32,
InvalidName = 0x33,
InvalidChecksum = 0x34,
InvalidType = 0x35,
};
struct Nickname {
static constexpr std::size_t MaxNameSize = 10;
std::array<char16_t, MaxNameSize> data;
std::array<char16_t, MaxNameSize> data{};
// Checks for null or dirty strings
bool IsValid() const {
@ -613,7 +614,7 @@ struct Nickname {
}
std::size_t index = 1;
while (data[index] != 0) {
while (index < MaxNameSize && data[index] != 0) {
index++;
}
while (index < MaxNameSize && data[index] == 0) {

View File

@ -28,6 +28,32 @@ public:
return Common::swap16(static_cast<u16>(crc));
}
static u16 CalculateDeviceCrc16(const Common::UUID& uuid, std::size_t data_size) {
constexpr u16 magic{0x1021};
s32 crc{};
for (std::size_t i = 0; i < uuid.uuid.size(); i++) {
for (std::size_t j = 0; j < 8; j++) {
crc <<= 1;
if ((crc & 0x10000) != 0) {
crc = crc ^ magic;
}
}
crc ^= uuid.uuid[i];
}
// As much as this looks wrong this is what N's does
for (std::size_t i = 0; i < data_size * 8; i++) {
crc <<= 1;
if ((crc & 0x10000) != 0) {
crc = crc ^ magic;
}
}
return Common::swap16(static_cast<u16>(crc));
}
static Common::UUID MakeCreateId() {
return Common::UUID::MakeRandomRFC4122V4();
}

View File

@ -150,7 +150,7 @@ ValidationResult CharInfo::Verify() const {
if (eyebrow_x > MaxEyebrowX) {
return ValidationResult::InvalidEyebrowX;
}
if (eyebrow_y > MaxEyebrowY) {
if (eyebrow_y - 3 > MaxEyebrowY) {
return ValidationResult::InvalidEyebrowY;
}
if (nose_type > NoseType::Max) {
@ -189,7 +189,7 @@ ValidationResult CharInfo::Verify() const {
if (mustache_scale > MaxMustacheScale) {
return ValidationResult::InvalidMustacheScale;
}
if (mustache_y > MasMustacheY) {
if (mustache_y > MaxMustacheY) {
return ValidationResult::InvalidMustacheY;
}
if (glass_type > GlassType::Max) {

View File

@ -70,59 +70,59 @@ public:
bool operator==(const CharInfo& info);
private:
Common::UUID create_id;
Nickname name;
u16 null_terminator;
FontRegion font_region;
FavoriteColor favorite_color;
Gender gender;
u8 height;
u8 build;
u8 type;
u8 region_move;
FacelineType faceline_type;
FacelineColor faceline_color;
FacelineWrinkle faceline_wrinkle;
FacelineMake faceline_make;
HairType hair_type;
CommonColor hair_color;
HairFlip hair_flip;
EyeType eye_type;
CommonColor eye_color;
u8 eye_scale;
u8 eye_aspect;
u8 eye_rotate;
u8 eye_x;
u8 eye_y;
EyebrowType eyebrow_type;
CommonColor eyebrow_color;
u8 eyebrow_scale;
u8 eyebrow_aspect;
u8 eyebrow_rotate;
u8 eyebrow_x;
u8 eyebrow_y;
NoseType nose_type;
u8 nose_scale;
u8 nose_y;
MouthType mouth_type;
CommonColor mouth_color;
u8 mouth_scale;
u8 mouth_aspect;
u8 mouth_y;
CommonColor beard_color;
BeardType beard_type;
MustacheType mustache_type;
u8 mustache_scale;
u8 mustache_y;
GlassType glass_type;
CommonColor glass_color;
u8 glass_scale;
u8 glass_y;
MoleType mole_type;
u8 mole_scale;
u8 mole_x;
u8 mole_y;
u8 padding;
Common::UUID create_id{};
Nickname name{};
u16 null_terminator{};
FontRegion font_region{};
FavoriteColor favorite_color{};
Gender gender{};
u8 height{};
u8 build{};
u8 type{};
u8 region_move{};
FacelineType faceline_type{};
FacelineColor faceline_color{};
FacelineWrinkle faceline_wrinkle{};
FacelineMake faceline_make{};
HairType hair_type{};
CommonColor hair_color{};
HairFlip hair_flip{};
EyeType eye_type{};
CommonColor eye_color{};
u8 eye_scale{};
u8 eye_aspect{};
u8 eye_rotate{};
u8 eye_x{};
u8 eye_y{};
EyebrowType eyebrow_type{};
CommonColor eyebrow_color{};
u8 eyebrow_scale{};
u8 eyebrow_aspect{};
u8 eyebrow_rotate{};
u8 eyebrow_x{};
u8 eyebrow_y{};
NoseType nose_type{};
u8 nose_scale{};
u8 nose_y{};
MouthType mouth_type{};
CommonColor mouth_color{};
u8 mouth_scale{};
u8 mouth_aspect{};
u8 mouth_y{};
CommonColor beard_color{};
BeardType beard_type{};
MustacheType mustache_type{};
u8 mustache_scale{};
u8 mustache_y{};
GlassType glass_type{};
CommonColor glass_color{};
u8 glass_scale{};
u8 glass_y{};
MoleType mole_type{};
u8 mole_scale{};
u8 mole_x{};
u8 mole_y{};
u8 padding{};
};
static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
static_assert(std::has_unique_object_representations_v<CharInfo>,

View File

@ -3,6 +3,7 @@
#include "common/assert.h"
#include "core/hle/service/mii/mii_util.h"
#include "core/hle/service/mii/types/char_info.h"
#include "core/hle/service/mii/types/core_data.h"
#include "core/hle/service/mii/types/raw_data.h"
@ -170,7 +171,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
u8 glasses_type{};
while (glasses_type_start < glasses_type_info.values[glasses_type]) {
if (++glasses_type >= glasses_type_info.values_count) {
ASSERT(false);
glasses_type = 0;
break;
}
}
@ -178,6 +179,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
SetGlassType(static_cast<GlassType>(glasses_type));
SetGlassColor(RawData::GetGlassColorFromVer3(0));
SetGlassScale(4);
SetGlassY(static_cast<u8>(axis_y + 10));
SetMoleType(MoleType::None);
SetMoleScale(4);
@ -185,9 +187,211 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
SetMoleY(20);
}
u32 CoreData::IsValid() const {
// TODO: Complete this
return 0;
void CoreData::BuildFromCharInfo(const CharInfo& char_info) {
name = char_info.GetNickname();
SetFontRegion(char_info.GetFontRegion());
SetFavoriteColor(char_info.GetFavoriteColor());
SetGender(char_info.GetGender());
SetHeight(char_info.GetHeight());
SetBuild(char_info.GetBuild());
SetType(char_info.GetType());
SetRegionMove(char_info.GetRegionMove());
SetFacelineType(char_info.GetFacelineType());
SetFacelineColor(char_info.GetFacelineColor());
SetFacelineWrinkle(char_info.GetFacelineWrinkle());
SetFacelineMake(char_info.GetFacelineMake());
SetHairType(char_info.GetHairType());
SetHairColor(char_info.GetHairColor());
SetHairFlip(char_info.GetHairFlip());
SetEyeType(char_info.GetEyeType());
SetEyeColor(char_info.GetEyeColor());
SetEyeScale(char_info.GetEyeScale());
SetEyeAspect(char_info.GetEyeAspect());
SetEyeRotate(char_info.GetEyeRotate());
SetEyeX(char_info.GetEyeX());
SetEyeY(char_info.GetEyeY());
SetEyebrowType(char_info.GetEyebrowType());
SetEyebrowColor(char_info.GetEyebrowColor());
SetEyebrowScale(char_info.GetEyebrowScale());
SetEyebrowAspect(char_info.GetEyebrowAspect());
SetEyebrowRotate(char_info.GetEyebrowRotate());
SetEyebrowX(char_info.GetEyebrowX());
SetEyebrowY(char_info.GetEyebrowY() - 3);
SetNoseType(char_info.GetNoseType());
SetNoseScale(char_info.GetNoseScale());
SetNoseY(char_info.GetNoseY());
SetMouthType(char_info.GetMouthType());
SetMouthColor(char_info.GetMouthColor());
SetMouthScale(char_info.GetMouthScale());
SetMouthAspect(char_info.GetMouthAspect());
SetMouthY(char_info.GetMouthY());
SetBeardColor(char_info.GetBeardColor());
SetBeardType(char_info.GetBeardType());
SetMustacheType(char_info.GetMustacheType());
SetMustacheScale(char_info.GetMustacheScale());
SetMustacheY(char_info.GetMustacheY());
SetGlassType(char_info.GetGlassType());
SetGlassColor(char_info.GetGlassColor());
SetGlassScale(char_info.GetGlassScale());
SetGlassY(char_info.GetGlassY());
SetMoleType(char_info.GetMoleType());
SetMoleScale(char_info.GetMoleScale());
SetMoleX(char_info.GetMoleX());
SetMoleY(char_info.GetMoleY());
}
ValidationResult CoreData::IsValid() const {
if (!name.IsValid()) {
return ValidationResult::InvalidName;
}
if (GetFontRegion() > FontRegion::Max) {
return ValidationResult::InvalidFont;
}
if (GetFavoriteColor() > FavoriteColor::Max) {
return ValidationResult::InvalidColor;
}
if (GetGender() > Gender::Max) {
return ValidationResult::InvalidGender;
}
if (GetHeight() > MaxHeight) {
return ValidationResult::InvalidHeight;
}
if (GetBuild() > MaxBuild) {
return ValidationResult::InvalidBuild;
}
if (GetType() > MaxType) {
return ValidationResult::InvalidType;
}
if (GetRegionMove() > MaxRegionMove) {
return ValidationResult::InvalidRegionMove;
}
if (GetFacelineType() > FacelineType::Max) {
return ValidationResult::InvalidFacelineType;
}
if (GetFacelineColor() > FacelineColor::Max) {
return ValidationResult::InvalidFacelineColor;
}
if (GetFacelineWrinkle() > FacelineWrinkle::Max) {
return ValidationResult::InvalidFacelineWrinkle;
}
if (GetFacelineMake() > FacelineMake::Max) {
return ValidationResult::InvalidFacelineMake;
}
if (GetHairType() > HairType::Max) {
return ValidationResult::InvalidHairType;
}
if (GetHairColor() > CommonColor::Max) {
return ValidationResult::InvalidHairColor;
}
if (GetHairFlip() > HairFlip::Max) {
return ValidationResult::InvalidHairFlip;
}
if (GetEyeType() > EyeType::Max) {
return ValidationResult::InvalidEyeType;
}
if (GetEyeColor() > CommonColor::Max) {
return ValidationResult::InvalidEyeColor;
}
if (GetEyeScale() > MaxEyeScale) {
return ValidationResult::InvalidEyeScale;
}
if (GetEyeAspect() > MaxEyeAspect) {
return ValidationResult::InvalidEyeAspect;
}
if (GetEyeRotate() > MaxEyeRotate) {
return ValidationResult::InvalidEyeRotate;
}
if (GetEyeX() > MaxEyeX) {
return ValidationResult::InvalidEyeX;
}
if (GetEyeY() > MaxEyeY) {
return ValidationResult::InvalidEyeY;
}
if (GetEyebrowType() > EyebrowType::Max) {
return ValidationResult::InvalidEyebrowType;
}
if (GetEyebrowColor() > CommonColor::Max) {
return ValidationResult::InvalidEyebrowColor;
}
if (GetEyebrowScale() > MaxEyebrowScale) {
return ValidationResult::InvalidEyebrowScale;
}
if (GetEyebrowAspect() > MaxEyebrowAspect) {
return ValidationResult::InvalidEyebrowAspect;
}
if (GetEyebrowRotate() > MaxEyebrowRotate) {
return ValidationResult::InvalidEyebrowRotate;
}
if (GetEyebrowX() > MaxEyebrowX) {
return ValidationResult::InvalidEyebrowX;
}
if (GetEyebrowY() > MaxEyebrowY) {
return ValidationResult::InvalidEyebrowY;
}
if (GetNoseType() > NoseType::Max) {
return ValidationResult::InvalidNoseType;
}
if (GetNoseScale() > MaxNoseScale) {
return ValidationResult::InvalidNoseScale;
}
if (GetNoseY() > MaxNoseY) {
return ValidationResult::InvalidNoseY;
}
if (GetMouthType() > MouthType::Max) {
return ValidationResult::InvalidMouthType;
}
if (GetMouthColor() > CommonColor::Max) {
return ValidationResult::InvalidMouthColor;
}
if (GetMouthScale() > MaxMouthScale) {
return ValidationResult::InvalidMouthScale;
}
if (GetMouthAspect() > MaxMoutAspect) {
return ValidationResult::InvalidMouthAspect;
}
if (GetMouthY() > MaxMouthY) {
return ValidationResult::InvalidMouthY;
}
if (GetBeardColor() > CommonColor::Max) {
return ValidationResult::InvalidBeardColor;
}
if (GetBeardType() > BeardType::Max) {
return ValidationResult::InvalidBeardType;
}
if (GetMustacheType() > MustacheType::Max) {
return ValidationResult::InvalidMustacheType;
}
if (GetMustacheScale() > MaxMustacheScale) {
return ValidationResult::InvalidMustacheScale;
}
if (GetMustacheY() > MaxMustacheY) {
return ValidationResult::InvalidMustacheY;
}
if (GetGlassType() > GlassType::Max) {
return ValidationResult::InvalidGlassType;
}
if (GetGlassColor() > CommonColor::Max) {
return ValidationResult::InvalidGlassColor;
}
if (GetGlassScale() > MaxGlassScale) {
return ValidationResult::InvalidGlassScale;
}
if (GetGlassY() > MaxGlassY) {
return ValidationResult::InvalidGlassY;
}
if (GetMoleType() > MoleType::Max) {
return ValidationResult::InvalidMoleType;
}
if (GetMoleScale() > MaxMoleScale) {
return ValidationResult::InvalidMoleScale;
}
if (GetMoleX() > MaxMoleX) {
return ValidationResult::InvalidMoleX;
}
if (GetMoleY() > MaxMoleY) {
return ValidationResult::InvalidMoleY;
}
return ValidationResult::NoErrors;
}
void CoreData::SetFontRegion(FontRegion value) {
@ -314,8 +518,8 @@ void CoreData::SetNoseY(u8 value) {
data.nose_y.Assign(value);
}
void CoreData::SetMouthType(u8 value) {
data.mouth_type.Assign(value);
void CoreData::SetMouthType(MouthType value) {
data.mouth_type.Assign(static_cast<u32>(value));
}
void CoreData::SetMouthColor(CommonColor value) {

View File

@ -6,6 +6,7 @@
#include "core/hle/service/mii/mii_types.h"
namespace Service::Mii {
class CharInfo;
struct StoreDataBitFields {
union {
@ -100,8 +101,9 @@ class CoreData {
public:
void SetDefault();
void BuildRandom(Age age, Gender gender, Race race);
void BuildFromCharInfo(const CharInfo& char_info);
u32 IsValid() const;
ValidationResult IsValid() const;
void SetFontRegion(FontRegion value);
void SetFavoriteColor(FavoriteColor value);
@ -134,7 +136,7 @@ public:
void SetNoseType(NoseType value);
void SetNoseScale(u8 value);
void SetNoseY(u8 value);
void SetMouthType(u8 value);
void SetMouthType(MouthType value);
void SetMouthColor(CommonColor value);
void SetMouthScale(u8 value);
void SetMouthAspect(u8 value);
@ -212,5 +214,6 @@ private:
Nickname name{};
};
static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size.");
static_assert(std::is_trivially_copyable_v<CoreData>, "CoreData type must be trivially copyable.");
}; // namespace Service::Mii

View File

@ -1716,18 +1716,18 @@ const std::array<RandomMiiData4, 18> RandomMiiMouthType{
const std::array<RandomMiiData2, 3> RandomMiiGlassType{
RandomMiiData2{
.arg_1 = 0,
.values_count = 9,
.values = {90, 94, 96, 100, 0, 0, 0, 0, 0},
.values_count = 4,
.values = {90, 94, 96, 100},
},
RandomMiiData2{
.arg_1 = 1,
.values_count = 9,
.values = {83, 86, 90, 93, 94, 96, 98, 100, 0},
.values_count = 8,
.values = {83, 86, 90, 93, 94, 96, 98, 100},
},
RandomMiiData2{
.arg_1 = 2,
.values_count = 9,
.values = {78, 83, 0, 93, 0, 0, 98, 100, 0},
.values_count = 8,
.values = {78, 83, 0, 93, 0, 0, 98, 100},
},
};

View File

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/mii/mii_result.h"
#include "core/hle/service/mii/mii_util.h"
#include "core/hle/service/mii/types/raw_data.h"
#include "core/hle/service/mii/types/store_data.h"
@ -35,13 +36,13 @@ void StoreData::BuildDefault(u32 mii_index) {
core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y));
core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3));
core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type));
core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type));
core_data.SetMouthColor(
RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
@ -75,10 +76,8 @@ void StoreData::BuildDefault(u32 mii_index) {
core_data.SetType(static_cast<u8>(default_mii.type));
core_data.SetNickname(default_mii.nickname);
const auto device_id = MiiUtil::GetDeviceId();
create_id = MiiUtil::MakeCreateId();
device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
SetChecksum();
}
void StoreData::BuildBase(Gender gender) {
@ -109,13 +108,13 @@ void StoreData::BuildBase(Gender gender) {
core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y));
core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3));
core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type));
core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type));
core_data.SetMouthColor(
RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
@ -149,37 +148,51 @@ void StoreData::BuildBase(Gender gender) {
core_data.SetType(static_cast<u8>(default_mii.type));
core_data.SetNickname(default_mii.nickname);
const auto device_id = MiiUtil::GetDeviceId();
create_id = MiiUtil::MakeCreateId();
device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
SetChecksum();
}
void StoreData::BuildRandom(Age age, Gender gender, Race race) {
core_data.BuildRandom(age, gender, race);
const auto device_id = MiiUtil::GetDeviceId();
create_id = MiiUtil::MakeCreateId();
device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
SetChecksum();
}
void StoreData::SetInvalidName() {
const auto& invalid_name = core_data.GetInvalidNickname();
void StoreData::BuildWithCharInfo(const CharInfo& char_info) {
core_data.BuildFromCharInfo(char_info);
create_id = MiiUtil::MakeCreateId();
SetChecksum();
}
void StoreData::BuildWithCoreData(const CoreData& in_core_data) {
core_data = in_core_data;
create_id = MiiUtil::MakeCreateId();
SetChecksum();
}
Result StoreData::Restore() {
// TODO: Implement this
return ResultNotUpdated;
}
ValidationResult StoreData::IsValid() const {
if (core_data.IsValid() != ValidationResult::NoErrors) {
return core_data.IsValid();
}
if (data_crc != MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID))) {
return ValidationResult::InvalidChecksum;
}
const auto device_id = MiiUtil::GetDeviceId();
core_data.SetNickname(invalid_name);
device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
if (device_crc != MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData))) {
return ValidationResult::InvalidChecksum;
}
return ValidationResult::NoErrors;
}
bool StoreData::IsSpecial() const {
return GetType() == 1;
}
u32 StoreData::IsValid() const {
// TODO: complete this
return 0;
}
void StoreData::SetFontRegion(FontRegion value) {
core_data.SetFontRegion(value);
}
@ -304,7 +317,7 @@ void StoreData::SetNoseY(u8 value) {
core_data.SetNoseY(value);
}
void StoreData::SetMouthType(u8 value) {
void StoreData::SetMouthType(MouthType value) {
core_data.SetMouthType(value);
}
@ -380,6 +393,26 @@ void StoreData::SetNickname(Nickname value) {
core_data.SetNickname(value);
}
void StoreData::SetInvalidName() {
const auto& invalid_name = core_data.GetInvalidNickname();
core_data.SetNickname(invalid_name);
SetChecksum();
}
void StoreData::SetChecksum() {
SetDataChecksum();
SetDeviceChecksum();
}
void StoreData::SetDataChecksum() {
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID));
}
void StoreData::SetDeviceChecksum() {
const auto device_id = MiiUtil::GetDeviceId();
device_crc = MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData));
}
Common::UUID StoreData::GetCreateId() const {
return create_id;
}
@ -585,7 +618,7 @@ Nickname StoreData::GetNickname() const {
}
bool StoreData::operator==(const StoreData& data) {
bool is_identical = data.core_data.IsValid() == 0;
bool is_identical = data.core_data.IsValid() == ValidationResult::NoErrors;
is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data;
is_identical &= GetCreateId() == data.GetCreateId();
is_identical &= GetFontRegion() == data.GetFontRegion();

View File

@ -3,6 +3,7 @@
#pragma once
#include "core/hle/result.h"
#include "core/hle/service/mii/mii_types.h"
#include "core/hle/service/mii/types/core_data.h"
@ -10,18 +11,17 @@ namespace Service::Mii {
class StoreData {
public:
// nn::mii::detail::StoreDataRaw::BuildDefault
void BuildDefault(u32 mii_index);
// nn::mii::detail::StoreDataRaw::BuildDefault
void BuildBase(Gender gender);
// nn::mii::detail::StoreDataRaw::BuildRandom
void BuildRandom(Age age, Gender gender, Race race);
void BuildWithCharInfo(const CharInfo& char_info);
void BuildWithCoreData(const CoreData& in_core_data);
Result Restore();
ValidationResult IsValid() const;
bool IsSpecial() const;
u32 IsValid() const;
void SetFontRegion(FontRegion value);
void SetFavoriteColor(FavoriteColor value);
void SetGender(Gender value);
@ -53,7 +53,7 @@ public:
void SetNoseType(NoseType value);
void SetNoseScale(u8 value);
void SetNoseY(u8 value);
void SetMouthType(u8 value);
void SetMouthType(MouthType value);
void SetMouthColor(CommonColor value);
void SetMouthScale(u8 value);
void SetMouthAspect(u8 value);
@ -73,6 +73,9 @@ public:
void SetMoleY(u8 value);
void SetNickname(Nickname nickname);
void SetInvalidName();
void SetChecksum();
void SetDataChecksum();
void SetDeviceChecksum();
Common::UUID GetCreateId() const;
FontRegion GetFontRegion() const;
@ -135,6 +138,8 @@ private:
u16 device_crc{};
};
static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size.");
static_assert(std::is_trivially_copyable_v<StoreData>,
"StoreData type must be trivially copyable.");
struct StoreDataElement {
StoreData store_data{};

View File

@ -22,12 +22,6 @@ void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) {
void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
out_store_data.BuildBase(Gender::Male);
if (!IsValid()) {
return;
}
// TODO: We are ignoring a bunch of data from the mii_v3
out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value()));
out_store_data.SetFavoriteColor(
static_cast<FavoriteColor>(mii_information.favorite_color.Value()));
@ -36,65 +30,71 @@ void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
out_store_data.SetNickname(mii_name);
out_store_data.SetFontRegion(
static_cast<FontRegion>(static_cast<u8>(region_information.font_region)));
static_cast<FontRegion>(static_cast<u8>(region_information.font_region.Value())));
out_store_data.SetFacelineType(
static_cast<FacelineType>(appearance_bits1.faceline_type.Value()));
out_store_data.SetFacelineColor(
static_cast<FacelineColor>(appearance_bits1.faceline_color.Value()));
RawData::GetFacelineColorFromVer3(appearance_bits1.faceline_color.Value()));
out_store_data.SetFacelineWrinkle(
static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value()));
out_store_data.SetFacelineMake(
static_cast<FacelineMake>(appearance_bits2.faceline_make.Value()));
out_store_data.SetHairType(static_cast<HairType>(hair_type));
out_store_data.SetHairColor(static_cast<CommonColor>(appearance_bits3.hair_color.Value()));
out_store_data.SetHairColor(RawData::GetHairColorFromVer3(appearance_bits3.hair_color.Value()));
out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value()));
out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value()));
out_store_data.SetEyeColor(static_cast<CommonColor>(appearance_bits4.eye_color.Value()));
out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale));
out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect));
out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate));
out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x));
out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y));
out_store_data.SetEyeColor(RawData::GetEyeColorFromVer3(appearance_bits4.eye_color.Value()));
out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale.Value()));
out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect.Value()));
out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate.Value()));
out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x.Value()));
out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y.Value()));
out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value()));
out_store_data.SetEyebrowColor(
static_cast<CommonColor>(appearance_bits5.eyebrow_color.Value()));
out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale));
out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect));
out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate));
out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x));
out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y));
RawData::GetHairColorFromVer3(appearance_bits5.eyebrow_color.Value()));
out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale.Value()));
out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect.Value()));
out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate.Value()));
out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x.Value()));
out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y.Value() - 3));
out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value()));
out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale));
out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y));
out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale.Value()));
out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y.Value()));
out_store_data.SetMouthType(static_cast<u8>(appearance_bits7.mouth_type));
out_store_data.SetMouthColor(static_cast<CommonColor>(appearance_bits7.mouth_color.Value()));
out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale));
out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect));
out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y));
out_store_data.SetMouthType(static_cast<MouthType>(appearance_bits7.mouth_type.Value()));
out_store_data.SetMouthColor(
RawData::GetMouthColorFromVer3(appearance_bits7.mouth_color.Value()));
out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale.Value()));
out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect.Value()));
out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y.Value()));
out_store_data.SetMustacheType(
static_cast<MustacheType>(appearance_bits8.mustache_type.Value()));
out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale));
out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y));
out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale.Value()));
out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y.Value()));
out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value()));
out_store_data.SetBeardColor(static_cast<CommonColor>(appearance_bits9.beard_color.Value()));
out_store_data.SetBeardColor(
RawData::GetHairColorFromVer3(appearance_bits9.beard_color.Value()));
// Glass type is compatible as it is. It doesn't need a table
out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value()));
out_store_data.SetGlassColor(static_cast<CommonColor>(appearance_bits10.glass_color.Value()));
out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale));
out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y));
out_store_data.SetGlassColor(
RawData::GetGlassColorFromVer3(appearance_bits10.glass_color.Value()));
out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale.Value()));
out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y.Value()));
out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value()));
out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale));
out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x));
out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y));
out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale.Value()));
out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x.Value()));
out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y.Value()));
out_store_data.SetChecksum();
}
void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
@ -220,7 +220,7 @@ u32 Ver3StoreData::IsValid() const {
is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max));
is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale);
is_valid = is_valid && (appearance_bits9.mustache_y <= MasMustacheY);
is_valid = is_valid && (appearance_bits9.mustache_y <= MaxMustacheY);
is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max));
is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor);
@ -228,7 +228,7 @@ u32 Ver3StoreData::IsValid() const {
is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType);
is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2);
is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale);
is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassScale);
is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassY);
is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max));
is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale);

View File

@ -439,6 +439,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
device_state = DeviceState::TagMounted;
mount_target = mount_target_;
return ResultSuccess;
}
@ -716,12 +717,13 @@ Result NfcDevice::GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info
return ResultRegistrationIsNotInitialized;
}
Service::Mii::MiiManager manager;
Mii::StoreData store_data{};
const auto& settings = tag_data.settings;
tag_data.owner_mii.BuildToStoreData(store_data);
// TODO: Validate and complete this data
register_info = {
.mii_store_data = {},
.mii_store_data = store_data,
.creation_date = settings.init_date.GetWriteDate(),
.amiibo_name = GetAmiiboName(settings),
.font_region = settings.settings.font_region,
@ -1372,7 +1374,7 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co
// Convert from utf16 to utf8
const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size() - 1);
return amiibo_name;
}

View File

@ -144,7 +144,7 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch
{3, &IPlatformServiceManager::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"},
{4, &IPlatformServiceManager::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"},
{5, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
{6, nullptr, "GetSharedFontInOrderOfPriorityForSystem"},
{6, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriorityForSystem"},
{100, nullptr, "RequestApplicationFunctionAuthorization"},
{101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"},
{102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"},
@ -262,8 +262,17 @@ void IPlatformServiceManager::GetSharedMemoryNativeHandle(HLERequestContext& ctx
}
void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext& ctx) {
// The maximum number of elements that can be returned is 6. Regardless of the available fonts
// or buffer size.
constexpr std::size_t MaxElementCount = 6;
IPC::RequestParser rp{ctx};
const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for
const std::size_t font_codes_count =
std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(0));
const std::size_t font_offsets_count =
std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(1));
const std::size_t font_sizes_count =
std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(2));
LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code);
IPC::ResponseBuilder rb{ctx, 4};
@ -280,9 +289,9 @@ void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext&
}
// Resize buffers if game requests smaller size output
font_codes.resize(std::min(font_codes.size(), ctx.GetWriteBufferNumElements<u32>(0)));
font_offsets.resize(std::min(font_offsets.size(), ctx.GetWriteBufferNumElements<u32>(1)));
font_sizes.resize(std::min(font_sizes.size(), ctx.GetWriteBufferNumElements<u32>(2)));
font_codes.resize(std::min(font_codes.size(), font_codes_count));
font_offsets.resize(std::min(font_offsets.size(), font_offsets_count));
font_sizes.resize(std::min(font_sizes.size(), font_sizes_count));
ctx.WriteBuffer(font_codes, 0);
ctx.WriteBuffer(font_offsets, 1);

View File

@ -7,7 +7,7 @@
#include "common/dynamic_library.h"
#include "core/tools/renderdoc.h"
#ifdef WIN32
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>

View File

@ -7,15 +7,12 @@
namespace Shader::Backend::SPIRV {
namespace {
Id Image(EmitContext& ctx, const IR::Value& index, IR::TextureInstInfo info) {
if (!index.IsImmediate()) {
throw NotImplementedException("Indirect image indexing");
}
Id Image(EmitContext& ctx, IR::TextureInstInfo info) {
if (info.type == TextureType::Buffer) {
const ImageBufferDefinition def{ctx.image_buffers.at(index.U32())};
const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)};
return def.id;
} else {
const ImageDefinition def{ctx.images.at(index.U32())};
const ImageDefinition def{ctx.images.at(info.descriptor_index)};
return def.id;
}
}
@ -28,8 +25,12 @@ std::pair<Id, Id> AtomicArgs(EmitContext& ctx) {
Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id value,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
if (!index.IsImmediate() || index.U32() != 0) {
// TODO: handle layers
throw NotImplementedException("Image indexing");
}
const auto info{inst->Flags<IR::TextureInstInfo>()};
const Id image{Image(ctx, index, info)};
const Id image{Image(ctx, info)};
const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, image, coords, ctx.Const(0U))};
const auto [scope, semantics]{AtomicArgs(ctx)};
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value);

View File

@ -74,11 +74,6 @@ spv::ImageFormat GetImageFormat(ImageFormat format) {
throw InvalidArgument("Invalid image format {}", format);
}
spv::ImageFormat GetImageFormatForBuffer(ImageFormat format) {
const auto spv_format = GetImageFormat(format);
return spv_format == spv::ImageFormat::Unknown ? spv::ImageFormat::R32ui : spv_format;
}
Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) {
const spv::ImageFormat format{GetImageFormat(desc.format)};
const Id type{ctx.U32[1]};
@ -1275,7 +1270,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
if (desc.count != 1) {
throw NotImplementedException("Array of image buffers");
}
const spv::ImageFormat format{GetImageFormatForBuffer(desc.format)};
const spv::ImageFormat format{GetImageFormat(desc.format)};
const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)};
const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)};
const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)};

View File

@ -95,6 +95,12 @@ add_library(video_core STATIC
memory_manager.h
precompiled_headers.h
pte_kind.h
query_cache/bank_base.h
query_cache/query_base.h
query_cache/query_cache_base.h
query_cache/query_cache.h
query_cache/query_stream.h
query_cache/types.h
query_cache.h
rasterizer_accelerated.cpp
rasterizer_accelerated.h

View File

@ -272,13 +272,19 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
if (!cpu_addr) {
return {&slot_buffers[NULL_BUFFER_ID], 0};
}
const BufferId buffer_id = FindBuffer(*cpu_addr, size);
return ObtainCPUBuffer(*cpu_addr, size, sync_info, post_op);
}
template <class P>
std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainCPUBuffer(
VAddr cpu_addr, u32 size, ObtainBufferSynchronize sync_info, ObtainBufferOperation post_op) {
const BufferId buffer_id = FindBuffer(cpu_addr, size);
Buffer& buffer = slot_buffers[buffer_id];
// synchronize op
switch (sync_info) {
case ObtainBufferSynchronize::FullSynchronize:
SynchronizeBuffer(buffer, *cpu_addr, size);
SynchronizeBuffer(buffer, cpu_addr, size);
break;
default:
break;
@ -286,11 +292,11 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
switch (post_op) {
case ObtainBufferOperation::MarkAsWritten:
MarkWrittenBuffer(buffer_id, *cpu_addr, size);
MarkWrittenBuffer(buffer_id, cpu_addr, size);
break;
case ObtainBufferOperation::DiscardWrite: {
VAddr cpu_addr_start = Common::AlignDown(*cpu_addr, 64);
VAddr cpu_addr_end = Common::AlignUp(*cpu_addr + size, 64);
VAddr cpu_addr_start = Common::AlignDown(cpu_addr, 64);
VAddr cpu_addr_end = Common::AlignUp(cpu_addr + size, 64);
IntervalType interval{cpu_addr_start, cpu_addr_end};
ClearDownload(interval);
common_ranges.subtract(interval);
@ -300,7 +306,7 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
break;
}
return {&buffer, buffer.Offset(*cpu_addr)};
return {&buffer, buffer.Offset(cpu_addr)};
}
template <class P>

View File

@ -295,6 +295,10 @@ public:
[[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(GPUVAddr gpu_addr, u32 size,
ObtainBufferSynchronize sync_info,
ObtainBufferOperation post_op);
[[nodiscard]] std::pair<Buffer*, u32> ObtainCPUBuffer(VAddr gpu_addr, u32 size,
ObtainBufferSynchronize sync_info,
ObtainBufferOperation post_op);
void FlushCachedWrites();
/// Return true when there are uncommitted buffers to be downloaded
@ -335,6 +339,14 @@ public:
[[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer();
template <typename Func>
void BufferOperations(Func&& func) {
do {
channel_state->has_deleted_buffers = false;
func();
} while (channel_state->has_deleted_buffers);
}
std::recursive_mutex mutex;
Runtime& runtime;

View File

@ -51,7 +51,7 @@ public:
virtual void CreateChannel(Tegra::Control::ChannelState& channel);
/// Bind a channel for execution.
void BindToChannel(s32 id);
virtual void BindToChannel(s32 id);
/// Erase channel's state.
void EraseChannel(s32 id);

View File

@ -46,6 +46,7 @@ public:
};
struct IndirectParams {
bool is_byte_count;
bool is_indexed;
bool include_count;
GPUVAddr count_start_address;

View File

@ -20,8 +20,6 @@
namespace Tegra::Engines {
using VideoCore::QueryType;
/// First register id that is actually a Macro call.
constexpr u32 MacroRegistersStart = 0xE00;
@ -500,27 +498,21 @@ void Maxwell3D::StampQueryResult(u64 payload, bool long_query) {
}
void Maxwell3D::ProcessQueryGet() {
VideoCommon::QueryPropertiesFlags flags{};
if (regs.report_semaphore.query.short_query == 0) {
flags |= VideoCommon::QueryPropertiesFlags::HasTimeout;
}
const GPUVAddr sequence_address{regs.report_semaphore.Address()};
const VideoCommon::QueryType query_type =
static_cast<VideoCommon::QueryType>(regs.report_semaphore.query.report.Value());
const u32 payload = regs.report_semaphore.payload;
const u32 subreport = regs.report_semaphore.query.sub_report;
switch (regs.report_semaphore.query.operation) {
case Regs::ReportSemaphore::Operation::Release:
if (regs.report_semaphore.query.short_query != 0) {
const GPUVAddr sequence_address{regs.report_semaphore.Address()};
const u32 payload = regs.report_semaphore.payload;
std::function<void()> operation([this, sequence_address, payload] {
memory_manager.Write<u32>(sequence_address, payload);
});
rasterizer->SignalFence(std::move(operation));
} else {
struct LongQueryResult {
u64_le value;
u64_le timestamp;
};
const GPUVAddr sequence_address{regs.report_semaphore.Address()};
const u32 payload = regs.report_semaphore.payload;
[this, sequence_address, payload] {
memory_manager.Write<u64>(sequence_address + sizeof(u64), system.GPU().GetTicks());
memory_manager.Write<u64>(sequence_address, payload);
}();
flags |= VideoCommon::QueryPropertiesFlags::IsAFence;
}
rasterizer->Query(sequence_address, query_type, flags, payload, subreport);
break;
case Regs::ReportSemaphore::Operation::Acquire:
// TODO(Blinkhawk): Under this operation, the GPU waits for the CPU to write a value that
@ -528,11 +520,7 @@ void Maxwell3D::ProcessQueryGet() {
UNIMPLEMENTED_MSG("Unimplemented query operation ACQUIRE");
break;
case Regs::ReportSemaphore::Operation::ReportOnly:
if (const std::optional<u64> result = GetQueryResult()) {
// If the query returns an empty optional it means it's cached and deferred.
// In this case we have a non-empty result, so we stamp it immediately.
StampQueryResult(*result, regs.report_semaphore.query.short_query == 0);
}
rasterizer->Query(sequence_address, query_type, flags, payload, subreport);
break;
case Regs::ReportSemaphore::Operation::Trap:
UNIMPLEMENTED_MSG("Unimplemented query operation TRAP");
@ -544,6 +532,10 @@ void Maxwell3D::ProcessQueryGet() {
}
void Maxwell3D::ProcessQueryCondition() {
if (rasterizer->AccelerateConditionalRendering()) {
execute_on = true;
return;
}
const GPUVAddr condition_address{regs.render_enable.Address()};
switch (regs.render_enable_override) {
case Regs::RenderEnable::Override::AlwaysRender:
@ -553,10 +545,6 @@ void Maxwell3D::ProcessQueryCondition() {
execute_on = false;
break;
case Regs::RenderEnable::Override::UseRenderEnable: {
if (rasterizer->AccelerateConditionalRendering()) {
execute_on = true;
return;
}
switch (regs.render_enable.mode) {
case Regs::RenderEnable::Mode::True: {
execute_on = true;
@ -598,15 +586,9 @@ void Maxwell3D::ProcessQueryCondition() {
}
void Maxwell3D::ProcessCounterReset() {
#if ANDROID
if (!Settings::IsGPULevelHigh()) {
// This is problematic on Android, disable on GPU Normal.
return;
}
#endif
switch (regs.clear_report_value) {
case Regs::ClearReport::ZPassPixelCount:
rasterizer->ResetCounter(QueryType::SamplesPassed);
rasterizer->ResetCounter(VideoCommon::QueryType::ZPassPixelCount64);
break;
default:
LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}", regs.clear_report_value);
@ -620,28 +602,6 @@ void Maxwell3D::ProcessSyncPoint() {
rasterizer->SignalSyncPoint(sync_point);
}
std::optional<u64> Maxwell3D::GetQueryResult() {
switch (regs.report_semaphore.query.report) {
case Regs::ReportSemaphore::Report::Payload:
return regs.report_semaphore.payload;
case Regs::ReportSemaphore::Report::ZPassPixelCount64:
#if ANDROID
if (!Settings::IsGPULevelHigh()) {
// This is problematic on Android, disable on GPU Normal.
return 120;
}
#endif
// Deferred.
rasterizer->Query(regs.report_semaphore.Address(), QueryType::SamplesPassed,
system.GPU().GetTicks());
return std::nullopt;
default:
LOG_DEBUG(HW_GPU, "Unimplemented query report type {}",
regs.report_semaphore.query.report.Value());
return 1;
}
}
void Maxwell3D::ProcessCBBind(size_t stage_index) {
// Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader
// stage.

View File

@ -3182,9 +3182,6 @@ private:
/// Handles writes to syncing register.
void ProcessSyncPoint();
/// Returns a query's value or an empty object if the value will be deferred through a cache.
std::optional<u64> GetQueryResult();
void RefreshParametersImpl();
bool IsMethodExecutable(u32 method);

View File

@ -109,10 +109,11 @@ void MaxwellDMA::Launch() {
const bool is_const_a_dst = regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A;
if (regs.launch_dma.remap_enable != 0 && is_const_a_dst) {
ASSERT(regs.remap_const.component_size_minus_one == 3);
accelerate.BufferClear(regs.offset_out, regs.line_length_in, regs.remap_consta_value);
accelerate.BufferClear(regs.offset_out, regs.line_length_in,
regs.remap_const.remap_consta_value);
read_buffer.resize_destructive(regs.line_length_in * sizeof(u32));
std::span<u32> span(reinterpret_cast<u32*>(read_buffer.data()), regs.line_length_in);
std::ranges::fill(span, regs.remap_consta_value);
std::ranges::fill(span, regs.remap_const.remap_consta_value);
memory_manager.WriteBlockUnsafe(regs.offset_out,
reinterpret_cast<u8*>(read_buffer.data()),
regs.line_length_in * sizeof(u32));
@ -361,21 +362,17 @@ void MaxwellDMA::ReleaseSemaphore() {
const auto type = regs.launch_dma.semaphore_type;
const GPUVAddr address = regs.semaphore.address;
const u32 payload = regs.semaphore.payload;
VideoCommon::QueryPropertiesFlags flags{VideoCommon::QueryPropertiesFlags::IsAFence};
switch (type) {
case LaunchDMA::SemaphoreType::NONE:
break;
case LaunchDMA::SemaphoreType::RELEASE_ONE_WORD_SEMAPHORE: {
std::function<void()> operation(
[this, address, payload] { memory_manager.Write<u32>(address, payload); });
rasterizer->SignalFence(std::move(operation));
rasterizer->Query(address, VideoCommon::QueryType::Payload, flags, payload, 0);
break;
}
case LaunchDMA::SemaphoreType::RELEASE_FOUR_WORD_SEMAPHORE: {
std::function<void()> operation([this, address, payload] {
memory_manager.Write<u64>(address + sizeof(u64), system.GPU().GetTicks());
memory_manager.Write<u64>(address, payload);
});
rasterizer->SignalFence(std::move(operation));
rasterizer->Query(address, VideoCommon::QueryType::Payload,
flags | VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0);
break;
}
default:

View File

@ -214,14 +214,15 @@ public:
NO_WRITE = 6,
};
PackedGPUVAddr address;
u32 remap_consta_value;
u32 remap_constb_value;
union {
BitField<0, 12, u32> dst_components_raw;
BitField<0, 3, Swizzle> dst_x;
BitField<4, 3, Swizzle> dst_y;
BitField<8, 3, Swizzle> dst_z;
BitField<12, 3, Swizzle> dst_w;
BitField<0, 12, u32> dst_components_raw;
BitField<16, 2, u32> component_size_minus_one;
BitField<20, 2, u32> num_src_components_minus_one;
BitField<24, 2, u32> num_dst_components_minus_one;
@ -274,55 +275,57 @@ private:
struct Regs {
union {
struct {
u32 reserved[0x40];
INSERT_PADDING_BYTES_NOINIT(0x100);
u32 nop;
u32 reserved01[0xf];
INSERT_PADDING_BYTES_NOINIT(0x3C);
u32 pm_trigger;
u32 reserved02[0x3f];
INSERT_PADDING_BYTES_NOINIT(0xFC);
Semaphore semaphore;
u32 reserved03[0x2];
INSERT_PADDING_BYTES_NOINIT(0x8);
RenderEnable render_enable;
PhysMode src_phys_mode;
PhysMode dst_phys_mode;
u32 reserved04[0x26];
INSERT_PADDING_BYTES_NOINIT(0x98);
LaunchDMA launch_dma;
u32 reserved05[0x3f];
INSERT_PADDING_BYTES_NOINIT(0xFC);
PackedGPUVAddr offset_in;
PackedGPUVAddr offset_out;
s32 pitch_in;
s32 pitch_out;
u32 line_length_in;
u32 line_count;
u32 reserved06[0xb6];
u32 remap_consta_value;
u32 remap_constb_value;
INSERT_PADDING_BYTES_NOINIT(0x2E0);
RemapConst remap_const;
DMA::Parameters dst_params;
u32 reserved07[0x1];
INSERT_PADDING_BYTES_NOINIT(0x4);
DMA::Parameters src_params;
u32 reserved08[0x275];
INSERT_PADDING_BYTES_NOINIT(0x9D4);
u32 pm_trigger_end;
u32 reserved09[0x3ba];
INSERT_PADDING_BYTES_NOINIT(0xEE8);
};
std::array<u32, NUM_REGS> reg_array;
};
} regs{};
static_assert(sizeof(Regs) == NUM_REGS * 4);
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(MaxwellDMA::Regs, field_name) == position * 4, \
static_assert(offsetof(MaxwellDMA::Regs, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_REG_POSITION(launch_dma, 0xC0);
ASSERT_REG_POSITION(offset_in, 0x100);
ASSERT_REG_POSITION(offset_out, 0x102);
ASSERT_REG_POSITION(pitch_in, 0x104);
ASSERT_REG_POSITION(pitch_out, 0x105);
ASSERT_REG_POSITION(line_length_in, 0x106);
ASSERT_REG_POSITION(line_count, 0x107);
ASSERT_REG_POSITION(remap_const, 0x1C0);
ASSERT_REG_POSITION(dst_params, 0x1C3);
ASSERT_REG_POSITION(src_params, 0x1CA);
ASSERT_REG_POSITION(semaphore, 0x240);
ASSERT_REG_POSITION(render_enable, 0x254);
ASSERT_REG_POSITION(src_phys_mode, 0x260);
ASSERT_REG_POSITION(launch_dma, 0x300);
ASSERT_REG_POSITION(offset_in, 0x400);
ASSERT_REG_POSITION(offset_out, 0x408);
ASSERT_REG_POSITION(pitch_in, 0x410);
ASSERT_REG_POSITION(pitch_out, 0x414);
ASSERT_REG_POSITION(line_length_in, 0x418);
ASSERT_REG_POSITION(line_count, 0x41C);
ASSERT_REG_POSITION(remap_const, 0x700);
ASSERT_REG_POSITION(dst_params, 0x70C);
ASSERT_REG_POSITION(src_params, 0x728);
ASSERT_REG_POSITION(pm_trigger_end, 0x1114);
#undef ASSERT_REG_POSITION
};

View File

@ -82,10 +82,8 @@ void Puller::ProcessSemaphoreTriggerMethod() {
if (op == GpuSemaphoreOperation::WriteLong) {
const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
const u32 payload = regs.semaphore_sequence;
[this, sequence_address, payload] {
memory_manager.Write<u64>(sequence_address + sizeof(u64), gpu.GetTicks());
memory_manager.Write<u64>(sequence_address, payload);
}();
rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload,
VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0);
} else {
do {
const u32 word{memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress())};
@ -120,10 +118,8 @@ void Puller::ProcessSemaphoreTriggerMethod() {
void Puller::ProcessSemaphoreRelease() {
const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
const u32 payload = regs.semaphore_release;
std::function<void()> operation([this, sequence_address, payload] {
memory_manager.Write<u32>(sequence_address, payload);
});
rasterizer->SignalFence(std::move(operation));
rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload,
VideoCommon::QueryPropertiesFlags::IsAFence, payload, 0);
}
void Puller::ProcessSemaphoreAcquire() {
@ -132,7 +128,6 @@ void Puller::ProcessSemaphoreAcquire() {
while (word != value) {
regs.acquire_active = true;
regs.acquire_value = value;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
rasterizer->ReleaseFences();
word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
// TODO(kemathe73) figure out how to do the acquire_timeout

View File

@ -55,6 +55,9 @@ public:
// Unlike other fences, this one doesn't
void SignalOrdering() {
if constexpr (!can_async_check) {
TryReleasePendingFences<false>();
}
std::scoped_lock lock{buffer_cache.mutex};
buffer_cache.AccumulateFlushes();
}
@ -104,9 +107,25 @@ public:
SignalFence(std::move(func));
}
void WaitPendingFences() {
void WaitPendingFences([[maybe_unused]] bool force) {
if constexpr (!can_async_check) {
TryReleasePendingFences<true>();
} else {
if (!force) {
return;
}
std::mutex wait_mutex;
std::condition_variable wait_cv;
std::atomic<bool> wait_finished{};
std::function<void()> func([&] {
std::scoped_lock lk(wait_mutex);
wait_finished.store(true, std::memory_order_relaxed);
wait_cv.notify_all();
});
SignalFence(std::move(func));
std::unique_lock lk(wait_mutex);
wait_cv.wait(
lk, [&wait_finished] { return wait_finished.load(std::memory_order_relaxed); });
}
}

View File

@ -102,7 +102,8 @@ struct GPU::Impl {
/// Signal the ending of command list.
void OnCommandListEnd() {
rasterizer->ReleaseFences();
rasterizer->ReleaseFences(false);
Settings::UpdateGPUAccuracy();
}
/// Request a host GPU memory flush from the CPU.
@ -220,6 +221,7 @@ struct GPU::Impl {
/// This can be used to launch any necessary threads and register any necessary
/// core timing events.
void Start() {
Settings::UpdateGPUAccuracy();
gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler);
}

View File

@ -41,6 +41,9 @@ set(SHADER_FILES
pitch_unswizzle.comp
present_bicubic.frag
present_gaussian.frag
queries_prefix_scan_sum.comp
queries_prefix_scan_sum_nosubgroups.comp
resolve_conditional_render.comp
smaa_edge_detection.vert
smaa_edge_detection.frag
smaa_blending_weight_calculation.vert
@ -70,6 +73,7 @@ if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND")
endif()
set(GLSL_FLAGS "")
set(SPIR_V_VERSION "spirv1.3")
set(QUIET_FLAG "--quiet")
set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
@ -123,7 +127,7 @@ foreach(FILENAME IN ITEMS ${SHADER_FILES})
OUTPUT
${SPIRV_HEADER_FILE}
COMMAND
${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE}
${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}
MAIN_DEPENDENCY
${SOURCE_FILE}
)

View File

@ -0,0 +1,173 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 460 core
#extension GL_KHR_shader_subgroup_basic : require
#extension GL_KHR_shader_subgroup_shuffle : require
#extension GL_KHR_shader_subgroup_shuffle_relative : require
#extension GL_KHR_shader_subgroup_arithmetic : require
#ifdef VULKAN
#define HAS_EXTENDED_TYPES 1
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
#define END_PUSH_CONSTANTS };
#define UNIFORM(n)
#define BINDING_INPUT_BUFFER 0
#define BINDING_OUTPUT_IMAGE 1
#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
#extension GL_NV_gpu_shader5 : enable
#ifdef GL_NV_gpu_shader5
#define HAS_EXTENDED_TYPES 1
#else
#define HAS_EXTENDED_TYPES 0
#endif
#define BEGIN_PUSH_CONSTANTS
#define END_PUSH_CONSTANTS
#define UNIFORM(n) layout(location = n) uniform
#define BINDING_INPUT_BUFFER 0
#define BINDING_OUTPUT_IMAGE 0
#endif
BEGIN_PUSH_CONSTANTS
UNIFORM(0) uint min_accumulation_base;
UNIFORM(1) uint max_accumulation_base;
UNIFORM(2) uint accumulation_limit;
UNIFORM(3) uint buffer_offset;
END_PUSH_CONSTANTS
#define LOCAL_RESULTS 8
#define QUERIES_PER_INVOC 2048
layout(local_size_x = QUERIES_PER_INVOC / LOCAL_RESULTS) in;
layout(std430, binding = 0) readonly buffer block1 {
uvec2 input_data[];
};
layout(std430, binding = 1) coherent buffer block2 {
uvec2 output_data[];
};
layout(std430, binding = 2) coherent buffer block3 {
uvec2 accumulated_data;
};
shared uvec2 shared_data[128];
// Simple Uint64 add that uses 2 uint variables for GPUs that don't support uint64
uvec2 AddUint64(uvec2 value_1, uvec2 value_2) {
uint carry = 0;
uvec2 result;
result.x = uaddCarry(value_1.x, value_2.x, carry);
result.y = value_1.y + value_2.y + carry;
return result;
}
// do subgroup Prefix Sum using Hillis and Steele's algorithm
uvec2 subgroupInclusiveAddUint64(uvec2 value) {
uvec2 result = value;
for (uint i = 1; i < gl_SubgroupSize; i *= 2) {
uvec2 other = subgroupShuffleUp(result, i); // get value from subgroup_inv_id - i;
if (i <= gl_SubgroupInvocationID) {
result = AddUint64(result, other);
}
}
return result;
}
// Writes down the results to the output buffer and to the accumulation buffer
void WriteResults(uvec2 results[LOCAL_RESULTS]) {
const uint current_id = gl_LocalInvocationID.x;
const uvec2 accum = accumulated_data;
for (uint i = 0; i < LOCAL_RESULTS; i++) {
uvec2 base_data = current_id * LOCAL_RESULTS + i < min_accumulation_base ? accum : uvec2(0, 0);
AddUint64(results[i], base_data);
}
for (uint i = 0; i < LOCAL_RESULTS; i++) {
output_data[buffer_offset + current_id * LOCAL_RESULTS + i] = results[i];
}
uint index = accumulation_limit % LOCAL_RESULTS;
uint base_id = accumulation_limit / LOCAL_RESULTS;
if (min_accumulation_base >= accumulation_limit + 1) {
if (current_id == base_id) {
accumulated_data = results[index];
}
return;
}
// We have that ugly case in which the accumulation data is reset in the middle somewhere.
barrier();
groupMemoryBarrier();
if (current_id == base_id) {
uvec2 reset_value = output_data[max_accumulation_base - 1];
// Calculate two complement / negate manually
reset_value = AddUint64(uvec2(1,0), ~reset_value);
accumulated_data = AddUint64(results[index], reset_value);
}
}
void main() {
const uint subgroup_inv_id = gl_SubgroupInvocationID;
const uint subgroup_id = gl_SubgroupID + gl_WorkGroupID.x * gl_NumSubgroups;
const uint last_subgroup_id = subgroupMax(subgroup_inv_id);
const uint current_id = gl_LocalInvocationID.x;
const uint total_work = accumulation_limit;
const uint last_result_id = LOCAL_RESULTS - 1;
uvec2 data[LOCAL_RESULTS];
for (uint i = 0; i < LOCAL_RESULTS; i++) {
data[i] = input_data[buffer_offset + current_id * LOCAL_RESULTS + i];
}
uvec2 results[LOCAL_RESULTS];
results[0] = data[0];
for (uint i = 1; i < LOCAL_RESULTS; i++) {
results[i] = AddUint64(data[i], results[i - 1]);
}
// make sure all input data has been loaded
subgroupBarrier();
subgroupMemoryBarrier();
// on the last local result, do a subgroup inclusive scan sum
results[last_result_id] = subgroupInclusiveAddUint64(results[last_result_id]);
// get the last local result from the subgroup behind the current
uvec2 result_behind = subgroupShuffleUp(results[last_result_id], 1);
if (subgroup_inv_id != 0) {
for (uint i = 1; i < LOCAL_RESULTS; i++) {
results[i - 1] = AddUint64(results[i - 1], result_behind);
}
}
// if we had less queries than our subgroup, just write down the results.
if (total_work <= gl_SubgroupSize * LOCAL_RESULTS) { // This condition is constant per dispatch.
WriteResults(results);
return;
}
// We now have more, so lets write the last result into shared memory.
// Only pick the last subgroup.
if (subgroup_inv_id == last_subgroup_id) {
shared_data[subgroup_id] = results[last_result_id];
}
// wait until everyone loaded their stuffs
barrier();
memoryBarrierShared();
// only if it's not the first subgroup
if (subgroup_id != 0) {
// get the results from some previous invocation
uvec2 tmp = shared_data[subgroup_inv_id];
subgroupBarrier();
subgroupMemoryBarrierShared();
tmp = subgroupInclusiveAddUint64(tmp);
// obtain the result that would be equivalent to the previous result
uvec2 shuffled_result = subgroupShuffle(tmp, subgroup_id - 1);
for (uint i = 0; i < LOCAL_RESULTS; i++) {
results[i] = AddUint64(results[i], shuffled_result);
}
}
WriteResults(results);
}

View File

@ -0,0 +1,138 @@
// SPDX-FileCopyrightText: Copyright 2015 Graham Sellers, Richard Wright Jr. and Nicholas Haemel
// SPDX-License-Identifier: MIT
// Code obtained from OpenGL SuperBible, Seventh Edition by Graham Sellers, Richard Wright Jr. and
// Nicholas Haemel. Modified to suit needs.
#version 460 core
#ifdef VULKAN
#define HAS_EXTENDED_TYPES 1
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
#define END_PUSH_CONSTANTS };
#define UNIFORM(n)
#define BINDING_INPUT_BUFFER 0
#define BINDING_OUTPUT_IMAGE 1
#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
#extension GL_NV_gpu_shader5 : enable
#ifdef GL_NV_gpu_shader5
#define HAS_EXTENDED_TYPES 1
#else
#define HAS_EXTENDED_TYPES 0
#endif
#define BEGIN_PUSH_CONSTANTS
#define END_PUSH_CONSTANTS
#define UNIFORM(n) layout(location = n) uniform
#define BINDING_INPUT_BUFFER 0
#define BINDING_OUTPUT_IMAGE 0
#endif
BEGIN_PUSH_CONSTANTS
UNIFORM(0) uint min_accumulation_base;
UNIFORM(1) uint max_accumulation_base;
UNIFORM(2) uint accumulation_limit;
UNIFORM(3) uint buffer_offset;
END_PUSH_CONSTANTS
#define LOCAL_RESULTS 4
#define QUERIES_PER_INVOC 2048
layout(local_size_x = QUERIES_PER_INVOC / LOCAL_RESULTS) in;
layout(std430, binding = 0) readonly buffer block1 {
uvec2 input_data[gl_WorkGroupSize.x * LOCAL_RESULTS];
};
layout(std430, binding = 1) writeonly coherent buffer block2 {
uvec2 output_data[gl_WorkGroupSize.x * LOCAL_RESULTS];
};
layout(std430, binding = 2) coherent buffer block3 {
uvec2 accumulated_data;
};
shared uvec2 shared_data[gl_WorkGroupSize.x * LOCAL_RESULTS];
uvec2 AddUint64(uvec2 value_1, uvec2 value_2) {
uint carry = 0;
uvec2 result;
result.x = uaddCarry(value_1.x, value_2.x, carry);
result.y = value_1.y + value_2.y + carry;
return result;
}
void main(void) {
uint id = gl_LocalInvocationID.x;
uvec2 base_value[LOCAL_RESULTS];
const uvec2 accum = accumulated_data;
for (uint i = 0; i < LOCAL_RESULTS; i++) {
base_value[i] = (buffer_offset + id * LOCAL_RESULTS + i) < min_accumulation_base
? accumulated_data
: uvec2(0);
}
uint work_size = gl_WorkGroupSize.x;
uint rd_id;
uint wr_id;
uint mask;
uvec2 inputs[LOCAL_RESULTS];
for (uint i = 0; i < LOCAL_RESULTS; i++) {
inputs[i] = input_data[buffer_offset + id * LOCAL_RESULTS + i];
}
// The number of steps is the log base 2 of the
// work group size, which should be a power of 2
const uint steps = uint(log2(work_size)) + uint(log2(LOCAL_RESULTS));
uint step = 0;
// Each invocation is responsible for the content of
// two elements of the output array
for (uint i = 0; i < LOCAL_RESULTS; i++) {
shared_data[id * LOCAL_RESULTS + i] = inputs[i];
}
// Synchronize to make sure that everyone has initialized
// their elements of shared_data[] with data loaded from
// the input arrays
barrier();
memoryBarrierShared();
// For each step...
for (step = 0; step < steps; step++) {
// Calculate the read and write index in the
// shared array
mask = (1 << step) - 1;
rd_id = ((id >> step) << (step + 1)) + mask;
wr_id = rd_id + 1 + (id & mask);
// Accumulate the read data into our element
shared_data[wr_id] = AddUint64(shared_data[rd_id], shared_data[wr_id]);
// Synchronize again to make sure that everyone
// has caught up with us
barrier();
memoryBarrierShared();
}
// Add the accumulation
for (uint i = 0; i < LOCAL_RESULTS; i++) {
shared_data[id * LOCAL_RESULTS + i] =
AddUint64(shared_data[id * LOCAL_RESULTS + i], base_value[i]);
}
barrier();
memoryBarrierShared();
// Finally write our data back to the output buffer
for (uint i = 0; i < LOCAL_RESULTS; i++) {
output_data[buffer_offset + id * LOCAL_RESULTS + i] = shared_data[id * LOCAL_RESULTS + i];
}
if (id == 0) {
if (min_accumulation_base >= accumulation_limit + 1) {
accumulated_data = shared_data[accumulation_limit];
return;
}
uvec2 reset_value = shared_data[max_accumulation_base - 1];
uvec2 final_value = shared_data[accumulation_limit];
// Two complements
reset_value = AddUint64(uvec2(1, 0), ~reset_value);
accumulated_data = AddUint64(final_value, reset_value);
}
}

View File

@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 450
layout(local_size_x = 1) in;
layout(std430, binding = 0) buffer Query {
uvec2 initial;
uvec2 unknown;
uvec2 current;
};
layout(std430, binding = 1) buffer Result {
uint result;
};
void main() {
result = all(equal(initial, current)) ? 1 : 0;
}

View File

@ -67,6 +67,7 @@ public:
}
auto& params = maxwell3d.draw_manager->GetIndirectParams();
params.is_byte_count = false;
params.is_indexed = false;
params.include_count = false;
params.count_start_address = 0;
@ -161,6 +162,7 @@ public:
0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance);
}
auto& params = maxwell3d.draw_manager->GetIndirectParams();
params.is_byte_count = false;
params.is_indexed = true;
params.include_count = false;
params.count_start_address = 0;
@ -256,6 +258,7 @@ public:
const u32 estimate = static_cast<u32>(maxwell3d.EstimateIndexBufferSize());
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
auto& params = maxwell3d.draw_manager->GetIndirectParams();
params.is_byte_count = false;
params.is_indexed = true;
params.include_count = true;
params.count_start_address = maxwell3d.GetMacroAddress(4);
@ -319,6 +322,47 @@ private:
}
};
class HLE_DrawIndirectByteCount final : public HLEMacroImpl {
public:
explicit HLE_DrawIndirectByteCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0] & 0xFFFFU);
if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) {
Fallback(parameters);
return;
}
auto& params = maxwell3d.draw_manager->GetIndirectParams();
params.is_byte_count = true;
params.is_indexed = false;
params.include_count = false;
params.count_start_address = 0;
params.indirect_start_address = maxwell3d.GetMacroAddress(2);
params.buffer_size = 4;
params.max_draw_counts = 1;
params.stride = parameters[1];
maxwell3d.regs.draw.begin = parameters[0];
maxwell3d.regs.draw_auto_stride = parameters[1];
maxwell3d.regs.draw_auto_byte_count = parameters[2];
maxwell3d.draw_manager->DrawArrayIndirect(topology);
}
private:
void Fallback(const std::vector<u32>& parameters) {
maxwell3d.RefreshParameters();
maxwell3d.regs.draw.begin = parameters[0];
maxwell3d.regs.draw_auto_stride = parameters[1];
maxwell3d.regs.draw_auto_byte_count = parameters[2];
maxwell3d.draw_manager->DrawArray(
maxwell3d.regs.draw.topology, 0,
maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1);
}
};
class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl {
public:
explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
@ -536,6 +580,11 @@ HLEMacro::HLEMacro(Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {
[](Maxwell3D& maxwell3d__) -> std::unique_ptr<CachedMacro> {
return std::make_unique<HLE_TransformFeedbackSetup>(maxwell3d__);
}));
builders.emplace(0xB5F74EDB717278ECULL,
std::function<std::unique_ptr<CachedMacro>(Maxwell3D&)>(
[](Maxwell3D& maxwell3d__) -> std::unique_ptr<CachedMacro> {
return std::make_unique<HLE_DrawIndirectByteCount>(maxwell3d__);
}));
}
HLEMacro::~HLEMacro() = default;

View File

@ -25,6 +25,13 @@
#include "video_core/rasterizer_interface.h"
#include "video_core/texture_cache/slot_vector.h"
namespace VideoCore {
enum class QueryType {
SamplesPassed,
};
constexpr std::size_t NumQueryTypes = 1;
} // namespace VideoCore
namespace VideoCommon {
using AsyncJobId = SlotId;
@ -98,10 +105,10 @@ private:
};
template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter>
class QueryCacheBase : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
class QueryCacheLegacy : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
public:
explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_,
Core::Memory::Memory& cpu_memory_)
explicit QueryCacheLegacy(VideoCore::RasterizerInterface& rasterizer_,
Core::Memory::Memory& cpu_memory_)
: rasterizer{rasterizer_},
// Use reinterpret_cast instead of static_cast as workaround for
// UBSan bug (https://github.com/llvm/llvm-project/issues/59060)

View File

@ -0,0 +1,104 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <atomic>
#include <deque>
#include <utility>
#include "common/common_types.h"
namespace VideoCommon {
class BankBase {
protected:
const size_t base_bank_size{};
size_t bank_size{};
std::atomic<size_t> references{};
size_t current_slot{};
public:
explicit BankBase(size_t bank_size_) : base_bank_size{bank_size_}, bank_size(bank_size_) {}
virtual ~BankBase() = default;
virtual std::pair<bool, size_t> Reserve() {
if (IsClosed()) {
return {false, bank_size};
}
const size_t result = current_slot++;
return {true, result};
}
virtual void Reset() {
current_slot = 0;
references = 0;
bank_size = base_bank_size;
}
size_t Size() const {
return bank_size;
}
void AddReference(size_t how_many = 1) {
references.fetch_add(how_many, std::memory_order_relaxed);
}
void CloseReference(size_t how_many = 1) {
if (how_many > references.load(std::memory_order_relaxed)) {
UNREACHABLE();
}
references.fetch_sub(how_many, std::memory_order_relaxed);
}
void Close() {
bank_size = current_slot;
}
bool IsClosed() const {
return current_slot >= bank_size;
}
bool IsDead() const {
return IsClosed() && references == 0;
}
};
template <typename BankType>
class BankPool {
private:
std::deque<BankType> bank_pool;
std::deque<size_t> bank_indices;
public:
BankPool() = default;
~BankPool() = default;
// Reserve a bank from the pool and return its index
template <typename Func>
size_t ReserveBank(Func&& builder) {
if (!bank_indices.empty() && bank_pool[bank_indices.front()].IsDead()) {
size_t new_index = bank_indices.front();
bank_indices.pop_front();
bank_pool[new_index].Reset();
return new_index;
}
size_t new_index = bank_pool.size();
builder(bank_pool, new_index);
bank_indices.push_back(new_index);
return new_index;
}
// Get a reference to a bank using its index
BankType& GetBank(size_t index) {
return bank_pool[index];
}
// Get the total number of banks in the pool
size_t BankCount() const {
return bank_pool.size();
}
};
} // namespace VideoCommon

View File

@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace VideoCommon {
enum class QueryFlagBits : u32 {
HasTimestamp = 1 << 0, ///< Indicates if this query has a timestamp.
IsFinalValueSynced = 1 << 1, ///< Indicates if the query has been synced in the host
IsHostSynced = 1 << 2, ///< Indicates if the query has been synced in the host
IsGuestSynced = 1 << 3, ///< Indicates if the query has been synced with the guest.
IsHostManaged = 1 << 4, ///< Indicates if this query points to a host query
IsRewritten = 1 << 5, ///< Indicates if this query was rewritten by another query
IsInvalidated = 1 << 6, ///< Indicates the value of th query has been nullified.
IsOrphan = 1 << 7, ///< Indicates the query has not been set by a guest query.
IsFence = 1 << 8, ///< Indicates the query is a fence.
};
DECLARE_ENUM_FLAG_OPERATORS(QueryFlagBits)
class QueryBase {
public:
VAddr guest_address{};
QueryFlagBits flags{};
u64 value{};
protected:
// Default constructor
QueryBase() = default;
// Parameterized constructor
QueryBase(VAddr address, QueryFlagBits flags_, u64 value_)
: guest_address(address), flags(flags_), value{value_} {}
};
class GuestQuery : public QueryBase {
public:
// Parameterized constructor
GuestQuery(bool isLong, VAddr address, u64 queryValue)
: QueryBase(address, QueryFlagBits::IsFinalValueSynced, queryValue) {
if (isLong) {
flags |= QueryFlagBits::HasTimestamp;
}
}
};
class HostQueryBase : public QueryBase {
public:
// Default constructor
HostQueryBase() : QueryBase(0, QueryFlagBits::IsHostManaged | QueryFlagBits::IsOrphan, 0) {}
// Parameterized constructor
HostQueryBase(bool has_timestamp, VAddr address)
: QueryBase(address, QueryFlagBits::IsHostManaged, 0), start_bank_id{}, size_banks{},
start_slot{}, size_slots{} {
if (has_timestamp) {
flags |= QueryFlagBits::HasTimestamp;
}
}
u32 start_bank_id{};
u32 size_banks{};
size_t start_slot{};
size_t size_slots{};
};
} // namespace VideoCommon

View File

@ -0,0 +1,580 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <deque>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <utility>
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/memory.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
#include "video_core/query_cache/bank_base.h"
#include "video_core/query_cache/query_base.h"
#include "video_core/query_cache/query_cache_base.h"
#include "video_core/query_cache/query_stream.h"
#include "video_core/query_cache/types.h"
namespace VideoCommon {
using Maxwell = Tegra::Engines::Maxwell3D;
struct SyncValuesStruct {
VAddr address;
u64 value;
u64 size;
static constexpr bool GeneratesBaseBuffer = true;
};
template <typename Traits>
class GuestStreamer : public SimpleStreamer<GuestQuery> {
public:
using RuntimeType = typename Traits::RuntimeType;
GuestStreamer(size_t id_, RuntimeType& runtime_)
: SimpleStreamer<GuestQuery>(id_), runtime{runtime_} {}
virtual ~GuestStreamer() = default;
size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
std::optional<u32> subreport = std::nullopt) override {
auto new_id = BuildQuery(has_timestamp, address, static_cast<u64>(value));
pending_sync.push_back(new_id);
return new_id;
}
bool HasPendingSync() const override {
return !pending_sync.empty();
}
void SyncWrites() override {
if (pending_sync.empty()) {
return;
}
std::vector<SyncValuesStruct> sync_values;
sync_values.reserve(pending_sync.size());
for (size_t pending_id : pending_sync) {
auto& query = slot_queries[pending_id];
if (True(query.flags & QueryFlagBits::IsRewritten) ||
True(query.flags & QueryFlagBits::IsInvalidated)) {
continue;
}
query.flags |= QueryFlagBits::IsHostSynced;
sync_values.emplace_back(SyncValuesStruct{
.address = query.guest_address,
.value = query.value,
.size = static_cast<u64>(True(query.flags & QueryFlagBits::HasTimestamp) ? 8 : 4)});
}
pending_sync.clear();
if (sync_values.size() > 0) {
runtime.template SyncValues<SyncValuesStruct>(sync_values);
}
}
private:
RuntimeType& runtime;
std::deque<size_t> pending_sync;
};
template <typename Traits>
class StubStreamer : public GuestStreamer<Traits> {
public:
using RuntimeType = typename Traits::RuntimeType;
StubStreamer(size_t id_, RuntimeType& runtime_, u32 stub_value_)
: GuestStreamer<Traits>(id_, runtime_), stub_value{stub_value_} {}
~StubStreamer() override = default;
size_t WriteCounter(VAddr address, bool has_timestamp, [[maybe_unused]] u32 value,
std::optional<u32> subreport = std::nullopt) override {
size_t new_id =
GuestStreamer<Traits>::WriteCounter(address, has_timestamp, stub_value, subreport);
return new_id;
}
private:
u32 stub_value;
};
template <typename Traits>
struct QueryCacheBase<Traits>::QueryCacheBaseImpl {
using RuntimeType = typename Traits::RuntimeType;
QueryCacheBaseImpl(QueryCacheBase<Traits>* owner_, VideoCore::RasterizerInterface& rasterizer_,
Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_, Tegra::GPU& gpu_)
: owner{owner_}, rasterizer{rasterizer_},
cpu_memory{cpu_memory_}, runtime{runtime_}, gpu{gpu_} {
streamer_mask = 0;
for (size_t i = 0; i < static_cast<size_t>(QueryType::MaxQueryTypes); i++) {
streamers[i] = runtime.GetStreamerInterface(static_cast<QueryType>(i));
if (streamers[i]) {
streamer_mask |= 1ULL << streamers[i]->GetId();
}
}
}
template <typename Func>
void ForEachStreamerIn(u64 mask, Func&& func) {
static constexpr bool RETURNS_BOOL =
std::is_same_v<std::invoke_result<Func, StreamerInterface*>, bool>;
while (mask != 0) {
size_t position = std::countr_zero(mask);
mask &= ~(1ULL << position);
if constexpr (RETURNS_BOOL) {
if (func(streamers[position])) {
return;
}
} else {
func(streamers[position]);
}
}
}
template <typename Func>
void ForEachStreamer(Func&& func) {
ForEachStreamerIn(streamer_mask, func);
}
QueryBase* ObtainQuery(QueryCacheBase<Traits>::QueryLocation location) {
size_t which_stream = location.stream_id.Value();
auto* streamer = streamers[which_stream];
if (!streamer) {
return nullptr;
}
return streamer->GetQuery(location.query_id.Value());
}
QueryCacheBase<Traits>* owner;
VideoCore::RasterizerInterface& rasterizer;
Core::Memory::Memory& cpu_memory;
RuntimeType& runtime;
Tegra::GPU& gpu;
std::array<StreamerInterface*, static_cast<size_t>(QueryType::MaxQueryTypes)> streamers;
u64 streamer_mask;
std::mutex flush_guard;
std::deque<u64> flushes_pending;
std::vector<QueryCacheBase<Traits>::QueryLocation> pending_unregister;
};
template <typename Traits>
QueryCacheBase<Traits>::QueryCacheBase(Tegra::GPU& gpu_,
VideoCore::RasterizerInterface& rasterizer_,
Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_)
: cached_queries{} {
impl = std::make_unique<QueryCacheBase<Traits>::QueryCacheBaseImpl>(
this, rasterizer_, cpu_memory_, runtime_, gpu_);
}
template <typename Traits>
QueryCacheBase<Traits>::~QueryCacheBase() = default;
template <typename Traits>
void QueryCacheBase<Traits>::CounterEnable(QueryType counter_type, bool is_enabled) {
size_t index = static_cast<size_t>(counter_type);
StreamerInterface* streamer = impl->streamers[index];
if (!streamer) [[unlikely]] {
UNREACHABLE();
return;
}
if (is_enabled) {
streamer->StartCounter();
} else {
streamer->PauseCounter();
}
}
template <typename Traits>
void QueryCacheBase<Traits>::CounterClose(QueryType counter_type) {
size_t index = static_cast<size_t>(counter_type);
StreamerInterface* streamer = impl->streamers[index];
if (!streamer) [[unlikely]] {
UNREACHABLE();
return;
}
streamer->CloseCounter();
}
template <typename Traits>
void QueryCacheBase<Traits>::CounterReset(QueryType counter_type) {
size_t index = static_cast<size_t>(counter_type);
StreamerInterface* streamer = impl->streamers[index];
if (!streamer) [[unlikely]] {
UNIMPLEMENTED();
return;
}
streamer->ResetCounter();
}
template <typename Traits>
void QueryCacheBase<Traits>::BindToChannel(s32 id) {
VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo>::BindToChannel(id);
impl->runtime.Bind3DEngine(maxwell3d);
}
template <typename Traits>
void QueryCacheBase<Traits>::CounterReport(GPUVAddr addr, QueryType counter_type,
QueryPropertiesFlags flags, u32 payload, u32 subreport) {
const bool has_timestamp = True(flags & QueryPropertiesFlags::HasTimeout);
const bool is_fence = True(flags & QueryPropertiesFlags::IsAFence);
size_t streamer_id = static_cast<size_t>(counter_type);
auto* streamer = impl->streamers[streamer_id];
if (streamer == nullptr) [[unlikely]] {
counter_type = QueryType::Payload;
payload = 1U;
streamer_id = static_cast<size_t>(counter_type);
streamer = impl->streamers[streamer_id];
}
auto cpu_addr_opt = gpu_memory->GpuToCpuAddress(addr);
if (!cpu_addr_opt) [[unlikely]] {
return;
}
VAddr cpu_addr = *cpu_addr_opt;
const size_t new_query_id = streamer->WriteCounter(cpu_addr, has_timestamp, payload, subreport);
auto* query = streamer->GetQuery(new_query_id);
if (is_fence) {
query->flags |= QueryFlagBits::IsFence;
}
QueryLocation query_location{};
query_location.stream_id.Assign(static_cast<u32>(streamer_id));
query_location.query_id.Assign(static_cast<u32>(new_query_id));
const auto gen_caching_indexing = [](VAddr cur_addr) {
return std::make_pair<u64, u32>(cur_addr >> Core::Memory::YUZU_PAGEBITS,
static_cast<u32>(cur_addr & Core::Memory::YUZU_PAGEMASK));
};
u8* pointer = impl->cpu_memory.GetPointer(cpu_addr);
u8* pointer_timestamp = impl->cpu_memory.GetPointer(cpu_addr + 8);
bool is_synced = !Settings::IsGPULevelHigh() && is_fence;
std::function<void()> operation([this, is_synced, streamer, query_base = query, query_location,
pointer, pointer_timestamp] {
if (True(query_base->flags & QueryFlagBits::IsInvalidated)) {
if (!is_synced) [[likely]] {
impl->pending_unregister.push_back(query_location);
}
return;
}
if (False(query_base->flags & QueryFlagBits::IsFinalValueSynced)) [[unlikely]] {
UNREACHABLE();
return;
}
query_base->value += streamer->GetAmmendValue();
streamer->SetAccumulationValue(query_base->value);
if (True(query_base->flags & QueryFlagBits::HasTimestamp)) {
u64 timestamp = impl->gpu.GetTicks();
std::memcpy(pointer_timestamp, &timestamp, sizeof(timestamp));
std::memcpy(pointer, &query_base->value, sizeof(query_base->value));
} else {
u32 value = static_cast<u32>(query_base->value);
std::memcpy(pointer, &value, sizeof(value));
}
if (!is_synced) [[likely]] {
impl->pending_unregister.push_back(query_location);
}
});
if (is_fence) {
impl->rasterizer.SignalFence(std::move(operation));
} else {
if (!Settings::IsGPULevelHigh() && counter_type == QueryType::Payload) {
if (has_timestamp) {
u64 timestamp = impl->gpu.GetTicks();
u64 value = static_cast<u64>(payload);
std::memcpy(pointer_timestamp, &timestamp, sizeof(timestamp));
std::memcpy(pointer, &value, sizeof(value));
} else {
std::memcpy(pointer, &payload, sizeof(payload));
}
streamer->Free(new_query_id);
return;
}
impl->rasterizer.SyncOperation(std::move(operation));
}
if (is_synced) {
streamer->Free(new_query_id);
return;
}
auto [cont_addr, base] = gen_caching_indexing(cpu_addr);
{
std::scoped_lock lock(cache_mutex);
auto it1 = cached_queries.try_emplace(cont_addr);
auto& sub_container = it1.first->second;
auto it_current = sub_container.find(base);
if (it_current == sub_container.end()) {
sub_container.insert_or_assign(base, query_location);
return;
}
auto* old_query = impl->ObtainQuery(it_current->second);
old_query->flags |= QueryFlagBits::IsRewritten;
sub_container.insert_or_assign(base, query_location);
}
}
template <typename Traits>
void QueryCacheBase<Traits>::UnregisterPending() {
const auto gen_caching_indexing = [](VAddr cur_addr) {
return std::make_pair<u64, u32>(cur_addr >> Core::Memory::YUZU_PAGEBITS,
static_cast<u32>(cur_addr & Core::Memory::YUZU_PAGEMASK));
};
std::scoped_lock lock(cache_mutex);
for (QueryLocation loc : impl->pending_unregister) {
const auto [streamer_id, query_id] = loc.unpack();
auto* streamer = impl->streamers[streamer_id];
if (!streamer) [[unlikely]] {
continue;
}
auto* query = streamer->GetQuery(query_id);
auto [cont_addr, base] = gen_caching_indexing(query->guest_address);
auto it1 = cached_queries.find(cont_addr);
if (it1 != cached_queries.end()) {
auto it2 = it1->second.find(base);
if (it2 != it1->second.end()) {
if (it2->second.raw == loc.raw) {
it1->second.erase(it2);
}
}
}
streamer->Free(query_id);
}
impl->pending_unregister.clear();
}
template <typename Traits>
void QueryCacheBase<Traits>::NotifyWFI() {
bool should_sync = false;
impl->ForEachStreamer(
[&should_sync](StreamerInterface* streamer) { should_sync |= streamer->HasPendingSync(); });
if (!should_sync) {
return;
}
impl->ForEachStreamer([](StreamerInterface* streamer) { streamer->PresyncWrites(); });
impl->runtime.Barriers(true);
impl->ForEachStreamer([](StreamerInterface* streamer) { streamer->SyncWrites(); });
impl->runtime.Barriers(false);
}
template <typename Traits>
void QueryCacheBase<Traits>::NotifySegment(bool resume) {
if (resume) {
impl->runtime.ResumeHostConditionalRendering();
} else {
CounterClose(VideoCommon::QueryType::ZPassPixelCount64);
CounterClose(VideoCommon::QueryType::StreamingByteCount);
impl->runtime.PauseHostConditionalRendering();
}
}
template <typename Traits>
bool QueryCacheBase<Traits>::AccelerateHostConditionalRendering() {
bool qc_dirty = false;
const auto gen_lookup = [this, &qc_dirty](GPUVAddr address) -> VideoCommon::LookupData {
auto cpu_addr_opt = gpu_memory->GpuToCpuAddress(address);
if (!cpu_addr_opt) [[unlikely]] {
return VideoCommon::LookupData{
.address = 0,
.found_query = nullptr,
};
}
VAddr cpu_addr = *cpu_addr_opt;
std::scoped_lock lock(cache_mutex);
auto it1 = cached_queries.find(cpu_addr >> Core::Memory::YUZU_PAGEBITS);
if (it1 == cached_queries.end()) {
return VideoCommon::LookupData{
.address = cpu_addr,
.found_query = nullptr,
};
}
auto& sub_container = it1->second;
auto it_current = sub_container.find(cpu_addr & Core::Memory::YUZU_PAGEMASK);
if (it_current == sub_container.end()) {
auto it_current_2 = sub_container.find((cpu_addr & Core::Memory::YUZU_PAGEMASK) + 4);
if (it_current_2 == sub_container.end()) {
return VideoCommon::LookupData{
.address = cpu_addr,
.found_query = nullptr,
};
}
}
auto* query = impl->ObtainQuery(it_current->second);
qc_dirty |= True(query->flags & QueryFlagBits::IsHostManaged) &&
False(query->flags & QueryFlagBits::IsGuestSynced);
return VideoCommon::LookupData{
.address = cpu_addr,
.found_query = query,
};
};
auto& regs = maxwell3d->regs;
if (regs.render_enable_override != Maxwell::Regs::RenderEnable::Override::UseRenderEnable) {
impl->runtime.EndHostConditionalRendering();
return false;
}
const ComparisonMode mode = static_cast<ComparisonMode>(regs.render_enable.mode);
const GPUVAddr address = regs.render_enable.Address();
switch (mode) {
case ComparisonMode::True:
impl->runtime.EndHostConditionalRendering();
return false;
case ComparisonMode::False:
impl->runtime.EndHostConditionalRendering();
return false;
case ComparisonMode::Conditional: {
VideoCommon::LookupData object_1{gen_lookup(address)};
return impl->runtime.HostConditionalRenderingCompareValue(object_1, qc_dirty);
}
case ComparisonMode::IfEqual: {
VideoCommon::LookupData object_1{gen_lookup(address)};
VideoCommon::LookupData object_2{gen_lookup(address + 16)};
return impl->runtime.HostConditionalRenderingCompareValues(object_1, object_2, qc_dirty,
true);
}
case ComparisonMode::IfNotEqual: {
VideoCommon::LookupData object_1{gen_lookup(address)};
VideoCommon::LookupData object_2{gen_lookup(address + 16)};
return impl->runtime.HostConditionalRenderingCompareValues(object_1, object_2, qc_dirty,
false);
}
default:
return false;
}
}
// Async downloads
template <typename Traits>
void QueryCacheBase<Traits>::CommitAsyncFlushes() {
// Make sure to have the results synced in Host.
NotifyWFI();
u64 mask{};
{
std::scoped_lock lk(impl->flush_guard);
impl->ForEachStreamer([&mask](StreamerInterface* streamer) {
bool local_result = streamer->HasUnsyncedQueries();
if (local_result) {
mask |= 1ULL << streamer->GetId();
}
});
impl->flushes_pending.push_back(mask);
}
std::function<void()> func([this] { UnregisterPending(); });
impl->rasterizer.SyncOperation(std::move(func));
if (mask == 0) {
return;
}
u64 ran_mask = ~mask;
while (mask) {
impl->ForEachStreamerIn(mask, [&mask, &ran_mask](StreamerInterface* streamer) {
u64 dep_mask = streamer->GetDependentMask();
if ((dep_mask & ~ran_mask) != 0) {
return;
}
u64 index = streamer->GetId();
ran_mask |= (1ULL << index);
mask &= ~(1ULL << index);
streamer->PushUnsyncedQueries();
});
}
}
template <typename Traits>
bool QueryCacheBase<Traits>::HasUncommittedFlushes() const {
bool result = false;
impl->ForEachStreamer([&result](StreamerInterface* streamer) {
result |= streamer->HasUnsyncedQueries();
return result;
});
return result;
}
template <typename Traits>
bool QueryCacheBase<Traits>::ShouldWaitAsyncFlushes() {
std::scoped_lock lk(impl->flush_guard);
return !impl->flushes_pending.empty() && impl->flushes_pending.front() != 0ULL;
}
template <typename Traits>
void QueryCacheBase<Traits>::PopAsyncFlushes() {
u64 mask;
{
std::scoped_lock lk(impl->flush_guard);
mask = impl->flushes_pending.front();
impl->flushes_pending.pop_front();
}
if (mask == 0) {
return;
}
u64 ran_mask = ~mask;
while (mask) {
impl->ForEachStreamerIn(mask, [&mask, &ran_mask](StreamerInterface* streamer) {
u64 dep_mask = streamer->GetDependenceMask();
if ((dep_mask & ~ran_mask) != 0) {
return;
}
u64 index = streamer->GetId();
ran_mask |= (1ULL << index);
mask &= ~(1ULL << index);
streamer->PopUnsyncedQueries();
});
}
}
// Invalidation
template <typename Traits>
void QueryCacheBase<Traits>::InvalidateQuery(QueryCacheBase<Traits>::QueryLocation location) {
auto* query_base = impl->ObtainQuery(location);
if (!query_base) {
return;
}
query_base->flags |= QueryFlagBits::IsInvalidated;
}
template <typename Traits>
bool QueryCacheBase<Traits>::IsQueryDirty(QueryCacheBase<Traits>::QueryLocation location) {
auto* query_base = impl->ObtainQuery(location);
if (!query_base) {
return false;
}
return True(query_base->flags & QueryFlagBits::IsHostManaged) &&
False(query_base->flags & QueryFlagBits::IsGuestSynced);
}
template <typename Traits>
bool QueryCacheBase<Traits>::SemiFlushQueryDirty(QueryCacheBase<Traits>::QueryLocation location) {
auto* query_base = impl->ObtainQuery(location);
if (!query_base) {
return false;
}
if (True(query_base->flags & QueryFlagBits::IsFinalValueSynced) &&
False(query_base->flags & QueryFlagBits::IsGuestSynced)) {
auto* ptr = impl->cpu_memory.GetPointer(query_base->guest_address);
if (True(query_base->flags & QueryFlagBits::HasTimestamp)) {
std::memcpy(ptr, &query_base->value, sizeof(query_base->value));
return false;
}
u32 value_l = static_cast<u32>(query_base->value);
std::memcpy(ptr, &value_l, sizeof(value_l));
return false;
}
return True(query_base->flags & QueryFlagBits::IsHostManaged) &&
False(query_base->flags & QueryFlagBits::IsGuestSynced);
}
template <typename Traits>
void QueryCacheBase<Traits>::RequestGuestHostSync() {
impl->rasterizer.ReleaseFences();
}
} // namespace VideoCommon

View File

@ -0,0 +1,181 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <functional>
#include <mutex>
#include <optional>
#include <span>
#include <unordered_map>
#include <utility>
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/memory.h"
#include "video_core/control/channel_state_cache.h"
#include "video_core/query_cache/query_base.h"
#include "video_core/query_cache/types.h"
namespace Core::Memory {
class Memory;
}
namespace VideoCore {
class RasterizerInterface;
}
namespace Tegra {
class GPU;
}
namespace VideoCommon {
struct LookupData {
VAddr address;
QueryBase* found_query;
};
template <typename Traits>
class QueryCacheBase : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
using RuntimeType = typename Traits::RuntimeType;
public:
union QueryLocation {
BitField<27, 5, u32> stream_id;
BitField<0, 27, u32> query_id;
u32 raw;
std::pair<size_t, size_t> unpack() const {
return {static_cast<size_t>(stream_id.Value()), static_cast<size_t>(query_id.Value())};
}
};
explicit QueryCacheBase(Tegra::GPU& gpu, VideoCore::RasterizerInterface& rasterizer_,
Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_);
~QueryCacheBase();
void InvalidateRegion(VAddr addr, std::size_t size) {
IterateCache<true>(addr, size,
[this](QueryLocation location) { InvalidateQuery(location); });
}
void FlushRegion(VAddr addr, std::size_t size) {
bool result = false;
IterateCache<false>(addr, size, [this, &result](QueryLocation location) {
result |= SemiFlushQueryDirty(location);
return result;
});
if (result) {
RequestGuestHostSync();
}
}
static u64 BuildMask(std::span<const QueryType> types) {
u64 mask = 0;
for (auto query_type : types) {
mask |= 1ULL << (static_cast<u64>(query_type));
}
return mask;
}
/// Return true when a CPU region is modified from the GPU
[[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size) {
bool result = false;
IterateCache<false>(addr, size, [this, &result](QueryLocation location) {
result |= IsQueryDirty(location);
return result;
});
return result;
}
void CounterEnable(QueryType counter_type, bool is_enabled);
void CounterReset(QueryType counter_type);
void CounterClose(QueryType counter_type);
void CounterReport(GPUVAddr addr, QueryType counter_type, QueryPropertiesFlags flags,
u32 payload, u32 subreport);
void NotifyWFI();
bool AccelerateHostConditionalRendering();
// Async downloads
void CommitAsyncFlushes();
bool HasUncommittedFlushes() const;
bool ShouldWaitAsyncFlushes();
void PopAsyncFlushes();
void NotifySegment(bool resume);
void BindToChannel(s32 id) override;
protected:
template <bool remove_from_cache, typename Func>
void IterateCache(VAddr addr, std::size_t size, Func&& func) {
static constexpr bool RETURNS_BOOL =
std::is_same_v<std::invoke_result<Func, QueryLocation>, bool>;
const u64 addr_begin = addr;
const u64 addr_end = addr_begin + size;
const u64 page_end = addr_end >> Core::Memory::YUZU_PAGEBITS;
std::scoped_lock lock(cache_mutex);
for (u64 page = addr_begin >> Core::Memory::YUZU_PAGEBITS; page <= page_end; ++page) {
const u64 page_start = page << Core::Memory::YUZU_PAGEBITS;
const auto in_range = [page_start, addr_begin, addr_end](const u32 query_location) {
const u64 cache_begin = page_start + query_location;
const u64 cache_end = cache_begin + sizeof(u32);
return cache_begin < addr_end && addr_begin < cache_end;
};
const auto& it = cached_queries.find(page);
if (it == std::end(cached_queries)) {
continue;
}
auto& contents = it->second;
for (auto& query : contents) {
if (!in_range(query.first)) {
continue;
}
if constexpr (RETURNS_BOOL) {
if (func(query.second)) {
return;
}
} else {
func(query.second);
}
}
if constexpr (remove_from_cache) {
const auto in_range2 = [&](const std::pair<u32, QueryLocation>& pair) {
return in_range(pair.first);
};
std::erase_if(contents, in_range2);
}
}
}
using ContentCache = std::unordered_map<u64, std::unordered_map<u32, QueryLocation>>;
void InvalidateQuery(QueryLocation location);
bool IsQueryDirty(QueryLocation location);
bool SemiFlushQueryDirty(QueryLocation location);
void RequestGuestHostSync();
void UnregisterPending();
std::unordered_map<u64, std::unordered_map<u32, QueryLocation>> cached_queries;
std::mutex cache_mutex;
struct QueryCacheBaseImpl;
friend struct QueryCacheBaseImpl;
friend RuntimeType;
std::unique_ptr<QueryCacheBaseImpl> impl;
};
} // namespace VideoCommon

View File

@ -0,0 +1,149 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <deque>
#include <optional>
#include <vector>
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/query_cache/bank_base.h"
#include "video_core/query_cache/query_base.h"
namespace VideoCommon {
class StreamerInterface {
public:
explicit StreamerInterface(size_t id_) : id{id_}, dependence_mask{}, dependent_mask{} {}
virtual ~StreamerInterface() = default;
virtual QueryBase* GetQuery(size_t id) = 0;
virtual void StartCounter() {
/* Do Nothing */
}
virtual void PauseCounter() {
/* Do Nothing */
}
virtual void ResetCounter() {
/* Do Nothing */
}
virtual void CloseCounter() {
/* Do Nothing */
}
virtual bool HasPendingSync() const {
return false;
}
virtual void PresyncWrites() {
/* Do Nothing */
}
virtual void SyncWrites() {
/* Do Nothing */
}
virtual size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
std::optional<u32> subreport = std::nullopt) = 0;
virtual bool HasUnsyncedQueries() const {
return false;
}
virtual void PushUnsyncedQueries() {
/* Do Nothing */
}
virtual void PopUnsyncedQueries() {
/* Do Nothing */
}
virtual void Free(size_t query_id) = 0;
size_t GetId() const {
return id;
}
u64 GetDependenceMask() const {
return dependence_mask;
}
u64 GetDependentMask() const {
return dependence_mask;
}
u64 GetAmmendValue() const {
return ammend_value;
}
void SetAccumulationValue(u64 new_value) {
acumulation_value = new_value;
}
protected:
void MakeDependent(StreamerInterface* depend_on) {
dependence_mask |= 1ULL << depend_on->id;
depend_on->dependent_mask |= 1ULL << id;
}
const size_t id;
u64 dependence_mask;
u64 dependent_mask;
u64 ammend_value{};
u64 acumulation_value{};
};
template <typename QueryType>
class SimpleStreamer : public StreamerInterface {
public:
explicit SimpleStreamer(size_t id_) : StreamerInterface{id_} {}
virtual ~SimpleStreamer() = default;
protected:
virtual QueryType* GetQuery(size_t query_id) override {
if (query_id < slot_queries.size()) {
return &slot_queries[query_id];
}
return nullptr;
}
virtual void Free(size_t query_id) override {
std::scoped_lock lk(guard);
ReleaseQuery(query_id);
}
template <typename... Args, typename = decltype(QueryType(std::declval<Args>()...))>
size_t BuildQuery(Args&&... args) {
std::scoped_lock lk(guard);
if (!old_queries.empty()) {
size_t new_id = old_queries.front();
old_queries.pop_front();
new (&slot_queries[new_id]) QueryType(std::forward<Args>(args)...);
return new_id;
}
size_t new_id = slot_queries.size();
slot_queries.emplace_back(std::forward<Args>(args)...);
return new_id;
}
void ReleaseQuery(size_t query_id) {
if (query_id < slot_queries.size()) {
old_queries.push_back(query_id);
return;
}
UNREACHABLE();
}
std::mutex guard;
std::deque<QueryType> slot_queries;
std::deque<size_t> old_queries;
};
} // namespace VideoCommon

View File

@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace VideoCommon {
enum class QueryPropertiesFlags : u32 {
HasTimeout = 1 << 0,
IsAFence = 1 << 1,
};
DECLARE_ENUM_FLAG_OPERATORS(QueryPropertiesFlags)
// This should always be equivalent to maxwell3d Report Semaphore Reports
enum class QueryType : u32 {
Payload = 0, // "None" in docs, but confirmed via hardware to return the payload
VerticesGenerated = 1,
ZPassPixelCount = 2,
PrimitivesGenerated = 3,
AlphaBetaClocks = 4,
VertexShaderInvocations = 5,
StreamingPrimitivesNeededMinusSucceeded = 6,
GeometryShaderInvocations = 7,
GeometryShaderPrimitivesGenerated = 9,
ZCullStats0 = 10,
StreamingPrimitivesSucceeded = 11,
ZCullStats1 = 12,
StreamingPrimitivesNeeded = 13,
ZCullStats2 = 14,
ClipperInvocations = 15,
ZCullStats3 = 16,
ClipperPrimitivesGenerated = 17,
VtgPrimitivesOut = 18,
PixelShaderInvocations = 19,
ZPassPixelCount64 = 21,
IEEECleanColorTarget = 24,
IEEECleanZetaTarget = 25,
StreamingByteCount = 26,
TessellationInitInvocations = 27,
BoundingRectangle = 28,
TessellationShaderInvocations = 29,
TotalStreamingPrimitivesNeededMinusSucceeded = 30,
TessellationShaderPrimitivesGenerated = 31,
// max.
MaxQueryTypes,
};
// Comparison modes for Host Conditional Rendering
enum class ComparisonMode : u32 {
False = 0,
True = 1,
Conditional = 2,
IfEqual = 3,
IfNotEqual = 4,
MaxComparisonMode,
};
// Reduction ops.
enum class ReductionOp : u32 {
RedAdd = 0,
RedMin = 1,
RedMax = 2,
RedInc = 3,
RedDec = 4,
RedAnd = 5,
RedOr = 6,
RedXor = 7,
MaxReductionOp,
};
} // namespace VideoCommon

View File

@ -12,6 +12,7 @@
#include "video_core/cache_types.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/gpu.h"
#include "video_core/query_cache/types.h"
#include "video_core/rasterizer_download_area.h"
namespace Tegra {
@ -26,11 +27,6 @@ struct ChannelState;
namespace VideoCore {
enum class QueryType {
SamplesPassed,
};
constexpr std::size_t NumQueryTypes = 1;
enum class LoadCallbackStage {
Prepare,
Build,
@ -58,10 +54,11 @@ public:
virtual void DispatchCompute() = 0;
/// Resets the counter of a query
virtual void ResetCounter(QueryType type) = 0;
virtual void ResetCounter(VideoCommon::QueryType type) = 0;
/// Records a GPU query and caches it
virtual void Query(GPUVAddr gpu_addr, QueryType type, std::optional<u64> timestamp) = 0;
virtual void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) = 0;
/// Signal an uniform buffer binding
virtual void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@ -83,7 +80,7 @@ public:
virtual void SignalReference() = 0;
/// Release all pending fences.
virtual void ReleaseFences() = 0;
virtual void ReleaseFences(bool force = true) = 0;
/// Notify rasterizer that all caches should be flushed to Switch memory
virtual void FlushAll() = 0;

View File

@ -26,16 +26,18 @@ void RasterizerNull::Draw(bool is_indexed, u32 instance_count) {}
void RasterizerNull::DrawTexture() {}
void RasterizerNull::Clear(u32 layer_count) {}
void RasterizerNull::DispatchCompute() {}
void RasterizerNull::ResetCounter(VideoCore::QueryType type) {}
void RasterizerNull::Query(GPUVAddr gpu_addr, VideoCore::QueryType type,
std::optional<u64> timestamp) {
void RasterizerNull::ResetCounter(VideoCommon::QueryType type) {}
void RasterizerNull::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) {
if (!gpu_memory) {
return;
}
gpu_memory->Write(gpu_addr, u64{0});
if (timestamp) {
gpu_memory->Write(gpu_addr + 8, *timestamp);
if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) {
u64 ticks = m_gpu.GetTicks();
gpu_memory->Write<u64>(gpu_addr + 8, ticks);
gpu_memory->Write<u64>(gpu_addr, static_cast<u64>(payload));
} else {
gpu_memory->Write<u32>(gpu_addr, payload);
}
}
void RasterizerNull::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@ -74,7 +76,7 @@ void RasterizerNull::SignalSyncPoint(u32 value) {
syncpoint_manager.IncrementHost(value);
}
void RasterizerNull::SignalReference() {}
void RasterizerNull::ReleaseFences() {}
void RasterizerNull::ReleaseFences(bool) {}
void RasterizerNull::FlushAndInvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType) {}
void RasterizerNull::WaitForIdle() {}
void RasterizerNull::FragmentBarrier() {}

View File

@ -42,8 +42,9 @@ public:
void DrawTexture() override;
void Clear(u32 layer_count) override;
void DispatchCompute() override;
void ResetCounter(VideoCore::QueryType type) override;
void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override;
void ResetCounter(VideoCommon::QueryType type) override;
void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override;
void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override;
void DisableGraphicsUniformBuffer(size_t stage, u32 index) override;
void FlushAll() override;
@ -63,7 +64,7 @@ public:
void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override;
void SignalReference() override;
void ReleaseFences() override;
void ReleaseFences(bool force) override;
void FlushAndInvalidateRegion(
VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
void WaitForIdle() override;

View File

@ -27,7 +27,7 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) {
} // Anonymous namespace
QueryCache::QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_)
: QueryCacheBase(rasterizer_, cpu_memory_), gl_rasterizer{rasterizer_} {}
: QueryCacheLegacy(rasterizer_, cpu_memory_), gl_rasterizer{rasterizer_} {}
QueryCache::~QueryCache() = default;

View File

@ -26,7 +26,7 @@ class RasterizerOpenGL;
using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
class QueryCache final
: public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> {
: public VideoCommon::QueryCacheLegacy<QueryCache, CachedQuery, CounterStream, HostCounter> {
public:
explicit QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_);
~QueryCache();

View File

@ -396,13 +396,39 @@ void RasterizerOpenGL::DispatchCompute() {
has_written_global_memory |= pipeline->WritesGlobalMemory();
}
void RasterizerOpenGL::ResetCounter(VideoCore::QueryType type) {
query_cache.ResetCounter(type);
void RasterizerOpenGL::ResetCounter(VideoCommon::QueryType type) {
if (type == VideoCommon::QueryType::ZPassPixelCount64) {
query_cache.ResetCounter(VideoCore::QueryType::SamplesPassed);
}
}
void RasterizerOpenGL::Query(GPUVAddr gpu_addr, VideoCore::QueryType type,
std::optional<u64> timestamp) {
query_cache.Query(gpu_addr, type, timestamp);
void RasterizerOpenGL::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) {
if (type == VideoCommon::QueryType::ZPassPixelCount64) {
if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) {
query_cache.Query(gpu_addr, VideoCore::QueryType::SamplesPassed, {gpu.GetTicks()});
} else {
query_cache.Query(gpu_addr, VideoCore::QueryType::SamplesPassed, std::nullopt);
}
return;
}
if (type != VideoCommon::QueryType::Payload) {
payload = 1u;
}
std::function<void()> func([this, gpu_addr, flags, memory_manager = gpu_memory, payload]() {
if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) {
u64 ticks = gpu.GetTicks();
memory_manager->Write<u64>(gpu_addr + 8, ticks);
memory_manager->Write<u64>(gpu_addr, static_cast<u64>(payload));
} else {
memory_manager->Write<u32>(gpu_addr, payload);
}
});
if (True(flags & VideoCommon::QueryPropertiesFlags::IsAFence)) {
SignalFence(std::move(func));
return;
}
func();
}
void RasterizerOpenGL::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@ -573,8 +599,8 @@ void RasterizerOpenGL::SignalReference() {
fence_manager.SignalOrdering();
}
void RasterizerOpenGL::ReleaseFences() {
fence_manager.WaitPendingFences();
void RasterizerOpenGL::ReleaseFences(bool force) {
fence_manager.WaitPendingFences(force);
}
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size,

View File

@ -86,8 +86,9 @@ public:
void DrawTexture() override;
void Clear(u32 layer_count) override;
void DispatchCompute() override;
void ResetCounter(VideoCore::QueryType type) override;
void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override;
void ResetCounter(VideoCommon::QueryType type) override;
void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override;
void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override;
void DisableGraphicsUniformBuffer(size_t stage, u32 index) override;
void FlushAll() override;
@ -107,7 +108,7 @@ public:
void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override;
void SignalReference() override;
void ReleaseFences() override;
void ReleaseFences(bool force = true) override;
void FlushAndInvalidateRegion(
VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
void WaitForIdle() override;

View File

@ -185,7 +185,7 @@ struct FormatTuple {
{VK_FORMAT_BC2_SRGB_BLOCK}, // BC2_SRGB
{VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB
{VK_FORMAT_BC7_SRGB_BLOCK}, // BC7_SRGB
{VK_FORMAT_R4G4B4A4_UNORM_PACK16}, // A4B4G4R4_UNORM
{VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT}, // A4B4G4R4_UNORM
{VK_FORMAT_R4G4_UNORM_PACK8}, // G4R4_UNORM
{VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB
{VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB

View File

@ -61,6 +61,9 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
if (device.IsExtTransformFeedbackSupported()) {
flags |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
}
if (device.IsExtConditionalRendering()) {
flags |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT;
}
const VkBufferCreateInfo buffer_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,

View File

@ -12,6 +12,9 @@
#include "common/common_types.h"
#include "common/div_ceil.h"
#include "video_core/host_shaders/astc_decoder_comp_spv.h"
#include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h"
#include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h"
#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
@ -57,6 +60,30 @@ constexpr std::array<VkDescriptorSetLayoutBinding, 2> INPUT_OUTPUT_DESCRIPTOR_SE
},
}};
constexpr std::array<VkDescriptorSetLayoutBinding, 3> QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS{{
{
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
{
.binding = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
{
.binding = 2,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
}};
constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{
.uniform_buffers = 0,
.storage_buffers = 2,
@ -67,6 +94,16 @@ constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{
.score = 2,
};
constexpr DescriptorBankInfo QUERIES_SCAN_BANK_INFO{
.uniform_buffers = 0,
.storage_buffers = 3,
.texture_buffers = 0,
.image_buffers = 0,
.textures = 0,
.images = 0,
.score = 3,
};
constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> ASTC_DESCRIPTOR_SET_BINDINGS{{
{
.binding = ASTC_BINDING_INPUT_BUFFER,
@ -103,6 +140,15 @@ constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLAT
.stride = sizeof(DescriptorUpdateEntry),
};
constexpr VkDescriptorUpdateTemplateEntry QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLATE{
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 3,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = 0,
.stride = sizeof(DescriptorUpdateEntry),
};
constexpr std::array<VkDescriptorUpdateTemplateEntry, ASTC_NUM_BINDINGS>
ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{
{
@ -131,13 +177,21 @@ struct AstcPushConstants {
u32 block_height;
u32 block_height_mask;
};
struct QueriesPrefixScanPushConstants {
u32 min_accumulation_base;
u32 max_accumulation_base;
u32 accumulation_limit;
u32 buffer_offset;
};
} // Anonymous namespace
ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
vk::Span<VkDescriptorSetLayoutBinding> bindings,
vk::Span<VkDescriptorUpdateTemplateEntry> templates,
const DescriptorBankInfo& bank_info,
vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code)
vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code,
std::optional<u32> optional_subgroup_size)
: device{device_} {
descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout({
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
@ -178,13 +232,19 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
.pCode = code.data(),
});
device.SaveShader(code);
const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT,
.pNext = nullptr,
.requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U,
};
bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size;
pipeline = device.GetLogical().CreateComputePipeline({
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stage{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext = nullptr,
.pNext = use_setup_size ? &subgroup_size_ci : nullptr,
.flags = 0,
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
.module = *module,
@ -302,6 +362,123 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
return {staging.buffer, staging.offset};
}
ConditionalRenderingResolvePass::ConditionalRenderingResolvePass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS,
INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO, nullptr,
RESOLVE_CONDITIONAL_RENDER_COMP_SPV),
scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_buffer,
u32 src_offset, bool compare_to_zero) {
const size_t compare_size = compare_to_zero ? 8 : 24;
compute_pass_descriptor_queue.Acquire();
compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, compare_size);
compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, sizeof(u32));
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, descriptor_data](vk::CommandBuffer cmdbuf) {
static constexpr VkMemoryBarrier read_barrier{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
};
static constexpr VkMemoryBarrier write_barrier{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT,
};
const VkDescriptorSet set = descriptor_allocator.Commit();
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.Dispatch(1, 1, 1);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0, write_barrier);
});
}
QueriesPrefixScanPass::QueriesPrefixScanPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(
device_, descriptor_pool_, QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS,
QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLATE, QUERIES_SCAN_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(QueriesPrefixScanPushConstants)>,
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_BASIC_BIT) &&
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_ARITHMETIC_BIT) &&
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_BIT) &&
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT)
? std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_COMP_SPV)
: std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_NOSUBGROUPS_COMP_SPV)),
scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffer,
VkBuffer src_buffer, size_t number_of_sums,
size_t min_accumulation_limit, size_t max_accumulation_limit) {
size_t current_runs = number_of_sums;
size_t offset = 0;
while (current_runs != 0) {
static constexpr size_t DISPATCH_SIZE = 2048U;
size_t runs_to_do = std::min<size_t>(current_runs, DISPATCH_SIZE);
current_runs -= runs_to_do;
compute_pass_descriptor_queue.Acquire();
compute_pass_descriptor_queue.AddBuffer(src_buffer, 0, number_of_sums * sizeof(u64));
compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, number_of_sums * sizeof(u64));
compute_pass_descriptor_queue.AddBuffer(accumulation_buffer, 0, sizeof(u64));
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
size_t used_offset = offset;
offset += runs_to_do;
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, descriptor_data, min_accumulation_limit, max_accumulation_limit,
runs_to_do, used_offset](vk::CommandBuffer cmdbuf) {
static constexpr VkMemoryBarrier read_barrier{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
};
static constexpr VkMemoryBarrier write_barrier{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT |
VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_INDEX_READ_BIT |
VK_ACCESS_UNIFORM_READ_BIT |
VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT,
};
const QueriesPrefixScanPushConstants uniforms{
.min_accumulation_base = static_cast<u32>(min_accumulation_limit),
.max_accumulation_base = static_cast<u32>(max_accumulation_limit),
.accumulation_limit = static_cast<u32>(runs_to_do - 1),
.buffer_offset = static_cast<u32>(used_offset),
};
const VkDescriptorSet set = descriptor_allocator.Commit();
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, uniforms);
cmdbuf.Dispatch(1, 1, 1);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0,
write_barrier);
});
}
}
ASTCDecoderPass::ASTCDecoderPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,

View File

@ -3,6 +3,7 @@
#pragma once
#include <optional>
#include <span>
#include <utility>
@ -31,7 +32,8 @@ public:
vk::Span<VkDescriptorSetLayoutBinding> bindings,
vk::Span<VkDescriptorUpdateTemplateEntry> templates,
const DescriptorBankInfo& bank_info,
vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code);
vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code,
std::optional<u32> optional_subgroup_size = std::nullopt);
~ComputePass();
protected:
@ -82,6 +84,33 @@ private:
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class ConditionalRenderingResolvePass final : public ComputePass {
public:
explicit ConditionalRenderingResolvePass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
void Resolve(VkBuffer dst_buffer, VkBuffer src_buffer, u32 src_offset, bool compare_to_zero);
private:
Scheduler& scheduler;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class QueriesPrefixScanPass final : public ComputePass {
public:
explicit QueriesPrefixScanPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
void Run(VkBuffer accumulation_buffer, VkBuffer dst_buffer, VkBuffer src_buffer,
size_t number_of_sums, size_t min_accumulation_limit, size_t max_accumulation_limit);
private:
Scheduler& scheduler;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class ASTCDecoderPass final : public ComputePass {
public:
explicit ASTCDecoderPass(const Device& device_, Scheduler& scheduler_,

View File

@ -7,6 +7,7 @@
#include "video_core/fence_manager.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_query_cache.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
namespace Core {
@ -20,7 +21,6 @@ class RasterizerInterface;
namespace Vulkan {
class Device;
class QueryCache;
class Scheduler;
class InnerFence : public VideoCommon::FenceBase {

File diff suppressed because it is too large Load Diff

View File

@ -1,101 +1,75 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <cstddef>
#include <memory>
#include <utility>
#include <vector>
#include "common/common_types.h"
#include "video_core/query_cache.h"
#include "video_core/renderer_vulkan/vk_resource_pool.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/query_cache/query_cache_base.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
namespace VideoCore {
class RasterizerInterface;
}
namespace VideoCommon {
class StreamerInterface;
}
namespace Vulkan {
class CachedQuery;
class Device;
class HostCounter;
class QueryCache;
class Scheduler;
class StagingBufferPool;
using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
struct QueryCacheRuntimeImpl;
class QueryPool final : public ResourcePool {
class QueryCacheRuntime {
public:
explicit QueryPool(const Device& device, Scheduler& scheduler, VideoCore::QueryType type);
~QueryPool() override;
explicit QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer,
Core::Memory::Memory& cpu_memory_,
Vulkan::BufferCache& buffer_cache_, const Device& device_,
const MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
StagingBufferPool& staging_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue,
DescriptorPool& descriptor_pool);
~QueryCacheRuntime();
std::pair<VkQueryPool, u32> Commit();
template <typename SyncValuesType>
void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = nullptr);
void Reserve(std::pair<VkQueryPool, u32> query);
void Barriers(bool is_prebarrier);
protected:
void Allocate(std::size_t begin, std::size_t end) override;
void EndHostConditionalRendering();
void PauseHostConditionalRendering();
void ResumeHostConditionalRendering();
bool HostConditionalRenderingCompareValue(VideoCommon::LookupData object_1, bool qc_dirty);
bool HostConditionalRenderingCompareValues(VideoCommon::LookupData object_1,
VideoCommon::LookupData object_2, bool qc_dirty,
bool equal_check);
VideoCommon::StreamerInterface* GetStreamerInterface(VideoCommon::QueryType query_type);
void Bind3DEngine(Tegra::Engines::Maxwell3D* maxwell3d);
template <typename Func>
void View3DRegs(Func&& func);
private:
static constexpr std::size_t GROW_STEP = 512;
const Device& device;
const VideoCore::QueryType type;
std::vector<vk::QueryPool> pools;
std::vector<bool> usage;
void HostConditionalRenderingCompareValueImpl(VideoCommon::LookupData object, bool is_equal);
void HostConditionalRenderingCompareBCImpl(VAddr address, bool is_equal);
friend struct QueryCacheRuntimeImpl;
std::unique_ptr<QueryCacheRuntimeImpl> impl;
};
class QueryCache final
: public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> {
public:
explicit QueryCache(VideoCore::RasterizerInterface& rasterizer_,
Core::Memory::Memory& cpu_memory_, const Device& device_,
Scheduler& scheduler_);
~QueryCache();
std::pair<VkQueryPool, u32> AllocateQuery(VideoCore::QueryType type);
void Reserve(VideoCore::QueryType type, std::pair<VkQueryPool, u32> query);
const Device& GetDevice() const noexcept {
return device;
}
Scheduler& GetScheduler() const noexcept {
return scheduler;
}
private:
const Device& device;
Scheduler& scheduler;
std::array<QueryPool, VideoCore::NumQueryTypes> query_pools;
struct QueryCacheParams {
using RuntimeType = typename Vulkan::QueryCacheRuntime;
};
class HostCounter final : public VideoCommon::HostCounterBase<QueryCache, HostCounter> {
public:
explicit HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
VideoCore::QueryType type_);
~HostCounter();
void EndQuery();
private:
u64 BlockingQuery(bool async = false) const override;
QueryCache& cache;
const VideoCore::QueryType type;
const std::pair<VkQueryPool, u32> query;
const u64 tick;
};
class CachedQuery : public VideoCommon::CachedQueryBase<HostCounter> {
public:
explicit CachedQuery(QueryCache&, VideoCore::QueryType, VAddr cpu_addr_, u8* host_ptr_)
: CachedQueryBase{cpu_addr_, host_ptr_} {}
};
using QueryCache = VideoCommon::QueryCacheBase<QueryCacheParams>;
} // namespace Vulkan

Some files were not shown because too many files have changed in this diff Show More