diff options
35 files changed, 887 insertions, 368 deletions
| diff --git a/.gitignore b/.gitignore index a5f7248c7..fbadb208b 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ CMakeSettings.json  # OSX global filetypes  # Created by Finder or Spotlight in directories for various OS functionality (indexing, etc)  .DS_Store +.DS_Store? +._*  .AppleDouble  .LSOverride  .Spotlight-V100 diff --git a/externals/vcpkg b/externals/vcpkg -Subproject 656fcc6ab2b05c6d999b7eaca717027ac3738f7 +Subproject a487471068f4cb1cbb4eeb340763cdcc0a75fd6 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) { diff --git a/vcpkg.json b/vcpkg.json index 26f545c6c..2fa2c80be 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,7 +1,7 @@  {      "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",      "name": "yuzu", -    "builtin-baseline": "656fcc6ab2b05c6d999b7eaca717027ac3738f71", +    "builtin-baseline": "a487471068f4cb1cbb4eeb340763cdcc0a75fd68",      "version": "1.0",      "dependencies": [          "boost-algorithm", | 
