Compare commits

...

23 Commits

Author SHA1 Message Date
32b1f38aa8 Android #72 2023-09-16 00:57:16 +00:00
aa6afb0cfe Merge pull request #11508 from t895/audio-mute
android: Remove settings interface specifically for audio mute
2023-09-15 00:00:56 -04:00
832a2fcc69 android: Remove settings interface specifically for audio mute 2023-09-14 23:46:19 -04:00
958bed4545 Merge pull request #11507 from t895/emulation-stop-fix
android: Return the correct status code on emulation stop
2023-09-14 23:22:00 -04:00
0c688b0bf5 Merge pull request #11506 from t895/icon-fix
android: Use resource as shortcut intermediary
2023-09-14 23:21:45 -04:00
7e2bd395bc android: Return the correct status code on emulation stop 2023-09-14 23:08:06 -04:00
19053ab631 Merge pull request #11505 from t895/config-patch
android: Don't reinitialize settings on emulation start
2023-09-14 22:20:45 -04:00
6481f4e937 android: Use resource as shortcut intermediary
Fixes issue where the shortcut icon would appear cropped on certain devices
2023-09-14 22:19:08 -04:00
d1deff6b07 Merge pull request #11504 from t895/emu-args
android: Fix emulation to settings navigation args
2023-09-14 21:25:58 -04:00
e8aaab2fc1 android: Don't reinitialize settings on emulation start
Config is already initialized on application start
2023-09-14 21:25:17 -04:00
21b133de40 android: Fix emulation to settings navigation args 2023-09-14 20:24:43 -04:00
0c55248f92 Merge pull request #11503 from t895/stateflow-patch
android: Delay collecting UI state in games fragment
2023-09-14 20:23:49 -04:00
b394389170 android: Delay collecting UI state in games fragment 2023-09-14 20:02:48 -04:00
5eceab3ce6 Merge pull request #11425 from t895/stateflows
android: Use StateFlow instead of LiveData
2023-09-14 16:54:21 -04:00
8baed5d95d android: Refactor menu tags to enum 2023-09-14 15:18:56 -04:00
4a3cbf0021 android: Use StateFlow instead of LiveData 2023-09-14 15:18:56 -04:00
04352a9aef Merge pull request #11496 from liamwhite/ngc
ngc: implement service
2023-09-14 09:24:46 -04:00
48dec7e0c9 Merge pull request #11479 from Kelebek1/check_all_fbs
Look for the most recently modified image for present
2023-09-14 09:24:27 -04:00
b5f99164f1 Merge pull request #11433 from liamwhite/shutdown-oopsie
polyfill_thread: ensure mutex was locked before signaling stop
2023-09-14 09:24:20 -04:00
eb4ddb2868 shader_recompiler: skip sampler for buffer textures (#11435) 2023-09-14 15:23:50 +02:00
9d7eebde7b ngc: implement service 2023-09-14 09:14:08 -04:00
baad1238c3 Look for the most recently modified image for present 2023-09-11 03:11:29 +01:00
bdd09d6844 polyfill_thread: ensure mutex was locked before signaling stop 2023-09-02 11:51:40 -04:00
44 changed files with 655 additions and 421 deletions

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

@ -307,21 +307,6 @@ object NativeLibrary {
*/
external fun isPaused(): Boolean
/**
* Mutes emulation sound
*/
external fun muteAudio(): Boolean
/**
* Unmutes emulation sound
*/
external fun unmuteAudio(): Boolean
/**
* Returns true if emulation audio is muted.
*/
external fun isMuted(): Boolean
/**
* Returns the performance stats for the current game
*/

View File

@ -332,7 +332,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
pictureInPictureActions.add(pauseRemoteAction)
}
if (NativeLibrary.isMuted()) {
if (BooleanSetting.AUDIO_MUTED.boolean) {
val unmuteIcon = Icon.createWithResource(
this@EmulationActivity,
R.drawable.ic_pip_unmute
@ -389,9 +389,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
}
if (intent.action == actionUnmute) {
if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
} else if (intent.action == actionMute) {
if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio()
if (!BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(true)
}
buildPictureInPictureParams()
}
@ -417,7 +417,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
} catch (ignored: Exception) {
}
// Always resume audio, since there is no UI button
if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
}
}

View File

@ -4,7 +4,8 @@
package org.yuzu.yuzu_emu.adapters
import android.content.Intent
import android.graphics.drawable.BitmapDrawable
import android.graphics.Bitmap
import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.text.TextUtils
import android.view.LayoutInflater
@ -15,7 +16,10 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toDrawable
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
@ -87,11 +91,24 @@ class GameAdapter(private val activity: AppCompatActivity) :
action = Intent.ACTION_VIEW
data = Uri.parse(holder.game.path)
}
val layerDrawable = ResourcesCompat.getDrawable(
YuzuApplication.appContext.resources,
R.drawable.shortcut,
null
) as LayerDrawable
layerDrawable.setDrawableByLayerId(
R.id.shortcut_foreground,
GameIconUtils.getGameIcon(holder.game).toDrawable(YuzuApplication.appContext.resources)
)
val inset = YuzuApplication.appContext.resources
.getDimensionPixelSize(R.dimen.icon_inset)
layerDrawable.setLayerInset(1, inset, inset, inset, inset)
val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
.setShortLabel(holder.game.title)
.setIcon(
IconCompat.createWithBitmap(
(holder.binding.imageGameScreen.drawable as BitmapDrawable).bitmap
IconCompat.createWithAdaptiveBitmap(
layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
)
)
.setIntent(openIntent)

View File

@ -10,8 +10,12 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
@ -86,7 +90,11 @@ class HomeSettingAdapter(
binding.optionIcon.alpha = 0.5f
}
option.details.observe(viewLifecycle) { updateOptionDetails(it) }
viewLifecycle.lifecycleScope.launch {
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
option.details.collect { updateOptionDetails(it) }
}
}
binding.optionDetail.postDelayed(
{
binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE

View File

@ -10,6 +10,7 @@ enum class BooleanSetting(
override val category: Settings.Category,
override val androidDefault: Boolean? = null
) : AbstractBooleanSetting {
AUDIO_MUTED("audio_muted", Settings.Category.Audio),
CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),

View File

@ -80,6 +80,17 @@ object Settings {
const val SECTION_THEME = "Theme"
const val SECTION_DEBUG = "Debug"
enum class MenuTag(val titleId: Int) {
SECTION_ROOT(R.string.advanced_settings),
SECTION_GENERAL(R.string.preferences_general),
SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics),
SECTION_AUDIO(R.string.preferences_audio),
SECTION_CPU(R.string.cpu),
SECTION_THEME(R.string.preferences_theme),
SECTION_DEBUG(R.string.preferences_debug);
}
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
const val PREF_OVERLAY_VERSION = "OverlayVersion"

View File

@ -3,10 +3,12 @@
package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.Settings
class SubmenuSetting(
titleId: Int,
descriptionId: Int,
val menuKey: String
val menuKey: Settings.MenuTag
) : SettingsItem(emptySetting, titleId, descriptionId) {
override val type = TYPE_SUBMENU
}

View File

@ -13,9 +13,14 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navArgs
import com.google.android.material.color.MaterialColors
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.io.IOException
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
@ -66,25 +71,39 @@ class SettingsActivity : AppCompatActivity() {
)
}
settingsViewModel.shouldRecreate.observe(this) {
if (it) {
settingsViewModel.setShouldRecreate(false)
recreate()
lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
settingsViewModel.shouldRecreate.collectLatest {
if (it) {
settingsViewModel.setShouldRecreate(false)
recreate()
}
}
}
}
}
settingsViewModel.shouldNavigateBack.observe(this) {
if (it) {
settingsViewModel.setShouldNavigateBack(false)
navigateBack()
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
settingsViewModel.shouldNavigateBack.collectLatest {
if (it) {
settingsViewModel.setShouldNavigateBack(false)
navigateBack()
}
}
}
}
}
settingsViewModel.shouldShowResetSettingsDialog.observe(this) {
if (it) {
settingsViewModel.setShouldShowResetSettingsDialog(false)
ResetSettingsDialogFragment().show(
supportFragmentManager,
ResetSettingsDialogFragment.TAG
)
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
if (it) {
settingsViewModel.setShouldShowResetSettingsDialog(false)
ResetSettingsDialogFragment().show(
supportFragmentManager,
ResetSettingsDialogFragment.TAG
)
}
}
}
}
}

View File

@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.features.settings.ui
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -13,14 +14,19 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.model.SettingsViewModel
class SettingsFragment : Fragment() {
@ -51,15 +57,17 @@ class SettingsFragment : Fragment() {
return binding.root
}
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
settingsAdapter = SettingsAdapter(this, requireContext())
presenter = SettingsFragmentPresenter(
settingsViewModel,
settingsAdapter!!,
args.menuTag,
args.game?.gameId ?: ""
args.menuTag
)
binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
val dividerDecoration = MaterialDividerItemDecoration(
requireContext(),
LinearLayoutManager.VERTICAL
@ -75,28 +83,31 @@ class SettingsFragment : Fragment() {
settingsViewModel.setShouldNavigateBack(true)
}
settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) {
if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it
}
settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
if (it) {
settingsViewModel.setShouldReloadSettingsList(false)
presenter.loadSettingsList()
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
settingsViewModel.shouldReloadSettingsList.collectLatest {
if (it) {
settingsViewModel.setShouldReloadSettingsList(false)
presenter.loadSettingsList()
}
}
}
}
launch {
settingsViewModel.isUsingSearch.collectLatest {
if (it) {
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
} else {
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
}
}
}
}
settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) {
if (it) {
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
} else {
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
}
}
if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) {
if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {
binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
binding.toolbarSettings.setOnMenuItemClickListener {
when (it.itemId) {

View File

@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.text.TextUtils
import android.widget.Toast
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.R
@ -20,15 +19,13 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.SettingsViewModel
import org.yuzu.yuzu_emu.utils.NativeConfig
class SettingsFragmentPresenter(
private val settingsViewModel: SettingsViewModel,
private val adapter: SettingsAdapter,
private var menuTag: String,
private var gameId: String
private var menuTag: Settings.MenuTag
) {
private var settingsList = ArrayList<SettingsItem>()
@ -53,24 +50,15 @@ class SettingsFragmentPresenter(
}
fun loadSettingsList() {
if (!TextUtils.isEmpty(gameId)) {
settingsViewModel.setToolbarTitle(
context.getString(
R.string.advanced_settings_game,
gameId
)
)
}
val sl = ArrayList<SettingsItem>()
when (menuTag) {
SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
Settings.SECTION_GENERAL -> addGeneralSettings(sl)
Settings.SECTION_SYSTEM -> addSystemSettings(sl)
Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
Settings.SECTION_AUDIO -> addAudioSettings(sl)
Settings.SECTION_THEME -> addThemeSettings(sl)
Settings.SECTION_DEBUG -> addDebugSettings(sl)
Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl)
Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl)
Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
else -> {
val context = YuzuApplication.appContext
Toast.makeText(
@ -86,13 +74,12 @@ class SettingsFragmentPresenter(
}
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
sl.apply {
add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL))
add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM))
add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER))
add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO))
add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG))
add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL))
add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM))
add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER))
add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO))
add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG))
add(
RunnableSetting(R.string.reset_to_default, 0, false) {
settingsViewModel.setShouldShowResetSettingsDialog(true)
@ -102,7 +89,6 @@ class SettingsFragmentPresenter(
}
private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
sl.apply {
add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
add(ShortSetting.RENDERER_SPEED_LIMIT.key)
@ -112,7 +98,6 @@ class SettingsFragmentPresenter(
}
private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
sl.apply {
add(BooleanSetting.USE_DOCKED_MODE.key)
add(IntSetting.REGION_INDEX.key)
@ -123,7 +108,6 @@ class SettingsFragmentPresenter(
}
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
sl.apply {
add(IntSetting.RENDERER_ACCURACY.key)
add(IntSetting.RENDERER_RESOLUTION.key)
@ -140,7 +124,6 @@ class SettingsFragmentPresenter(
}
private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
sl.apply {
add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
add(ByteSetting.AUDIO_VOLUME.key)
@ -148,7 +131,6 @@ class SettingsFragmentPresenter(
}
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
sl.apply {
val theme: AbstractIntSetting = object : AbstractIntSetting {
override val int: Int
@ -261,7 +243,6 @@ class SettingsFragmentPresenter(
}
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
sl.apply {
add(HeaderSetting(R.string.gpu))
add(IntSetting.RENDERER_BACKEND.key)

View File

@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
@ -49,7 +50,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.EmulationViewModel
import org.yuzu.yuzu_emu.overlay.InputOverlay
@ -129,6 +129,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return binding.root
}
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.surfaceEmulation.holder.addCallback(this)
binding.showFpsText.setTextColor(Color.YELLOW)
@ -163,7 +165,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_settings -> {
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null,
SettingsFile.FILE_NAME_CONFIG
Settings.MenuTag.SECTION_ROOT
)
binding.root.findNavController().navigate(action)
true
@ -205,59 +207,80 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
)
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(requireContext())
.windowLayoutInfo(requireActivity())
.collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
}
}
GameIconUtils.loadGameIcon(game, binding.loadingImage)
binding.loadingTitle.text = game.title
binding.loadingTitle.isSelected = true
binding.loadingText.isSelected = true
emulationViewModel.shaderProgress.observe(viewLifecycleOwner) {
if (it > 0 && it != emulationViewModel.totalShaders.value!!) {
binding.loadingProgressIndicator.isIndeterminate = false
if (it < binding.loadingProgressIndicator.max) {
binding.loadingProgressIndicator.progress = it
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(requireContext())
.windowLayoutInfo(requireActivity())
.collect {
updateFoldableLayout(requireActivity() as EmulationActivity, it)
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.shaderProgress.collectLatest {
if (it > 0 && it != emulationViewModel.totalShaders.value) {
binding.loadingProgressIndicator.isIndeterminate = false
if (it == emulationViewModel.totalShaders.value!!) {
binding.loadingText.setText(R.string.loading)
binding.loadingProgressIndicator.isIndeterminate = true
if (it < binding.loadingProgressIndicator.max) {
binding.loadingProgressIndicator.progress = it
}
}
if (it == emulationViewModel.totalShaders.value) {
binding.loadingText.setText(R.string.loading)
binding.loadingProgressIndicator.isIndeterminate = true
}
}
}
}
}
emulationViewModel.totalShaders.observe(viewLifecycleOwner) {
binding.loadingProgressIndicator.max = it
}
emulationViewModel.shaderMessage.observe(viewLifecycleOwner) {
if (it.isNotEmpty()) {
binding.loadingText.text = it
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.totalShaders.collectLatest {
binding.loadingProgressIndicator.max = it
}
}
}
}
emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started ->
if (started) {
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
ViewUtils.showView(binding.surfaceInputOverlay)
ViewUtils.hideView(binding.loadingIndicator)
// Setup overlay
updateShowFpsOverlay()
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.shaderMessage.collectLatest {
if (it.isNotEmpty()) {
binding.loadingText.text = it
}
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.emulationStarted.collectLatest {
if (it) {
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
ViewUtils.showView(binding.surfaceInputOverlay)
ViewUtils.hideView(binding.loadingIndicator)
emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) {
if (it) {
binding.loadingText.setText(R.string.shutting_down)
ViewUtils.showView(binding.loadingIndicator)
ViewUtils.hideView(binding.inputContainer)
ViewUtils.hideView(binding.showFpsText)
// Setup overlay
updateShowFpsOverlay()
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.isEmulationStopping.collectLatest {
if (it) {
binding.loadingText.setText(R.string.shutting_down)
ViewUtils.showView(binding.loadingIndicator)
ViewUtils.hideView(binding.inputContainer)
ViewUtils.hideView(binding.showFpsText)
}
}
}
}
}
}
@ -274,9 +297,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
} else {
if (EmulationMenuSettings.showOverlay &&
emulationViewModel.emulationStarted.value == true
) {
if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
binding.surfaceInputOverlay.post {
binding.surfaceInputOverlay.visibility = View.VISIBLE
}

View File

@ -37,7 +37,6 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.HomeSetting
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
@ -78,7 +77,7 @@ class HomeSettingsFragment : Fragment() {
{
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null,
SettingsFile.FILE_NAME_CONFIG
Settings.MenuTag.SECTION_ROOT
)
binding.root.findNavController().navigate(action)
}
@ -100,7 +99,7 @@ class HomeSettingsFragment : Fragment() {
{
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null,
Settings.SECTION_THEME
Settings.MenuTag.SECTION_THEME
)
binding.root.findNavController().navigate(action)
}

View File

@ -9,8 +9,12 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel
@ -28,21 +32,27 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
.create()
dialog.setCanceledOnTouchOutside(false)
taskViewModel.isComplete.observe(this) { complete ->
if (complete) {
dialog.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
)
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
taskViewModel.isComplete.collect {
if (it) {
dialog.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
)
}
taskViewModel.clear()
}
}
taskViewModel.clear()
}
}
if (taskViewModel.isRunning.value == false) {
if (!taskViewModel.isRunning.value) {
taskViewModel.runTask()
}
return dialog

View File

@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.fragments
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
@ -17,9 +18,13 @@ import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.PreferenceManager
import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler
import kotlinx.coroutines.launch
import java.util.Locale
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
@ -52,6 +57,8 @@ class SearchFragment : Fragment() {
return binding.root
}
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
homeViewModel.setNavigationVisibility(visible = true, animated = false)
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@ -79,21 +86,32 @@ class SearchFragment : Fragment() {
filterAndSearch()
}
gamesViewModel.apply {
searchFocused.observe(viewLifecycleOwner) { searchFocused ->
if (searchFocused) {
focusSearch()
gamesViewModel.setSearchFocused(false)
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
gamesViewModel.searchFocused.collect {
if (it) {
focusSearch()
gamesViewModel.setSearchFocused(false)
}
}
}
}
games.observe(viewLifecycleOwner) { filterAndSearch() }
searchedGames.observe(viewLifecycleOwner) {
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
if (it.isEmpty()) {
binding.noResultsView.visibility = View.VISIBLE
} else {
binding.noResultsView.visibility = View.GONE
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
gamesViewModel.games.collect { filterAndSearch() }
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
gamesViewModel.searchedGames.collect {
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
if (it.isEmpty()) {
binding.noResultsView.visibility = View.VISIBLE
} else {
binding.noResultsView.visibility = View.GONE
}
}
}
}
}
@ -109,7 +127,7 @@ class SearchFragment : Fragment() {
private inner class ScoredGame(val score: Double, val item: Game)
private fun filterAndSearch() {
val baseList = gamesViewModel.games.value!!
val baseList = gamesViewModel.games.value
val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
R.id.chip_recently_played -> {
baseList.filter {

View File

@ -15,10 +15,14 @@ import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.transition.MaterialSharedAxis
import info.debatty.java.stringsimilarity.Cosine
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
@ -79,10 +83,14 @@ class SettingsSearchFragment : Fragment() {
search()
binding.settingsList.smoothScrollToPosition(0)
}
settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
if (it) {
settingsViewModel.setShouldReloadSettingsList(false)
search()
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
settingsViewModel.shouldReloadSettingsList.collect {
if (it) {
settingsViewModel.setShouldReloadSettingsList(false)
search()
}
}
}
}

View File

@ -22,10 +22,14 @@ import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.google.android.material.transition.MaterialFadeThrough
import kotlinx.coroutines.launch
import java.io.File
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
@ -206,10 +210,14 @@ class SetupFragment : Fragment() {
)
}
homeViewModel.shouldPageForward.observe(viewLifecycleOwner) {
if (it) {
pageForward()
homeViewModel.setShouldPageForward(false)
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.shouldPageForward.collect {
if (it) {
pageForward()
homeViewModel.setShouldPageForward(false)
}
}
}
}

View File

@ -3,28 +3,28 @@
package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class EmulationViewModel : ViewModel() {
private val _emulationStarted = MutableLiveData(false)
val emulationStarted: LiveData<Boolean> get() = _emulationStarted
val emulationStarted: StateFlow<Boolean> get() = _emulationStarted
private val _emulationStarted = MutableStateFlow(false)
private val _isEmulationStopping = MutableLiveData(false)
val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping
val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping
private val _isEmulationStopping = MutableStateFlow(false)
private val _shaderProgress = MutableLiveData(0)
val shaderProgress: LiveData<Int> get() = _shaderProgress
val shaderProgress: StateFlow<Int> get() = _shaderProgress
private val _shaderProgress = MutableStateFlow(0)
private val _totalShaders = MutableLiveData(0)
val totalShaders: LiveData<Int> get() = _totalShaders
val totalShaders: StateFlow<Int> get() = _totalShaders
private val _totalShaders = MutableStateFlow(0)
private val _shaderMessage = MutableLiveData("")
val shaderMessage: LiveData<String> get() = _shaderMessage
val shaderMessage: StateFlow<String> get() = _shaderMessage
private val _shaderMessage = MutableStateFlow("")
fun setEmulationStarted(started: Boolean) {
_emulationStarted.postValue(started)
_emulationStarted.value = started
}
fun setIsEmulationStopping(value: Boolean) {
@ -50,10 +50,18 @@ class EmulationViewModel : ViewModel() {
}
fun clear() {
_emulationStarted.value = false
_isEmulationStopping.value = false
_shaderProgress.value = 0
_totalShaders.value = 0
_shaderMessage.value = ""
setEmulationStarted(false)
setIsEmulationStopping(false)
setShaderProgress(0)
setTotalShaders(0)
setShaderMessage("")
}
companion object {
const val KEY_EMULATION_STARTED = "EmulationStarted"
const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
const val KEY_SHADER_PROGRESS = "ShaderProgress"
const val KEY_TOTAL_SHADERS = "TotalShaders"
const val KEY_SHADER_MESSAGE = "ShaderMessage"
}
}

View File

@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager
import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper
@OptIn(ExperimentalSerializationApi::class)
class GamesViewModel : ViewModel() {
private val _games = MutableLiveData<List<Game>>(emptyList())
val games: LiveData<List<Game>> get() = _games
val games: StateFlow<List<Game>> get() = _games
private val _games = MutableStateFlow(emptyList<Game>())
private val _searchedGames = MutableLiveData<List<Game>>(emptyList())
val searchedGames: LiveData<List<Game>> get() = _searchedGames
val searchedGames: StateFlow<List<Game>> get() = _searchedGames
private val _searchedGames = MutableStateFlow(emptyList<Game>())
private val _isReloading = MutableLiveData(false)
val isReloading: LiveData<Boolean> get() = _isReloading
val isReloading: StateFlow<Boolean> get() = _isReloading
private val _isReloading = MutableStateFlow(false)
private val _shouldSwapData = MutableLiveData(false)
val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData
val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData
private val _shouldSwapData = MutableStateFlow(false)
private val _shouldScrollToTop = MutableLiveData(false)
val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop
val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop
private val _shouldScrollToTop = MutableStateFlow(false)
private val _searchFocused = MutableLiveData(false)
val searchFocused: LiveData<Boolean> get() = _searchFocused
val searchFocused: StateFlow<Boolean> get() = _searchFocused
private val _searchFocused = MutableStateFlow(false)
init {
// Ensure keys are loaded so that ROM metadata can be decrypted.
@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {
)
)
_games.postValue(sortedList)
_games.value = sortedList
}
fun setSearchedGames(games: List<Game>) {
_searchedGames.postValue(games)
_searchedGames.value = games
}
fun setShouldSwapData(shouldSwap: Boolean) {
_shouldSwapData.postValue(shouldSwap)
_shouldSwapData.value = shouldSwap
}
fun setShouldScrollToTop(shouldScroll: Boolean) {
_shouldScrollToTop.postValue(shouldScroll)
_shouldScrollToTop.value = shouldScroll
}
fun setSearchFocused(searchFocused: Boolean) {
_searchFocused.postValue(searchFocused)
_searchFocused.value = searchFocused
}
fun reloadGames(directoryChanged: Boolean) {
if (isReloading.value == true) {
if (isReloading.value) {
return
}
_isReloading.postValue(true)
_isReloading.value = true
viewModelScope.launch {
withContext(Dispatchers.IO) {
NativeLibrary.resetRomMetadata()
setGames(GameHelper.getGames())
_isReloading.postValue(false)
_isReloading.value = false
if (directoryChanged) {
setShouldSwapData(true)

View File

@ -3,8 +3,8 @@
package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
data class HomeSetting(
val titleId: Int,
@ -14,5 +14,5 @@ data class HomeSetting(
val isEnabled: () -> Boolean = { true },
val disabledTitleId: Int = 0,
val disabledMessageId: Int = 0,
val details: LiveData<String> = MutableLiveData("")
val details: StateFlow<String> = MutableStateFlow("")
)

View File

@ -5,47 +5,43 @@ package org.yuzu.yuzu_emu.model
import android.net.Uri
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper
class HomeViewModel : ViewModel() {
private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>()
val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible
val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
private val _navigationVisible = MutableStateFlow(Pair(false, false))
private val _statusBarShadeVisible = MutableLiveData(true)
val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible
val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible
private val _statusBarShadeVisible = MutableStateFlow(true)
private val _shouldPageForward = MutableLiveData(false)
val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward
val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
private val _shouldPageForward = MutableStateFlow(false)
private val _gamesDir = MutableLiveData(
val gamesDir: StateFlow<String> get() = _gamesDir
private val _gamesDir = MutableStateFlow(
Uri.parse(
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
.getString(GameHelper.KEY_GAME_PATH, "")
).path ?: ""
)
val gamesDir: LiveData<String> get() = _gamesDir
var navigatedToSetup = false
init {
_navigationVisible.value = Pair(false, false)
}
fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
if (_navigationVisible.value?.first == visible) {
if (navigationVisible.value.first == visible) {
return
}
_navigationVisible.value = Pair(visible, animated)
}
fun setStatusBarShadeVisibility(visible: Boolean) {
if (_statusBarShadeVisible.value == visible) {
if (statusBarShadeVisible.value == visible) {
return
}
_statusBarShadeVisible.value = visible

View File

@ -3,48 +3,43 @@
package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
class SettingsViewModel : ViewModel() {
var game: Game? = null
var shouldSave = false
var clickedItem: SettingsItem? = null
private val _toolbarTitle = MutableLiveData("")
val toolbarTitle: LiveData<String> get() = _toolbarTitle
val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
private val _shouldRecreate = MutableStateFlow(false)
private val _shouldRecreate = MutableLiveData(false)
val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate
val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack
private val _shouldNavigateBack = MutableStateFlow(false)
private val _shouldNavigateBack = MutableLiveData(false)
val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack
val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog
private val _shouldShowResetSettingsDialog = MutableStateFlow(false)
private val _shouldShowResetSettingsDialog = MutableLiveData(false)
val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog
val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
private val _shouldReloadSettingsList = MutableStateFlow(false)
private val _shouldReloadSettingsList = MutableLiveData(false)
val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList
val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch
private val _isUsingSearch = MutableStateFlow(false)
private val _isUsingSearch = MutableLiveData(false)
val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
val sliderProgress: StateFlow<Int> get() = _sliderProgress
private val _sliderProgress = MutableStateFlow(-1)
val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1)
val sliderTextValue: StateFlow<String> get() = _sliderTextValue
private val _sliderTextValue = MutableStateFlow("")
val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "")
val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
fun setToolbarTitle(value: String) {
_toolbarTitle.value = value
}
val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
private val _adapterItemChanged = MutableStateFlow(-1)
fun setShouldRecreate(value: Boolean) {
_shouldRecreate.value = value
@ -67,8 +62,8 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
}
fun setSliderTextValue(value: Float, units: String) {
savedStateHandle[KEY_SLIDER_PROGRESS] = value
savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format(
_sliderProgress.value = value.toInt()
_sliderTextValue.value = String.format(
YuzuApplication.appContext.getString(R.string.value_with_units),
value.toInt().toString(),
units
@ -76,21 +71,15 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
}
fun setSliderProgress(value: Float) {
savedStateHandle[KEY_SLIDER_PROGRESS] = value
_sliderProgress.value = value.toInt()
}
fun setAdapterItemChanged(value: Int) {
savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value
_adapterItemChanged.value = value
}
fun clear() {
game = null
shouldSave = false
}
companion object {
const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
const val KEY_SLIDER_PROGRESS = "SliderProgress"
const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
}
}

View File

@ -3,29 +3,25 @@
package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class TaskViewModel : ViewModel() {
private val _result = MutableLiveData<Any>()
val result: LiveData<Any> = _result
val result: StateFlow<Any> get() = _result
private val _result = MutableStateFlow(Any())
private val _isComplete = MutableLiveData<Boolean>()
val isComplete: LiveData<Boolean> = _isComplete
val isComplete: StateFlow<Boolean> get() = _isComplete
private val _isComplete = MutableStateFlow(false)
private val _isRunning = MutableLiveData<Boolean>()
val isRunning: LiveData<Boolean> = _isRunning
val isRunning: StateFlow<Boolean> get() = _isRunning
private val _isRunning = MutableStateFlow(false)
lateinit var task: () -> Any
init {
clear()
}
fun clear() {
_result.value = Any()
_isComplete.value = false
@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() {
}
fun runTask() {
if (_isRunning.value == true) {
if (isRunning.value) {
return
}
_isRunning.value = true
viewModelScope.launch(Dispatchers.IO) {
val res = task()
_result.postValue(res)
_isComplete.postValue(true)
_result.value = res
_isComplete.value = true
_isRunning.value = false
}
}
}

View File

@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.ui
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.color.MaterialColors
import com.google.android.material.transition.MaterialFadeThrough
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.GameAdapter
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
@ -44,6 +49,8 @@ class GamesFragment : Fragment() {
return binding.root
}
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
homeViewModel.setNavigationVisibility(visible = true, animated = false)
@ -80,37 +87,48 @@ class GamesFragment : Fragment() {
if (_binding == null) {
return@post
}
binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!!
binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value
}
}
gamesViewModel.apply {
// Watch for when we get updates to any of our games lists
isReloading.observe(viewLifecycleOwner) { isReloading ->
binding.swipeRefresh.isRefreshing = isReloading
}
games.observe(viewLifecycleOwner) {
(binding.gridGames.adapter as GameAdapter).submitList(it)
if (it.isEmpty()) {
binding.noticeText.visibility = View.VISIBLE
} else {
binding.noticeText.visibility = View.GONE
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it }
}
}
shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
if (shouldSwapData) {
(binding.gridGames.adapter as GameAdapter).submitList(
gamesViewModel.games.value!!
)
gamesViewModel.setShouldSwapData(false)
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
gamesViewModel.games.collect {
(binding.gridGames.adapter as GameAdapter).submitList(it)
if (it.isEmpty()) {
binding.noticeText.visibility = View.VISIBLE
} else {
binding.noticeText.visibility = View.GONE
}
}
}
}
// Check if the user reselected the games menu item and then scroll to top of the list
shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll ->
if (shouldScroll) {
scrollToTop()
gamesViewModel.setShouldScrollToTop(false)
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
gamesViewModel.shouldSwapData.collect {
if (it) {
(binding.gridGames.adapter as GameAdapter).submitList(
gamesViewModel.games.value
)
gamesViewModel.setShouldSwapData(false)
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
gamesViewModel.shouldScrollToTop.collect {
if (it) {
scrollToTop()
gamesViewModel.setShouldScrollToTop(false)
}
}
}
}
}

View File

@ -19,7 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
@ -40,7 +42,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.GamesViewModel
@ -107,7 +108,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
R.id.homeSettingsFragment -> {
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null,
SettingsFile.FILE_NAME_CONFIG
Settings.MenuTag.SECTION_ROOT
)
navHostFragment.navController.navigate(action)
}
@ -115,16 +116,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
// Prevents navigation from being drawn for a short time on recreation if set to hidden
if (!homeViewModel.navigationVisible.value?.first!!) {
if (!homeViewModel.navigationVisible.value.first) {
binding.navigationView.visibility = View.INVISIBLE
binding.statusBarShade.visibility = View.INVISIBLE
}
homeViewModel.navigationVisible.observe(this) {
showNavigation(it.first, it.second)
}
homeViewModel.statusBarShadeVisible.observe(this) { visible ->
showStatusBarShade(visible)
lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
}
}
}
// Dismiss previous notifications (should not happen unless a crash occurred)

View File

@ -6,9 +6,11 @@ package org.yuzu.yuzu_emu.utils
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.widget.ImageView
import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toDrawable
import coil.ImageLoader
import coil.decode.DataSource
import coil.executeBlocking
import coil.fetch.DrawableResult
import coil.fetch.FetchResult
import coil.fetch.Fetcher
@ -74,4 +76,13 @@ object GameIconUtils {
.build()
imageLoader.enqueue(request)
}
fun getGameIcon(game: Game): Bitmap {
val request = ImageRequest.Builder(YuzuApplication.appContext)
.data(game)
.error(R.drawable.default_icon)
.build()
return imageLoader.executeBlocking(request)
.drawable!!.toBitmap(config = Bitmap.Config.ARGB_8888)
}
}

View File

@ -262,9 +262,6 @@ public:
Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
std::scoped_lock lock(m_mutex);
// Loads the configuration.
Config{};
// Create the render window.
m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
m_vulkan_library);
@ -330,12 +327,13 @@ public:
m_system.ShutdownMainProcess();
m_detached_tasks.WaitForAllTasks();
m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
m_window.reset();
OnEmulationStopped(Core::SystemResultStatus::Success);
return;
}
// Tear down the render window.
m_window.reset();
OnEmulationStopped(m_load_result);
}
void PauseEmulation() {
@ -672,18 +670,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) {
Settings::values.audio_muted = true;
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) {
Settings::values.audio_muted = false;
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) {
return static_cast<jboolean>(Settings::values.audio_muted.GetValue());
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
return EmulationSession::GetInstance().IsHandheldOnly();
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="@android:color/white" />
</item>
<item android:id="@+id/shortcut_foreground">
<bitmap android:src="@drawable/default_icon" />
</item>
</layer-list>

View File

@ -27,7 +27,7 @@
app:nullable="true" />
<argument
android:name="menuTag"
app:argType="string" />
app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
</activity>
<action

View File

@ -82,7 +82,7 @@
app:nullable="true" />
<argument
android:name="menuTag"
app:argType="string" />
app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
</activity>
<action

View File

@ -10,7 +10,7 @@
android:label="SettingsFragment">
<argument
android:name="menuTag"
app:argType="string" />
app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
<argument
android:name="game"
app:argType="org.yuzu.yuzu_emu.model.Game"

View File

@ -12,6 +12,7 @@
<dimen name="spacing_refresh_end">72dp</dimen>
<dimen name="menu_width">256dp</dimen>
<dimen name="card_width">165dp</dimen>
<dimen name="icon_inset">24dp</dimen>
<dimen name="dialog_margin">20dp</dimen>
<dimen name="elevated_app_bar">3dp</dimen>

View File

@ -112,7 +112,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Service, NCM) \
SUB(Service, NFC) \
SUB(Service, NFP) \
SUB(Service, NGCT) \
SUB(Service, NGC) \
SUB(Service, NIFM) \
SUB(Service, NIM) \
SUB(Service, NOTIF) \

View File

@ -80,7 +80,7 @@ enum class Class : u8 {
Service_NCM, ///< The NCM service
Service_NFC, ///< The NFC (Near-field communication) service
Service_NFP, ///< The NFP service
Service_NGCT, ///< The NGCT (No Good Content for Terra) service
Service_NGC, ///< The NGC (No Good Content) service
Service_NIFM, ///< The NIFM (Network interface) service
Service_NIM, ///< The NIM service
Service_NOTIF, ///< The NOTIF (Notification) service

View File

@ -19,8 +19,8 @@
namespace Common {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) {
cv.wait(lock, token, std::move(pred));
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
cv.wait(lk, token, std::move(pred));
}
template <typename Rep, typename Period>
@ -332,13 +332,17 @@ private:
namespace Common {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) {
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred pred) {
if (token.stop_requested()) {
return;
}
std::stop_callback callback(token, [&] { cv.notify_all(); });
cv.wait(lock, [&] { return pred() || token.stop_requested(); });
std::stop_callback callback(token, [&] {
{ std::scoped_lock lk2{*lk.mutex()}; }
cv.notify_all();
});
cv.wait(lk, [&] { return pred() || token.stop_requested(); });
}
template <typename Rep, typename Period>
@ -353,8 +357,10 @@ bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep,
std::stop_callback cb(token, [&] {
// Wake up the waiting thread.
std::unique_lock lk{m};
stop_requested = true;
{
std::scoped_lock lk{m};
stop_requested = true;
}
cv.notify_one();
});

View File

@ -627,8 +627,8 @@ add_library(core STATIC
hle/service/nfp/nfp_interface.h
hle/service/nfp/nfp_result.h
hle/service/nfp/nfp_types.h
hle/service/ngct/ngct.cpp
hle/service/ngct/ngct.h
hle/service/ngc/ngc.cpp
hle/service/ngc/ngc.h
hle/service/nifm/nifm.cpp
hle/service/nifm/nifm.h
hle/service/nim/nim.cpp

View File

@ -0,0 +1,150 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ngc/ngc.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
namespace Service::NGC {
class NgctServiceImpl final : public ServiceFramework<NgctServiceImpl> {
public:
explicit NgctServiceImpl(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NgctServiceImpl::Match, "Match"},
{1, &NgctServiceImpl::Filter, "Filter"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void Match(HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(buffer.data()), buffer.size());
LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
// Return false since we don't censor anything
rb.Push(false);
}
void Filter(HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(buffer.data()), buffer.size());
LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
// Return the same string since we don't censor anything
ctx.WriteBuffer(buffer);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
};
class NgcServiceImpl final : public ServiceFramework<NgcServiceImpl> {
public:
explicit NgcServiceImpl(Core::System& system_) : ServiceFramework(system_, "ngc:u") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NgcServiceImpl::GetContentVersion, "GetContentVersion"},
{1, &NgcServiceImpl::Check, "Check"},
{2, &NgcServiceImpl::Mask, "Mask"},
{3, &NgcServiceImpl::Reload, "Reload"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
static constexpr u32 NgcContentVersion = 1;
// This is nn::ngc::detail::ProfanityFilterOption
struct ProfanityFilterOption {
INSERT_PADDING_BYTES_NOINIT(0x20);
};
static_assert(sizeof(ProfanityFilterOption) == 0x20,
"ProfanityFilterOption has incorrect size");
void GetContentVersion(HLERequestContext& ctx) {
LOG_INFO(Service_NGC, "(STUBBED) called");
// This calls nn::ngc::ProfanityFilter::GetContentVersion
const u32 version = NgcContentVersion;
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(version);
}
void Check(HLERequestContext& ctx) {
LOG_INFO(Service_NGC, "(STUBBED) called");
struct InputParameters {
u32 flags;
ProfanityFilterOption option;
};
IPC::RequestParser rp{ctx};
[[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
[[maybe_unused]] const auto input = ctx.ReadBuffer(0);
// This calls nn::ngc::ProfanityFilter::CheckProfanityWords
const u32 out_flags = 0;
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(out_flags);
}
void Mask(HLERequestContext& ctx) {
LOG_INFO(Service_NGC, "(STUBBED) called");
struct InputParameters {
u32 flags;
ProfanityFilterOption option;
};
IPC::RequestParser rp{ctx};
[[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
const auto input = ctx.ReadBuffer(0);
// This calls nn::ngc::ProfanityFilter::MaskProfanityWordsInText
const u32 out_flags = 0;
ctx.WriteBuffer(input);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(out_flags);
}
void Reload(HLERequestContext& ctx) {
LOG_INFO(Service_NGC, "(STUBBED) called");
// This reloads the database.
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system));
server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system));
ServerManager::RunServer(std::move(server_manager));
}
} // namespace Service::NGC

View File

@ -7,8 +7,8 @@ namespace Core {
class System;
}
namespace Service::NGCT {
namespace Service::NGC {
void LoopProcess(Core::System& system);
} // namespace Service::NGCT
} // namespace Service::NGC

View File

@ -1,62 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ngct/ngct.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
namespace Service::NGCT {
class IService final : public ServiceFramework<IService> {
public:
explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IService::Match, "Match"},
{1, &IService::Filter, "Filter"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void Match(HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(buffer.data()), buffer.size());
LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
// Return false since we don't censor anything
rb.Push(false);
}
void Filter(HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(buffer.data()), buffer.size());
LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
// Return the same string since we don't censor anything
ctx.WriteBuffer(buffer);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system));
ServerManager::RunServer(std::move(server_manager));
}
} // namespace Service::NGCT

View File

@ -43,7 +43,7 @@
#include "core/hle/service/ncm/ncm.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/ngct/ngct.h"
#include "core/hle/service/ngc/ngc.h"
#include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/nim/nim.h"
#include "core/hle/service/npns/npns.h"
@ -257,7 +257,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("ngct", [&] { NGCT::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); });

View File

@ -204,9 +204,7 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
if (def.count > 1) {
throw NotImplementedException("Indirect texture sample");
}
const Id sampler_id{def.id};
const Id id{ctx.OpLoad(ctx.sampled_texture_buffer_type, sampler_id)};
return ctx.OpImage(ctx.image_buffer_type, id);
return ctx.OpLoad(ctx.image_buffer_type, def.id);
} else {
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
if (def.count > 1) {

View File

@ -1247,9 +1247,8 @@ void EmitContext::DefineTextureBuffers(const Info& info, u32& binding) {
}
const spv::ImageFormat format{spv::ImageFormat::Unknown};
image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format);
sampled_texture_buffer_type = TypeSampledImage(image_buffer_type);
const Id type{TypePointer(spv::StorageClass::UniformConstant, sampled_texture_buffer_type)};
const Id type{TypePointer(spv::StorageClass::UniformConstant, image_buffer_type)};
texture_buffers.reserve(info.texture_buffer_descriptors.size());
for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) {
if (desc.count != 1) {

View File

@ -206,7 +206,6 @@ public:
Id output_u32{};
Id image_buffer_type{};
Id sampled_texture_buffer_type{};
Id image_u32{};
std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{};

View File

@ -719,6 +719,7 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
return nullptr;
}
const auto& image_map_ids = it->second;
boost::container::small_vector<const ImageBase*, 4> valid_images;
for (const ImageMapId map_id : image_map_ids) {
const ImageMapView& map = slot_map_views[map_id];
const ImageBase& image = slot_images[map.image_id];
@ -728,8 +729,20 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
if (image.image_view_ids.empty()) {
continue;
}
return &slot_image_views[image.image_view_ids.at(0)];
valid_images.push_back(&image);
}
if (valid_images.size() == 1) [[likely]] {
return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
}
if (valid_images.size() > 0) [[unlikely]] {
std::ranges::sort(valid_images, [](const auto* a, const auto* b) {
return a->modification_tick > b->modification_tick;
});
return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
}
return nullptr;
}