diff options
Diffstat (limited to 'src')
32 files changed, 884 insertions, 367 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index fe613d339..a637db78a 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -9,6 +9,7 @@ plugins { id("org.jetbrains.kotlin.android") id("kotlin-parcelize") kotlin("plugin.serialization") version "1.8.21" + id("androidx.navigation.safeargs.kotlin") } /** diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index 55f62b4b9..a6f87fc2e 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml @@ -53,8 +53,8 @@ SPDX-License-Identifier: GPL-3.0-or-later <activity android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" android:theme="@style/Theme.Yuzu.Main" - android:launchMode="singleTop" - android:screenOrientation="userLandscape" + android:supportsPictureInPicture="true" + android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" android:exported="true"> <intent-filter> diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 4be9ade14..22f0a2646 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -283,6 +283,11 @@ object NativeLibrary { external fun isRunning(): Boolean /** + * Returns true if emulation is paused. + */ + external fun isPaused(): Boolean + + /** * Returns the performance stats for the current game */ external fun getPerfStats(): DoubleArray diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 20a0394f5..5ca519f0a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -4,14 +4,23 @@ package org.yuzu.yuzu_emu.activities import android.app.Activity +import android.app.PendingIntent +import android.app.PictureInPictureParams +import android.app.RemoteAction +import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.content.IntentFilter +import android.content.res.Configuration import android.graphics.Rect +import android.graphics.drawable.Icon import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager +import android.os.Build import android.os.Bundle +import android.util.Rational import android.view.InputDevice import android.view.KeyEvent import android.view.MotionEvent @@ -23,30 +32,27 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.window.layout.WindowInfoTracker -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +import androidx.navigation.fragment.NavHostFragment import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding +import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting +import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel -import org.yuzu.yuzu_emu.fragments.EmulationFragment import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.utils.ControllerMappingHelper import org.yuzu.yuzu_emu.utils.ForegroundService import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.NfcReader -import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable import org.yuzu.yuzu_emu.utils.ThemeHelper import kotlin.math.roundToInt class EmulationActivity : AppCompatActivity(), SensorEventListener { + private lateinit var binding: ActivityEmulationBinding + private var controllerMappingHelper: ControllerMappingHelper? = null var isActivityRecreated = false - private var emulationFragment: EmulationFragment? = null private lateinit var nfcReader: NfcReader private lateinit var inputHandler: InputHandler @@ -55,7 +61,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { private var motionTimestamp: Long = 0 private var flipMotionOrientation: Boolean = false - private lateinit var game: Game + private val actionPause = "ACTION_EMULATOR_PAUSE" + private val actionPlay = "ACTION_EMULATOR_PLAY" private val settingsViewModel: SettingsViewModel by viewModels() @@ -70,47 +77,31 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { settingsViewModel.settings.loadSettings() super.onCreate(savedInstanceState) - if (savedInstanceState == null) { - // Get params we were passed - game = intent.parcelable(EXTRA_SELECTED_GAME)!! - isActivityRecreated = false - } else { - isActivityRecreated = true - restoreState(savedInstanceState) - } + + binding = ActivityEmulationBinding.inflate(layoutInflater) + setContentView(binding.root) + + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment + val navController = navHostFragment.navController + navController + .setGraph(R.navigation.emulation_navigation, intent.extras) + + isActivityRecreated = savedInstanceState != null + controllerMappingHelper = ControllerMappingHelper() // Set these options now so that the SurfaceView the game renders into is the right size. enableFullscreenImmersive() - setContentView(R.layout.activity_emulation) window.decorView.setBackgroundColor(getColor(android.R.color.black)) - // Find or create the EmulationFragment - emulationFragment = - supportFragmentManager.findFragmentById(R.id.frame_emulation_fragment) as EmulationFragment? - if (emulationFragment == null) { - emulationFragment = EmulationFragment.newInstance(game) - supportFragmentManager.beginTransaction() - .add(R.id.frame_emulation_fragment, emulationFragment!!) - .commit() - } - title = game.title - nfcReader = NfcReader(this) nfcReader.initialize() inputHandler = InputHandler() inputHandler.initialize() - lifecycleScope.launch(Dispatchers.Main) { - lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - WindowInfoTracker.getOrCreate(this@EmulationActivity) - .windowLayoutInfo(this@EmulationActivity) - .collect { emulationFragment?.updateCurrentLayout(this@EmulationActivity, it) } - } - } - // Start a foreground service to prevent the app from getting killed in the background val startIntent = Intent(this, ForegroundService::class.java) startForegroundService(startIntent) @@ -143,6 +134,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { super.onResume() nfcReader.startScanning() startMotionSensorListener() + + buildPictureInPictureParams() } override fun onPause() { @@ -151,17 +144,22 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { stopMotionSensorListener() } + override fun onUserLeaveHint() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + if (BooleanSetting.PICTURE_IN_PICTURE.boolean && !isInPictureInPictureMode) { + val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() + .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() + enterPictureInPictureMode(pictureInPictureParamsBuilder.build()) + } + } + } + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) nfcReader.onNewIntent(intent) } - override fun onSaveInstanceState(outState: Bundle) { - outState.putParcelable(EXTRA_SELECTED_GAME, game) - super.onSaveInstanceState(outState) - } - override fun dispatchKeyEvent(event: KeyEvent): Boolean { if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD @@ -248,10 +246,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { override fun onAccuracyChanged(sensor: Sensor, i: Int) {} - private fun restoreState(savedInstanceState: Bundle) { - game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!! - } - private fun enableFullscreenImmersive() { WindowCompat.setDecorFitsSystemWindows(window, false) @@ -262,6 +256,96 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { } } + private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder(): PictureInPictureParams.Builder { + val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) { + 0 -> Rational(16, 9) + 1 -> Rational(4, 3) + 2 -> Rational(21, 9) + 3 -> Rational(16, 10) + else -> null // Best fit + } + return this.apply { aspectRatio?.let { setAspectRatio(it) } } + } + + private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder(): PictureInPictureParams.Builder { + val pictureInPictureActions: MutableList<RemoteAction> = mutableListOf() + val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + + if (NativeLibrary.isPaused()) { + val playIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_play) + val playPendingIntent = PendingIntent.getBroadcast( + this@EmulationActivity, + R.drawable.ic_pip_play, + Intent(actionPlay), + pendingFlags + ) + val playRemoteAction = RemoteAction( + playIcon, + getString(R.string.play), + getString(R.string.play), + playPendingIntent + ) + pictureInPictureActions.add(playRemoteAction) + } else { + val pauseIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_pause) + val pausePendingIntent = PendingIntent.getBroadcast( + this@EmulationActivity, + R.drawable.ic_pip_pause, + Intent(actionPause), + pendingFlags + ) + val pauseRemoteAction = RemoteAction( + pauseIcon, + getString(R.string.pause), + getString(R.string.pause), + pausePendingIntent + ) + pictureInPictureActions.add(pauseRemoteAction) + } + + return this.apply { setActions(pictureInPictureActions) } + } + + fun buildPictureInPictureParams() { + val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() + .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + pictureInPictureParamsBuilder.setAutoEnterEnabled(BooleanSetting.PICTURE_IN_PICTURE.boolean) + } + setPictureInPictureParams(pictureInPictureParamsBuilder.build()) + } + + private var pictureInPictureReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent) { + if (intent.action == actionPlay) { + if (NativeLibrary.isPaused()) NativeLibrary.unPauseEmulation() + } else if (intent.action == actionPause) { + if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation() + } + buildPictureInPictureParams() + } + } + + override fun onPictureInPictureModeChanged( + isInPictureInPictureMode: Boolean, + newConfig: Configuration + ) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) + if (isInPictureInPictureMode) { + IntentFilter().apply { + addAction(actionPause) + addAction(actionPlay) + }.also { + registerReceiver(pictureInPictureReceiver, it) + } + } else { + try { + unregisterReceiver(pictureInPictureReceiver) + } catch (ignored : Exception) { + } + } + } + private fun startMotionSensorListener() { val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index 7f9e2e2d4..83d08841b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -16,6 +16,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.navigation.findNavController import androidx.preference.PreferenceManager import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil @@ -23,6 +24,7 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import coil.load import kotlinx.coroutines.launch +import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication @@ -78,7 +80,8 @@ class GameAdapter(private val activity: AppCompatActivity) : ) .apply() - EmulationActivity.launch(activity, holder.game) + val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game) + view.findNavController().navigate(action) } inner class GameViewHolder(val binding: CardGameBinding) : diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 3dfd66779..63b4df273 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -8,6 +8,7 @@ enum class BooleanSetting( override val section: String, override val defaultValue: Boolean ) : AbstractBooleanSetting { + PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true), USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); override var boolean: Boolean = defaultValue @@ -27,6 +28,7 @@ enum class BooleanSetting( companion object { private val NOT_RUNTIME_EDITABLE = listOf( + PICTURE_IN_PICTURE, USE_CUSTOM_RTC ) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index fa84f94f5..4427a7d9d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt @@ -93,6 +93,11 @@ enum class IntSetting( Settings.SECTION_RENDERER, 0 ), + RENDERER_SCREEN_LAYOUT( + "screen_layout", + Settings.SECTION_RENDERER, + Settings.LayoutOption_MobileLandscape + ), RENDERER_ASPECT_RATIO( "aspect_ratio", Settings.SECTION_RENDERER, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index 8df20b928..6bcb7bee0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt @@ -133,7 +133,6 @@ class Settings { const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" - const val PREF_MENU_SETTINGS_LANDSCAPE = "EmulationMenuSettings_LandscapeScreenLayout" const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" @@ -144,6 +143,10 @@ class Settings { private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap() + const val LayoutOption_Unspecified = 0 + const val LayoutOption_MobilePortrait = 4 + const val LayoutOption_MobileLandscape = 5 + init { configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] = listOf( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt index 72e2cce2a..da7062b87 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt @@ -16,6 +16,7 @@ import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import android.view.ViewGroup.MarginLayoutParams import androidx.activity.OnBackPressedCallback +import androidx.activity.result.ActivityResultLauncher import androidx.core.view.updatePadding import com.google.android.material.color.MaterialColors import org.yuzu.yuzu_emu.NativeLibrary @@ -239,5 +240,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { settings.putExtra(ARG_GAME_ID, gameId) context.startActivity(settings) } + + fun launch( + context: Context, + launcher: ActivityResultLauncher<Intent>, + menuTag: String?, + gameId: String? + ) { + val settings = Intent(context, SettingsActivity::class.java) + settings.putExtra(ARG_MENU_TAG, menuTag) + settings.putExtra(ARG_GAME_ID, gameId) + launcher.launch(settings) + } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 1ceaa6fb4..b611389a1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -166,6 +166,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.CPU_ACCURACY.defaultValue ) ) + add( + SwitchSetting( + BooleanSetting.PICTURE_IN_PICTURE, + R.string.picture_in_picture, + R.string.picture_in_picture_description, + BooleanSetting.PICTURE_IN_PICTURE.key, + BooleanSetting.PICTURE_IN_PICTURE.defaultValue + ) + ) } } @@ -285,6 +294,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SingleChoiceSetting( + IntSetting.RENDERER_SCREEN_LAYOUT, + R.string.renderer_screen_layout, + 0, + R.array.rendererScreenLayoutNames, + R.array.rendererScreenLayoutValues, + IntSetting.RENDERER_SCREEN_LAYOUT.key, + IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue + ) + ) + add( + SingleChoiceSetting( IntSetting.RENDERER_ASPECT_RATIO, R.string.renderer_aspect_ratio, 0, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 9523381cd..4b2305892 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -7,30 +7,39 @@ import android.annotation.SuppressLint import android.app.AlertDialog import android.content.Context import android.content.DialogInterface +import android.content.Intent import android.content.SharedPreferences import android.content.pm.ActivityInfo -import android.content.res.Resources +import android.content.res.Configuration import android.graphics.Color import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Rational -import android.util.TypedValue import android.view.* import android.widget.TextView import androidx.activity.OnBackPressedCallback +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.PopupMenu import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.Insets import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat -import androidx.core.view.updatePadding +import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.navArgs import androidx.preference.PreferenceManager import androidx.window.layout.FoldingFeature +import androidx.window.layout.WindowInfoTracker 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.launch import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication @@ -41,9 +50,8 @@ 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.ui.SettingsActivity import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile -import org.yuzu.yuzu_emu.model.Game +import org.yuzu.yuzu_emu.overlay.InputOverlay import org.yuzu.yuzu_emu.utils.* -import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable class EmulationFragment : Fragment(), SurfaceHolder.Callback { private lateinit var preferences: SharedPreferences @@ -54,13 +62,42 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { private var _binding: FragmentEmulationBinding? = null private val binding get() = _binding!! - private lateinit var game: Game + val args by navArgs<EmulationFragmentArgs>() + + private var isInFoldableLayout = false + + private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent> override fun onAttach(context: Context) { super.onAttach(context) if (context is EmulationActivity) { emulationActivity = context NativeLibrary.setEmulationActivity(context) + + lifecycleScope.launch(Dispatchers.Main) { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + WindowInfoTracker.getOrCreate(context) + .windowLayoutInfo(context) + .collect { updateFoldableLayout(context, it) } + } + } + + onReturnFromSettings = context.activityResultRegistry.register( + "SettingsResult", ActivityResultContracts.StartActivityForResult() + ) { + 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) + } + ) + emulationActivity?.buildPictureInPictureParams() + updateScreenLayout() + } } else { throw IllegalStateException("EmulationFragment must have EmulationActivity parent") } @@ -75,8 +112,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { // So this fragment doesn't restart on configuration changes; i.e. rotation. retainInstance = true preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - game = requireArguments().parcelable(EmulationActivity.EXTRA_SELECTED_GAME)!! - emulationState = EmulationState(game.path) + emulationState = EmulationState(args.game.path) } /** @@ -100,7 +136,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { updateShowFpsOverlay() binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = - game.title + args.game.title binding.inGameMenu.setNavigationItemSelectedListener { when (it.itemId) { R.id.menu_pause_emulation -> { @@ -125,7 +161,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } R.id.menu_settings -> { - SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") + SettingsActivity.launch( + requireContext(), + onReturnFromSettings, + SettingsFile.FILE_NAME_CONFIG, + "" + ) true } @@ -153,6 +194,40 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open() } }) + + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + WindowInfoTracker.getOrCreate(requireContext()) + .windowLayoutInfo(requireActivity()) + .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) } + } + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + if (emulationActivity?.isInPictureInPictureMode == true) { + if (binding.drawerLayout.isOpen) { + binding.drawerLayout.close() + } + if (EmulationMenuSettings.showOverlay) { + binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false } + } + } else { + if (EmulationMenuSettings.showOverlay) { + binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true } + } + if (!isInFoldableLayout) { + if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { + binding.surfaceInputOverlay.orientation = InputOverlay.PORTRAIT + } else { + binding.surfaceInputOverlay.orientation = InputOverlay.LANDSCAPE + } + } + if (!binding.surfaceInputOverlay.isInEditMode) { + refreshInputOverlay() + } + } } override fun onResume() { @@ -172,6 +247,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } ) + updateScreenLayout() + emulationState.run(emulationActivity!!.isActivityRecreated) } @@ -231,31 +308,50 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } - private val Number.toPx get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics).toInt() + @SuppressLint("SourceLockedOrientationActivity") + private fun updateScreenLayout() { + emulationActivity?.let { + it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { + Settings.LayoutOption_MobileLandscape -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE + Settings.LayoutOption_MobilePortrait -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT + Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE + } + } + onConfigurationChanged(resources.configuration) + } - fun updateCurrentLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) { + private fun updateFoldableLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) { val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let { if (it.isSeparating) { emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) { - binding.surfaceEmulation.layoutParams.height = it.bounds.top + // Restrict emulation and overlays to the top of the screen + binding.emulationContainer.layoutParams.height = it.bounds.top + binding.overlayContainer.layoutParams.height = it.bounds.top + // Restrict input and menu drawer to the bottom of the screen + binding.inputContainer.layoutParams.height = it.bounds.bottom binding.inGameMenu.layoutParams.height = it.bounds.bottom - binding.overlayContainer.layoutParams.height = it.bounds.bottom - 48.toPx - binding.overlayContainer.updatePadding(0, 0, 0, 24.toPx) + + isInFoldableLayout = true + binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE + refreshInputOverlay() } } it.isSeparating } ?: false if (!isFolding) { - binding.surfaceEmulation.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT - binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT + binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT + binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT - binding.overlayContainer.updatePadding(0, 0, 0, 0) - emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE + binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT + isInFoldableLayout = false + updateScreenLayout() } - binding.surfaceInputOverlay.requestLayout() - binding.inGameMenu.requestLayout() + binding.emulationContainer.requestLayout() + binding.inputContainer.requestLayout() binding.overlayContainer.requestLayout() + binding.inGameMenu.requestLayout() } override fun surfaceCreated(holder: SurfaceHolder) { @@ -385,7 +481,19 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { popup.show() } + @SuppressLint("SourceLockedOrientationActivity") private fun startConfiguringControls() { + // Lock the current orientation to prevent editing inconsistencies + if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) { + emulationActivity?.let { + it.requestedOrientation = + if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { + ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT + } else { + ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE + } + } + } binding.doneControlConfig.visibility = View.VISIBLE binding.surfaceInputOverlay.setIsInEditMode(true) } @@ -393,6 +501,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { private fun stopConfiguringControls() { binding.doneControlConfig.visibility = View.GONE binding.surfaceInputOverlay.setIsInEditMode(false) + // Unlock the orientation if it was locked for editing + if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) { + emulationActivity?.let { + it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + } + } } @SuppressLint("SetTextI18n") @@ -601,13 +715,5 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { companion object { private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!) - - fun newInstance(game: Game): EmulationFragment { - val args = Bundle() - args.putParcelable(EmulationActivity.EXTRA_SELECTED_GAME, game) - val fragment = EmulationFragment() - fragment.arguments = args - return fragment - } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt index aa424c768..d12d08e9f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt @@ -51,12 +51,14 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context private lateinit var windowInsets: WindowInsets + var orientation = LANDSCAPE + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) windowInsets = rootWindowInsets - if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) { + if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) { defaultOverlay() } @@ -233,10 +235,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context val fingerPositionX = event.getX(pointerIndex).toInt() val fingerPositionY = event.getY(pointerIndex).toInt() - // TODO: Provide support for portrait layout - //val orientation = - // if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else "" - for (button in overlayButtons) { // Determine the button state to apply based on the MotionEvent action flag. when (event.action and MotionEvent.ACTION_MASK) { @@ -266,7 +264,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context buttonBeingConfigured!!.buttonId, buttonBeingConfigured!!.bounds.centerX(), buttonBeingConfigured!!.bounds.centerY(), - "" + orientation ) buttonBeingConfigured = null } @@ -299,7 +297,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context dpadBeingConfigured!!.upId, dpadBeingConfigured!!.bounds.centerX(), dpadBeingConfigured!!.bounds.centerY(), - "" + orientation ) dpadBeingConfigured = null } @@ -330,7 +328,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context joystickBeingConfigured!!.buttonId, joystickBeingConfigured!!.bounds.centerX(), joystickBeingConfigured!!.bounds.centerY(), - "" + orientation ) joystickBeingConfigured = null } @@ -533,8 +531,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context overlayButtons.clear() overlayDpads.clear() overlayJoysticks.clear() - val orientation = - if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else "" // Add all the enabled overlay items back to the HashSet. if (EmulationMenuSettings.showOverlay) { @@ -548,8 +544,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context val min = windowSize.first val max = windowSize.second PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() - .putFloat("$sharedPrefsId$orientation-X", (x - min.x).toFloat() / max.x) - .putFloat("$sharedPrefsId$orientation-Y", (y - min.y).toFloat() / max.y) + .putFloat("$sharedPrefsId-X$orientation", (x - min.x).toFloat() / max.x) + .putFloat("$sharedPrefsId-Y$orientation", (y - min.y).toFloat() / max.y) .apply() } @@ -558,145 +554,250 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context } private fun defaultOverlay() { - if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) { - defaultOverlayLandscape() + if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) { + defaultOverlayByLayout(orientation) } resetButtonPlacement() preferences.edit() - .putBoolean(Settings.PREF_OVERLAY_INIT, true) + .putBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", true) .apply() } fun resetButtonPlacement() { - defaultOverlayLandscape() + defaultOverlayByLayout(orientation) refreshControls() } - private fun defaultOverlayLandscape() { + private val landscapeResources = arrayOf( + R.integer.SWITCH_BUTTON_A_X, + R.integer.SWITCH_BUTTON_A_Y, + R.integer.SWITCH_BUTTON_B_X, + R.integer.SWITCH_BUTTON_B_Y, + R.integer.SWITCH_BUTTON_X_X, + R.integer.SWITCH_BUTTON_X_Y, + R.integer.SWITCH_BUTTON_Y_X, + R.integer.SWITCH_BUTTON_Y_Y, + R.integer.SWITCH_TRIGGER_ZL_X, + R.integer.SWITCH_TRIGGER_ZL_Y, + R.integer.SWITCH_TRIGGER_ZR_X, + R.integer.SWITCH_TRIGGER_ZR_Y, + R.integer.SWITCH_BUTTON_DPAD_X, + R.integer.SWITCH_BUTTON_DPAD_Y, + R.integer.SWITCH_TRIGGER_L_X, + R.integer.SWITCH_TRIGGER_L_Y, + R.integer.SWITCH_TRIGGER_R_X, + R.integer.SWITCH_TRIGGER_R_Y, + R.integer.SWITCH_BUTTON_PLUS_X, + R.integer.SWITCH_BUTTON_PLUS_Y, + R.integer.SWITCH_BUTTON_MINUS_X, + R.integer.SWITCH_BUTTON_MINUS_Y, + R.integer.SWITCH_BUTTON_HOME_X, + R.integer.SWITCH_BUTTON_HOME_Y, + R.integer.SWITCH_BUTTON_CAPTURE_X, + R.integer.SWITCH_BUTTON_CAPTURE_Y, + R.integer.SWITCH_STICK_R_X, + R.integer.SWITCH_STICK_R_Y, + R.integer.SWITCH_STICK_L_X, + R.integer.SWITCH_STICK_L_Y + ) + + private val portraitResources = arrayOf( + R.integer.SWITCH_BUTTON_A_X_PORTRAIT, + R.integer.SWITCH_BUTTON_A_Y_PORTRAIT, + R.integer.SWITCH_BUTTON_B_X_PORTRAIT, + R.integer.SWITCH_BUTTON_B_Y_PORTRAIT, + R.integer.SWITCH_BUTTON_X_X_PORTRAIT, + R.integer.SWITCH_BUTTON_X_Y_PORTRAIT, + R.integer.SWITCH_BUTTON_Y_X_PORTRAIT, + R.integer.SWITCH_BUTTON_Y_Y_PORTRAIT, + R.integer.SWITCH_TRIGGER_ZL_X_PORTRAIT, + R.integer.SWITCH_TRIGGER_ZL_Y_PORTRAIT, + R.integer.SWITCH_TRIGGER_ZR_X_PORTRAIT, + R.integer.SWITCH_TRIGGER_ZR_Y_PORTRAIT, + R.integer.SWITCH_BUTTON_DPAD_X_PORTRAIT, + R.integer.SWITCH_BUTTON_DPAD_Y_PORTRAIT, + R.integer.SWITCH_TRIGGER_L_X_PORTRAIT, + R.integer.SWITCH_TRIGGER_L_Y_PORTRAIT, + R.integer.SWITCH_TRIGGER_R_X_PORTRAIT, + R.integer.SWITCH_TRIGGER_R_Y_PORTRAIT, + R.integer.SWITCH_BUTTON_PLUS_X_PORTRAIT, + R.integer.SWITCH_BUTTON_PLUS_Y_PORTRAIT, + R.integer.SWITCH_BUTTON_MINUS_X_PORTRAIT, + R.integer.SWITCH_BUTTON_MINUS_Y_PORTRAIT, + R.integer.SWITCH_BUTTON_HOME_X_PORTRAIT, + R.integer.SWITCH_BUTTON_HOME_Y_PORTRAIT, + R.integer.SWITCH_BUTTON_CAPTURE_X_PORTRAIT, + R.integer.SWITCH_BUTTON_CAPTURE_Y_PORTRAIT, + R.integer.SWITCH_STICK_R_X_PORTRAIT, + R.integer.SWITCH_STICK_R_Y_PORTRAIT, + R.integer.SWITCH_STICK_L_X_PORTRAIT, + R.integer.SWITCH_STICK_L_Y_PORTRAIT + ) + + private val foldableResources = arrayOf( + R.integer.SWITCH_BUTTON_A_X_FOLDABLE, + R.integer.SWITCH_BUTTON_A_Y_FOLDABLE, + R.integer.SWITCH_BUTTON_B_X_FOLDABLE, + R.integer.SWITCH_BUTTON_B_Y_FOLDABLE, + R.integer.SWITCH_BUTTON_X_X_FOLDABLE, + R.integer.SWITCH_BUTTON_X_Y_FOLDABLE, + R.integer.SWITCH_BUTTON_Y_X_FOLDABLE, + R.integer.SWITCH_BUTTON_Y_Y_FOLDABLE, + R.integer.SWITCH_TRIGGER_ZL_X_FOLDABLE, + R.integer.SWITCH_TRIGGER_ZL_Y_FOLDABLE, + R.integer.SWITCH_TRIGGER_ZR_X_FOLDABLE, + R.integer.SWITCH_TRIGGER_ZR_Y_FOLDABLE, + R.integer.SWITCH_BUTTON_DPAD_X_FOLDABLE, + R.integer.SWITCH_BUTTON_DPAD_Y_FOLDABLE, + R.integer.SWITCH_TRIGGER_L_X_FOLDABLE, + R.integer.SWITCH_TRIGGER_L_Y_FOLDABLE, + R.integer.SWITCH_TRIGGER_R_X_FOLDABLE, + R.integer.SWITCH_TRIGGER_R_Y_FOLDABLE, + R.integer.SWITCH_BUTTON_PLUS_X_FOLDABLE, + R.integer.SWITCH_BUTTON_PLUS_Y_FOLDABLE, + R.integer.SWITCH_BUTTON_MINUS_X_FOLDABLE, + R.integer.SWITCH_BUTTON_MINUS_Y_FOLDABLE, + R.integer.SWITCH_BUTTON_HOME_X_FOLDABLE, + R.integer.SWITCH_BUTTON_HOME_Y_FOLDABLE, + R.integer.SWITCH_BUTTON_CAPTURE_X_FOLDABLE, + R.integer.SWITCH_BUTTON_CAPTURE_Y_FOLDABLE, + R.integer.SWITCH_STICK_R_X_FOLDABLE, + R.integer.SWITCH_STICK_R_Y_FOLDABLE, + R.integer.SWITCH_STICK_L_X_FOLDABLE, + R.integer.SWITCH_STICK_L_Y_FOLDABLE + ) + + private fun getResourceValue(orientation: String, position: Int) : Float { + return when (orientation) { + PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000 + FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000 + else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000 + } + } + + private fun defaultOverlayByLayout(orientation: String) { // Each value represents the position of the button in relation to the screen size without insets. preferences.edit() .putFloat( - ButtonType.BUTTON_A.toString() + "-X", - resources.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000 + ButtonType.BUTTON_A.toString() + "-X$orientation", + getResourceValue(orientation, 0) ) .putFloat( - ButtonType.BUTTON_A.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000 + ButtonType.BUTTON_A.toString() + "-Y$orientation", + getResourceValue(orientation, 1) ) .putFloat( - ButtonType.BUTTON_B.toString() + "-X", - resources.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000 + ButtonType.BUTTON_B.toString() + "-X$orientation", + getResourceValue(orientation, 2) ) .putFloat( - ButtonType.BUTTON_B.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000 + ButtonType.BUTTON_B.toString() + "-Y$orientation", + getResourceValue(orientation, 3) ) .putFloat( - ButtonType.BUTTON_X.toString() + "-X", - resources.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000 + ButtonType.BUTTON_X.toString() + "-X$orientation", + getResourceValue(orientation, 4) ) .putFloat( - ButtonType.BUTTON_X.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000 + ButtonType.BUTTON_X.toString() + "-Y$orientation", + getResourceValue(orientation, 5) ) .putFloat( - ButtonType.BUTTON_Y.toString() + "-X", - resources.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000 + ButtonType.BUTTON_Y.toString() + "-X$orientation", + getResourceValue(orientation, 6) ) .putFloat( - ButtonType.BUTTON_Y.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000 + ButtonType.BUTTON_Y.toString() + "-Y$orientation", + getResourceValue(orientation, 7) ) .putFloat( - ButtonType.TRIGGER_ZL.toString() + "-X", - resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000 + ButtonType.TRIGGER_ZL.toString() + "-X$orientation", + getResourceValue(orientation, 8) ) .putFloat( - ButtonType.TRIGGER_ZL.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000 + ButtonType.TRIGGER_ZL.toString() + "-Y$orientation", + getResourceValue(orientation, 9) ) .putFloat( - ButtonType.TRIGGER_ZR.toString() + "-X", - resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000 + ButtonType.TRIGGER_ZR.toString() + "-X$orientation", + getResourceValue(orientation, 10) ) .putFloat( - ButtonType.TRIGGER_ZR.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000 + ButtonType.TRIGGER_ZR.toString() + "-Y$orientation", + getResourceValue(orientation, 11) ) .putFloat( - ButtonType.DPAD_UP.toString() + "-X", - resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000 + ButtonType.DPAD_UP.toString() + "-X$orientation", + getResourceValue(orientation, 12) ) .putFloat( - ButtonType.DPAD_UP.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000 + ButtonType.DPAD_UP.toString() + "-Y$orientation", + getResourceValue(orientation, 13) ) .putFloat( - ButtonType.TRIGGER_L.toString() + "-X", - resources.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000 + ButtonType.TRIGGER_L.toString() + "-X$orientation", + getResourceValue(orientation, 14) ) .putFloat( - ButtonType.TRIGGER_L.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000 + ButtonType.TRIGGER_L.toString() + "-Y$orientation", + getResourceValue(orientation, 15) ) .putFloat( - ButtonType.TRIGGER_R.toString() + "-X", - resources.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000 + ButtonType.TRIGGER_R.toString() + "-X$orientation", + getResourceValue(orientation, 16) ) .putFloat( - ButtonType.TRIGGER_R.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000 + ButtonType.TRIGGER_R.toString() + "-Y$orientation", + getResourceValue(orientation, 17) ) .putFloat( - ButtonType.BUTTON_PLUS.toString() + "-X", - resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000 + ButtonType.BUTTON_PLUS.toString() + "-X$orientation", + getResourceValue(orientation, 18) ) .putFloat( - ButtonType.BUTTON_PLUS.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000 + ButtonType.BUTTON_PLUS.toString() + "-Y$orientation", + getResourceValue(orientation, 19) ) .putFloat( - ButtonType.BUTTON_MINUS.toString() + "-X", - resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000 + ButtonType.BUTTON_MINUS.toString() + "-X$orientation", + getResourceValue(orientation, 20) ) .putFloat( - ButtonType.BUTTON_MINUS.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000 + ButtonType.BUTTON_MINUS.toString() + "-Y$orientation", + getResourceValue(orientation, 21) ) .putFloat( - ButtonType.BUTTON_HOME.toString() + "-X", - resources.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000 + ButtonType.BUTTON_HOME.toString() + "-X$orientation", + getResourceValue(orientation, 22) ) .putFloat( - ButtonType.BUTTON_HOME.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000 + ButtonType.BUTTON_HOME.toString() + "-Y$orientation", + getResourceValue(orientation, 23) ) .putFloat( - ButtonType.BUTTON_CAPTURE.toString() + "-X", - resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X) - .toFloat() / 1000 + ButtonType.BUTTON_CAPTURE.toString() + "-X$orientation", + getResourceValue(orientation, 24) ) .putFloat( - ButtonType.BUTTON_CAPTURE.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y) - .toFloat() / 1000 + ButtonType.BUTTON_CAPTURE.toString() + "-Y$orientation", + getResourceValue(orientation, 25) ) .putFloat( - ButtonType.STICK_R.toString() + "-X", - resources.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000 + ButtonType.STICK_R.toString() + "-X$orientation", + getResourceValue(orientation, 26) ) .putFloat( - ButtonType.STICK_R.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000 + ButtonType.STICK_R.toString() + "-Y$orientation", + getResourceValue(orientation, 27) ) .putFloat( - ButtonType.STICK_L.toString() + "-X", - resources.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000 + ButtonType.STICK_L.toString() + "-X$orientation", + getResourceValue(orientation, 28) ) .putFloat( - ButtonType.STICK_L.toString() + "-Y", - resources.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000 + ButtonType.STICK_L.toString() + "-Y$orientation", + getResourceValue(orientation, 29) ) .apply() } @@ -709,6 +810,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) + const val LANDSCAPE = "" + const val PORTRAIT = "_Portrait" + const val FOLDABLE = "_Foldable" + /** * Resizes a [Bitmap] by a given scale factor * @@ -754,9 +859,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context */ private fun getSafeScreenSize(context: Context): Pair<Point, Point> { // Get screen size - val windowMetrics = - WindowMetricsCalculator.getOrCreate() - .computeCurrentWindowMetrics(context as Activity) + val windowMetrics = WindowMetricsCalculator.getOrCreate() + .computeCurrentWindowMetrics(context as Activity) var maxY = windowMetrics.bounds.height().toFloat() var maxX = windowMetrics.bounds.width().toFloat() var minY = 0 @@ -769,9 +873,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout if (insets != null) { if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2) - insets.boundingRectTop.bottom.toFloat() else maxY + maxY = insets.boundingRectTop.bottom.toFloat() if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2) - insets.boundingRectRight.left.toFloat() else maxX + maxX = insets.boundingRectRight.left.toFloat() minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom @@ -878,8 +982,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. // These were set in the input overlay configuration menu. - val xKey = "$buttonId$orientation-X" - val yKey = "$buttonId$orientation-Y" + val xKey = "$buttonId-X$orientation" + val yKey = "$buttonId-Y$orientation" val drawableXPercent = sPrefs.getFloat(xKey, 0f) val drawableYPercent = sPrefs.getFloat(yKey, 0f) val drawableX = (drawableXPercent * max.x + min.x).toInt() @@ -959,8 +1063,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. // These were set in the input overlay configuration menu. - val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f) - val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f) + val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-X$orientation", 0f) + val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-Y$orientation", 0f) val drawableX = (drawableXPercent * max.x + min.x).toInt() val drawableY = (drawableYPercent * max.y + min.y).toInt() val width = overlayDrawable.width @@ -1026,8 +1130,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. // These were set in the input overlay configuration menu. - val drawableXPercent = sPrefs.getFloat("$button$orientation-X", 0f) - val drawableYPercent = sPrefs.getFloat("$button$orientation-Y", 0f) + val drawableXPercent = sPrefs.getFloat("$button-X$orientation", 0f) + val drawableYPercent = sPrefs.getFloat("$button-Y$orientation", 0f) val drawableX = (drawableXPercent * max.x + min.x).toInt() val drawableY = (drawableYPercent * max.y + min.y).toInt() val outerScale = 1.66f diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt index e1e7a59d7..7e8f058c1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt @@ -11,14 +11,6 @@ object EmulationMenuSettings { private val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - // These must match what is defined in src/core/settings.h - const val LayoutOption_Default = 0 - const val LayoutOption_SingleScreen = 1 - const val LayoutOption_LargeScreen = 2 - const val LayoutOption_SideScreen = 3 - const val LayoutOption_MobilePortrait = 4 - const val LayoutOption_MobileLandscape = 5 - var joystickRelCenter: Boolean get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true) set(value) { @@ -41,16 +33,6 @@ object EmulationMenuSettings { .apply() } - var landscapeScreenLayout: Int - get() = preferences.getInt( - Settings.PREF_MENU_SETTINGS_LANDSCAPE, - LayoutOption_MobileLandscape - ) - set(value) { - preferences.edit() - .putInt(Settings.PREF_MENU_SETTINGS_LANDSCAPE, value) - .apply() - } var showFps: Boolean get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false) set(value) { diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 4091c23d1..f9617202b 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -202,6 +202,11 @@ public: return m_is_running; } + bool IsPaused() const { + std::scoped_lock lock(m_mutex); + return m_is_running && m_is_paused; + } + const Core::PerfStatsResults& PerfStats() const { std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); return m_perf_stats; @@ -287,11 +292,13 @@ public: void PauseEmulation() { std::scoped_lock lock(m_mutex); m_system.Pause(); + m_is_paused = true; } void UnPauseEmulation() { std::scoped_lock lock(m_mutex); m_system.Run(); + m_is_paused = false; } void HaltEmulation() { @@ -473,6 +480,7 @@ private: std::shared_ptr<FileSys::VfsFilesystem> m_vfs; Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; bool m_is_running{}; + bool m_is_paused{}; SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; @@ -583,6 +591,11 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); } +jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused([[maybe_unused]] JNIEnv* env, + [[maybe_unused]] jclass clazz) { + return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused()); +} + jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz) { return EmulationSession::GetInstance().IsHandheldOnly(); diff --git a/src/android/app/src/main/res/drawable/ic_pip_pause.xml b/src/android/app/src/main/res/drawable/ic_pip_pause.xml new file mode 100644 index 000000000..4a7d4ea03 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_pip_pause.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z" /> +</vector> diff --git a/src/android/app/src/main/res/drawable/ic_pip_play.xml b/src/android/app/src/main/res/drawable/ic_pip_play.xml new file mode 100644 index 000000000..2303a4623 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_pip_play.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M8,5v14l11,-7z" /> +</vector> diff --git a/src/android/app/src/main/res/layout/activity_emulation.xml b/src/android/app/src/main/res/layout/activity_emulation.xml index f6360a65b..139065d3d 100644 --- a/src/android/app/src/main/res/layout/activity_emulation.xml +++ b/src/android/app/src/main/res/layout/activity_emulation.xml @@ -1,13 +1,9 @@ -<FrameLayout +<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/frame_content" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/fragment_container" + android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" - android:keepScreenOn="true"> - - <FrameLayout - android:id="@+id/frame_emulation_fragment" - android:layout_width="match_parent" - android:layout_height="match_parent" /> - -</FrameLayout> + android:keepScreenOn="true" + app:defaultNavHost="true" /> diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml index 09b789b6b..e54a10e8f 100644 --- a/src/android/app/src/main/res/layout/fragment_emulation.xml +++ b/src/android/app/src/main/res/layout/fragment_emulation.xml @@ -12,49 +12,65 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <!-- This is what everything is rendered to during emulation --> - <org.yuzu.yuzu_emu.views.FixedRatioSurfaceView - android:id="@+id/surface_emulation" + <FrameLayout + android:id="@+id/emulation_container" android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center" - android:focusable="false" - android:focusableInTouchMode="false" /> + android:layout_height="match_parent"> + + <!-- This is what everything is rendered to during emulation --> + <org.yuzu.yuzu_emu.views.FixedRatioSurfaceView + android:id="@+id/surface_emulation" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + android:focusable="false" + android:focusableInTouchMode="false" /> + + </FrameLayout> <FrameLayout - android:id="@+id/overlay_container" + android:id="@+id/input_container" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="bottom"> - <!-- This is the onscreen input overlay --> - <org.yuzu.yuzu_emu.overlay.InputOverlay - android:id="@+id/surface_input_overlay" + <!-- This is the onscreen input overlay --> + <org.yuzu.yuzu_emu.overlay.InputOverlay + android:id="@+id/surface_input_overlay" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + android:focusable="true" + android:focusableInTouchMode="true" /> + + <Button + style="@style/Widget.Material3.Button.ElevatedButton" + android:id="@+id/done_control_config" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:text="@string/emulation_done" + android:visibility="gone" /> + + </FrameLayout> + + <FrameLayout + android:id="@+id/overlay_container" android:layout_width="match_parent" - android:layout_height="match_parent" - android:focusable="true" - android:focusableInTouchMode="true" /> + android:layout_height="match_parent"> - <TextView - android:id="@+id/show_fps_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="left" - android:clickable="false" - android:focusable="false" - android:shadowColor="@android:color/black" - android:textColor="@android:color/white" - android:textSize="12sp" - tools:ignore="RtlHardcoded" /> + <TextView + android:id="@+id/show_fps_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="left" + android:clickable="false" + android:focusable="false" + android:shadowColor="@android:color/black" + android:textColor="@android:color/white" + android:textSize="12sp" + tools:ignore="RtlHardcoded" /> - <Button - style="@style/Widget.Material3.Button.ElevatedButton" - android:id="@+id/done_control_config" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:text="@string/emulation_done" - android:visibility="gone" /> </FrameLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml new file mode 100644 index 000000000..8208f4c2c --- /dev/null +++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<navigation xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/emulation_navigation" + app:startDestination="@id/emulationFragment"> + + <fragment + android:id="@+id/emulationFragment" + android:name="org.yuzu.yuzu_emu.fragments.EmulationFragment" + android:label="fragment_emulation" + tools:layout="@layout/fragment_emulation" > + <argument + android:name="game" + app:argType="org.yuzu.yuzu_emu.model.Game" /> + </fragment> + +</navigation> diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml index 48072683e..fcebba726 100644 --- a/src/android/app/src/main/res/navigation/home_navigation.xml +++ b/src/android/app/src/main/res/navigation/home_navigation.xml @@ -56,4 +56,18 @@ android:name="org.yuzu.yuzu_emu.fragments.LicensesFragment" android:label="LicensesFragment" /> + <activity + android:id="@+id/emulationActivity" + android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" + android:label="EmulationActivity"> + <argument + android:name="game" + app:argType="org.yuzu.yuzu_emu.model.Game" /> + </activity> + + <action + android:id="@+id/action_global_emulationActivity" + app:destination="@id/emulationActivity" + app:launchSingleTop="true" /> + </navigation> diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index ea20cb17c..7f7b1938c 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -119,6 +119,18 @@ <item>3</item> </integer-array> + <string-array name="rendererScreenLayoutNames"> + <item>@string/screen_layout_landscape</item> + <item>@string/screen_layout_portrait</item> + <item>@string/screen_layout_auto</item> + </string-array> + + <integer-array name="rendererScreenLayoutValues"> + <item>5</item> + <item>4</item> + <item>0</item> + </integer-array> + <string-array name="rendererAspectRatioNames"> <item>@string/ratio_default</item> <item>@string/ratio_force_four_three</item> diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml index bc614b81d..2e93b408c 100644 --- a/src/android/app/src/main/res/values/integers.xml +++ b/src/android/app/src/main/res/values/integers.xml @@ -34,4 +34,68 @@ <integer name="SWITCH_BUTTON_DPAD_X">260</integer> <integer name="SWITCH_BUTTON_DPAD_Y">790</integer> + <!-- Default SWITCH portrait layout --> + <integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer> + <integer name="SWITCH_BUTTON_A_Y_PORTRAIT">840</integer> + <integer name="SWITCH_BUTTON_B_X_PORTRAIT">740</integer> + <integer name="SWITCH_BUTTON_B_Y_PORTRAIT">880</integer> + <integer name="SWITCH_BUTTON_X_X_PORTRAIT">740</integer> + <integer name="SWITCH_BUTTON_X_Y_PORTRAIT">800</integer> + <integer name="SWITCH_BUTTON_Y_X_PORTRAIT">640</integer> + <integer name="SWITCH_BUTTON_Y_Y_PORTRAIT">840</integer> + <integer name="SWITCH_STICK_L_X_PORTRAIT">180</integer> + <integer name="SWITCH_STICK_L_Y_PORTRAIT">660</integer> + <integer name="SWITCH_STICK_R_X_PORTRAIT">820</integer> + <integer name="SWITCH_STICK_R_Y_PORTRAIT">660</integer> + <integer name="SWITCH_TRIGGER_L_X_PORTRAIT">140</integer> + <integer name="SWITCH_TRIGGER_L_Y_PORTRAIT">260</integer> + <integer name="SWITCH_TRIGGER_R_X_PORTRAIT">860</integer> + <integer name="SWITCH_TRIGGER_R_Y_PORTRAIT">260</integer> + <integer name="SWITCH_TRIGGER_ZL_X_PORTRAIT">140</integer> + <integer name="SWITCH_TRIGGER_ZL_Y_PORTRAIT">200</integer> + <integer name="SWITCH_TRIGGER_ZR_X_PORTRAIT">860</integer> + <integer name="SWITCH_TRIGGER_ZR_Y_PORTRAIT">200</integer> + <integer name="SWITCH_BUTTON_MINUS_X_PORTRAIT">440</integer> + <integer name="SWITCH_BUTTON_MINUS_Y_PORTRAIT">950</integer> + <integer name="SWITCH_BUTTON_PLUS_X_PORTRAIT">560</integer> + <integer name="SWITCH_BUTTON_PLUS_Y_PORTRAIT">950</integer> + <integer name="SWITCH_BUTTON_HOME_X_PORTRAIT">680</integer> + <integer name="SWITCH_BUTTON_HOME_Y_PORTRAIT">950</integer> + <integer name="SWITCH_BUTTON_CAPTURE_X_PORTRAIT">320</integer> + <integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer> + <integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer> + <integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer> + + <!-- Default SWITCH foldable layout --> + <integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer> + <integer name="SWITCH_BUTTON_A_Y_FOLDABLE">390</integer> + <integer name="SWITCH_BUTTON_B_X_FOLDABLE">740</integer> + <integer name="SWITCH_BUTTON_B_Y_FOLDABLE">430</integer> + <integer name="SWITCH_BUTTON_X_X_FOLDABLE">740</integer> + <integer name="SWITCH_BUTTON_X_Y_FOLDABLE">350</integer> + <integer name="SWITCH_BUTTON_Y_X_FOLDABLE">640</integer> + <integer name="SWITCH_BUTTON_Y_Y_FOLDABLE">390</integer> + <integer name="SWITCH_STICK_L_X_FOLDABLE">180</integer> + <integer name="SWITCH_STICK_L_Y_FOLDABLE">250</integer> + <integer name="SWITCH_STICK_R_X_FOLDABLE">820</integer> + <integer name="SWITCH_STICK_R_Y_FOLDABLE">250</integer> + <integer name="SWITCH_TRIGGER_L_X_FOLDABLE">140</integer> + <integer name="SWITCH_TRIGGER_L_Y_FOLDABLE">130</integer> + <integer name="SWITCH_TRIGGER_R_X_FOLDABLE">860</integer> + <integer name="SWITCH_TRIGGER_R_Y_FOLDABLE">130</integer> + <integer name="SWITCH_TRIGGER_ZL_X_FOLDABLE">140</integer> + <integer name="SWITCH_TRIGGER_ZL_Y_FOLDABLE">70</integer> + <integer name="SWITCH_TRIGGER_ZR_X_FOLDABLE">860</integer> + <integer name="SWITCH_TRIGGER_ZR_Y_FOLDABLE">70</integer> + <integer name="SWITCH_BUTTON_MINUS_X_FOLDABLE">440</integer> + <integer name="SWITCH_BUTTON_MINUS_Y_FOLDABLE">470</integer> + <integer name="SWITCH_BUTTON_PLUS_X_FOLDABLE">560</integer> + <integer name="SWITCH_BUTTON_PLUS_Y_FOLDABLE">470</integer> + <integer name="SWITCH_BUTTON_HOME_X_FOLDABLE">680</integer> + <integer name="SWITCH_BUTTON_HOME_Y_FOLDABLE">470</integer> + <integer name="SWITCH_BUTTON_CAPTURE_X_FOLDABLE">320</integer> + <integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer> + <integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer> + <integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer> + </resources> diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index c236811fa..b5bc249d4 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -162,6 +162,7 @@ <string name="renderer_accuracy">Accuracy level</string> <string name="renderer_resolution">Resolution (Handheld/Docked)</string> <string name="renderer_vsync">VSync mode</string> + <string name="renderer_screen_layout">Orientation</string> <string name="renderer_aspect_ratio">Aspect ratio</string> <string name="renderer_scaling_filter">Window adapting filter</string> <string name="renderer_anti_aliasing">Anti-aliasing method</string> @@ -326,6 +327,11 @@ <string name="anti_aliasing_fxaa">FXAA</string> <string name="anti_aliasing_smaa">SMAA</string> + <!-- Screen Layouts --> + <string name="screen_layout_landscape">Landscape</string> + <string name="screen_layout_portrait">Portrait</string> + <string name="screen_layout_auto">Auto</string> + <!-- Aspect Ratios --> <string name="ratio_default">Default (16:9)</string> <string name="ratio_force_four_three">Force 4:3</string> @@ -364,6 +370,12 @@ <string name="use_black_backgrounds">Black backgrounds</string> <string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string> + <!-- Picture-In-Picture --> + <string name="picture_in_picture">Picture in Picture</string> + <string name="picture_in_picture_description">Minimize window when placed in the background</string> + <string name="pause">Pause</string> + <string name="play">Play</string> + <!-- Licenses screen strings --> <string name="licenses">Licenses</string> <string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string> diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts index e19e8ce58..80f370c16 100644 --- a/src/android/build.gradle.kts +++ b/src/android/build.gradle.kts @@ -11,3 +11,12 @@ plugins { tasks.register("clean").configure { delete(rootProject.buildDir) } + +buildscript { + repositories { + google() + } + dependencies { + classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0") + } +} diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index cc0076238..7a15d8438 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -25,6 +25,8 @@ namespace FS = Common::FS; namespace { +constexpr size_t MaxOpenFiles = 512; + constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) { switch (mode) { case Mode::Read: @@ -73,28 +75,30 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); - if (const auto weak_iter = cache.find(path); weak_iter != cache.cend()) { - const auto& weak = weak_iter->second; - - if (!weak.expired()) { - return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms)); + if (auto it = cache.find(path); it != cache.end()) { + if (auto file = it->second.lock(); file) { + return file; } } - auto backing = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); - - if (!backing) { + if (!FS::Exists(path) || !FS::IsFile(path)) { return nullptr; } - cache.insert_or_assign(path, std::move(backing)); + auto reference = std::make_unique<FileReference>(); + this->InsertReferenceIntoList(*reference); - // Cannot use make_shared as RealVfsFile constructor is private - return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms)); + auto file = + std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, std::move(reference), path, perms)); + cache[path] = file; + + return file; } VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + cache.erase(path); + // Current usages of CreateFile expect to delete the contents of an existing file. if (FS::IsFile(path)) { FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile}; @@ -123,51 +127,22 @@ VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); - const auto cached_file_iter = cache.find(old_path); - - if (cached_file_iter != cache.cend()) { - auto file = cached_file_iter->second.lock(); - - if (!cached_file_iter->second.expired()) { - file->Close(); - } - - if (!FS::RenameFile(old_path, new_path)) { - return nullptr; - } - - cache.erase(old_path); - file->Open(new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); - if (file->IsOpen()) { - cache.insert_or_assign(new_path, std::move(file)); - } else { - LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path); - } - } else { - ASSERT(false); + cache.erase(old_path); + cache.erase(new_path); + if (!FS::RenameFile(old_path, new_path)) { return nullptr; } - return OpenFile(new_path, Mode::ReadWrite); } bool RealVfsFilesystem::DeleteFile(std::string_view path_) { const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); - const auto cached_iter = cache.find(path); - - if (cached_iter != cache.cend()) { - if (!cached_iter->second.expired()) { - cached_iter->second.lock()->Close(); - } - cache.erase(path); - } - + cache.erase(path); return FS::RemoveFile(path); } VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); - // Cannot use make_shared as RealVfsDirectory constructor is private return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); } @@ -176,7 +151,6 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms if (!FS::CreateDirs(path)) { return nullptr; } - // Cannot use make_shared as RealVfsDirectory constructor is private return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); } @@ -194,73 +168,102 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, if (!FS::RenameDir(old_path, new_path)) { return nullptr; } + return OpenDirectory(new_path, Mode::ReadWrite); +} - for (auto& kv : cache) { - // If the path in the cache doesn't start with old_path, then bail on this file. - if (kv.first.rfind(old_path, 0) != 0) { - continue; - } +bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + return FS::RemoveDirRecursively(path); +} - const auto file_old_path = - FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault); - auto file_new_path = FS::SanitizePath(new_path + '/' + kv.first.substr(old_path.size()), - FS::DirectorySeparator::PlatformDefault); - const auto& cached = cache[file_old_path]; +void RealVfsFilesystem::RefreshReference(const std::string& path, Mode perms, + FileReference& reference) { + // Temporarily remove from list. + this->RemoveReferenceFromList(reference); - if (cached.expired()) { - continue; - } + // Restore file if needed. + if (!reference.file) { + this->EvictSingleReference(); - auto file = cached.lock(); - cache.erase(file_old_path); - file->Open(file_new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); - if (file->IsOpen()) { - cache.insert_or_assign(std::move(file_new_path), std::move(file)); - } else { - LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", file_new_path); + reference.file = + FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); + if (reference.file) { + num_open_files++; } } - return OpenDirectory(new_path, Mode::ReadWrite); + // Reinsert into list. + this->InsertReferenceIntoList(reference); } -bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { - const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); +void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) { + // Remove from list. + this->RemoveReferenceFromList(*reference); - for (auto& kv : cache) { - // If the path in the cache doesn't start with path, then bail on this file. - if (kv.first.rfind(path, 0) != 0) { - continue; - } + // Close the file. + if (reference->file) { + reference->file.reset(); + num_open_files--; + } +} - const auto& entry = cache[kv.first]; - if (!entry.expired()) { - entry.lock()->Close(); - } +void RealVfsFilesystem::EvictSingleReference() { + if (num_open_files < MaxOpenFiles || open_references.empty()) { + return; + } + + // Get and remove from list. + auto& reference = open_references.back(); + this->RemoveReferenceFromList(reference); - cache.erase(kv.first); + // Close the file. + if (reference.file) { + reference.file.reset(); + num_open_files--; } - return FS::RemoveDirRecursively(path); + // Reinsert into closed list. + this->InsertReferenceIntoList(reference); +} + +void RealVfsFilesystem::InsertReferenceIntoList(FileReference& reference) { + if (reference.file) { + open_references.push_front(reference); + } else { + closed_references.push_front(reference); + } +} + +void RealVfsFilesystem::RemoveReferenceFromList(FileReference& reference) { + if (reference.file) { + open_references.erase(open_references.iterator_to(reference)); + } else { + closed_references.erase(closed_references.iterator_to(reference)); + } } -RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_, +RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_, const std::string& path_, Mode perms_) - : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)), - path_components(FS::SplitPathComponents(path_)), perms(perms_) {} + : base(base_), reference(std::move(reference_)), path(path_), + parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponents(path_)), + perms(perms_) {} -RealVfsFile::~RealVfsFile() = default; +RealVfsFile::~RealVfsFile() { + base.DropReference(std::move(reference)); +} std::string RealVfsFile::GetName() const { return path_components.back(); } std::size_t RealVfsFile::GetSize() const { - return backing->GetSize(); + base.RefreshReference(path, perms, *reference); + return reference->file ? reference->file->GetSize() : 0; } bool RealVfsFile::Resize(std::size_t new_size) { - return backing->SetSize(new_size); + base.RefreshReference(path, perms, *reference); + return reference->file ? reference->file->SetSize(new_size) : false; } VirtualDir RealVfsFile::GetContainingDirectory() const { @@ -276,27 +279,25 @@ bool RealVfsFile::IsReadable() const { } std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { - if (!backing->Seek(static_cast<s64>(offset))) { + base.RefreshReference(path, perms, *reference); + if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) { return 0; } - return backing->ReadSpan(std::span{data, length}); + return reference->file->ReadSpan(std::span{data, length}); } std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { - if (!backing->Seek(static_cast<s64>(offset))) { + base.RefreshReference(path, perms, *reference); + if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) { return 0; } - return backing->WriteSpan(std::span{data, length}); + return reference->file->WriteSpan(std::span{data, length}); } bool RealVfsFile::Rename(std::string_view name) { return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; } -void RealVfsFile::Close() { - backing->Close(); -} - // TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if // constexpr' because there is a compile error in the branch not used. diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index b92c84316..d8c900e33 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -3,8 +3,9 @@ #pragma once +#include <map> #include <string_view> -#include <boost/container/flat_map.hpp> +#include "common/intrusive_list.h" #include "core/file_sys/mode.h" #include "core/file_sys/vfs.h" @@ -14,6 +15,11 @@ class IOFile; namespace FileSys { +struct FileReference : public Common::IntrusiveListBaseNode<FileReference> { + std::shared_ptr<Common::FS::IOFile> file{}; +}; + +class RealVfsFile; class RealVfsFilesystem : public VfsFilesystem { public: RealVfsFilesystem(); @@ -35,7 +41,21 @@ public: bool DeleteDirectory(std::string_view path) override; private: - boost::container::flat_map<std::string, std::weak_ptr<Common::FS::IOFile>> cache; + using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType; + std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache; + ReferenceListType open_references; + ReferenceListType closed_references; + size_t num_open_files{}; + +private: + friend class RealVfsFile; + void RefreshReference(const std::string& path, Mode perms, FileReference& reference); + void DropReference(std::unique_ptr<FileReference>&& reference); + void EvictSingleReference(); + +private: + void InsertReferenceIntoList(FileReference& reference); + void RemoveReferenceFromList(FileReference& reference); }; // An implementation of VfsFile that represents a file on the user's computer. @@ -57,13 +77,11 @@ public: bool Rename(std::string_view name) override; private: - RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing, + RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference, const std::string& path, Mode perms = Mode::Read); - void Close(); - RealVfsFilesystem& base; - std::shared_ptr<Common::FS::IOFile> backing; + std::unique_ptr<FileReference> reference; std::string path; std::string parent_path; std::vector<std::string> path_components; diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 251a4a880..9bafd8cc0 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -715,7 +715,7 @@ void BufferCache<P>::BindHostIndexBuffer() { template <class P> void BufferCache<P>::BindHostVertexBuffers() { - HostBindings host_bindings; + HostBindings<typename P::Buffer> host_bindings; bool any_valid{false}; auto& flags = maxwell3d->dirty.flags; for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { @@ -741,7 +741,7 @@ void BufferCache<P>::BindHostVertexBuffers() { const u32 stride = maxwell3d->regs.vertex_streams[index].stride; const u32 offset = buffer.Offset(binding.cpu_addr); - host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer)); + host_bindings.buffers.push_back(&buffer); host_bindings.offsets.push_back(offset); host_bindings.sizes.push_back(binding.size); host_bindings.strides.push_back(stride); @@ -900,7 +900,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() { if (maxwell3d->regs.transform_feedback_enabled == 0) { return; } - HostBindings host_bindings; + HostBindings<typename P::Buffer> host_bindings; for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) { const Binding& binding = channel_state->transform_feedback_buffers[index]; if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 && @@ -913,7 +913,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() { SynchronizeBuffer(buffer, binding.cpu_addr, size); const u32 offset = buffer.Offset(binding.cpu_addr); - host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer)); + host_bindings.buffers.push_back(&buffer); host_bindings.offsets.push_back(offset); host_bindings.sizes.push_back(binding.size); } diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index cf359e241..63a120f7a 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -105,8 +105,9 @@ static constexpr Binding NULL_BINDING{ .buffer_id = NULL_BUFFER_ID, }; +template <typename Buffer> struct HostBindings { - boost::container::small_vector<void*, NUM_VERTEX_BUFFERS> buffers; + boost::container::small_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers; boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets; boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes; boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides; diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index 0cc546a3a..38d553d3c 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -232,12 +232,12 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, } } -void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) { - for (u32 index = 0; index < bindings.buffers.size(); index++) { - BindVertexBuffer( - bindings.min_index + index, *reinterpret_cast<Buffer*>(bindings.buffers[index]), - static_cast<u32>(bindings.offsets[index]), static_cast<u32>(bindings.sizes[index]), - static_cast<u32>(bindings.strides[index])); +void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) { + for (u32 index = 0; index < bindings.buffers.size(); ++index) { + BindVertexBuffer(bindings.min_index + index, *bindings.buffers[index], + static_cast<u32>(bindings.offsets[index]), + static_cast<u32>(bindings.sizes[index]), + static_cast<u32>(bindings.strides[index])); } } @@ -329,10 +329,9 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, Buffer& buffer, static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); } -void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) { - for (u32 index = 0; index < bindings.buffers.size(); index++) { - glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index, - reinterpret_cast<Buffer*>(bindings.buffers[index])->Handle(), +void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings) { + for (u32 index = 0; index < bindings.buffers.size(); ++index) { + glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index, bindings.buffers[index]->Handle(), static_cast<GLintptr>(bindings.offsets[index]), static_cast<GLsizeiptr>(bindings.sizes[index])); } diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index e4e000284..41b746f3b 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -87,7 +87,8 @@ public: void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size); void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride); - void BindVertexBuffers(VideoCommon::HostBindings& bindings); + + void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings); void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size); @@ -100,7 +101,8 @@ public: bool is_written); void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size); - void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); + + void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings); void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, VideoCore::Surface::PixelFormat format); diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index d72d99899..8c33722d3 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -501,11 +501,10 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset } } -void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) { +void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) { boost::container::small_vector<VkBuffer, 32> buffer_handles; - for (u32 index = 0; index < bindings.buffers.size(); index++) { - auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]); - auto handle = buffer.Handle(); + for (u32 index = 0; index < bindings.buffers.size(); ++index) { + auto handle = bindings.buffers[index]->Handle(); if (handle == VK_NULL_HANDLE) { bindings.offsets[index] = 0; bindings.sizes[index] = VK_WHOLE_SIZE; @@ -521,16 +520,13 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { cmdbuf.BindVertexBuffers2EXT( bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(), - reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()), - reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data()), - reinterpret_cast<const VkDeviceSize*>(bindings.strides.data())); + bindings.offsets.data(), bindings.sizes.data(), bindings.strides.data()); }); } else { scheduler.Record([bindings = bindings, buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { - cmdbuf.BindVertexBuffers( - bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(), - reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data())); + cmdbuf.BindVertexBuffers(bindings.min_index, bindings.max_index - bindings.min_index, + buffer_handles.data(), bindings.offsets.data()); }); } } @@ -556,22 +552,20 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, }); } -void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) { +void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings) { if (!device.IsExtTransformFeedbackSupported()) { // Already logged in the rasterizer return; } boost::container::small_vector<VkBuffer, 4> buffer_handles; - for (u32 index = 0; index < bindings.buffers.size(); index++) { - auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]); - buffer_handles.push_back(buffer.Handle()); + for (u32 index = 0; index < bindings.buffers.size(); ++index) { + buffer_handles.push_back(bindings.buffers[index]->Handle()); } scheduler.Record( [bindings = bindings, buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { - cmdbuf.BindTransformFeedbackBuffersEXT( - 0, static_cast<u32>(buffer_handles.size()), buffer_handles.data(), - reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()), - reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data())); + cmdbuf.BindTransformFeedbackBuffersEXT(0, static_cast<u32>(buffer_handles.size()), + buffer_handles.data(), bindings.offsets.data(), + bindings.sizes.data()); }); } diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 92d3e9f32..cdeef8846 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -97,10 +97,12 @@ public: void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count); void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride); - void BindVertexBuffers(VideoCommon::HostBindings& bindings); + + void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings); void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size); - void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); + + void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings); std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage, [[maybe_unused]] u32 binding_index, u32 size) { |