early-access version 3680
This commit is contained in:
parent
399502a03e
commit
22fb9ebe53
34 changed files with 840 additions and 241 deletions
|
@ -1,7 +1,7 @@
|
||||||
yuzu emulator early access
|
yuzu emulator early access
|
||||||
=============
|
=============
|
||||||
|
|
||||||
This is the source code for early-access 3678.
|
This is the source code for early-access 3680.
|
||||||
|
|
||||||
## Legal Notice
|
## Legal Notice
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
<activity
|
<activity
|
||||||
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
|
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
|
||||||
android:theme="@style/Theme.Yuzu.Main"
|
android:theme="@style/Theme.Yuzu.Main"
|
||||||
android:screenOrientation="userLandscape"
|
android:supportsPictureInPicture="true"
|
||||||
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
|
@ -282,6 +282,11 @@ object NativeLibrary {
|
||||||
*/
|
*/
|
||||||
external fun isRunning(): Boolean
|
external fun isRunning(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if emulation is paused.
|
||||||
|
*/
|
||||||
|
external fun isPaused(): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the performance stats for the current game
|
* Returns the performance stats for the current game
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -4,14 +4,23 @@
|
||||||
package org.yuzu.yuzu_emu.activities
|
package org.yuzu.yuzu_emu.activities
|
||||||
|
|
||||||
import android.app.Activity
|
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.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
import android.hardware.Sensor
|
import android.hardware.Sensor
|
||||||
import android.hardware.SensorEvent
|
import android.hardware.SensorEvent
|
||||||
import android.hardware.SensorEventListener
|
import android.hardware.SensorEventListener
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Rational
|
||||||
import android.view.InputDevice
|
import android.view.InputDevice
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
@ -27,6 +36,8 @@ import androidx.navigation.fragment.NavHostFragment
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
|
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.features.settings.model.SettingsViewModel
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
|
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
|
||||||
|
@ -50,6 +61,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
private var motionTimestamp: Long = 0
|
private var motionTimestamp: Long = 0
|
||||||
private var flipMotionOrientation: Boolean = false
|
private var flipMotionOrientation: Boolean = false
|
||||||
|
|
||||||
|
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
||||||
|
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
||||||
|
|
||||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
@ -120,6 +134,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
nfcReader.startScanning()
|
nfcReader.startScanning()
|
||||||
startMotionSensorListener()
|
startMotionSensorListener()
|
||||||
|
|
||||||
|
buildPictureInPictureParams()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
@ -128,6 +144,16 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
stopMotionSensorListener()
|
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) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
setIntent(intent)
|
setIntent(intent)
|
||||||
|
@ -230,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() {
|
private fun startMotionSensorListener() {
|
||||||
val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||||
val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
|
val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
|
||||||
|
|
|
@ -8,6 +8,7 @@ enum class BooleanSetting(
|
||||||
override val section: String,
|
override val section: String,
|
||||||
override val defaultValue: Boolean
|
override val defaultValue: Boolean
|
||||||
) : AbstractBooleanSetting {
|
) : AbstractBooleanSetting {
|
||||||
|
PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true),
|
||||||
USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
|
USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
|
||||||
|
|
||||||
override var boolean: Boolean = defaultValue
|
override var boolean: Boolean = defaultValue
|
||||||
|
@ -27,6 +28,7 @@ enum class BooleanSetting(
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val NOT_RUNTIME_EDITABLE = listOf(
|
private val NOT_RUNTIME_EDITABLE = listOf(
|
||||||
|
PICTURE_IN_PICTURE,
|
||||||
USE_CUSTOM_RTC
|
USE_CUSTOM_RTC
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -93,6 +93,11 @@ enum class IntSetting(
|
||||||
Settings.SECTION_RENDERER,
|
Settings.SECTION_RENDERER,
|
||||||
0
|
0
|
||||||
),
|
),
|
||||||
|
RENDERER_SCREEN_LAYOUT(
|
||||||
|
"screen_layout",
|
||||||
|
Settings.SECTION_RENDERER,
|
||||||
|
Settings.LayoutOption_MobileLandscape
|
||||||
|
),
|
||||||
RENDERER_ASPECT_RATIO(
|
RENDERER_ASPECT_RATIO(
|
||||||
"aspect_ratio",
|
"aspect_ratio",
|
||||||
Settings.SECTION_RENDERER,
|
Settings.SECTION_RENDERER,
|
||||||
|
|
|
@ -133,7 +133,6 @@ class Settings {
|
||||||
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
|
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
|
||||||
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
|
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
|
||||||
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
|
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_FPS = "EmulationMenuSettings_ShowFps"
|
||||||
const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
|
const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
|
||||||
|
|
||||||
|
@ -144,6 +143,10 @@ class Settings {
|
||||||
|
|
||||||
private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
|
private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
|
||||||
|
|
||||||
|
const val LayoutOption_Unspecified = 0
|
||||||
|
const val LayoutOption_MobilePortrait = 4
|
||||||
|
const val LayoutOption_MobileLandscape = 5
|
||||||
|
|
||||||
init {
|
init {
|
||||||
configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
|
configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
|
||||||
listOf(
|
listOf(
|
||||||
|
|
|
@ -16,6 +16,7 @@ import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import android.view.ViewGroup.MarginLayoutParams
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
|
@ -239,5 +240,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||||
settings.putExtra(ARG_GAME_ID, gameId)
|
settings.putExtra(ARG_GAME_ID, gameId)
|
||||||
context.startActivity(settings)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,6 +166,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||||
IntSetting.CPU_ACCURACY.defaultValue
|
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
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,6 +292,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||||
IntSetting.RENDERER_ANTI_ALIASING.defaultValue
|
IntSetting.RENDERER_ANTI_ALIASING.defaultValue
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
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(
|
add(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_ASPECT_RATIO,
|
IntSetting.RENDERER_ASPECT_RATIO,
|
||||||
|
|
|
@ -7,24 +7,26 @@ import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.content.res.Resources
|
import android.content.res.Configuration
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Rational
|
import android.util.Rational
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
@ -48,6 +50,7 @@ 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.model.Settings
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
||||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||||
|
import org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||||
import org.yuzu.yuzu_emu.utils.*
|
import org.yuzu.yuzu_emu.utils.*
|
||||||
|
|
||||||
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
@ -61,11 +64,40 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
|
||||||
val args by navArgs<EmulationFragmentArgs>()
|
val args by navArgs<EmulationFragmentArgs>()
|
||||||
|
|
||||||
|
private var isInFoldableLayout = false
|
||||||
|
|
||||||
|
private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent>
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
if (context is EmulationActivity) {
|
if (context is EmulationActivity) {
|
||||||
emulationActivity = context
|
emulationActivity = context
|
||||||
NativeLibrary.setEmulationActivity(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 {
|
} else {
|
||||||
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
|
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
|
||||||
}
|
}
|
||||||
|
@ -129,7 +161,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_settings -> {
|
R.id.menu_settings -> {
|
||||||
SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "")
|
SettingsActivity.launch(
|
||||||
|
requireContext(),
|
||||||
|
onReturnFromSettings,
|
||||||
|
SettingsFile.FILE_NAME_CONFIG,
|
||||||
|
""
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +199,33 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
WindowInfoTracker.getOrCreate(requireContext())
|
WindowInfoTracker.getOrCreate(requireContext())
|
||||||
.windowLayoutInfo(requireActivity())
|
.windowLayoutInfo(requireActivity())
|
||||||
.collect { updateCurrentLayout(requireActivity() as EmulationActivity, it) }
|
.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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,6 +247,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
updateScreenLayout()
|
||||||
|
|
||||||
emulationState.run(emulationActivity!!.isActivityRecreated)
|
emulationState.run(emulationActivity!!.isActivityRecreated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,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 {
|
val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let {
|
||||||
if (it.isSeparating) {
|
if (it.isSeparating) {
|
||||||
emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||||
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
|
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.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
|
it.isSeparating
|
||||||
} ?: false
|
} ?: false
|
||||||
if (!isFolding) {
|
if (!isFolding) {
|
||||||
binding.surfaceEmulation.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
binding.overlayContainer.updatePadding(0, 0, 0, 0)
|
binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
isInFoldableLayout = false
|
||||||
|
updateScreenLayout()
|
||||||
}
|
}
|
||||||
binding.surfaceInputOverlay.requestLayout()
|
binding.emulationContainer.requestLayout()
|
||||||
binding.inGameMenu.requestLayout()
|
binding.inputContainer.requestLayout()
|
||||||
binding.overlayContainer.requestLayout()
|
binding.overlayContainer.requestLayout()
|
||||||
|
binding.inGameMenu.requestLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||||
|
@ -397,7 +481,19 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
popup.show()
|
popup.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SourceLockedOrientationActivity")
|
||||||
private fun startConfiguringControls() {
|
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.doneControlConfig.visibility = View.VISIBLE
|
||||||
binding.surfaceInputOverlay.setIsInEditMode(true)
|
binding.surfaceInputOverlay.setIsInEditMode(true)
|
||||||
}
|
}
|
||||||
|
@ -405,6 +501,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
private fun stopConfiguringControls() {
|
private fun stopConfiguringControls() {
|
||||||
binding.doneControlConfig.visibility = View.GONE
|
binding.doneControlConfig.visibility = View.GONE
|
||||||
binding.surfaceInputOverlay.setIsInEditMode(false)
|
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")
|
@SuppressLint("SetTextI18n")
|
||||||
|
|
|
@ -51,12 +51,14 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||||
|
|
||||||
private lateinit var windowInsets: WindowInsets
|
private lateinit var windowInsets: WindowInsets
|
||||||
|
|
||||||
|
var orientation = LANDSCAPE
|
||||||
|
|
||||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||||
super.onLayout(changed, left, top, right, bottom)
|
super.onLayout(changed, left, top, right, bottom)
|
||||||
|
|
||||||
windowInsets = rootWindowInsets
|
windowInsets = rootWindowInsets
|
||||||
|
|
||||||
if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) {
|
if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) {
|
||||||
defaultOverlay()
|
defaultOverlay()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,10 +235,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||||
val fingerPositionX = event.getX(pointerIndex).toInt()
|
val fingerPositionX = event.getX(pointerIndex).toInt()
|
||||||
val fingerPositionY = event.getY(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) {
|
for (button in overlayButtons) {
|
||||||
// Determine the button state to apply based on the MotionEvent action flag.
|
// Determine the button state to apply based on the MotionEvent action flag.
|
||||||
when (event.action and MotionEvent.ACTION_MASK) {
|
when (event.action and MotionEvent.ACTION_MASK) {
|
||||||
|
@ -266,7 +264,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||||
buttonBeingConfigured!!.buttonId,
|
buttonBeingConfigured!!.buttonId,
|
||||||
buttonBeingConfigured!!.bounds.centerX(),
|
buttonBeingConfigured!!.bounds.centerX(),
|
||||||
buttonBeingConfigured!!.bounds.centerY(),
|
buttonBeingConfigured!!.bounds.centerY(),
|
||||||
""
|
orientation
|
||||||
)
|
)
|
||||||
buttonBeingConfigured = null
|
buttonBeingConfigured = null
|
||||||
}
|
}
|
||||||
|
@ -299,7 +297,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||||
dpadBeingConfigured!!.upId,
|
dpadBeingConfigured!!.upId,
|
||||||
dpadBeingConfigured!!.bounds.centerX(),
|
dpadBeingConfigured!!.bounds.centerX(),
|
||||||
dpadBeingConfigured!!.bounds.centerY(),
|
dpadBeingConfigured!!.bounds.centerY(),
|
||||||
""
|
orientation
|
||||||
)
|
)
|
||||||
dpadBeingConfigured = null
|
dpadBeingConfigured = null
|
||||||
}
|
}
|
||||||
|
@ -330,7 +328,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||||
joystickBeingConfigured!!.buttonId,
|
joystickBeingConfigured!!.buttonId,
|
||||||
joystickBeingConfigured!!.bounds.centerX(),
|
joystickBeingConfigured!!.bounds.centerX(),
|
||||||
joystickBeingConfigured!!.bounds.centerY(),
|
joystickBeingConfigured!!.bounds.centerY(),
|
||||||
""
|
orientation
|
||||||
)
|
)
|
||||||
joystickBeingConfigured = null
|
joystickBeingConfigured = null
|
||||||
}
|
}
|
||||||
|
@ -533,8 +531,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||||
overlayButtons.clear()
|
overlayButtons.clear()
|
||||||
overlayDpads.clear()
|
overlayDpads.clear()
|
||||||
overlayJoysticks.clear()
|
overlayJoysticks.clear()
|
||||||
val orientation =
|
|
||||||
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else ""
|
|
||||||
|
|
||||||
// Add all the enabled overlay items back to the HashSet.
|
// Add all the enabled overlay items back to the HashSet.
|
||||||
if (EmulationMenuSettings.showOverlay) {
|
if (EmulationMenuSettings.showOverlay) {
|
||||||
|
@ -548,8 +544,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||||
val min = windowSize.first
|
val min = windowSize.first
|
||||||
val max = windowSize.second
|
val max = windowSize.second
|
||||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
|
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
|
||||||
.putFloat("$sharedPrefsId$orientation-X", (x - min.x).toFloat() / max.x)
|
.putFloat("$sharedPrefsId-X$orientation", (x - min.x).toFloat() / max.x)
|
||||||
.putFloat("$sharedPrefsId$orientation-Y", (y - min.y).toFloat() / max.y)
|
.putFloat("$sharedPrefsId-Y$orientation", (y - min.y).toFloat() / max.y)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,145 +554,250 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun defaultOverlay() {
|
private fun defaultOverlay() {
|
||||||
if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) {
|
if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) {
|
||||||
defaultOverlayLandscape()
|
defaultOverlayByLayout(orientation)
|
||||||
}
|
}
|
||||||
|
|
||||||
resetButtonPlacement()
|
resetButtonPlacement()
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putBoolean(Settings.PREF_OVERLAY_INIT, true)
|
.putBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", true)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetButtonPlacement() {
|
fun resetButtonPlacement() {
|
||||||
defaultOverlayLandscape()
|
defaultOverlayByLayout(orientation)
|
||||||
refreshControls()
|
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.
|
// Each value represents the position of the button in relation to the screen size without insets.
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_A.toString() + "-X",
|
ButtonType.BUTTON_A.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000
|
getResourceValue(orientation, 0)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_A.toString() + "-Y",
|
ButtonType.BUTTON_A.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000
|
getResourceValue(orientation, 1)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_B.toString() + "-X",
|
ButtonType.BUTTON_B.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000
|
getResourceValue(orientation, 2)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_B.toString() + "-Y",
|
ButtonType.BUTTON_B.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000
|
getResourceValue(orientation, 3)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_X.toString() + "-X",
|
ButtonType.BUTTON_X.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000
|
getResourceValue(orientation, 4)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_X.toString() + "-Y",
|
ButtonType.BUTTON_X.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000
|
getResourceValue(orientation, 5)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_Y.toString() + "-X",
|
ButtonType.BUTTON_Y.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000
|
getResourceValue(orientation, 6)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_Y.toString() + "-Y",
|
ButtonType.BUTTON_Y.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000
|
getResourceValue(orientation, 7)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_ZL.toString() + "-X",
|
ButtonType.TRIGGER_ZL.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000
|
getResourceValue(orientation, 8)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_ZL.toString() + "-Y",
|
ButtonType.TRIGGER_ZL.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000
|
getResourceValue(orientation, 9)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_ZR.toString() + "-X",
|
ButtonType.TRIGGER_ZR.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000
|
getResourceValue(orientation, 10)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_ZR.toString() + "-Y",
|
ButtonType.TRIGGER_ZR.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000
|
getResourceValue(orientation, 11)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.DPAD_UP.toString() + "-X",
|
ButtonType.DPAD_UP.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000
|
getResourceValue(orientation, 12)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.DPAD_UP.toString() + "-Y",
|
ButtonType.DPAD_UP.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000
|
getResourceValue(orientation, 13)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_L.toString() + "-X",
|
ButtonType.TRIGGER_L.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000
|
getResourceValue(orientation, 14)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_L.toString() + "-Y",
|
ButtonType.TRIGGER_L.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000
|
getResourceValue(orientation, 15)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_R.toString() + "-X",
|
ButtonType.TRIGGER_R.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000
|
getResourceValue(orientation, 16)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_R.toString() + "-Y",
|
ButtonType.TRIGGER_R.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000
|
getResourceValue(orientation, 17)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_PLUS.toString() + "-X",
|
ButtonType.BUTTON_PLUS.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000
|
getResourceValue(orientation, 18)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_PLUS.toString() + "-Y",
|
ButtonType.BUTTON_PLUS.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000
|
getResourceValue(orientation, 19)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_MINUS.toString() + "-X",
|
ButtonType.BUTTON_MINUS.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000
|
getResourceValue(orientation, 20)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_MINUS.toString() + "-Y",
|
ButtonType.BUTTON_MINUS.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000
|
getResourceValue(orientation, 21)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_HOME.toString() + "-X",
|
ButtonType.BUTTON_HOME.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000
|
getResourceValue(orientation, 22)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_HOME.toString() + "-Y",
|
ButtonType.BUTTON_HOME.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000
|
getResourceValue(orientation, 23)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_CAPTURE.toString() + "-X",
|
ButtonType.BUTTON_CAPTURE.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X)
|
getResourceValue(orientation, 24)
|
||||||
.toFloat() / 1000
|
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_CAPTURE.toString() + "-Y",
|
ButtonType.BUTTON_CAPTURE.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y)
|
getResourceValue(orientation, 25)
|
||||||
.toFloat() / 1000
|
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.STICK_R.toString() + "-X",
|
ButtonType.STICK_R.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000
|
getResourceValue(orientation, 26)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.STICK_R.toString() + "-Y",
|
ButtonType.STICK_R.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000
|
getResourceValue(orientation, 27)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.STICK_L.toString() + "-X",
|
ButtonType.STICK_L.toString() + "-X$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000
|
getResourceValue(orientation, 28)
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.STICK_L.toString() + "-Y",
|
ButtonType.STICK_L.toString() + "-Y$orientation",
|
||||||
resources.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000
|
getResourceValue(orientation, 29)
|
||||||
)
|
)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
@ -709,6 +810,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||||
private val preferences: SharedPreferences =
|
private val preferences: SharedPreferences =
|
||||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||||
|
|
||||||
|
const val LANDSCAPE = ""
|
||||||
|
const val PORTRAIT = "_Portrait"
|
||||||
|
const val FOLDABLE = "_Foldable"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resizes a [Bitmap] by a given scale factor
|
* 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> {
|
private fun getSafeScreenSize(context: Context): Pair<Point, Point> {
|
||||||
// Get screen size
|
// Get screen size
|
||||||
val windowMetrics =
|
val windowMetrics = WindowMetricsCalculator.getOrCreate()
|
||||||
WindowMetricsCalculator.getOrCreate()
|
.computeCurrentWindowMetrics(context as Activity)
|
||||||
.computeCurrentWindowMetrics(context as Activity)
|
|
||||||
var maxY = windowMetrics.bounds.height().toFloat()
|
var maxY = windowMetrics.bounds.height().toFloat()
|
||||||
var maxX = windowMetrics.bounds.width().toFloat()
|
var maxX = windowMetrics.bounds.width().toFloat()
|
||||||
var minY = 0
|
var minY = 0
|
||||||
|
@ -769,9 +873,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||||
val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
|
val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
|
||||||
if (insets != null) {
|
if (insets != null) {
|
||||||
if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
|
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)
|
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
|
minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
|
||||||
minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
|
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.
|
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
|
||||||
// These were set in the input overlay configuration menu.
|
// These were set in the input overlay configuration menu.
|
||||||
val xKey = "$buttonId$orientation-X"
|
val xKey = "$buttonId-X$orientation"
|
||||||
val yKey = "$buttonId$orientation-Y"
|
val yKey = "$buttonId-Y$orientation"
|
||||||
val drawableXPercent = sPrefs.getFloat(xKey, 0f)
|
val drawableXPercent = sPrefs.getFloat(xKey, 0f)
|
||||||
val drawableYPercent = sPrefs.getFloat(yKey, 0f)
|
val drawableYPercent = sPrefs.getFloat(yKey, 0f)
|
||||||
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
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.
|
// The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
|
||||||
// These were set in the input overlay configuration menu.
|
// These were set in the input overlay configuration menu.
|
||||||
val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f)
|
val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-X$orientation", 0f)
|
||||||
val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f)
|
val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-Y$orientation", 0f)
|
||||||
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
||||||
val drawableY = (drawableYPercent * max.y + min.y).toInt()
|
val drawableY = (drawableYPercent * max.y + min.y).toInt()
|
||||||
val width = overlayDrawable.width
|
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.
|
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
|
||||||
// These were set in the input overlay configuration menu.
|
// These were set in the input overlay configuration menu.
|
||||||
val drawableXPercent = sPrefs.getFloat("$button$orientation-X", 0f)
|
val drawableXPercent = sPrefs.getFloat("$button-X$orientation", 0f)
|
||||||
val drawableYPercent = sPrefs.getFloat("$button$orientation-Y", 0f)
|
val drawableYPercent = sPrefs.getFloat("$button-Y$orientation", 0f)
|
||||||
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
||||||
val drawableY = (drawableYPercent * max.y + min.y).toInt()
|
val drawableY = (drawableYPercent * max.y + min.y).toInt()
|
||||||
val outerScale = 1.66f
|
val outerScale = 1.66f
|
||||||
|
|
|
@ -11,14 +11,6 @@ object EmulationMenuSettings {
|
||||||
private val preferences =
|
private val preferences =
|
||||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
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
|
var joystickRelCenter: Boolean
|
||||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true)
|
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true)
|
||||||
set(value) {
|
set(value) {
|
||||||
|
@ -41,16 +33,6 @@ object EmulationMenuSettings {
|
||||||
.apply()
|
.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
|
var showFps: Boolean
|
||||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false)
|
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false)
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|
|
@ -202,6 +202,11 @@ public:
|
||||||
return m_is_running;
|
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 {
|
const Core::PerfStatsResults& PerfStats() const {
|
||||||
std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
|
std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
|
||||||
return m_perf_stats;
|
return m_perf_stats;
|
||||||
|
@ -287,11 +292,13 @@ public:
|
||||||
void PauseEmulation() {
|
void PauseEmulation() {
|
||||||
std::scoped_lock lock(m_mutex);
|
std::scoped_lock lock(m_mutex);
|
||||||
m_system.Pause();
|
m_system.Pause();
|
||||||
|
m_is_paused = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnPauseEmulation() {
|
void UnPauseEmulation() {
|
||||||
std::scoped_lock lock(m_mutex);
|
std::scoped_lock lock(m_mutex);
|
||||||
m_system.Run();
|
m_system.Run();
|
||||||
|
m_is_paused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HaltEmulation() {
|
void HaltEmulation() {
|
||||||
|
@ -473,6 +480,7 @@ private:
|
||||||
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
|
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
|
||||||
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
|
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
|
||||||
bool m_is_running{};
|
bool m_is_running{};
|
||||||
|
bool m_is_paused{};
|
||||||
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
|
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
|
||||||
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
|
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());
|
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,
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env,
|
||||||
[[maybe_unused]] jclass clazz) {
|
[[maybe_unused]] jclass clazz) {
|
||||||
return EmulationSession::GetInstance().IsHandheldOnly();
|
return EmulationSession::GetInstance().IsHandheldOnly();
|
||||||
|
|
9
src/android/app/src/main/res/drawable/ic_pip_pause.xml
Executable file
9
src/android/app/src/main/res/drawable/ic_pip_pause.xml
Executable file
|
@ -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>
|
9
src/android/app/src/main/res/drawable/ic_pip_play.xml
Executable file
9
src/android/app/src/main/res/drawable/ic_pip_play.xml
Executable file
|
@ -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>
|
|
@ -12,49 +12,65 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<!-- This is what everything is rendered to during emulation -->
|
<FrameLayout
|
||||||
<org.yuzu.yuzu_emu.views.FixedRatioSurfaceView
|
android:id="@+id/emulation_container"
|
||||||
android:id="@+id/surface_emulation"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:layout_gravity="center"
|
|
||||||
android:focusable="false"
|
<!-- This is what everything is rendered to during emulation -->
|
||||||
android:focusableInTouchMode="false" />
|
<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
|
<FrameLayout
|
||||||
android:id="@+id/overlay_container"
|
android:id="@+id/input_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="bottom">
|
android:layout_gravity="bottom">
|
||||||
|
|
||||||
<!-- This is the onscreen input overlay -->
|
<!-- This is the onscreen input overlay -->
|
||||||
<org.yuzu.yuzu_emu.overlay.InputOverlay
|
<org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||||
android:id="@+id/surface_input_overlay"
|
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_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:focusable="true"
|
|
||||||
android:focusableInTouchMode="true" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/show_fps_text"
|
android:id="@+id/show_fps_text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="left"
|
android:layout_gravity="left"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:focusable="false"
|
android:focusable="false"
|
||||||
android:shadowColor="@android:color/black"
|
android:shadowColor="@android:color/black"
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
tools:ignore="RtlHardcoded" />
|
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>
|
</FrameLayout>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
|
@ -119,6 +119,18 @@
|
||||||
<item>3</item>
|
<item>3</item>
|
||||||
</integer-array>
|
</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">
|
<string-array name="rendererAspectRatioNames">
|
||||||
<item>@string/ratio_default</item>
|
<item>@string/ratio_default</item>
|
||||||
<item>@string/ratio_force_four_three</item>
|
<item>@string/ratio_force_four_three</item>
|
||||||
|
|
|
@ -34,4 +34,68 @@
|
||||||
<integer name="SWITCH_BUTTON_DPAD_X">260</integer>
|
<integer name="SWITCH_BUTTON_DPAD_X">260</integer>
|
||||||
<integer name="SWITCH_BUTTON_DPAD_Y">790</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>
|
</resources>
|
||||||
|
|
|
@ -162,6 +162,7 @@
|
||||||
<string name="renderer_accuracy">Accuracy level</string>
|
<string name="renderer_accuracy">Accuracy level</string>
|
||||||
<string name="renderer_resolution">Resolution (Handheld/Docked)</string>
|
<string name="renderer_resolution">Resolution (Handheld/Docked)</string>
|
||||||
<string name="renderer_vsync">VSync mode</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_aspect_ratio">Aspect ratio</string>
|
||||||
<string name="renderer_scaling_filter">Window adapting filter</string>
|
<string name="renderer_scaling_filter">Window adapting filter</string>
|
||||||
<string name="renderer_anti_aliasing">Anti-aliasing method</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_fxaa">FXAA</string>
|
||||||
<string name="anti_aliasing_smaa">SMAA</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 -->
|
<!-- Aspect Ratios -->
|
||||||
<string name="ratio_default">Default (16:9)</string>
|
<string name="ratio_default">Default (16:9)</string>
|
||||||
<string name="ratio_force_four_three">Force 4:3</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">Black backgrounds</string>
|
||||||
<string name="use_black_backgrounds_description">When using the dark theme, apply 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 -->
|
<!-- Licenses screen strings -->
|
||||||
<string name="licenses">Licenses</string>
|
<string name="licenses">Licenses</string>
|
||||||
<string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string>
|
<string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string>
|
||||||
|
|
|
@ -142,9 +142,13 @@ void NfcInterface::AttachAvailabilityChangeEvent(HLERequestContext& ctx) {
|
||||||
void NfcInterface::StartDetection(HLERequestContext& ctx) {
|
void NfcInterface::StartDetection(HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
const auto device_handle{rp.Pop<u64>()};
|
const auto device_handle{rp.Pop<u64>()};
|
||||||
const auto tag_protocol{rp.PopEnum<NfcProtocol>()};
|
auto tag_protocol{NfcProtocol::All};
|
||||||
LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol);
|
|
||||||
|
|
||||||
|
if (backend_type == BackendType::Nfc) {
|
||||||
|
tag_protocol = rp.PopEnum<NfcProtocol>();
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol);
|
||||||
auto result = GetManager()->StartDetection(device_handle, tag_protocol);
|
auto result = GetManager()->StartDetection(device_handle, tag_protocol);
|
||||||
result = TranslateResultToServiceError(result);
|
result = TranslateResultToServiceError(result);
|
||||||
|
|
||||||
|
|
|
@ -59,14 +59,11 @@ enum class PackedTagType : u8 {
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is nn::nfc::NfcProtocol
|
// This is nn::nfc::NfcProtocol
|
||||||
// Verify this enum. It might be completely wrong default protocol is 0x48
|
|
||||||
enum class NfcProtocol : u32 {
|
enum class NfcProtocol : u32 {
|
||||||
None,
|
None,
|
||||||
TypeA = 1U << 0, // ISO14443A
|
TypeA = 1U << 0, // ISO14443A
|
||||||
TypeB = 1U << 1, // ISO14443B
|
TypeB = 1U << 1, // ISO14443B
|
||||||
TypeF = 1U << 2, // Sony FeliCa
|
TypeF = 1U << 2, // Sony FeliCa
|
||||||
Unknown1 = 1U << 3,
|
|
||||||
Unknown2 = 1U << 5,
|
|
||||||
All = 0xFFFFFFFFU,
|
All = 0xFFFFFFFFU,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -275,9 +275,9 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c
|
||||||
template <typename Spec>
|
template <typename Spec>
|
||||||
void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
std::array<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views;
|
std::array<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views;
|
||||||
std::array<GLuint, MAX_TEXTURES> samplers;
|
std::array<const Sampler*, MAX_TEXTURES> samplers;
|
||||||
size_t views_index{};
|
size_t views_index{};
|
||||||
GLsizei sampler_binding{};
|
size_t samplers_index{};
|
||||||
|
|
||||||
texture_cache.SynchronizeGraphicsDescriptors();
|
texture_cache.SynchronizeGraphicsDescriptors();
|
||||||
|
|
||||||
|
@ -337,7 +337,6 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
const auto handle{read_handle(desc, index)};
|
const auto handle{read_handle(desc, index)};
|
||||||
views[views_index++] = {handle.first};
|
views[views_index++] = {handle.first};
|
||||||
samplers[sampler_binding++] = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,7 +351,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
views[views_index++] = {handle.first};
|
views[views_index++] = {handle.first};
|
||||||
|
|
||||||
Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)};
|
Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)};
|
||||||
samplers[sampler_binding++] = sampler->Handle();
|
samplers[samplers_index++] = sampler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if constexpr (Spec::has_images) {
|
if constexpr (Spec::has_images) {
|
||||||
|
@ -445,10 +444,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
program_manager.BindSourcePrograms(source_programs);
|
program_manager.BindSourcePrograms(source_programs);
|
||||||
}
|
}
|
||||||
const VideoCommon::ImageViewInOut* views_it{views.data()};
|
const VideoCommon::ImageViewInOut* views_it{views.data()};
|
||||||
|
const Sampler** samplers_it{samplers.data()};
|
||||||
GLsizei texture_binding = 0;
|
GLsizei texture_binding = 0;
|
||||||
GLsizei image_binding = 0;
|
GLsizei image_binding = 0;
|
||||||
|
GLsizei sampler_binding{};
|
||||||
std::array<GLuint, MAX_TEXTURES> textures;
|
std::array<GLuint, MAX_TEXTURES> textures;
|
||||||
std::array<GLuint, MAX_IMAGES> images;
|
std::array<GLuint, MAX_IMAGES> images;
|
||||||
|
std::array<GLuint, MAX_TEXTURES> gl_samplers;
|
||||||
const auto prepare_stage{[&](size_t stage) {
|
const auto prepare_stage{[&](size_t stage) {
|
||||||
buffer_cache.runtime.SetImagePointers(&textures[texture_binding], &images[image_binding]);
|
buffer_cache.runtime.SetImagePointers(&textures[texture_binding], &images[image_binding]);
|
||||||
buffer_cache.BindHostStageBuffers(stage);
|
buffer_cache.BindHostStageBuffers(stage);
|
||||||
|
@ -465,6 +467,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
u32 stage_image_binding{};
|
u32 stage_image_binding{};
|
||||||
|
|
||||||
const auto& info{stage_infos[stage]};
|
const auto& info{stage_infos[stage]};
|
||||||
|
if constexpr (Spec::has_texture_buffers) {
|
||||||
|
for (const auto& desc : info.texture_buffer_descriptors) {
|
||||||
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
|
gl_samplers[sampler_binding++] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const auto& desc : info.texture_descriptors) {
|
for (const auto& desc : info.texture_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
|
ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
|
||||||
|
@ -474,6 +483,12 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
}
|
}
|
||||||
++texture_binding;
|
++texture_binding;
|
||||||
++stage_texture_binding;
|
++stage_texture_binding;
|
||||||
|
|
||||||
|
const Sampler& sampler{**(samplers_it++)};
|
||||||
|
const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
|
||||||
|
!image_view.SupportsAnisotropy()};
|
||||||
|
gl_samplers[sampler_binding++] =
|
||||||
|
use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() : sampler.Handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto& desc : info.image_descriptors) {
|
for (const auto& desc : info.image_descriptors) {
|
||||||
|
@ -534,7 +549,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
if (texture_binding != 0) {
|
if (texture_binding != 0) {
|
||||||
ASSERT(texture_binding == sampler_binding);
|
ASSERT(texture_binding == sampler_binding);
|
||||||
glBindTextures(0, texture_binding, textures.data());
|
glBindTextures(0, texture_binding, textures.data());
|
||||||
glBindSamplers(0, sampler_binding, samplers.data());
|
glBindSamplers(0, sampler_binding, gl_samplers.data());
|
||||||
}
|
}
|
||||||
if (image_binding != 0) {
|
if (image_binding != 0) {
|
||||||
glBindImageTextures(0, image_binding, images.data());
|
glBindImageTextures(0, image_binding, images.data());
|
||||||
|
|
|
@ -16,9 +16,9 @@ struct ShaderPools {
|
||||||
inst.ReleaseContents();
|
inst.ReleaseContents();
|
||||||
}
|
}
|
||||||
|
|
||||||
Shader::ObjectPool<Shader::IR::Inst> inst;
|
Shader::ObjectPool<Shader::IR::Inst> inst{8192};
|
||||||
Shader::ObjectPool<Shader::IR::Block> block;
|
Shader::ObjectPool<Shader::IR::Block> block{32};
|
||||||
Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block;
|
Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Context {
|
struct Context {
|
||||||
|
|
|
@ -1268,36 +1268,48 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const TSCEntry& config) {
|
||||||
|
|
||||||
UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1);
|
UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1);
|
||||||
|
|
||||||
sampler.Create();
|
const f32 max_anisotropy = std::clamp(config.MaxAnisotropy(), 1.0f, 16.0f);
|
||||||
const GLuint handle = sampler.handle;
|
|
||||||
glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(config.wrap_u));
|
|
||||||
glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(config.wrap_v));
|
|
||||||
glSamplerParameteri(handle, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(config.wrap_p));
|
|
||||||
glSamplerParameteri(handle, GL_TEXTURE_COMPARE_MODE, compare_mode);
|
|
||||||
glSamplerParameteri(handle, GL_TEXTURE_COMPARE_FUNC, compare_func);
|
|
||||||
glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag);
|
|
||||||
glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min);
|
|
||||||
glSamplerParameterf(handle, GL_TEXTURE_LOD_BIAS, config.LodBias());
|
|
||||||
glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, config.MinLod());
|
|
||||||
glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, config.MaxLod());
|
|
||||||
glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, config.BorderColor().data());
|
|
||||||
|
|
||||||
if (GLAD_GL_ARB_texture_filter_anisotropic || GLAD_GL_EXT_texture_filter_anisotropic) {
|
const auto create_sampler = [&](const f32 anisotropy) {
|
||||||
const f32 max_anisotropy = std::clamp(config.MaxAnisotropy(), 1.0f, 16.0f);
|
OGLSampler new_sampler;
|
||||||
glSamplerParameterf(handle, GL_TEXTURE_MAX_ANISOTROPY, max_anisotropy);
|
new_sampler.Create();
|
||||||
} else {
|
const GLuint handle = new_sampler.handle;
|
||||||
LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_anisotropic is required");
|
glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(config.wrap_u));
|
||||||
}
|
glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(config.wrap_v));
|
||||||
if (GLAD_GL_ARB_texture_filter_minmax || GLAD_GL_EXT_texture_filter_minmax) {
|
glSamplerParameteri(handle, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(config.wrap_p));
|
||||||
glSamplerParameteri(handle, GL_TEXTURE_REDUCTION_MODE_ARB, reduction_filter);
|
glSamplerParameteri(handle, GL_TEXTURE_COMPARE_MODE, compare_mode);
|
||||||
} else if (reduction_filter != GL_WEIGHTED_AVERAGE_ARB) {
|
glSamplerParameteri(handle, GL_TEXTURE_COMPARE_FUNC, compare_func);
|
||||||
LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_minmax is required");
|
glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag);
|
||||||
}
|
glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min);
|
||||||
if (GLAD_GL_ARB_seamless_cubemap_per_texture || GLAD_GL_AMD_seamless_cubemap_per_texture) {
|
glSamplerParameterf(handle, GL_TEXTURE_LOD_BIAS, config.LodBias());
|
||||||
glSamplerParameteri(handle, GL_TEXTURE_CUBE_MAP_SEAMLESS, seamless);
|
glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, config.MinLod());
|
||||||
} else if (seamless == GL_FALSE) {
|
glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, config.MaxLod());
|
||||||
// We default to false because it's more common
|
glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, config.BorderColor().data());
|
||||||
LOG_WARNING(Render_OpenGL, "GL_ARB_seamless_cubemap_per_texture is required");
|
|
||||||
|
if (GLAD_GL_ARB_texture_filter_anisotropic || GLAD_GL_EXT_texture_filter_anisotropic) {
|
||||||
|
glSamplerParameterf(handle, GL_TEXTURE_MAX_ANISOTROPY, anisotropy);
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_anisotropic is required");
|
||||||
|
}
|
||||||
|
if (GLAD_GL_ARB_texture_filter_minmax || GLAD_GL_EXT_texture_filter_minmax) {
|
||||||
|
glSamplerParameteri(handle, GL_TEXTURE_REDUCTION_MODE_ARB, reduction_filter);
|
||||||
|
} else if (reduction_filter != GL_WEIGHTED_AVERAGE_ARB) {
|
||||||
|
LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_minmax is required");
|
||||||
|
}
|
||||||
|
if (GLAD_GL_ARB_seamless_cubemap_per_texture || GLAD_GL_AMD_seamless_cubemap_per_texture) {
|
||||||
|
glSamplerParameteri(handle, GL_TEXTURE_CUBE_MAP_SEAMLESS, seamless);
|
||||||
|
} else if (seamless == GL_FALSE) {
|
||||||
|
// We default to false because it's more common
|
||||||
|
LOG_WARNING(Render_OpenGL, "GL_ARB_seamless_cubemap_per_texture is required");
|
||||||
|
}
|
||||||
|
return new_sampler;
|
||||||
|
};
|
||||||
|
|
||||||
|
sampler = create_sampler(max_anisotropy);
|
||||||
|
|
||||||
|
const f32 max_anisotropy_default = static_cast<f32>(1U << config.max_anisotropy);
|
||||||
|
if (max_anisotropy > max_anisotropy_default) {
|
||||||
|
sampler_default_anisotropy = create_sampler(max_anisotropy_default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -309,12 +309,21 @@ class Sampler {
|
||||||
public:
|
public:
|
||||||
explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&);
|
explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&);
|
||||||
|
|
||||||
GLuint Handle() const noexcept {
|
[[nodiscard]] GLuint Handle() const noexcept {
|
||||||
return sampler.handle;
|
return sampler.handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] GLuint HandleWithDefaultAnisotropy() const noexcept {
|
||||||
|
return sampler_default_anisotropy.handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool HasAddedAnisotropy() const noexcept {
|
||||||
|
return static_cast<bool>(sampler_default_anisotropy.handle);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OGLSampler sampler;
|
OGLSampler sampler;
|
||||||
|
OGLSampler sampler_default_anisotropy;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Framebuffer {
|
class Framebuffer {
|
||||||
|
|
|
@ -178,7 +178,7 @@ public:
|
||||||
inline void PushImageDescriptors(TextureCache& texture_cache,
|
inline void PushImageDescriptors(TextureCache& texture_cache,
|
||||||
GuestDescriptorQueue& guest_descriptor_queue,
|
GuestDescriptorQueue& guest_descriptor_queue,
|
||||||
const Shader::Info& info, RescalingPushConstant& rescaling,
|
const Shader::Info& info, RescalingPushConstant& rescaling,
|
||||||
const VkSampler*& samplers,
|
const Sampler**& samplers,
|
||||||
const VideoCommon::ImageViewInOut*& views) {
|
const VideoCommon::ImageViewInOut*& views) {
|
||||||
const u32 num_texture_buffers = Shader::NumDescriptors(info.texture_buffer_descriptors);
|
const u32 num_texture_buffers = Shader::NumDescriptors(info.texture_buffer_descriptors);
|
||||||
const u32 num_image_buffers = Shader::NumDescriptors(info.image_buffer_descriptors);
|
const u32 num_image_buffers = Shader::NumDescriptors(info.image_buffer_descriptors);
|
||||||
|
@ -187,10 +187,14 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
|
||||||
for (const auto& desc : info.texture_descriptors) {
|
for (const auto& desc : info.texture_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
const VideoCommon::ImageViewId image_view_id{(views++)->id};
|
const VideoCommon::ImageViewId image_view_id{(views++)->id};
|
||||||
const VkSampler sampler{*(samplers++)};
|
|
||||||
ImageView& image_view{texture_cache.GetImageView(image_view_id)};
|
ImageView& image_view{texture_cache.GetImageView(image_view_id)};
|
||||||
const VkImageView vk_image_view{image_view.Handle(desc.type)};
|
const VkImageView vk_image_view{image_view.Handle(desc.type)};
|
||||||
guest_descriptor_queue.AddSampledImage(vk_image_view, sampler);
|
const Sampler& sampler{**(samplers++)};
|
||||||
|
const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
|
||||||
|
!image_view.SupportsAnisotropy()};
|
||||||
|
const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
|
||||||
|
: sampler.Handle()};
|
||||||
|
guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler);
|
||||||
rescaling.PushTexture(texture_cache.IsRescaling(image_view));
|
rescaling.PushTexture(texture_cache.IsRescaling(image_view));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||||
|
|
||||||
static constexpr size_t max_elements = 64;
|
static constexpr size_t max_elements = 64;
|
||||||
boost::container::static_vector<VideoCommon::ImageViewInOut, max_elements> views;
|
boost::container::static_vector<VideoCommon::ImageViewInOut, max_elements> views;
|
||||||
boost::container::static_vector<VkSampler, max_elements> samplers;
|
boost::container::static_vector<const Sampler*, max_elements> samplers;
|
||||||
|
|
||||||
const auto& qmd{kepler_compute.launch_description};
|
const auto& qmd{kepler_compute.launch_description};
|
||||||
const auto& cbufs{qmd.const_buffer_config};
|
const auto& cbufs{qmd.const_buffer_config};
|
||||||
|
@ -161,7 +161,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||||
views.push_back({handle.first});
|
views.push_back({handle.first});
|
||||||
|
|
||||||
Sampler* const sampler = texture_cache.GetComputeSampler(handle.second);
|
Sampler* const sampler = texture_cache.GetComputeSampler(handle.second);
|
||||||
samplers.push_back(sampler->Handle());
|
samplers.push_back(sampler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto& desc : info.image_descriptors) {
|
for (const auto& desc : info.image_descriptors) {
|
||||||
|
@ -192,7 +192,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||||
buffer_cache.BindHostComputeBuffers();
|
buffer_cache.BindHostComputeBuffers();
|
||||||
|
|
||||||
RescalingPushConstant rescaling;
|
RescalingPushConstant rescaling;
|
||||||
const VkSampler* samplers_it{samplers.data()};
|
const Sampler** samplers_it{samplers.data()};
|
||||||
const VideoCommon::ImageViewInOut* views_it{views.data()};
|
const VideoCommon::ImageViewInOut* views_it{views.data()};
|
||||||
PushImageDescriptors(texture_cache, guest_descriptor_queue, info, rescaling, samplers_it,
|
PushImageDescriptors(texture_cache, guest_descriptor_queue, info, rescaling, samplers_it,
|
||||||
views_it);
|
views_it);
|
||||||
|
|
|
@ -298,7 +298,7 @@ void GraphicsPipeline::AddTransition(GraphicsPipeline* transition) {
|
||||||
template <typename Spec>
|
template <typename Spec>
|
||||||
void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
std::array<VideoCommon::ImageViewInOut, MAX_IMAGE_ELEMENTS> views;
|
std::array<VideoCommon::ImageViewInOut, MAX_IMAGE_ELEMENTS> views;
|
||||||
std::array<VkSampler, MAX_IMAGE_ELEMENTS> samplers;
|
std::array<const Sampler*, MAX_IMAGE_ELEMENTS> samplers;
|
||||||
size_t sampler_index{};
|
size_t sampler_index{};
|
||||||
size_t view_index{};
|
size_t view_index{};
|
||||||
|
|
||||||
|
@ -368,7 +368,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
views[view_index++] = {handle.first};
|
views[view_index++] = {handle.first};
|
||||||
|
|
||||||
Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)};
|
Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)};
|
||||||
samplers[sampler_index++] = sampler->Handle();
|
samplers[sampler_index++] = sampler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if constexpr (Spec::has_images) {
|
if constexpr (Spec::has_images) {
|
||||||
|
@ -453,7 +453,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
|
|
||||||
RescalingPushConstant rescaling;
|
RescalingPushConstant rescaling;
|
||||||
RenderAreaPushConstant render_area;
|
RenderAreaPushConstant render_area;
|
||||||
const VkSampler* samplers_it{samplers.data()};
|
const Sampler** samplers_it{samplers.data()};
|
||||||
const VideoCommon::ImageViewInOut* views_it{views.data()};
|
const VideoCommon::ImageViewInOut* views_it{views.data()};
|
||||||
const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
|
const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
|
||||||
buffer_cache.BindHostStageBuffers(stage);
|
buffer_cache.BindHostStageBuffers(stage);
|
||||||
|
|
|
@ -92,9 +92,9 @@ struct ShaderPools {
|
||||||
inst.ReleaseContents();
|
inst.ReleaseContents();
|
||||||
}
|
}
|
||||||
|
|
||||||
Shader::ObjectPool<Shader::IR::Inst> inst;
|
Shader::ObjectPool<Shader::IR::Inst> inst{8192};
|
||||||
Shader::ObjectPool<Shader::IR::Block> block;
|
Shader::ObjectPool<Shader::IR::Block> block{32};
|
||||||
Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block;
|
Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32};
|
||||||
};
|
};
|
||||||
|
|
||||||
class PipelineCache : public VideoCommon::ShaderCache {
|
class PipelineCache : public VideoCommon::ShaderCache {
|
||||||
|
|
|
@ -1803,27 +1803,36 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t
|
||||||
// Some games have samplers with garbage. Sanitize them here.
|
// Some games have samplers with garbage. Sanitize them here.
|
||||||
const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f);
|
const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f);
|
||||||
|
|
||||||
sampler = device.GetLogical().CreateSampler(VkSamplerCreateInfo{
|
const auto create_sampler = [&](const f32 anisotropy) {
|
||||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
return device.GetLogical().CreateSampler(VkSamplerCreateInfo{
|
||||||
.pNext = pnext,
|
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||||
.flags = 0,
|
.pNext = pnext,
|
||||||
.magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter),
|
.flags = 0,
|
||||||
.minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter),
|
.magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter),
|
||||||
.mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter),
|
.minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter),
|
||||||
.addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter),
|
.mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter),
|
||||||
.addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter),
|
.addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter),
|
||||||
.addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter),
|
.addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter),
|
||||||
.mipLodBias = tsc.LodBias(),
|
.addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter),
|
||||||
.anisotropyEnable = static_cast<VkBool32>(max_anisotropy > 1.0f ? VK_TRUE : VK_FALSE),
|
.mipLodBias = tsc.LodBias(),
|
||||||
.maxAnisotropy = max_anisotropy,
|
.anisotropyEnable = static_cast<VkBool32>(anisotropy > 1.0f ? VK_TRUE : VK_FALSE),
|
||||||
.compareEnable = tsc.depth_compare_enabled,
|
.maxAnisotropy = anisotropy,
|
||||||
.compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func),
|
.compareEnable = tsc.depth_compare_enabled,
|
||||||
.minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(),
|
.compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func),
|
||||||
.maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(),
|
.minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(),
|
||||||
.borderColor =
|
.maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(),
|
||||||
arbitrary_borders ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT : ConvertBorderColor(color),
|
.borderColor =
|
||||||
.unnormalizedCoordinates = VK_FALSE,
|
arbitrary_borders ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT : ConvertBorderColor(color),
|
||||||
});
|
.unnormalizedCoordinates = VK_FALSE,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
sampler = create_sampler(max_anisotropy);
|
||||||
|
|
||||||
|
const f32 max_anisotropy_default = static_cast<f32>(1U << tsc.max_anisotropy);
|
||||||
|
if (max_anisotropy > max_anisotropy_default) {
|
||||||
|
sampler_default_anisotropy = create_sampler(max_anisotropy_default);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers,
|
Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers,
|
||||||
|
|
|
@ -279,8 +279,17 @@ public:
|
||||||
return *sampler;
|
return *sampler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] VkSampler HandleWithDefaultAnisotropy() const noexcept {
|
||||||
|
return *sampler_default_anisotropy;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool HasAddedAnisotropy() const noexcept {
|
||||||
|
return static_cast<bool>(sampler_default_anisotropy);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
vk::Sampler sampler;
|
vk::Sampler sampler;
|
||||||
|
vk::Sampler sampler_default_anisotropy;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Framebuffer {
|
class Framebuffer {
|
||||||
|
|
|
@ -45,4 +45,52 @@ ImageViewBase::ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_in
|
||||||
|
|
||||||
ImageViewBase::ImageViewBase(const NullImageViewParams&) : image_id{NULL_IMAGE_ID} {}
|
ImageViewBase::ImageViewBase(const NullImageViewParams&) : image_id{NULL_IMAGE_ID} {}
|
||||||
|
|
||||||
|
bool ImageViewBase::SupportsAnisotropy() const noexcept {
|
||||||
|
using namespace VideoCommon;
|
||||||
|
switch (format) {
|
||||||
|
case PixelFormat::R8_UNORM:
|
||||||
|
case PixelFormat::R8_SNORM:
|
||||||
|
case PixelFormat::R8_SINT:
|
||||||
|
case PixelFormat::R8_UINT:
|
||||||
|
case PixelFormat::BC4_UNORM:
|
||||||
|
case PixelFormat::BC4_SNORM:
|
||||||
|
case PixelFormat::BC5_UNORM:
|
||||||
|
case PixelFormat::BC5_SNORM:
|
||||||
|
case PixelFormat::R32G32_FLOAT:
|
||||||
|
case PixelFormat::R32G32_SINT:
|
||||||
|
case PixelFormat::R32_FLOAT:
|
||||||
|
case PixelFormat::R16_FLOAT:
|
||||||
|
case PixelFormat::R16_UNORM:
|
||||||
|
case PixelFormat::R16_SNORM:
|
||||||
|
case PixelFormat::R16_UINT:
|
||||||
|
case PixelFormat::R16_SINT:
|
||||||
|
case PixelFormat::R16G16_UNORM:
|
||||||
|
case PixelFormat::R16G16_FLOAT:
|
||||||
|
case PixelFormat::R16G16_UINT:
|
||||||
|
case PixelFormat::R16G16_SINT:
|
||||||
|
case PixelFormat::R16G16_SNORM:
|
||||||
|
case PixelFormat::R8G8_UNORM:
|
||||||
|
case PixelFormat::R8G8_SNORM:
|
||||||
|
case PixelFormat::R8G8_SINT:
|
||||||
|
case PixelFormat::R8G8_UINT:
|
||||||
|
case PixelFormat::R32G32_UINT:
|
||||||
|
case PixelFormat::R32_UINT:
|
||||||
|
case PixelFormat::R32_SINT:
|
||||||
|
case PixelFormat::G4R4_UNORM:
|
||||||
|
// Depth formats
|
||||||
|
case PixelFormat::D32_FLOAT:
|
||||||
|
case PixelFormat::D16_UNORM:
|
||||||
|
// Stencil formats
|
||||||
|
case PixelFormat::S8_UINT:
|
||||||
|
// DepthStencil formats
|
||||||
|
case PixelFormat::D24_UNORM_S8_UINT:
|
||||||
|
case PixelFormat::S8_UINT_D24_UNORM:
|
||||||
|
case PixelFormat::D32_FLOAT_S8_UINT:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return range.extent.levels > 1;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace VideoCommon
|
} // namespace VideoCommon
|
||||||
|
|
|
@ -33,6 +33,8 @@ struct ImageViewBase {
|
||||||
return type == ImageViewType::Buffer;
|
return type == ImageViewType::Buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool SupportsAnisotropy() const noexcept;
|
||||||
|
|
||||||
ImageId image_id{};
|
ImageId image_id{};
|
||||||
GPUVAddr gpu_addr = 0;
|
GPUVAddr gpu_addr = 0;
|
||||||
PixelFormat format{};
|
PixelFormat format{};
|
||||||
|
|
|
@ -62,12 +62,14 @@ std::array<float, 4> TSCEntry::BorderColor() const noexcept {
|
||||||
}
|
}
|
||||||
|
|
||||||
float TSCEntry::MaxAnisotropy() const noexcept {
|
float TSCEntry::MaxAnisotropy() const noexcept {
|
||||||
const bool is_unsupported_mipmap_filter = Settings::values.use_aggressive_anisotropic_filtering
|
const bool is_suitable_mipmap_filter = Settings::values.use_aggressive_anisotropic_filtering
|
||||||
? mipmap_filter == TextureMipmapFilter::None
|
? mipmap_filter != TextureMipmapFilter::None
|
||||||
: mipmap_filter != TextureMipmapFilter::Linear;
|
: mipmap_filter == TextureMipmapFilter::Linear;
|
||||||
const bool has_regular_lods = min_lod_clamp == 0 && max_lod_clamp >= 256;
|
const bool has_regular_lods = min_lod_clamp == 0 && max_lod_clamp >= 256;
|
||||||
if (max_anisotropy == 0 &&
|
const bool is_bilinear_filter = min_filter == TextureFilter::Linear &&
|
||||||
(depth_compare_enabled.Value() || !has_regular_lods || is_unsupported_mipmap_filter)) {
|
reduction_filter == SamplerReduction::WeightedAverage;
|
||||||
|
if (max_anisotropy == 0 && (depth_compare_enabled || !has_regular_lods || !is_bilinear_filter ||
|
||||||
|
!is_suitable_mipmap_filter)) {
|
||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
const auto anisotropic_settings = Settings::values.max_anisotropy.GetValue();
|
const auto anisotropic_settings = Settings::values.max_anisotropy.GetValue();
|
||||||
|
|
Loading…
Reference in a new issue