mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-21 07:18:33 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a72b9d622 | |||
| 1112f12af7 | |||
| ed7c256728 | |||
| bc96de8eb6 | |||
| 417d80fc42 | |||
| 0ea9b231da | |||
| bdcdb14fce | |||
| 9a8a30ba43 | |||
| 59384de777 | |||
| 9b20c6c609 | |||
| 1428abe1d1 |
+1
@@ -39,6 +39,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
RENDERER_SAMPLE_SHADING("sample_shading"),
|
||||
RENDERER_FRAME_GEN("frame_gen"),
|
||||
RENDERER_FRAME_GEN_FP16("frame_gen_fp16"),
|
||||
RENDERER_FRAME_GEN_FLOW_SCALE_AUTO("frame_gen_flow_scale_auto"),
|
||||
RENDERER_FRAME_GEN_DUMP_FLOW("frame_gen_dump_flow"),
|
||||
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
|
||||
PICTURE_IN_PICTURE("picture_in_picture"),
|
||||
|
||||
@@ -20,6 +20,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
RENDERER_ACCURACY("gpu_accuracy"),
|
||||
RENDERER_RESOLUTION("resolution_setup"),
|
||||
RENDERER_FRAME_GEN_MULTIPLIER("frame_gen_multiplier"),
|
||||
RENDERER_FRAME_GEN_TARGET_RATE("frame_gen_target_rate"),
|
||||
RENDERER_FRAME_GEN_QUEUE_TARGET("frame_gen_queue_target"),
|
||||
RENDERER_FRAME_GEN_FLOW_SCALE("frame_gen_flow_scale"),
|
||||
RENDERER_VSYNC("use_vsync"),
|
||||
RENDERER_SCALING_FILTER("scaling_filter"),
|
||||
|
||||
+48
@@ -72,6 +72,13 @@ abstract class SettingsItem(
|
||||
return false
|
||||
}
|
||||
|
||||
// A frame rate target moves the multiplier on its own
|
||||
if (setting.key == IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key &&
|
||||
frameGenTargetRate != 0
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Can't edit settings that aren't saveable in per-game config even if they are switchable
|
||||
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
|
||||
return false
|
||||
@@ -95,10 +102,26 @@ abstract class SettingsItem(
|
||||
val clearable: Boolean
|
||||
get() = !setting.global && NativeConfig.isPerGameConfigLoaded()
|
||||
|
||||
private val frameGenTargetRate: Int
|
||||
get() {
|
||||
val key = IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key
|
||||
val needsGlobal = if (NativeLibrary.isRunning() &&
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
!NativeConfig.usingGlobal(key)
|
||||
} else {
|
||||
NativeConfig.usingGlobal(key)
|
||||
}
|
||||
return IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.getInt(needsGlobal)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val frameGenKeys = setOf(
|
||||
BooleanSetting.RENDERER_FRAME_GEN.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key,
|
||||
BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key,
|
||||
BooleanSetting.RENDERER_FRAME_GEN_FP16.key,
|
||||
BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key
|
||||
@@ -638,6 +661,31 @@ abstract class SettingsItem(
|
||||
valuesId = R.array.frameGenMultiplierValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_TARGET_RATE,
|
||||
titleId = R.string.frame_gen_target_rate,
|
||||
descriptionId = R.string.frame_gen_target_rate_description,
|
||||
choicesId = R.array.frameGenTargetRateNames,
|
||||
valuesId = R.array.frameGenTargetRateValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET,
|
||||
titleId = R.string.frame_gen_queue_target,
|
||||
descriptionId = R.string.frame_gen_queue_target_description,
|
||||
choicesId = R.array.frameGenQueueTargetNames,
|
||||
valuesId = R.array.frameGenQueueTargetValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO,
|
||||
titleId = R.string.frame_gen_flow_scale_auto,
|
||||
descriptionId = R.string.frame_gen_flow_scale_auto_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SliderSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE,
|
||||
|
||||
+3
-1
@@ -382,7 +382,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
}
|
||||
scSetting.setSelectedValue(value)
|
||||
|
||||
if (scSetting.setting.key == IntSetting.RENDERER_SCALING_FILTER.key) {
|
||||
if (scSetting.setting.key == IntSetting.RENDERER_SCALING_FILTER.key ||
|
||||
scSetting.setting.key == IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key
|
||||
) {
|
||||
settingsViewModel.setShouldReloadSettingsList(true)
|
||||
}
|
||||
|
||||
|
||||
-51
@@ -33,7 +33,6 @@ import org.yuzu.yuzu_emu.features.input.NativeInput
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.PathSetting
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.fragments.ProgressDialogFragment
|
||||
import org.yuzu.yuzu_emu.utils.PathUtil
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
@@ -115,29 +114,6 @@ class SettingsFragment : Fragment() {
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setShouldReloadSettingsList(false) }
|
||||
) { if (it) presenter.loadSettingsList() }
|
||||
settingsViewModel.shouldShowLosslessInstaller.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setShouldShowLosslessInstaller(false) }
|
||||
) { if (it) losslessDllPickerLauncher.launch(arrayOf("*/*")) }
|
||||
settingsViewModel.shouldShowLosslessRemoveDialog.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setShouldShowLosslessRemoveDialog(false) }
|
||||
) {
|
||||
if (it) {
|
||||
MessageDialogFragment.newInstance(
|
||||
activity = requireActivity(),
|
||||
titleId = R.string.lossless_scaling_remove,
|
||||
descriptionId = R.string.lossless_scaling_remove_confirmation,
|
||||
positiveButtonTitleId = R.string.lossless_scaling_remove,
|
||||
positiveAction = {
|
||||
LosslessScalingHelper.remove()
|
||||
settingsViewModel.setShouldReloadSettingsList(true)
|
||||
},
|
||||
showNegativeButton = true,
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
settingsViewModel.adapterItemChanged.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setAdapterItemChanged(-1) }
|
||||
@@ -291,33 +267,6 @@ private fun getPlayerIndex(): Int =
|
||||
directoryPickerLauncher.launch(null)
|
||||
}
|
||||
|
||||
private val losslessDllPickerLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.OpenDocument()
|
||||
) { uri ->
|
||||
if (uri == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val resultStrings = resources.getStringArray(R.array.losslessDllResults)
|
||||
ProgressDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
R.string.lossless_scaling_installing,
|
||||
false
|
||||
) { _, _ ->
|
||||
val result = LosslessScalingHelper.install(uri)
|
||||
if (result == LosslessScalingHelper.RESULT_OK) {
|
||||
getString(R.string.lossless_scaling_install_success)
|
||||
} else {
|
||||
MessageDialogFragment.newInstance(
|
||||
titleId = R.string.lossless_scaling_install_failed,
|
||||
descriptionString = resultStrings[result]
|
||||
)
|
||||
}
|
||||
}.apply {
|
||||
onDialogComplete = { settingsViewModel.setShouldReloadSettingsList(true) }
|
||||
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
private val directoryPickerLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.OpenDocumentTree()
|
||||
) { uri ->
|
||||
|
||||
+15
-43
@@ -79,12 +79,7 @@ class SettingsFragmentPresenter(
|
||||
|
||||
private fun addFrameGenSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
val installed = LosslessScalingHelper.isInstalled()
|
||||
val supported = LosslessScalingHelper.isSupportedByGpu()
|
||||
|
||||
add(HeaderSetting(R.string.lossless_scaling))
|
||||
|
||||
if (!supported) {
|
||||
if (!LosslessScalingHelper.isSupportedByGpu()) {
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.frame_gen_unsupported,
|
||||
@@ -92,41 +87,27 @@ class SettingsFragmentPresenter(
|
||||
isRunnable = false
|
||||
) {}
|
||||
)
|
||||
}
|
||||
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = if (installed) {
|
||||
R.string.lossless_scaling_replace
|
||||
} else {
|
||||
R.string.lossless_scaling_install
|
||||
},
|
||||
descriptionId = if (installed) {
|
||||
R.string.lossless_scaling_replace_description
|
||||
} else {
|
||||
R.string.lossless_scaling_install_description
|
||||
},
|
||||
isRunnable = !NativeLibrary.isRunning(),
|
||||
iconId = R.drawable.ic_install
|
||||
) { settingsViewModel.setShouldShowLosslessInstaller(true) }
|
||||
)
|
||||
|
||||
if (installed) {
|
||||
} else if (!LosslessScalingHelper.isInstalled()) {
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.lossless_scaling_remove,
|
||||
descriptionId = R.string.lossless_scaling_remove_description,
|
||||
isRunnable = !NativeLibrary.isRunning(),
|
||||
iconId = R.drawable.ic_delete
|
||||
) { settingsViewModel.setShouldShowLosslessRemoveDialog(true) }
|
||||
titleId = R.string.lossless_scaling_missing,
|
||||
descriptionId = R.string.lossless_scaling_missing_description,
|
||||
isRunnable = false
|
||||
) {}
|
||||
)
|
||||
}
|
||||
|
||||
add(HeaderSetting(R.string.frame_gen))
|
||||
|
||||
add(BooleanSetting.RENDERER_FRAME_GEN.key)
|
||||
add(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key)
|
||||
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key)
|
||||
add(IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key)
|
||||
add(IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key)
|
||||
add(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key)
|
||||
if (!BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean(
|
||||
getNeedsGlobalForKey(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key)
|
||||
)
|
||||
) {
|
||||
add(IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key)
|
||||
}
|
||||
add(BooleanSetting.RENDERER_FRAME_GEN_FP16.key)
|
||||
}
|
||||
}
|
||||
@@ -336,15 +317,6 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
add(IntSetting.RENDERER_ANTI_ALIASING.key)
|
||||
|
||||
add(
|
||||
SubmenuSetting(
|
||||
titleId = R.string.frame_gen,
|
||||
descriptionId = R.string.frame_gen_submenu_description,
|
||||
iconId = R.drawable.ic_frames,
|
||||
menuKey = MenuTag.SECTION_FRAME_GEN
|
||||
)
|
||||
)
|
||||
|
||||
add(HeaderSetting(R.string.advanced))
|
||||
|
||||
add(IntSetting.RENDERER_ACCURACY.key)
|
||||
|
||||
+2
@@ -29,6 +29,7 @@ enum class SettingsSubscreen {
|
||||
DRIVER_MANAGER,
|
||||
DRIVER_FETCHER,
|
||||
FREEDRENO_SETTINGS,
|
||||
LOSSLESS_MANAGER,
|
||||
APPLET_LAUNCHER,
|
||||
INSTALLABLE,
|
||||
GAME_FOLDERS,
|
||||
@@ -126,6 +127,7 @@ class SettingsSubscreenActivity : AppCompatActivity() {
|
||||
SettingsSubscreen.DRIVER_MANAGER -> R.id.driverManagerFragment
|
||||
SettingsSubscreen.DRIVER_FETCHER -> R.id.driverFetcherFragment
|
||||
SettingsSubscreen.FREEDRENO_SETTINGS -> R.id.freedrenoSettingsFragment
|
||||
SettingsSubscreen.LOSSLESS_MANAGER -> R.id.losslessManagerFragment
|
||||
SettingsSubscreen.APPLET_LAUNCHER -> R.id.appletLauncherFragment
|
||||
SettingsSubscreen.INSTALLABLE -> R.id.installableFragment
|
||||
SettingsSubscreen.GAME_FOLDERS -> R.id.gameFoldersFragment
|
||||
|
||||
-14
@@ -36,12 +36,6 @@ class SettingsViewModel : ViewModel() {
|
||||
val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
|
||||
private val _shouldReloadSettingsList = MutableStateFlow(false)
|
||||
|
||||
val shouldShowLosslessInstaller: StateFlow<Boolean> get() = _shouldShowLosslessInstaller
|
||||
private val _shouldShowLosslessInstaller = MutableStateFlow(false)
|
||||
|
||||
val shouldShowLosslessRemoveDialog: StateFlow<Boolean> get() = _shouldShowLosslessRemoveDialog
|
||||
private val _shouldShowLosslessRemoveDialog = MutableStateFlow(false)
|
||||
|
||||
val sliderProgress: StateFlow<Int> get() = _sliderProgress
|
||||
private val _sliderProgress = MutableStateFlow(-1)
|
||||
|
||||
@@ -91,14 +85,6 @@ class SettingsViewModel : ViewModel() {
|
||||
_shouldReloadSettingsList.value = value
|
||||
}
|
||||
|
||||
fun setShouldShowLosslessInstaller(value: Boolean) {
|
||||
_shouldShowLosslessInstaller.value = value
|
||||
}
|
||||
|
||||
fun setShouldShowLosslessRemoveDialog(value: Boolean) {
|
||||
_shouldShowLosslessRemoveDialog.value = value
|
||||
}
|
||||
|
||||
fun setSliderTextValue(value: Float, units: String) {
|
||||
_sliderProgress.value = value.toInt()
|
||||
_sliderTextValue.value = String.format(
|
||||
|
||||
@@ -369,6 +369,21 @@ class GamePropertiesFragment : Fragment() {
|
||||
)
|
||||
)
|
||||
}
|
||||
add(
|
||||
SubmenuProperty(
|
||||
R.string.frame_gen,
|
||||
R.string.frame_gen_per_game_description,
|
||||
R.drawable.ic_duck,
|
||||
action = {
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
args.game,
|
||||
Settings.MenuTag.SECTION_FRAME_GEN
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
if (GpuDriverHelper.isAdrenoGpu()) {
|
||||
add(
|
||||
SubmenuProperty(
|
||||
|
||||
@@ -13,7 +13,6 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
@@ -176,8 +175,14 @@ class HomeSettingsFragment : Fragment() {
|
||||
HomeSetting(
|
||||
R.string.lossless_scaling,
|
||||
R.string.lossless_scaling_description,
|
||||
R.drawable.ic_frames,
|
||||
{ onLosslessScalingClicked() },
|
||||
R.drawable.ic_duck,
|
||||
{
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.LOSSLESS_MANAGER,
|
||||
null
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
},
|
||||
{ true },
|
||||
0,
|
||||
0,
|
||||
@@ -354,48 +359,6 @@ class HomeSettingsFragment : Fragment() {
|
||||
LosslessScalingHelper.refreshStatus()
|
||||
}
|
||||
|
||||
private fun onLosslessScalingClicked() {
|
||||
if (!LosslessScalingHelper.isInstalled()) {
|
||||
getLosslessDllLauncher.launch(arrayOf("*/*"))
|
||||
return
|
||||
}
|
||||
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.lossless_scaling,
|
||||
descriptionId = R.string.lossless_scaling_installed_description,
|
||||
positiveButtonTitleId = R.string.lossless_scaling_replace,
|
||||
positiveAction = { getLosslessDllLauncher.launch(arrayOf("*/*")) },
|
||||
showNegativeButton = true,
|
||||
negativeButtonTitleId = R.string.lossless_scaling_remove,
|
||||
negativeAction = { LosslessScalingHelper.remove() }
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
|
||||
private val getLosslessDllLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val resultStrings = resources.getStringArray(R.array.losslessDllResults)
|
||||
ProgressDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
R.string.lossless_scaling_installing,
|
||||
false
|
||||
) { _, _ ->
|
||||
val installResult = LosslessScalingHelper.install(result)
|
||||
if (installResult == LosslessScalingHelper.RESULT_OK) {
|
||||
getString(R.string.lossless_scaling_install_success)
|
||||
} else {
|
||||
MessageDialogFragment.newInstance(
|
||||
titleId = R.string.lossless_scaling_install_failed,
|
||||
descriptionString = resultStrings[installResult]
|
||||
)
|
||||
}
|
||||
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentLosslessManagerBinding
|
||||
import org.yuzu.yuzu_emu.features.fetcher.SpacingItemDecoration
|
||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class LosslessManagerFragment : Fragment() {
|
||||
private var _binding: FragmentLosslessManagerBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentLosslessManagerBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.toolbarLossless.setNavigationOnClickListener {
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
binding.losslessOptionsList.apply {
|
||||
layoutManager =
|
||||
GridLayoutManager(requireContext(), resources.getInteger(R.integer.grid_columns))
|
||||
addItemDecoration(
|
||||
SpacingItemDecoration(resources.getDimensionPixelSize(R.dimen.spacing_small))
|
||||
)
|
||||
}
|
||||
|
||||
LosslessScalingHelper.statusText.collect(viewLifecycleOwner) { refreshOptions() }
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
||||
private fun refreshOptions() {
|
||||
binding.losslessOptionsList.adapter = HomeSettingAdapter(
|
||||
requireActivity() as AppCompatActivity,
|
||||
viewLifecycleOwner,
|
||||
buildOptions()
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildOptions(): List<HomeSetting> {
|
||||
val installed = LosslessScalingHelper.isInstalled()
|
||||
return listOf(
|
||||
HomeSetting(
|
||||
if (installed) R.string.lossless_scaling_replace else R.string.lossless_scaling_install,
|
||||
if (installed) {
|
||||
R.string.lossless_scaling_replace_description
|
||||
} else {
|
||||
R.string.lossless_scaling_install_description
|
||||
},
|
||||
R.drawable.ic_install,
|
||||
{ dllPickerLauncher.launch(arrayOf("*/*")) },
|
||||
{ !NativeLibrary.isRunning() },
|
||||
R.string.lossless_scaling_locked,
|
||||
R.string.lossless_scaling_locked_description,
|
||||
LosslessScalingHelper.statusText
|
||||
),
|
||||
HomeSetting(
|
||||
R.string.lossless_scaling_remove,
|
||||
R.string.lossless_scaling_remove_description,
|
||||
R.drawable.ic_delete,
|
||||
{ confirmRemoval() },
|
||||
{ installed && !NativeLibrary.isRunning() },
|
||||
if (installed) {
|
||||
R.string.lossless_scaling_locked
|
||||
} else {
|
||||
R.string.lossless_scaling_remove_unavailable
|
||||
},
|
||||
if (installed) {
|
||||
R.string.lossless_scaling_locked_description
|
||||
} else {
|
||||
R.string.lossless_scaling_remove_unavailable_description
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun confirmRemoval() {
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.lossless_scaling_remove,
|
||||
descriptionId = R.string.lossless_scaling_remove_confirmation,
|
||||
positiveButtonTitleId = R.string.lossless_scaling_remove,
|
||||
positiveAction = { LosslessScalingHelper.remove() },
|
||||
showNegativeButton = true,
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
|
||||
private val dllPickerLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val resultStrings = resources.getStringArray(R.array.losslessDllResults)
|
||||
ProgressDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
R.string.lossless_scaling_installing,
|
||||
false
|
||||
) { _, _ ->
|
||||
val installResult = LosslessScalingHelper.install(result)
|
||||
if (installResult == LosslessScalingHelper.RESULT_OK) {
|
||||
getString(R.string.lossless_scaling_install_success)
|
||||
} else {
|
||||
MessageDialogFragment.newInstance(
|
||||
titleId = R.string.lossless_scaling_install_failed,
|
||||
descriptionString = resultStrings[installResult]
|
||||
)
|
||||
}
|
||||
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
binding.appbarLossless.updateMargins(
|
||||
left = barInsets.left + cutoutInsets.left,
|
||||
right = barInsets.right + cutoutInsets.right
|
||||
)
|
||||
|
||||
binding.scrollViewLossless.updatePadding(bottom = barInsets.bottom)
|
||||
|
||||
binding.losslessOptionsList.updatePadding(
|
||||
left = barInsets.left + cutoutInsets.left,
|
||||
right = barInsets.right + cutoutInsets.right
|
||||
)
|
||||
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,7 @@ class SetupFragment : Fragment() {
|
||||
)
|
||||
add(
|
||||
PageButton(
|
||||
R.drawable.ic_frames,
|
||||
R.drawable.ic_duck,
|
||||
R.string.lossless_scaling,
|
||||
R.string.lossless_scaling_setup_description,
|
||||
{
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M11,10.6a8,5.2 0 1,0 0,10.4a8,5.2 0 1,0 0,-10.4z" />
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M4.6,12.6L0.8,10.2L3.2,15.5z" />
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M15.3,3.5a4,4 0 1,0 0,8a4,4 0 1,0 0,-8zM16.6,4.9a1,1 0 1,0 0,2a1,1 0 1,0 0,-2z" />
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M18.6,6.5L23.2,7.8L18.6,9.3z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorSurface">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_lossless"
|
||||
style="@style/Widget.Eden.TransparentTopAppBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:touchscreenBlocksFocus="false"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar_lossless"
|
||||
style="@style/Widget.Eden.TransparentTopToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:touchscreenBlocksFocus="false"
|
||||
app:navigationIcon="@drawable/ic_back"
|
||||
app:title="@string/lossless_scaling" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/scroll_view_lossless"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:clipToPadding="false"
|
||||
android:defaultFocusHighlightEnabled="false"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar_lossless">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingTop="16dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/lossless_options_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:nestedScrollingEnabled="false" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -47,6 +47,12 @@
|
||||
android:defaultValue="@null" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/losslessManagerFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.LosslessManagerFragment"
|
||||
android:label="@string/lossless_scaling"
|
||||
tools:layout="@layout/fragment_lossless_manager" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/appletLauncherFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.AppletLauncherFragment"
|
||||
|
||||
@@ -174,6 +174,36 @@
|
||||
<item>4</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="frameGenTargetRateNames">
|
||||
<item>@string/frame_gen_target_rate_off</item>
|
||||
<item>@string/frame_gen_target_rate_60</item>
|
||||
<item>@string/frame_gen_target_rate_90</item>
|
||||
<item>@string/frame_gen_target_rate_120</item>
|
||||
<item>@string/frame_gen_target_rate_144</item>
|
||||
<item>@string/frame_gen_target_rate_165</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="frameGenTargetRateValues">
|
||||
<item>0</item>
|
||||
<item>60</item>
|
||||
<item>90</item>
|
||||
<item>120</item>
|
||||
<item>144</item>
|
||||
<item>165</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="frameGenQueueTargetNames">
|
||||
<item>@string/frame_gen_queue_target_0</item>
|
||||
<item>@string/frame_gen_queue_target_1</item>
|
||||
<item>@string/frame_gen_queue_target_2</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="frameGenQueueTargetValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="rendererVSyncNames">
|
||||
<item>@string/renderer_vsync_immediate</item>
|
||||
<item>@string/renderer_vsync_mailbox</item>
|
||||
|
||||
@@ -299,17 +299,32 @@
|
||||
<string name="gpu_driver_manager">GPU driver manager</string>
|
||||
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
||||
<string name="frame_gen">Frame generation</string>
|
||||
<string name="frame_gen_submenu_description">Manage and configure frame generation</string>
|
||||
<string name="frame_gen_per_game_description">Configure frame generation for this game</string>
|
||||
<string name="frame_gen_description">Insert interpolated frames between rendered ones using Lossless Scaling. Forces FIFO presentation while enabled.</string>
|
||||
<string name="frame_gen_multiplier">Frame multiplier</string>
|
||||
<string name="frame_gen_multiplier_description">How many frames to display for each rendered frame. Higher values cost proportionally more GPU time.</string>
|
||||
<string name="frame_gen_multiplier_description">How many frames to display for each rendered frame. Higher values cost proportionally more GPU time. Asking for more than your display can present will slow emulation down.</string>
|
||||
<string name="frame_gen_multiplier_2x">2x</string>
|
||||
<string name="frame_gen_multiplier_3x">3x</string>
|
||||
<string name="frame_gen_multiplier_4x">4x</string>
|
||||
<string name="frame_gen_target_rate">Target frame rate</string>
|
||||
<string name="frame_gen_target_rate_description">Pick the rate your display can actually show. The multiplier then rises or falls on its own to hold it, and rolls back any step that makes the game itself run slower.</string>
|
||||
<string name="frame_gen_target_rate_off">Use a fixed multiplier</string>
|
||||
<string name="frame_gen_target_rate_60">60 FPS</string>
|
||||
<string name="frame_gen_target_rate_90">90 FPS</string>
|
||||
<string name="frame_gen_target_rate_120">120 FPS</string>
|
||||
<string name="frame_gen_target_rate_144">144 FPS</string>
|
||||
<string name="frame_gen_target_rate_165">165 FPS</string>
|
||||
<string name="frame_gen_queue_target">Frame queue target</string>
|
||||
<string name="frame_gen_queue_target_description">How many finished frames may wait ahead of the display. Larger queues absorb GPU spikes at the cost of input latency.</string>
|
||||
<string name="frame_gen_queue_target_0">Lowest latency (Unbuffered)</string>
|
||||
<string name="frame_gen_queue_target_1">Balanced (1 frame)</string>
|
||||
<string name="frame_gen_queue_target_2">Smoothest (2 frames)</string>
|
||||
<string name="frame_gen_flow_scale_auto">Match motion estimation to the game</string>
|
||||
<string name="frame_gen_flow_scale_auto_description">Estimate motion at the resolution the game actually renders instead of the upscaled output. Costs nothing in accuracy, since upscaling adds no motion detail.</string>
|
||||
<string name="frame_gen_flow_scale">Motion estimation resolution</string>
|
||||
<string name="frame_gen_flow_scale_description">Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance.</string>
|
||||
<string name="frame_gen_fp16">Half precision shaders</string>
|
||||
<string name="frame_gen_fp16_description">Use the 16-bit shader variant shipped in Lossless.dll. Falls back automatically if the driver or the file lacks it.</string>
|
||||
<string name="frame_gen_fp16_description">Use the 16-bit shader variant. Falls back automatically if the driver or the file lacks it.</string>
|
||||
<string name="frame_gen_dump_flow">Dump generated frame</string>
|
||||
<string name="frame_gen_dump_flow_description">Write the optical flow mip levels and the interpolated frame to the lossless/debug folder once, for troubleshooting</string>
|
||||
<string name="frame_gen_unsupported">Frame generation unavailable</string>
|
||||
@@ -325,11 +340,16 @@
|
||||
<string name="lossless_scaling_description">Provide your own copy of Lossless.dll to enable frame generation</string>
|
||||
<string name="lossless_scaling_installed">Installed</string>
|
||||
<string name="lossless_scaling_not_installed">Not installed</string>
|
||||
<string name="lossless_scaling_installed_description">Lossless.dll is installed and contains every shader frame generation needs.</string>
|
||||
<string name="lossless_scaling_replace">Replace</string>
|
||||
<string name="lossless_scaling_remove">Remove</string>
|
||||
<string name="lossless_scaling_remove_description">Delete the installed Lossless.dll and its prepared shaders</string>
|
||||
<string name="lossless_scaling_remove_confirmation">Frame generation will stop working until you install Lossless.dll again. Your original file is not affected.</string>
|
||||
<string name="lossless_scaling_missing">Lossless.dll not installed</string>
|
||||
<string name="lossless_scaling_missing_description">Install it from Settings › Lossless Scaling to use frame generation.</string>
|
||||
<string name="lossless_scaling_locked">Close the game first</string>
|
||||
<string name="lossless_scaling_locked_description">Lossless.dll cannot be changed while a game is running.</string>
|
||||
<string name="lossless_scaling_remove_unavailable">Nothing to remove</string>
|
||||
<string name="lossless_scaling_remove_unavailable_description">Lossless.dll is not installed yet.</string>
|
||||
<string name="lossless_scaling_installing">Preparing frame generation shaders…</string>
|
||||
<string name="lossless_scaling_install_success">Lossless.dll installed successfully</string>
|
||||
<string name="lossless_scaling_install_failed">Could not install Lossless.dll</string>
|
||||
|
||||
@@ -380,6 +380,28 @@ void UpdateRescalingInfo() {
|
||||
TranslateResolutionInfo(setup, info);
|
||||
}
|
||||
|
||||
u32 FrameGenMultiplier() {
|
||||
return std::clamp(values.frame_gen_multiplier.GetValue(), MIN_FRAME_GEN_MULTIPLIER,
|
||||
MAX_FRAME_GEN_MULTIPLIER);
|
||||
}
|
||||
|
||||
size_t FrameGenGenerations() {
|
||||
if (!values.frame_gen.GetValue()) {
|
||||
return 0;
|
||||
}
|
||||
return FrameGenMultiplier() - 1;
|
||||
}
|
||||
|
||||
size_t FrameGenMaxGenerations() {
|
||||
if (!values.frame_gen.GetValue()) {
|
||||
return 0;
|
||||
}
|
||||
if (values.frame_gen_target_rate.GetValue() != 0) {
|
||||
return MAX_FRAME_GEN_MULTIPLIER - 1;
|
||||
}
|
||||
return FrameGenMultiplier() - 1;
|
||||
}
|
||||
|
||||
void RestoreGlobalState(bool is_powered_on) {
|
||||
// If a game is running, DO NOT restore the global settings state
|
||||
if (is_powered_on) {
|
||||
|
||||
@@ -402,6 +402,26 @@ struct Values {
|
||||
false,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_target_rate{linkage,
|
||||
0,
|
||||
0,
|
||||
240,
|
||||
"frame_gen_target_rate",
|
||||
Category::Renderer,
|
||||
Specialization::Countable,
|
||||
true,
|
||||
true,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<bool> frame_gen_flow_scale_auto{linkage,
|
||||
true,
|
||||
"frame_gen_flow_scale_auto",
|
||||
Category::Renderer,
|
||||
Specialization::Default,
|
||||
true,
|
||||
false,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_flow_scale{linkage,
|
||||
75,
|
||||
25,
|
||||
@@ -414,6 +434,17 @@ struct Values {
|
||||
true,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_queue_target{linkage,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
"frame_gen_queue_target",
|
||||
Category::Renderer,
|
||||
Specialization::Countable,
|
||||
true,
|
||||
false,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<bool> frame_gen_fp16{linkage, true, "frame_gen_fp16", Category::Renderer,
|
||||
Specialization::Default, true, false, &frame_gen};
|
||||
|
||||
@@ -902,10 +933,20 @@ struct Values {
|
||||
|
||||
// Per-game overrides
|
||||
bool use_squashed_iterated_blend;
|
||||
|
||||
};
|
||||
|
||||
extern Values values;
|
||||
|
||||
constexpr u32 MIN_FRAME_GEN_MULTIPLIER = 2;
|
||||
constexpr u32 MAX_FRAME_GEN_MULTIPLIER = 4;
|
||||
|
||||
[[nodiscard]] u32 FrameGenMultiplier();
|
||||
|
||||
[[nodiscard]] size_t FrameGenGenerations();
|
||||
|
||||
[[nodiscard]] size_t FrameGenMaxGenerations();
|
||||
|
||||
bool getDebugKnobAt(u8 i);
|
||||
|
||||
void UpdateGPUAccuracy();
|
||||
|
||||
@@ -127,6 +127,8 @@ add_library(video_core STATIC
|
||||
renderer_vulkan/present/filters.h
|
||||
renderer_vulkan/present/frame_gen.cpp
|
||||
renderer_vulkan/present/frame_gen.h
|
||||
renderer_vulkan/present/frame_gen_pacer.cpp
|
||||
renderer_vulkan/present/frame_gen_pacer.h
|
||||
renderer_vulkan/present/lsfg_alpha.cpp
|
||||
renderer_vulkan/present/lsfg_alpha.h
|
||||
renderer_vulkan/present/lsfg_beta.cpp
|
||||
|
||||
@@ -21,15 +21,27 @@ namespace {
|
||||
|
||||
constexpr size_t COLOR_CHANNELS = 4;
|
||||
constexpr u64 LSFG_REQUIRED_FRAMES = 2;
|
||||
constexpr u32 LSFG_RECURRENCE_FRAMES = 2;
|
||||
|
||||
[[nodiscard]] f32 ConfiguredFlowScale() {
|
||||
[[nodiscard]] f32 ManualFlowScale() {
|
||||
return static_cast<f32>(Settings::values.frame_gen_flow_scale.GetValue()) / 100.0f;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t ConfiguredGenerations() {
|
||||
const u32 multiplier = std::clamp<u32>(Settings::values.frame_gen_multiplier.GetValue(),
|
||||
LSFG_MIN_MULTIPLIER, LSFG_MAX_MULTIPLIER);
|
||||
return multiplier - 1;
|
||||
[[nodiscard]] f32 ConfiguredFlowScale(VkExtent2D guest_extent, VkExtent2D presented_extent) {
|
||||
if (!Settings::values.frame_gen_flow_scale_auto.GetValue()) {
|
||||
return ManualFlowScale();
|
||||
}
|
||||
if (guest_extent.width == 0 || presented_extent.width == 0) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
const f32 rendered_width = static_cast<f32>(guest_extent.width) *
|
||||
Settings::values.resolution_info.up_factor;
|
||||
const f32 ratio = rendered_width / static_cast<f32>(presented_extent.width);
|
||||
|
||||
constexpr f32 FLOW_SCALE_STEPS = 20.0f;
|
||||
const f32 stepped = std::ceil(ratio * FLOW_SCALE_STEPS) / FLOW_SCALE_STEPS;
|
||||
return std::clamp(stepped, 0.25f, 1.0f);
|
||||
}
|
||||
|
||||
bool IsBlueFirst(VkFormat format) {
|
||||
@@ -182,10 +194,16 @@ FrameGen::FrameGen(MemoryAllocator& memory_allocator_, Scheduler& scheduler_)
|
||||
|
||||
FrameGen::~FrameGen() = default;
|
||||
|
||||
void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, bool generate) {
|
||||
void FrameGen::Process(const Device& device, Frame* frame, VkFormat format,
|
||||
VkExtent2D guest_extent) {
|
||||
generated = false;
|
||||
|
||||
if (unavailable || !Settings::values.frame_gen.GetValue()) {
|
||||
if (chain) {
|
||||
scheduler.Finish();
|
||||
chain.reset();
|
||||
}
|
||||
warm_streak = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,20 +220,27 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, bool
|
||||
}
|
||||
}
|
||||
|
||||
peak_guest_extent.width = std::max(peak_guest_extent.width, guest_extent.width);
|
||||
peak_guest_extent.height = std::max(peak_guest_extent.height, guest_extent.height);
|
||||
|
||||
const VkExtent2D extent{.width = frame->width, .height = frame->height};
|
||||
const f32 flow_scale = ConfiguredFlowScale(peak_guest_extent, extent);
|
||||
if (!chain || built_extent.width != extent.width || built_extent.height != extent.height ||
|
||||
built_format != format || built_flow_scale != ConfiguredFlowScale() ||
|
||||
built_generations != ConfiguredGenerations()) {
|
||||
Rebuild(device, extent, format);
|
||||
built_format != format || built_flow_scale != flow_scale) {
|
||||
Rebuild(device, extent, format, flow_scale);
|
||||
}
|
||||
|
||||
const u64 count = frame_count++;
|
||||
last_count = count;
|
||||
generated = generate && count + 1 >= LSFG_REQUIRED_FRAMES;
|
||||
last_generations = plan.generations;
|
||||
|
||||
const bool warm = plan.warm && count + 1 >= LSFG_REQUIRED_FRAMES;
|
||||
warm_streak = warm ? warm_streak + 1 : 0;
|
||||
generated = warm && warm_streak >= LSFG_RECURRENCE_FRAMES && plan.generations > 0;
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([this, source = *frame->image, extent, count,
|
||||
dispatch = generated](vk::CommandBuffer cmdbuf) {
|
||||
dispatch = warm](vk::CommandBuffer cmdbuf) {
|
||||
CopyPresentedFrame(cmdbuf, source, chain->Input(count), extent);
|
||||
if (dispatch) {
|
||||
chain->DispatchShared(cmdbuf, count);
|
||||
@@ -231,41 +256,45 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, bool
|
||||
}
|
||||
}
|
||||
|
||||
size_t FrameGen::WantedGenerations() const {
|
||||
if (unavailable || !Settings::values.frame_gen.GetValue()) {
|
||||
size_t FrameGen::WantedGenerations(size_t capacity) {
|
||||
if (unavailable) {
|
||||
plan = {};
|
||||
return 0;
|
||||
}
|
||||
return ConfiguredGenerations();
|
||||
plan = pacer.Plan(capacity);
|
||||
return plan.generations;
|
||||
}
|
||||
|
||||
size_t FrameGen::GeneratedFrameCount() const {
|
||||
return generated && chain ? chain->GenerationCount() : 0;
|
||||
return generated ? last_generations : 0;
|
||||
}
|
||||
|
||||
void FrameGen::GenerateInto(const Device& device, Frame* destination, size_t generation) {
|
||||
chain->SetTarget(device, generation, destination->index, *destination->storage_view);
|
||||
chain->SetTarget(device, last_generations, generation, destination->index,
|
||||
*destination->storage_view);
|
||||
|
||||
const VkExtent2D extent{.width = destination->width, .height = destination->height};
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([this, count = last_count, generation, target = destination->index,
|
||||
image = *destination->image, extent](vk::CommandBuffer cmdbuf) {
|
||||
chain->DispatchGeneration(cmdbuf, count, generation, target, image, extent);
|
||||
scheduler.Record([this, count = last_count, generation_count = last_generations, generation,
|
||||
target = destination->index, image = *destination->image,
|
||||
extent](vk::CommandBuffer cmdbuf) {
|
||||
chain->DispatchGeneration(cmdbuf, count, generation_count, generation, target, image,
|
||||
extent);
|
||||
});
|
||||
}
|
||||
|
||||
void FrameGen::Rebuild(const Device& device, VkExtent2D extent, VkFormat format) {
|
||||
void FrameGen::Rebuild(const Device& device, VkExtent2D extent, VkFormat format, f32 flow_scale) {
|
||||
scheduler.Finish();
|
||||
chain.reset();
|
||||
|
||||
built_flow_scale = ConfiguredFlowScale();
|
||||
built_generations = ConfiguredGenerations();
|
||||
built_flow_scale = flow_scale;
|
||||
|
||||
chain.emplace(device, memory_allocator, *shaders, extent, format, built_flow_scale,
|
||||
built_generations);
|
||||
chain.emplace(device, memory_allocator, *shaders, extent, format, built_flow_scale);
|
||||
built_extent = extent;
|
||||
built_format = format;
|
||||
frame_count = 0;
|
||||
warm_streak = 0;
|
||||
generated = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <optional>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/frame_gen_pacer.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_chain.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
@@ -21,16 +22,16 @@ public:
|
||||
explicit FrameGen(MemoryAllocator& memory_allocator, Scheduler& scheduler);
|
||||
~FrameGen();
|
||||
|
||||
void Process(const Device& device, Frame* frame, VkFormat format, bool generate);
|
||||
void Process(const Device& device, Frame* frame, VkFormat format, VkExtent2D guest_extent);
|
||||
|
||||
[[nodiscard]] size_t WantedGenerations() const;
|
||||
[[nodiscard]] size_t WantedGenerations(size_t capacity);
|
||||
|
||||
[[nodiscard]] size_t GeneratedFrameCount() const;
|
||||
|
||||
void GenerateInto(const Device& device, Frame* destination, size_t generation);
|
||||
|
||||
private:
|
||||
void Rebuild(const Device& device, VkExtent2D extent, VkFormat format);
|
||||
void Rebuild(const Device& device, VkExtent2D extent, VkFormat format, f32 flow_scale);
|
||||
void DumpDebugImages(u64 count);
|
||||
|
||||
MemoryAllocator& memory_allocator;
|
||||
@@ -38,12 +39,16 @@ private:
|
||||
|
||||
std::optional<LsfgShaders> shaders;
|
||||
std::optional<LsfgChain> chain;
|
||||
FrameGenPacer pacer;
|
||||
FrameGenPlan plan{};
|
||||
VkExtent2D peak_guest_extent{};
|
||||
VkExtent2D built_extent{};
|
||||
VkFormat built_format{VK_FORMAT_UNDEFINED};
|
||||
f32 built_flow_scale{};
|
||||
size_t built_generations{};
|
||||
u64 frame_count{};
|
||||
u64 last_count{};
|
||||
size_t last_generations{};
|
||||
u32 warm_streak{};
|
||||
bool generated{};
|
||||
bool unavailable{};
|
||||
bool dumped{};
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "video_core/renderer_vulkan/present/frame_gen_pacer.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
constexpr f32 INTERVAL_SMOOTHING = 0.25f;
|
||||
constexpr f32 MINIMUM_BASE_RATE = 10.0f;
|
||||
constexpr f32 BURST_CADENCE_RATIO = 3.0f;
|
||||
constexpr f32 BURST_TARGET_RATIO = 2.0f;
|
||||
constexpr f32 PROBE_THROUGHPUT_TOLERANCE = 0.95f;
|
||||
constexpr f32 PROBE_BASE_COLLAPSE_RATIO = 0.70f;
|
||||
constexpr f32 PROBE_MARGINAL_GAIN = 1.15f;
|
||||
constexpr f32 TARGET_SATISFIED_RATIO = 0.95f;
|
||||
constexpr f32 UNLOADED_BASE_RETENTION = 0.75f;
|
||||
constexpr f32 CREDIT_EPSILON = 1.0e-4f;
|
||||
constexpr u32 MAX_PROBE_FAILURES = 4;
|
||||
|
||||
constexpr auto STABILIZATION_DURATION = std::chrono::seconds(1);
|
||||
constexpr auto PROBE_DURATION = std::chrono::seconds(1);
|
||||
constexpr auto DEFICIT_DURATION = std::chrono::seconds(1);
|
||||
constexpr auto PROBE_STEP_DELAY = std::chrono::milliseconds(250);
|
||||
|
||||
[[nodiscard]] Clock::duration ProbeBackoff(u32 failures) {
|
||||
switch (failures) {
|
||||
case 1:
|
||||
return std::chrono::seconds(5);
|
||||
case 2:
|
||||
return std::chrono::seconds(15);
|
||||
case 3:
|
||||
return std::chrono::seconds(30);
|
||||
default:
|
||||
return std::chrono::seconds(60);
|
||||
}
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
FrameGenPlan FrameGenPacer::Plan(size_t capacity) {
|
||||
const size_t ceiling = std::min(capacity, Settings::FrameGenMaxGenerations());
|
||||
if (ceiling == 0) {
|
||||
Reset();
|
||||
return {};
|
||||
}
|
||||
|
||||
const Clock::time_point now = Clock::now();
|
||||
const size_t previous_generations = std::exchange(issued_generations, 0);
|
||||
if (!last_frame) {
|
||||
last_frame = now;
|
||||
return {};
|
||||
}
|
||||
|
||||
const Clock::duration interval = now - *last_frame;
|
||||
const f32 interval_seconds = std::chrono::duration<f32>(interval).count();
|
||||
last_frame = now;
|
||||
|
||||
if (interval_seconds <= 0.0f) {
|
||||
Stabilize(now);
|
||||
return {};
|
||||
}
|
||||
|
||||
const f32 target_rate = static_cast<f32>(Settings::values.frame_gen_target_rate.GetValue());
|
||||
|
||||
if (smoothed_interval > 0.0f) {
|
||||
f32 burst_threshold = BURST_CADENCE_RATIO / smoothed_interval;
|
||||
if (target_rate > 0.0f) {
|
||||
burst_threshold = std::max(burst_threshold, target_rate * BURST_TARGET_RATIO);
|
||||
}
|
||||
if (1.0f / interval_seconds > burst_threshold) {
|
||||
DeferEvaluations(interval);
|
||||
output_credit = 0.0f;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (interval_seconds > 1.0f / MINIMUM_BASE_RATE) {
|
||||
Stabilize(now);
|
||||
return {};
|
||||
}
|
||||
|
||||
smoothed_interval = smoothed_interval > 0.0f
|
||||
? smoothed_interval +
|
||||
(interval_seconds - smoothed_interval) * INTERVAL_SMOOTHING
|
||||
: interval_seconds;
|
||||
|
||||
if (previous_generations == 0) {
|
||||
const f32 measured = 1.0f / smoothed_interval;
|
||||
unloaded_base_rate =
|
||||
unloaded_base_rate > 0.0f
|
||||
? unloaded_base_rate + (measured - unloaded_base_rate) * INTERVAL_SMOOTHING
|
||||
: measured;
|
||||
}
|
||||
|
||||
if (stable_until) {
|
||||
if (now < *stable_until) {
|
||||
return {};
|
||||
}
|
||||
stable_until.reset();
|
||||
}
|
||||
|
||||
if (target_rate == 0.0f) {
|
||||
limit = std::min(Settings::FrameGenGenerations(), ceiling);
|
||||
output_credit = 0.0f;
|
||||
issued_generations = limit;
|
||||
return {.generations = limit, .warm = limit > 0};
|
||||
}
|
||||
|
||||
UpdateLimit(now, 1.0f / smoothed_interval, target_rate, ceiling);
|
||||
|
||||
const size_t allowed = std::min(limit, ceiling);
|
||||
const f32 desired_outputs = smoothed_interval * target_rate;
|
||||
if (allowed == 0 || desired_outputs <= 1.0f) {
|
||||
output_credit = 0.0f;
|
||||
return {};
|
||||
}
|
||||
|
||||
output_credit += desired_outputs;
|
||||
const size_t outputs =
|
||||
std::max<size_t>(1, static_cast<size_t>(std::floor(output_credit + CREDIT_EPSILON)));
|
||||
const size_t generations = std::min(outputs - 1, allowed);
|
||||
|
||||
output_credit -= static_cast<f32>(generations + 1);
|
||||
if (output_credit < 0.0f) {
|
||||
output_credit = 0.0f;
|
||||
} else if (generations == allowed && output_credit >= 1.0f) {
|
||||
output_credit = std::fmod(output_credit, 1.0f);
|
||||
}
|
||||
|
||||
issued_generations = generations;
|
||||
return {.generations = generations, .warm = true};
|
||||
}
|
||||
|
||||
void FrameGenPacer::UpdateLimit(Clock::time_point now, f32 base_rate, f32 target_rate,
|
||||
size_t ceiling) {
|
||||
limit = std::min(limit, ceiling);
|
||||
|
||||
if (probe_until) {
|
||||
if (now < *probe_until) {
|
||||
return;
|
||||
}
|
||||
probe_until.reset();
|
||||
output_credit = 0.0f;
|
||||
|
||||
const f32 previous_output =
|
||||
std::min(target_rate, probe_base_rate * static_cast<f32>(probe_previous_limit + 1));
|
||||
const f32 current_output =
|
||||
std::min(target_rate, base_rate * static_cast<f32>(limit + 1));
|
||||
|
||||
const bool throughput_regressed =
|
||||
current_output < previous_output * PROBE_THROUGHPUT_TOLERANCE;
|
||||
const bool collapsed_for_marginal_gain =
|
||||
base_rate < probe_base_rate * PROBE_BASE_COLLAPSE_RATIO &&
|
||||
current_output < previous_output * PROBE_MARGINAL_GAIN;
|
||||
const bool emulation_slowed = unloaded_base_rate > 0.0f &&
|
||||
base_rate < unloaded_base_rate * UNLOADED_BASE_RETENTION;
|
||||
|
||||
if (throughput_regressed || collapsed_for_marginal_gain || emulation_slowed) {
|
||||
limit = probe_previous_limit;
|
||||
probe_failures = std::min(probe_failures + 1, MAX_PROBE_FAILURES);
|
||||
next_probe = now + ProbeBackoff(probe_failures);
|
||||
deficit_since.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
probe_failures = 0;
|
||||
next_probe = now + PROBE_STEP_DELAY;
|
||||
}
|
||||
|
||||
if (base_rate * static_cast<f32>(limit + 1) >= target_rate * TARGET_SATISFIED_RATIO ||
|
||||
limit >= ceiling) {
|
||||
deficit_since.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!deficit_since) {
|
||||
deficit_since = now;
|
||||
return;
|
||||
}
|
||||
if (now - *deficit_since < DEFICIT_DURATION) {
|
||||
return;
|
||||
}
|
||||
if (next_probe && now < *next_probe) {
|
||||
return;
|
||||
}
|
||||
|
||||
probe_previous_limit = limit;
|
||||
probe_base_rate = base_rate;
|
||||
++limit;
|
||||
probe_until = now + PROBE_DURATION;
|
||||
deficit_since.reset();
|
||||
output_credit = 0.0f;
|
||||
}
|
||||
|
||||
void FrameGenPacer::DeferEvaluations(Clock::duration amount) {
|
||||
const auto defer = [amount](std::optional<Clock::time_point>& deadline) {
|
||||
if (deadline) {
|
||||
*deadline += amount;
|
||||
}
|
||||
};
|
||||
defer(stable_until);
|
||||
defer(probe_until);
|
||||
defer(next_probe);
|
||||
deficit_since.reset();
|
||||
}
|
||||
|
||||
void FrameGenPacer::Stabilize(Clock::time_point now) {
|
||||
stable_until = now + STABILIZATION_DURATION;
|
||||
probe_until.reset();
|
||||
deficit_since.reset();
|
||||
smoothed_interval = 0.0f;
|
||||
output_credit = 0.0f;
|
||||
}
|
||||
|
||||
void FrameGenPacer::Reset() {
|
||||
last_frame.reset();
|
||||
stable_until.reset();
|
||||
probe_until.reset();
|
||||
next_probe.reset();
|
||||
deficit_since.reset();
|
||||
smoothed_interval = 0.0f;
|
||||
output_credit = 0.0f;
|
||||
probe_base_rate = 0.0f;
|
||||
unloaded_base_rate = 0.0f;
|
||||
issued_generations = 0;
|
||||
probe_previous_limit = 0;
|
||||
limit = 0;
|
||||
probe_failures = 0;
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,46 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
struct FrameGenPlan {
|
||||
size_t generations{};
|
||||
bool warm{};
|
||||
};
|
||||
|
||||
class FrameGenPacer {
|
||||
public:
|
||||
[[nodiscard]] FrameGenPlan Plan(size_t capacity);
|
||||
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
void Stabilize(Clock::time_point now);
|
||||
void DeferEvaluations(Clock::duration amount);
|
||||
void UpdateLimit(Clock::time_point now, f32 base_rate, f32 target_rate, size_t ceiling);
|
||||
|
||||
std::optional<Clock::time_point> last_frame;
|
||||
std::optional<Clock::time_point> stable_until;
|
||||
std::optional<Clock::time_point> probe_until;
|
||||
std::optional<Clock::time_point> next_probe;
|
||||
std::optional<Clock::time_point> deficit_since;
|
||||
f32 smoothed_interval{};
|
||||
f32 output_credit{};
|
||||
f32 probe_base_rate{};
|
||||
f32 unloaded_base_rate{};
|
||||
size_t issued_generations{};
|
||||
size_t probe_previous_limit{};
|
||||
size_t limit{};
|
||||
u32 probe_failures{};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -15,19 +15,18 @@ namespace Vulkan {
|
||||
namespace {
|
||||
|
||||
constexpr u32 FIXED_DESCRIPTOR_SETS = 64;
|
||||
constexpr u32 DESCRIPTOR_SETS_PER_GENERATION = 112;
|
||||
constexpr u32 DESCRIPTOR_SETS_PER_SLOT = 112;
|
||||
constexpr size_t FIRST_DELTA_LEVEL = 4;
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgChain::LsfgChain(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, VkExtent2D extent, VkFormat format,
|
||||
f32 flow_scale, size_t generation_count_)
|
||||
: generation_count{generation_count_},
|
||||
resources{device, memory_allocator, flow_scale},
|
||||
f32 flow_scale)
|
||||
: resources{device, memory_allocator, flow_scale},
|
||||
descriptor_pool{CreateLsfgDescriptorPool(
|
||||
device, FIXED_DESCRIPTOR_SETS +
|
||||
DESCRIPTOR_SETS_PER_GENERATION * static_cast<u32>(generation_count))} {
|
||||
DESCRIPTOR_SETS_PER_SLOT * static_cast<u32>(LSFG_GENERATION_SLOTS))} {
|
||||
for (auto& image : frames) {
|
||||
image = LsfgImage(device, memory_allocator, extent, format);
|
||||
}
|
||||
@@ -49,7 +48,7 @@ LsfgChain::LsfgChain(const Device& device, MemoryAllocator& memory_allocator,
|
||||
gamma[i] = LsfgGamma(device, memory_allocator, shaders, resources, descriptor_pool,
|
||||
alpha[level].Outputs(),
|
||||
beta.Output(std::min(level, LSFG_BETA_OUTPUTS - 1)),
|
||||
i == 0 ? nullptr : &gamma[i - 1].Output(), generation_count);
|
||||
i == 0 ? nullptr : &gamma[i - 1].Output());
|
||||
|
||||
if (i < FIRST_DELTA_LEVEL) {
|
||||
continue;
|
||||
@@ -60,13 +59,13 @@ LsfgChain::LsfgChain(const Device& device, MemoryAllocator& memory_allocator,
|
||||
device, memory_allocator, shaders, resources, descriptor_pool, alpha[level].Outputs(),
|
||||
beta.Output(level), i == FIRST_DELTA_LEVEL ? nullptr : &gamma[i - 1].Output(),
|
||||
i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output1(),
|
||||
i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output2(), generation_count);
|
||||
i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output2());
|
||||
}
|
||||
|
||||
generate = LsfgGenerate(device, shaders, resources, descriptor_pool, frames,
|
||||
gamma[LSFG_MIP_LEVELS - 1].Output(),
|
||||
delta[LSFG_DELTA_INSTANCES - 1].Output1(),
|
||||
delta[LSFG_DELTA_INSTANCES - 1].Output2(), generation_count);
|
||||
delta[LSFG_DELTA_INSTANCES - 1].Output2());
|
||||
}
|
||||
|
||||
void LsfgChain::DispatchShared(vk::CommandBuffer cmdbuf, u64 frame_count) {
|
||||
@@ -88,15 +87,17 @@ void LsfgChain::DispatchShared(vk::CommandBuffer cmdbuf, u64 frame_count) {
|
||||
beta.Dispatch(cmdbuf, frame_count);
|
||||
}
|
||||
|
||||
void LsfgChain::DispatchGeneration(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation,
|
||||
u32 target, VkImage image, VkExtent2D extent) {
|
||||
void LsfgChain::DispatchGeneration(vk::CommandBuffer cmdbuf, u64 frame_count,
|
||||
size_t generation_count, size_t generation, u32 target,
|
||||
VkImage image, VkExtent2D extent) {
|
||||
const size_t slot = LsfgGenerationSlot(generation_count, generation);
|
||||
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||
gamma[i].Dispatch(cmdbuf, frame_count, generation);
|
||||
gamma[i].Dispatch(cmdbuf, frame_count, slot);
|
||||
if (i >= FIRST_DELTA_LEVEL) {
|
||||
delta[i - FIRST_DELTA_LEVEL].Dispatch(cmdbuf, frame_count, generation);
|
||||
delta[i - FIRST_DELTA_LEVEL].Dispatch(cmdbuf, frame_count, slot);
|
||||
}
|
||||
}
|
||||
generate.Dispatch(cmdbuf, frame_count, generation, target, image, extent);
|
||||
generate.Dispatch(cmdbuf, frame_count, slot, target, image, extent);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -27,28 +27,25 @@ constexpr size_t LSFG_DELTA_INSTANCES = 3;
|
||||
class LsfgChain {
|
||||
public:
|
||||
LsfgChain(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
VkExtent2D extent, VkFormat format, f32 flow_scale, size_t generation_count_);
|
||||
VkExtent2D extent, VkFormat format, f32 flow_scale);
|
||||
|
||||
LsfgChain(const LsfgChain&) = delete;
|
||||
LsfgChain& operator=(const LsfgChain&) = delete;
|
||||
|
||||
void DispatchShared(vk::CommandBuffer cmdbuf, u64 frame_count);
|
||||
|
||||
void DispatchGeneration(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation,
|
||||
u32 target, VkImage image, VkExtent2D extent);
|
||||
void DispatchGeneration(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation_count,
|
||||
size_t generation, u32 target, VkImage image, VkExtent2D extent);
|
||||
|
||||
void SetTarget(const Device& device, size_t generation, u32 target, VkImageView view) {
|
||||
generate.SetTarget(device, generation, target, view);
|
||||
void SetTarget(const Device& device, size_t generation_count, size_t generation, u32 target,
|
||||
VkImageView view) {
|
||||
generate.SetTarget(device, LsfgGenerationSlot(generation_count, generation), target, view);
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& Input(u64 frame_count) {
|
||||
return frames[frame_count % frames.size()];
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t GenerationCount() const {
|
||||
return generation_count;
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& FlowLevel(size_t level) {
|
||||
return mipmaps.Output(level);
|
||||
}
|
||||
@@ -74,7 +71,6 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
size_t generation_count{};
|
||||
LsfgResources resources;
|
||||
vk::DescriptorPool descriptor_pool;
|
||||
|
||||
|
||||
@@ -27,15 +27,32 @@ constexpr VkFormat LSFG_FLOW_FORMAT = VK_FORMAT_R8_UNORM;
|
||||
constexpr VkFormat LSFG_MOTION_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
|
||||
constexpr size_t LSFG_HISTORY_SLOTS = 3;
|
||||
constexpr size_t LSFG_MIN_MULTIPLIER = 2;
|
||||
constexpr size_t LSFG_MAX_MULTIPLIER = 4;
|
||||
constexpr size_t LSFG_MAX_GENERATIONS = LSFG_MAX_MULTIPLIER - 1;
|
||||
constexpr size_t LSFG_MAX_TARGETS = 7;
|
||||
constexpr size_t LSFG_MAX_GENERATIONS = 3;
|
||||
|
||||
constexpr size_t LSFG_GENERATION_SLOTS = LSFG_MAX_GENERATIONS * (LSFG_MAX_GENERATIONS + 1) / 2;
|
||||
|
||||
[[nodiscard]] constexpr size_t LsfgGenerationSlot(size_t generation_count, size_t generation) {
|
||||
return (generation_count - 1) * generation_count / 2 + generation;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr f32 LsfgTimestamp(size_t generation, size_t generation_count) {
|
||||
return static_cast<f32>(generation + 1) / static_cast<f32>(generation_count + 1);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr size_t LsfgSlotCount(size_t slot) {
|
||||
size_t count = 1;
|
||||
while (LsfgGenerationSlot(count + 1, 0) <= slot) {
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr f32 LsfgSlotTimestamp(size_t slot) {
|
||||
const size_t count = LsfgSlotCount(slot);
|
||||
return LsfgTimestamp(slot - LsfgGenerationSlot(count, 0), count);
|
||||
}
|
||||
|
||||
class LsfgImage {
|
||||
public:
|
||||
LsfgImage() = default;
|
||||
|
||||
@@ -28,7 +28,7 @@ LsfgDelta::LsfgDelta(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImageHistory& inputs_,
|
||||
LsfgImage& flow_input_, LsfgImage* previous_gamma_, LsfgImage* previous1_,
|
||||
LsfgImage* previous2_, size_t generation_count)
|
||||
LsfgImage* previous2_)
|
||||
: inputs{&inputs_}, flow_input{&flow_input_}, previous_gamma{previous_gamma_},
|
||||
previous1{previous1_}, previous2{previous2_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
@@ -83,7 +83,7 @@ LsfgDelta::LsfgDelta(const Device& device, MemoryAllocator& memory_allocator,
|
||||
out_image2 = LsfgImage(device, memory_allocator, extent, LSFG_MOTION_FORMAT);
|
||||
|
||||
std::vector<VkDescriptorSetLayout> layouts;
|
||||
for (size_t generation = 0; generation < generation_count; ++generation) {
|
||||
for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) {
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
layouts.push_back(passes[0].SetLayout());
|
||||
}
|
||||
@@ -104,12 +104,11 @@ LsfgDelta::LsfgDelta(const Device& device, MemoryAllocator& memory_allocator,
|
||||
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
|
||||
const VkSampler edge_sampler =
|
||||
resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);
|
||||
generations.resize(generation_count);
|
||||
size_t next = 0;
|
||||
for (size_t generation = 0; generation < generation_count; ++generation) {
|
||||
Generation& pass = generations[generation];
|
||||
const VkBuffer buffer = resources.GetBuffer(
|
||||
LsfgTimestamp(generation, generation_count), false, previous_gamma == nullptr);
|
||||
for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) {
|
||||
Generation& pass = generations[slot];
|
||||
const VkBuffer buffer =
|
||||
resources.GetBuffer(LsfgSlotTimestamp(slot), false, previous_gamma == nullptr);
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
pass.first_descriptor_sets[i] = owned_sets[next++];
|
||||
@@ -197,23 +196,23 @@ LsfgDelta::LsfgDelta(const Device& device, MemoryAllocator& memory_allocator,
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgDelta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation) {
|
||||
const Generation& pass = generations[generation];
|
||||
void LsfgDelta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot) {
|
||||
const Generation& pass = generations[slot];
|
||||
|
||||
const VkExtent2D extent = temp1[0].Extent();
|
||||
const u32 groups_x = GroupCount(extent.width);
|
||||
const u32 groups_y = GroupCount(extent.height);
|
||||
|
||||
const size_t slot = frame_count % LSFG_HISTORY_SLOTS;
|
||||
const size_t previous_slot = (frame_count + 2) % LSFG_HISTORY_SLOTS;
|
||||
const size_t history = frame_count % LSFG_HISTORY_SLOTS;
|
||||
const size_t previous_history = (frame_count + 2) % LSFG_HISTORY_SLOTS;
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll((*inputs)[previous_slot])
|
||||
.WriteToReadAll((*inputs)[slot])
|
||||
.WriteToReadAll((*inputs)[previous_history])
|
||||
.WriteToReadAll((*inputs)[history])
|
||||
.WriteToRead(previous_gamma)
|
||||
.ReadToWriteAll(temp1)
|
||||
.Build();
|
||||
passes[0].Bind(cmdbuf, pass.first_descriptor_sets[slot]);
|
||||
passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
|
||||
@@ -238,13 +237,13 @@ void LsfgDelta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t gener
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll((*inputs)[previous_slot])
|
||||
.WriteToReadAll((*inputs)[slot])
|
||||
.WriteToReadAll((*inputs)[previous_history])
|
||||
.WriteToReadAll((*inputs)[history])
|
||||
.WriteToRead(previous_gamma)
|
||||
.WriteToRead(previous1)
|
||||
.ReadToWriteAll(temp2)
|
||||
.Build();
|
||||
passes[5].Bind(cmdbuf, pass.sixth_descriptor_sets[slot]);
|
||||
passes[5].Bind(cmdbuf, pass.sixth_descriptor_sets[history]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
@@ -26,9 +25,9 @@ public:
|
||||
LsfgDelta(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
|
||||
LsfgImageHistory& inputs, LsfgImage& flow_input, LsfgImage* previous_gamma,
|
||||
LsfgImage* previous1, LsfgImage* previous2, size_t generation_count);
|
||||
LsfgImage* previous1, LsfgImage* previous2);
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation);
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot);
|
||||
|
||||
[[nodiscard]] LsfgImage& Output1() {
|
||||
return out_image1;
|
||||
@@ -52,7 +51,7 @@ private:
|
||||
LsfgImage* previous2{};
|
||||
|
||||
std::array<LsfgPass, LSFG_DELTA_STAGES> passes;
|
||||
std::vector<Generation> generations;
|
||||
std::array<Generation, LSFG_GENERATION_SLOTS> generations{};
|
||||
vk::DescriptorSets owned_sets;
|
||||
|
||||
std::array<LsfgImage, LSFG_DELTA_TEMPS> temp1;
|
||||
|
||||
@@ -27,7 +27,7 @@ constexpr u32 DISPATCH_TILE_SHIFT = 3;
|
||||
LsfgGamma::LsfgGamma(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImageHistory& inputs_,
|
||||
LsfgImage& flow_input_, LsfgImage* previous_, size_t generation_count)
|
||||
LsfgImage& flow_input_, LsfgImage* previous_)
|
||||
: inputs{&inputs_}, flow_input{&flow_input_}, previous{previous_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
@@ -64,7 +64,7 @@ LsfgGamma::LsfgGamma(const Device& device, MemoryAllocator& memory_allocator,
|
||||
out_image = LsfgImage(device, memory_allocator, extent, LSFG_MOTION_FORMAT);
|
||||
|
||||
std::vector<VkDescriptorSetLayout> layouts;
|
||||
for (size_t generation = 0; generation < generation_count; ++generation) {
|
||||
for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) {
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
layouts.push_back(passes[0].SetLayout());
|
||||
}
|
||||
@@ -80,12 +80,11 @@ LsfgGamma::LsfgGamma(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const VkSampler edge_sampler =
|
||||
resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);
|
||||
|
||||
generations.resize(generation_count);
|
||||
size_t next = 0;
|
||||
for (size_t generation = 0; generation < generation_count; ++generation) {
|
||||
Generation& pass = generations[generation];
|
||||
const VkBuffer buffer = resources.GetBuffer(
|
||||
LsfgTimestamp(generation, generation_count), previous == nullptr);
|
||||
for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) {
|
||||
Generation& pass = generations[slot];
|
||||
const VkBuffer buffer =
|
||||
resources.GetBuffer(LsfgSlotTimestamp(slot), previous == nullptr);
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
pass.first_descriptor_sets[i] = owned_sets[next++];
|
||||
@@ -134,23 +133,23 @@ LsfgGamma::LsfgGamma(const Device& device, MemoryAllocator& memory_allocator,
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgGamma::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation) {
|
||||
const Generation& pass = generations[generation];
|
||||
void LsfgGamma::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot) {
|
||||
const Generation& pass = generations[slot];
|
||||
|
||||
const VkExtent2D extent = temp1[0].Extent();
|
||||
const u32 groups_x = GroupCount(extent.width);
|
||||
const u32 groups_y = GroupCount(extent.height);
|
||||
|
||||
const size_t slot = frame_count % LSFG_HISTORY_SLOTS;
|
||||
const size_t previous_slot = (frame_count + 2) % LSFG_HISTORY_SLOTS;
|
||||
const size_t history = frame_count % LSFG_HISTORY_SLOTS;
|
||||
const size_t previous_history = (frame_count + 2) % LSFG_HISTORY_SLOTS;
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll((*inputs)[previous_slot])
|
||||
.WriteToReadAll((*inputs)[slot])
|
||||
.WriteToReadAll((*inputs)[previous_history])
|
||||
.WriteToReadAll((*inputs)[history])
|
||||
.WriteToRead(previous)
|
||||
.ReadToWriteAll(temp1)
|
||||
.Build();
|
||||
passes[0].Bind(cmdbuf, pass.first_descriptor_sets[slot]);
|
||||
passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
@@ -25,10 +24,9 @@ public:
|
||||
LsfgGamma() = default;
|
||||
LsfgGamma(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
|
||||
LsfgImageHistory& inputs, LsfgImage& flow_input, LsfgImage* previous,
|
||||
size_t generation_count);
|
||||
LsfgImageHistory& inputs, LsfgImage& flow_input, LsfgImage* previous);
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation);
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot);
|
||||
|
||||
[[nodiscard]] LsfgImage& Output() {
|
||||
return out_image;
|
||||
@@ -45,7 +43,7 @@ private:
|
||||
LsfgImage* previous{};
|
||||
|
||||
std::array<LsfgPass, LSFG_GAMMA_STAGES> passes;
|
||||
std::vector<Generation> generations;
|
||||
std::array<Generation, LSFG_GENERATION_SLOTS> generations{};
|
||||
vk::DescriptorSets owned_sets;
|
||||
|
||||
std::array<LsfgImage, LSFG_GAMMA_TEMPS> temp1;
|
||||
|
||||
@@ -49,7 +49,7 @@ VkImageMemoryBarrier MakeTargetBarrier(VkImage image, VkAccessFlags src_access,
|
||||
LsfgGenerate::LsfgGenerate(const Device& device, const LsfgShaders& shaders,
|
||||
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
|
||||
LsfgImagePair& frames_, LsfgImage& motion_, LsfgImage& detail1_,
|
||||
LsfgImage& detail2_, size_t generation_count)
|
||||
LsfgImage& detail2_)
|
||||
: frames{&frames_}, motion{&motion_}, detail1{&detail1_}, detail2{&detail2_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
@@ -63,35 +63,33 @@ LsfgGenerate::LsfgGenerate(const Device& device, const LsfgShaders& shaders,
|
||||
edge_sampler =
|
||||
resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);
|
||||
|
||||
generations.resize(generation_count);
|
||||
|
||||
const std::vector<VkDescriptorSetLayout> layouts(
|
||||
generation_count * LSFG_MAX_TARGETS * 2, pass.SetLayout());
|
||||
LSFG_GENERATION_SLOTS * LSFG_MAX_TARGETS * 2, pass.SetLayout());
|
||||
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
size_t next = 0;
|
||||
for (size_t generation = 0; generation < generation_count; ++generation) {
|
||||
Generation& target = generations[generation];
|
||||
target.buffer = resources.GetBuffer(LsfgTimestamp(generation, generation_count));
|
||||
for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) {
|
||||
Generation& target = generations[slot];
|
||||
target.buffer = resources.GetBuffer(LsfgSlotTimestamp(slot));
|
||||
|
||||
for (auto& slot : target.targets) {
|
||||
for (auto& set : slot.descriptor_sets) {
|
||||
for (auto& entry : target.targets) {
|
||||
for (auto& set : entry.descriptor_sets) {
|
||||
set = owned_sets[next++];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgGenerate::SetTarget(const Device& device, size_t generation, u32 target, VkImageView view) {
|
||||
Target& slot = generations[generation].targets[target];
|
||||
if (slot.view == view) {
|
||||
void LsfgGenerate::SetTarget(const Device& device, size_t slot, u32 target, VkImageView view) {
|
||||
Target& entry = generations[slot].targets[target];
|
||||
if (entry.view == view) {
|
||||
return;
|
||||
}
|
||||
slot.view = view;
|
||||
entry.view = view;
|
||||
|
||||
for (size_t i = 0; i < slot.descriptor_sets.size(); ++i) {
|
||||
LsfgDescriptorWriter(slot.descriptor_sets[i])
|
||||
.AddUniformBuffer(generations[generation].buffer, LsfgResources::BufferSize())
|
||||
for (size_t i = 0; i < entry.descriptor_sets.size(); ++i) {
|
||||
LsfgDescriptorWriter(entry.descriptor_sets[i])
|
||||
.AddUniformBuffer(generations[slot].buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImage((*frames)[1 - i])
|
||||
@@ -104,9 +102,9 @@ void LsfgGenerate::SetTarget(const Device& device, size_t generation, u32 target
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgGenerate::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation,
|
||||
u32 target, VkImage image, VkExtent2D extent) {
|
||||
const Target& slot = generations[generation].targets[target];
|
||||
void LsfgGenerate::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot, u32 target,
|
||||
VkImage image, VkExtent2D extent) {
|
||||
const Target& entry = generations[slot].targets[target];
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll(*frames)
|
||||
@@ -116,7 +114,7 @@ void LsfgGenerate::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t ge
|
||||
.DiscardToWrite(image)
|
||||
.Build();
|
||||
|
||||
pass.Bind(cmdbuf, slot.descriptor_sets[frame_count % slot.descriptor_sets.size()]);
|
||||
pass.Bind(cmdbuf, entry.descriptor_sets[frame_count % entry.descriptor_sets.size()]);
|
||||
cmdbuf.Dispatch(GroupCount(extent.width), GroupCount(extent.height), 1);
|
||||
|
||||
const std::array after{MakeTargetBarrier(
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
@@ -22,11 +21,11 @@ public:
|
||||
LsfgGenerate() = default;
|
||||
LsfgGenerate(const Device& device, const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImagePair& frames, LsfgImage& motion,
|
||||
LsfgImage& detail1, LsfgImage& detail2, size_t generation_count);
|
||||
LsfgImage& detail1, LsfgImage& detail2);
|
||||
|
||||
void SetTarget(const Device& device, size_t generation, u32 target, VkImageView view);
|
||||
void SetTarget(const Device& device, size_t slot, u32 target, VkImageView view);
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation, u32 target,
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot, u32 target,
|
||||
VkImage image, VkExtent2D extent);
|
||||
|
||||
private:
|
||||
@@ -48,7 +47,7 @@ private:
|
||||
VkSampler edge_sampler{};
|
||||
|
||||
LsfgPass pass;
|
||||
std::vector<Generation> generations;
|
||||
std::array<Generation, LSFG_GENERATION_SLOTS> generations{};
|
||||
vk::DescriptorSets owned_sets;
|
||||
};
|
||||
|
||||
|
||||
@@ -49,6 +49,21 @@ constexpr VkExtent2D CaptureImageSize{
|
||||
.height = VideoCore::Capture::LinearHeight,
|
||||
};
|
||||
|
||||
[[nodiscard]] VkExtent2D GuestExtent(std::span<const Tegra::FramebufferConfig> framebuffers) {
|
||||
if (framebuffers.empty()) {
|
||||
return VkExtent2D{};
|
||||
}
|
||||
|
||||
const auto& framebuffer = framebuffers.front();
|
||||
if (framebuffer.crop_rect.IsEmpty()) {
|
||||
return VkExtent2D{.width = framebuffer.width, .height = framebuffer.height};
|
||||
}
|
||||
return VkExtent2D{
|
||||
.width = static_cast<u32>(framebuffer.crop_rect.GetWidth()),
|
||||
.height = static_cast<u32>(framebuffer.crop_rect.GetHeight()),
|
||||
};
|
||||
}
|
||||
|
||||
constexpr VkExtent3D CaptureImageExtent{
|
||||
.width = VideoCore::Capture::LinearWidth,
|
||||
.height = VideoCore::Capture::LinearHeight,
|
||||
@@ -193,10 +208,9 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
|
||||
render_window.GetFramebufferLayout(), swapchain.GetImageCount(),
|
||||
swapchain.GetImageViewFormat());
|
||||
|
||||
const size_t wanted = frame_gen.WantedGenerations();
|
||||
const bool can_present_all = wanted > 0 && present_manager.AvailableExtraFrames() >= wanted;
|
||||
void(frame_gen.WantedGenerations(present_manager.MaxExtraFrames()));
|
||||
|
||||
frame_gen.Process(device, frame, swapchain.GetImageFormat(), can_present_all);
|
||||
frame_gen.Process(device, frame, swapchain.GetImageFormat(), GuestExtent(framebuffers));
|
||||
|
||||
const size_t generated_frames = frame_gen.GeneratedFrameCount();
|
||||
for (size_t generation = 0; generation < generated_frames; ++generation) {
|
||||
@@ -210,6 +224,7 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
|
||||
scheduler.Flush(*frame->render_ready);
|
||||
|
||||
present_manager.Present(frame);
|
||||
scheduler.DispatchWork();
|
||||
|
||||
gpu.RendererFrameEndNotify();
|
||||
rasterizer.TickFrame();
|
||||
|
||||
@@ -193,9 +193,8 @@ void PresentManager::Present(Frame* frame) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t PresentManager::AvailableExtraFrames() {
|
||||
std::scoped_lock lock{free_mutex};
|
||||
return free_queue.size();
|
||||
size_t PresentManager::MaxExtraFrames() const {
|
||||
return image_count - 1;
|
||||
}
|
||||
|
||||
void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format,
|
||||
@@ -351,14 +350,11 @@ void PresentManager::SetImageCount() {
|
||||
// We cannot have more than 7 images in flight at any given time.
|
||||
// FRAMES_IN_FLIGHT is 8, and the cache TICKS_TO_DESTROY is 8.
|
||||
// Mali drivers will give us 6.
|
||||
const size_t generations =
|
||||
Settings::values.frame_gen.GetValue()
|
||||
? static_cast<size_t>(Settings::values.frame_gen_multiplier.GetValue()) - 1
|
||||
: 0;
|
||||
const size_t frames_per_composite = generations + 1;
|
||||
image_count = std::min<size_t>(
|
||||
std::max<size_t>(swapchain.GetImageCount() + generations, frames_per_composite * 2),
|
||||
MAX_FRAMES_IN_FLIGHT);
|
||||
const size_t generations = Settings::FrameGenMaxGenerations();
|
||||
const size_t queued_composites = Settings::values.frame_gen_queue_target.GetValue() + 1;
|
||||
image_count =
|
||||
std::clamp<size_t>((generations + 1) * queued_composites, swapchain.GetImageCount(),
|
||||
MAX_FRAMES_IN_FLIGHT);
|
||||
}
|
||||
|
||||
void PresentManager::CopyToSwapchain(Frame* frame) {
|
||||
|
||||
@@ -63,7 +63,7 @@ public:
|
||||
void WaitPresent();
|
||||
|
||||
/// How many additional frames can be queued without stalling the render thread
|
||||
size_t AvailableExtraFrames();
|
||||
[[nodiscard]] size_t MaxExtraFrames() const;
|
||||
|
||||
private:
|
||||
void PresentThread(std::stop_token token);
|
||||
|
||||
Reference in New Issue
Block a user