mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-29 18:08:08 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f4f4a3586 | |||
| 8470ad4b2a | |||
| 4b33a2746c |
@@ -65,6 +65,12 @@
|
|||||||
"repo": "eden-emulator/discord-rpc",
|
"repo": "eden-emulator/discord-rpc",
|
||||||
"version": "0d8b2d6a37"
|
"version": "0d8b2d6a37"
|
||||||
},
|
},
|
||||||
|
"dxbc": {
|
||||||
|
"bundled": true,
|
||||||
|
"hash": "196d26c07747d7aa2ced6fb1a5ae6f665e9b917de223a540038690b037c70a1eeca51c21d42aa5bec9fff7df0a6656a75ee8b4e2cb4008469f18a6812280f831",
|
||||||
|
"repo": "PancakeTAS/dxbc",
|
||||||
|
"version": "78ab59a8aaeb43cd1b0a5e91ba86722433a10b78"
|
||||||
|
},
|
||||||
"enet": {
|
"enet": {
|
||||||
"find_args": "MODULE",
|
"find_args": "MODULE",
|
||||||
"hash": "a0d2fa8c957704dd49e00a726284ac5ca034b50b00d2b20a94fa1bbfbb80841467834bfdc84aa0ed0d6aab894608fd6c86c3b94eee46343f0e6d9c22e391dbf9",
|
"hash": "a0d2fa8c957704dd49e00a726284ac5ca034b50b00d2b20a94fa1bbfbb80841467834bfdc84aa0ed0d6aab894608fd6c86c3b94eee46343f0e6d9c22e391dbf9",
|
||||||
|
|||||||
Vendored
+4
@@ -92,6 +92,10 @@ AddDependentPackages(vulkan-headers vulkan-utility-libraries)
|
|||||||
# frozen
|
# frozen
|
||||||
AddJsonPackage(frozen)
|
AddJsonPackage(frozen)
|
||||||
|
|
||||||
|
# DXVK's DXBC compiler, used to translate the frame generation shaders
|
||||||
|
# out of a user-supplied Lossless.dll into SPIR-V
|
||||||
|
AddJsonPackage(dxbc)
|
||||||
|
|
||||||
# DiscordRPC
|
# DiscordRPC
|
||||||
if (USE_DISCORD_PRESENCE)
|
if (USE_DISCORD_PRESENCE)
|
||||||
if (ARCHITECTURE_arm64)
|
if (ARCHITECTURE_arm64)
|
||||||
|
|||||||
@@ -539,6 +539,40 @@ object NativeLibrary {
|
|||||||
*/
|
*/
|
||||||
external fun installKeys(path: String, ext: String): Int
|
external fun installKeys(path: String, ext: String): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Whether this GPU can run the Lossless Scaling frame generation shaders,
|
||||||
|
* which are built against the Vulkan memory model.
|
||||||
|
*/
|
||||||
|
external fun supportsFrameGeneration(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Path the user-supplied Lossless Scaling library is expected at.
|
||||||
|
*/
|
||||||
|
external fun getLosslessDllPath(): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the installed Lossless Scaling library and checks that every shader the
|
||||||
|
* frame generation chain needs is present.
|
||||||
|
*
|
||||||
|
* @return The result code, matching the losslessDllResults array.
|
||||||
|
*/
|
||||||
|
external fun validateLosslessDll(): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the frame generation shaders out of the installed Lossless Scaling library
|
||||||
|
* and writes them to the SPIR-V cache. Slow, so call it off the main thread.
|
||||||
|
*
|
||||||
|
* @return The result code, matching the losslessDllResults array.
|
||||||
|
*/
|
||||||
|
external fun prepareLosslessDll(): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the installed Lossless Scaling library.
|
||||||
|
*
|
||||||
|
* @return Whether the library is gone after the call.
|
||||||
|
*/
|
||||||
|
external fun removeLosslessDll(): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the PatchManager for any addons that are available
|
* Checks the PatchManager for any addons that are available
|
||||||
*
|
*
|
||||||
|
|||||||
+2
@@ -37,6 +37,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
|
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
|
||||||
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
|
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
|
||||||
RENDERER_SAMPLE_SHADING("sample_shading"),
|
RENDERER_SAMPLE_SHADING("sample_shading"),
|
||||||
|
RENDERER_FRAME_GEN("frame_gen"),
|
||||||
|
RENDERER_FRAME_GEN_DUMP_FLOW("frame_gen_dump_flow"),
|
||||||
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
|
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
|
||||||
PICTURE_IN_PICTURE("picture_in_picture"),
|
PICTURE_IN_PICTURE("picture_in_picture"),
|
||||||
USE_CUSTOM_RTC("custom_rtc_enabled"),
|
USE_CUSTOM_RTC("custom_rtc_enabled"),
|
||||||
|
|||||||
+14
@@ -607,6 +607,20 @@ abstract class SettingsItem(
|
|||||||
valuesId = R.array.rendererAntiAliasingValues
|
valuesId = R.array.rendererAntiAliasingValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.RENDERER_FRAME_GEN,
|
||||||
|
titleId = R.string.frame_gen,
|
||||||
|
descriptionId = R.string.frame_gen_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW,
|
||||||
|
titleId = R.string.frame_gen_dump_flow,
|
||||||
|
descriptionId = R.string.frame_gen_dump_flow_description
|
||||||
|
)
|
||||||
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_SCREEN_LAYOUT,
|
IntSetting.RENDERER_SCREEN_LAYOUT,
|
||||||
|
|||||||
+32
@@ -33,6 +33,7 @@ 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.Settings
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.PathSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.PathSetting
|
||||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
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.PathUtil
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||||
import org.yuzu.yuzu_emu.utils.*
|
import org.yuzu.yuzu_emu.utils.*
|
||||||
@@ -114,6 +115,10 @@ class SettingsFragment : Fragment() {
|
|||||||
viewLifecycleOwner,
|
viewLifecycleOwner,
|
||||||
resetState = { settingsViewModel.setShouldReloadSettingsList(false) }
|
resetState = { settingsViewModel.setShouldReloadSettingsList(false) }
|
||||||
) { if (it) presenter.loadSettingsList() }
|
) { if (it) presenter.loadSettingsList() }
|
||||||
|
settingsViewModel.shouldShowLosslessInstaller.collect(
|
||||||
|
viewLifecycleOwner,
|
||||||
|
resetState = { settingsViewModel.setShouldShowLosslessInstaller(false) }
|
||||||
|
) { if (it) losslessDllPickerLauncher.launch(arrayOf("*/*")) }
|
||||||
settingsViewModel.adapterItemChanged.collect(
|
settingsViewModel.adapterItemChanged.collect(
|
||||||
viewLifecycleOwner,
|
viewLifecycleOwner,
|
||||||
resetState = { settingsViewModel.setAdapterItemChanged(-1) }
|
resetState = { settingsViewModel.setAdapterItemChanged(-1) }
|
||||||
@@ -267,6 +272,33 @@ private fun getPlayerIndex(): Int =
|
|||||||
directoryPickerLauncher.launch(null)
|
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(
|
private val directoryPickerLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.OpenDocumentTree()
|
ActivityResultContracts.OpenDocumentTree()
|
||||||
) { uri ->
|
) { uri ->
|
||||||
|
|||||||
+46
@@ -27,6 +27,7 @@ import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||||
|
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||||
import org.yuzu.yuzu_emu.utils.FullscreenHelper
|
import org.yuzu.yuzu_emu.utils.FullscreenHelper
|
||||||
@@ -76,6 +77,49 @@ class SettingsFragmentPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addFrameGenSettings(sl: ArrayList<SettingsItem>) {
|
||||||
|
sl.apply {
|
||||||
|
add(HeaderSetting(R.string.frame_gen))
|
||||||
|
|
||||||
|
val installed = LosslessScalingHelper.isInstalled()
|
||||||
|
|
||||||
|
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 (!LosslessScalingHelper.isSupportedByGpu()) {
|
||||||
|
add(
|
||||||
|
RunnableSetting(
|
||||||
|
titleId = R.string.frame_gen_unsupported,
|
||||||
|
descriptionId = R.string.frame_gen_unsupported_description,
|
||||||
|
isRunnable = false
|
||||||
|
) {}
|
||||||
|
)
|
||||||
|
return@apply
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!installed) {
|
||||||
|
return@apply
|
||||||
|
}
|
||||||
|
|
||||||
|
add(BooleanSetting.RENDERER_FRAME_GEN.key)
|
||||||
|
add(BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun isSharpnessScalingFilterSelected(): Boolean {
|
private fun isSharpnessScalingFilterSelected(): Boolean {
|
||||||
val needsGlobal = getNeedsGlobalForKey(IntSetting.RENDERER_SCALING_FILTER.key)
|
val needsGlobal = getNeedsGlobalForKey(IntSetting.RENDERER_SCALING_FILTER.key)
|
||||||
val selectedFilter = IntSetting.RENDERER_SCALING_FILTER.getInt(needsGlobal)
|
val selectedFilter = IntSetting.RENDERER_SCALING_FILTER.getInt(needsGlobal)
|
||||||
@@ -280,6 +324,8 @@ class SettingsFragmentPresenter(
|
|||||||
}
|
}
|
||||||
add(IntSetting.RENDERER_ANTI_ALIASING.key)
|
add(IntSetting.RENDERER_ANTI_ALIASING.key)
|
||||||
|
|
||||||
|
addFrameGenSettings(this)
|
||||||
|
|
||||||
add(HeaderSetting(R.string.advanced))
|
add(HeaderSetting(R.string.advanced))
|
||||||
|
|
||||||
add(IntSetting.RENDERER_ACCURACY.key)
|
add(IntSetting.RENDERER_ACCURACY.key)
|
||||||
|
|||||||
+7
@@ -36,6 +36,9 @@ class SettingsViewModel : ViewModel() {
|
|||||||
val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
|
val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
|
||||||
private val _shouldReloadSettingsList = MutableStateFlow(false)
|
private val _shouldReloadSettingsList = MutableStateFlow(false)
|
||||||
|
|
||||||
|
val shouldShowLosslessInstaller: StateFlow<Boolean> get() = _shouldShowLosslessInstaller
|
||||||
|
private val _shouldShowLosslessInstaller = MutableStateFlow(false)
|
||||||
|
|
||||||
val sliderProgress: StateFlow<Int> get() = _sliderProgress
|
val sliderProgress: StateFlow<Int> get() = _sliderProgress
|
||||||
private val _sliderProgress = MutableStateFlow(-1)
|
private val _sliderProgress = MutableStateFlow(-1)
|
||||||
|
|
||||||
@@ -85,6 +88,10 @@ class SettingsViewModel : ViewModel() {
|
|||||||
_shouldReloadSettingsList.value = value
|
_shouldReloadSettingsList.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setShouldShowLosslessInstaller(value: Boolean) {
|
||||||
|
_shouldShowLosslessInstaller.value = value
|
||||||
|
}
|
||||||
|
|
||||||
fun setSliderTextValue(value: Float, units: String) {
|
fun setSliderTextValue(value: Float, units: String) {
|
||||||
_sliderProgress.value = value.toInt()
|
_sliderProgress.value = value.toInt()
|
||||||
_sliderTextValue.value = String.format(
|
_sliderTextValue.value = String.format(
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
@@ -44,6 +45,7 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity
|
|||||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||||
import org.yuzu.yuzu_emu.utils.Log
|
import org.yuzu.yuzu_emu.utils.Log
|
||||||
|
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||||
|
|
||||||
class HomeSettingsFragment : Fragment() {
|
class HomeSettingsFragment : Fragment() {
|
||||||
@@ -170,6 +172,18 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
add(
|
||||||
|
HomeSetting(
|
||||||
|
R.string.lossless_scaling,
|
||||||
|
R.string.lossless_scaling_description,
|
||||||
|
R.drawable.ic_frames,
|
||||||
|
{ onLosslessScalingClicked() },
|
||||||
|
{ true },
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
LosslessScalingHelper.statusText
|
||||||
|
)
|
||||||
|
)
|
||||||
add(
|
add(
|
||||||
HomeSetting(
|
HomeSetting(
|
||||||
R.string.multiplayer,
|
R.string.multiplayer,
|
||||||
@@ -337,8 +351,51 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
driverViewModel.updateDriverNameForGame(null)
|
driverViewModel.updateDriverNameForGame(null)
|
||||||
|
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() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
_binding = null
|
_binding = null
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import org.yuzu.yuzu_emu.model.SetupPage
|
|||||||
import org.yuzu.yuzu_emu.model.PageState
|
import org.yuzu.yuzu_emu.model.PageState
|
||||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||||
|
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||||
@@ -202,6 +203,24 @@ class SetupFragment : Fragment() {
|
|||||||
R.string.install_firmware_warning_help,
|
R.string.install_firmware_warning_help,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
add(
|
||||||
|
PageButton(
|
||||||
|
R.drawable.ic_frames,
|
||||||
|
R.string.lossless_scaling,
|
||||||
|
R.string.lossless_scaling_setup_description,
|
||||||
|
{
|
||||||
|
pageButtonCallback = it
|
||||||
|
getLosslessDll.launch(arrayOf("*/*"))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
if (LosslessScalingHelper.isInstalled()) {
|
||||||
|
ButtonState.BUTTON_ACTION_COMPLETE
|
||||||
|
} else {
|
||||||
|
ButtonState.BUTTON_ACTION_INCOMPLETE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
add(
|
add(
|
||||||
PageButton(
|
PageButton(
|
||||||
R.drawable.ic_controller,
|
R.drawable.ic_controller,
|
||||||
@@ -446,6 +465,32 @@ class SetupFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val getLosslessDll =
|
||||||
|
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]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.apply {
|
||||||
|
onDialogComplete = { checkForButtonState.invoke() }
|
||||||
|
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
||||||
|
}
|
||||||
|
|
||||||
val getGamesDirectory =
|
val getGamesDirectory =
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
|||||||
@@ -72,6 +72,18 @@ class SystemInfoDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
val vulkanDriver = NativeLibrary.getVulkanDriverVersion()
|
val vulkanDriver = NativeLibrary.getVulkanDriverVersion()
|
||||||
appendLine("${getString(R.string.vulkan_driver_version)}: $vulkanDriver")
|
appendLine("${getString(R.string.vulkan_driver_version)}: $vulkanDriver")
|
||||||
|
|
||||||
|
val frameGen = NativeLibrary.supportsFrameGeneration()
|
||||||
|
appendLine(
|
||||||
|
"${getString(R.string.frame_generation_support)}: " +
|
||||||
|
getString(
|
||||||
|
if (frameGen) {
|
||||||
|
R.string.frame_generation_supported
|
||||||
|
} else {
|
||||||
|
R.string.frame_generation_unsupported
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
appendLine("${getString(R.string.error_getting_emulator_info)}: ${e.message}")
|
appendLine("${getString(R.string.error_getting_emulator_info)}: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
|
import org.yuzu.yuzu_emu.R
|
||||||
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object LosslessScalingHelper {
|
||||||
|
const val RESULT_OK = 0
|
||||||
|
const val RESULT_NOT_INSTALLED = 1
|
||||||
|
|
||||||
|
private val _statusText = MutableStateFlow("")
|
||||||
|
val statusText: StateFlow<String> = _statusText.asStateFlow()
|
||||||
|
|
||||||
|
private var installed: Boolean? = null
|
||||||
|
private var gpuSupported: Boolean? = null
|
||||||
|
|
||||||
|
fun isInstalled(): Boolean = installed ?: refreshStatus()
|
||||||
|
|
||||||
|
fun isSupportedByGpu(): Boolean {
|
||||||
|
val cached = gpuSupported
|
||||||
|
if (cached != null) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
val result = NativeLibrary.supportsFrameGeneration()
|
||||||
|
gpuSupported = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshStatus(): Boolean {
|
||||||
|
val result = NativeLibrary.validateLosslessDll() == RESULT_OK
|
||||||
|
installed = result
|
||||||
|
|
||||||
|
val context = YuzuApplication.appContext
|
||||||
|
_statusText.value = if (result) {
|
||||||
|
context.getString(R.string.lossless_scaling_installed)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.lossless_scaling_not_installed)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun install(source: Uri): Int {
|
||||||
|
val destination = File(NativeLibrary.getLosslessDllPath())
|
||||||
|
destination.parentFile?.mkdirs()
|
||||||
|
|
||||||
|
val copied = FileUtil.copyUriToInternalStorage(
|
||||||
|
source,
|
||||||
|
destination.parent!!,
|
||||||
|
destination.name
|
||||||
|
)
|
||||||
|
if (copied == null) {
|
||||||
|
refreshStatus()
|
||||||
|
return RESULT_NOT_INSTALLED
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = NativeLibrary.prepareLosslessDll()
|
||||||
|
if (result != RESULT_OK) {
|
||||||
|
NativeLibrary.removeLosslessDll()
|
||||||
|
}
|
||||||
|
refreshStatus()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(): Boolean {
|
||||||
|
val removed = NativeLibrary.removeLosslessDll()
|
||||||
|
refreshStatus()
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,7 @@ extern "C" {
|
|||||||
#include "common/android/android_common.h"
|
#include "common/android/android_common.h"
|
||||||
#include "common/android/id_cache.h"
|
#include "common/android/id_cache.h"
|
||||||
#include "common/dynamic_library.h"
|
#include "common/dynamic_library.h"
|
||||||
|
#include "common/fs/fs_util.h"
|
||||||
#include "common/fs/path_util.h"
|
#include "common/fs/path_util.h"
|
||||||
#include "common/logging.h"
|
#include "common/logging.h"
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
@@ -90,6 +91,7 @@ extern "C" {
|
|||||||
#include "hid_core/hid_types.h"
|
#include "hid_core/hid_types.h"
|
||||||
#include "input_common/drivers/virtual_amiibo.h"
|
#include "input_common/drivers/virtual_amiibo.h"
|
||||||
#include "jni/native.h"
|
#include "jni/native.h"
|
||||||
|
#include "video_core/frame_gen/lossless_dll.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
#include "video_core/capture.h"
|
#include "video_core/capture.h"
|
||||||
@@ -1090,6 +1092,34 @@ VkPhysicalDeviceProperties GetVulkanDeviceProperties() {
|
|||||||
const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld);
|
const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld);
|
||||||
return physical_device.GetProperties();
|
return physical_device.GetProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GetVulkanMemoryModelSupport() {
|
||||||
|
Common::DynamicLibrary library;
|
||||||
|
if (!library.Open("libvulkan.so")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vulkan::vk::InstanceDispatch dld;
|
||||||
|
const auto instance = Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_1);
|
||||||
|
const auto physical_devices = instance.EnumeratePhysicalDevices();
|
||||||
|
if (physical_devices.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld);
|
||||||
|
|
||||||
|
VkPhysicalDeviceVulkanMemoryModelFeatures memory_model{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES,
|
||||||
|
.pNext = nullptr,
|
||||||
|
};
|
||||||
|
VkPhysicalDeviceFeatures2 features{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||||||
|
.pNext = &memory_model,
|
||||||
|
};
|
||||||
|
physical_device.GetFeatures2(features);
|
||||||
|
|
||||||
|
return memory_model.vulkanMemoryModel == VK_TRUE;
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getVulkanDriverVersion(JNIEnv* env, jobject jobj) {
|
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getVulkanDriverVersion(JNIEnv* env, jobject jobj) {
|
||||||
@@ -1165,6 +1195,14 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getVulkanApiVersion(JNIEnv* env, j
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_supportsFrameGeneration(JNIEnv* env, jobject jobj) {
|
||||||
|
try {
|
||||||
|
return static_cast<jboolean>(GetVulkanMemoryModelSupport());
|
||||||
|
} catch (...) {
|
||||||
|
return static_cast<jboolean>(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGpuModel(JNIEnv* env, jobject jobj) {
|
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGpuModel(JNIEnv* env, jobject jobj) {
|
||||||
const auto props = GetVulkanDeviceProperties();
|
const auto props = GetVulkanDeviceProperties();
|
||||||
if (props.deviceID == 0) {
|
if (props.deviceID == 0) {
|
||||||
@@ -1390,6 +1428,23 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_installKeys(JNIEnv* env, jclass clazz
|
|||||||
return static_cast<int>(FirmwareManager::InstallKeys(path, ext));
|
return static_cast<int>(FirmwareManager::InstallKeys(path, ext));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getLosslessDllPath(JNIEnv* env, jclass clazz) {
|
||||||
|
const auto path = VideoCore::FrameGen::GetLosslessDllPath();
|
||||||
|
return Common::Android::ToJString(env, Common::FS::PathToUTF8String(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_validateLosslessDll(JNIEnv* env, jclass clazz) {
|
||||||
|
return static_cast<jint>(VideoCore::FrameGen::GetInstalledLosslessStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_prepareLosslessDll(JNIEnv* env, jclass clazz) {
|
||||||
|
return static_cast<jint>(VideoCore::FrameGen::BuildShaderCache());
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_removeLosslessDll(JNIEnv* env, jclass clazz) {
|
||||||
|
return static_cast<jboolean>(VideoCore::FrameGen::RemoveInstalledLosslessDll());
|
||||||
|
}
|
||||||
|
|
||||||
jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj,
|
jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj,
|
||||||
jstring jpath,
|
jstring jpath,
|
||||||
jstring jprogramId) {
|
jstring jprogramId) {
|
||||||
|
|||||||
@@ -638,6 +638,16 @@
|
|||||||
<item>@string/error_keys_failed_init</item>
|
<item>@string/error_keys_failed_init</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="losslessDllResults">
|
||||||
|
<item>""</item>
|
||||||
|
<item>@string/error_lossless_copy_failed</item>
|
||||||
|
<item>@string/error_lossless_unreadable</item>
|
||||||
|
<item>@string/error_lossless_not_pe</item>
|
||||||
|
<item>@string/error_lossless_missing_shaders</item>
|
||||||
|
<item>@string/error_lossless_translation_failed</item>
|
||||||
|
<item>@string/error_lossless_cache_failed</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<!-- GPU Logging Arrays -->
|
<!-- GPU Logging Arrays -->
|
||||||
<string-array name="gpuLogLevelEntries">
|
<string-array name="gpuLogLevelEntries">
|
||||||
<item>Off</item>
|
<item>Off</item>
|
||||||
|
|||||||
@@ -298,6 +298,35 @@
|
|||||||
<string name="gpu_driver_fetcher">GPU driver fetcher</string>
|
<string name="gpu_driver_fetcher">GPU driver fetcher</string>
|
||||||
<string name="gpu_driver_manager">GPU driver manager</string>
|
<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="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_description">Insert an interpolated frame between every pair of real frames using Lossless Scaling. Adds about one frame of input latency.</string>
|
||||||
|
<string name="frame_gen_dump_flow">Dump flow pyramid</string>
|
||||||
|
<string name="frame_gen_dump_flow_description">Write the optical flow mip levels to the lossless/debug folder once, for troubleshooting</string>
|
||||||
|
<string name="frame_gen_unsupported">Frame generation unavailable</string>
|
||||||
|
<string name="frame_gen_unsupported_description">This GPU driver does not support the Vulkan memory model, which the Lossless Scaling shaders require.</string>
|
||||||
|
<string name="lossless_scaling_setup_description">Optional. Provide your own Lossless.dll to enable frame generation later</string>
|
||||||
|
<string name="lossless_scaling_install">Install Lossless.dll</string>
|
||||||
|
<string name="lossless_scaling_install_description">Frame generation needs your own copy of Lossless.dll from Lossless Scaling</string>
|
||||||
|
<string name="lossless_scaling_replace_description">Select a different copy of Lossless.dll</string>
|
||||||
|
<string name="frame_generation_support">Frame generation</string>
|
||||||
|
<string name="frame_generation_supported">Supported</string>
|
||||||
|
<string name="frame_generation_unsupported">Unsupported (no Vulkan memory model)</string>
|
||||||
|
<string name="lossless_scaling">Lossless Scaling</string>
|
||||||
|
<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_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>
|
||||||
|
<string name="error_lossless_copy_failed">The selected file could not be copied.</string>
|
||||||
|
<string name="error_lossless_unreadable">The selected file could not be read.</string>
|
||||||
|
<string name="error_lossless_not_pe">The selected file is not a Windows library. Select Lossless.dll from your Lossless Scaling installation.</string>
|
||||||
|
<string name="error_lossless_missing_shaders">This copy of Lossless.dll does not contain the frame generation shaders. Update Lossless Scaling and try again.</string>
|
||||||
|
<string name="error_lossless_translation_failed">The frame generation shaders could not be translated. This version of Lossless Scaling is not supported yet.</string>
|
||||||
|
<string name="error_lossless_cache_failed">The translated shaders could not be written to storage. Check that there is free space available.</string>
|
||||||
<string name="advanced_settings">Advanced settings</string>
|
<string name="advanced_settings">Advanced settings</string>
|
||||||
<string name="settings_description">Configure emulator settings</string>
|
<string name="settings_description">Configure emulator settings</string>
|
||||||
<string name="search_recently_played">Recently played</string>
|
<string name="search_recently_played">Recently played</string>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#define KEYS_DIR "keys"
|
#define KEYS_DIR "keys"
|
||||||
#define LOAD_DIR "load"
|
#define LOAD_DIR "load"
|
||||||
#define LOG_DIR "log"
|
#define LOG_DIR "log"
|
||||||
|
#define LOSSLESS_DIR "lossless"
|
||||||
#define NAND_DIR "nand"
|
#define NAND_DIR "nand"
|
||||||
#define PLAY_TIME_DIR "play_time"
|
#define PLAY_TIME_DIR "play_time"
|
||||||
#define SCREENSHOTS_DIR "screenshots"
|
#define SCREENSHOTS_DIR "screenshots"
|
||||||
@@ -37,3 +38,5 @@
|
|||||||
|
|
||||||
// yuzu-specific files
|
// yuzu-specific files
|
||||||
#define LOG_FILE "eden_log.txt"
|
#define LOG_FILE "eden_log.txt"
|
||||||
|
#define LOSSLESS_DLL_FILE "Lossless.dll"
|
||||||
|
#define LOSSLESS_CACHE_FILE "lsfg_spirv.cache"
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ public:
|
|||||||
GenerateEdenPath(EdenPath::KeysDir, eden_path / KEYS_DIR);
|
GenerateEdenPath(EdenPath::KeysDir, eden_path / KEYS_DIR);
|
||||||
GenerateEdenPath(EdenPath::LoadDir, eden_path / LOAD_DIR);
|
GenerateEdenPath(EdenPath::LoadDir, eden_path / LOAD_DIR);
|
||||||
GenerateEdenPath(EdenPath::LogDir, eden_path / LOG_DIR);
|
GenerateEdenPath(EdenPath::LogDir, eden_path / LOG_DIR);
|
||||||
|
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
|
||||||
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
|
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
|
||||||
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
|
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
|
||||||
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
|
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ enum class EdenPath {
|
|||||||
KeysDir, // Where key files are stored.
|
KeysDir, // Where key files are stored.
|
||||||
LoadDir, // Where cheat/mod files are stored.
|
LoadDir, // Where cheat/mod files are stored.
|
||||||
LogDir, // Where log files are stored.
|
LogDir, // Where log files are stored.
|
||||||
|
LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
|
||||||
NANDDir, // Where the emulated NAND is stored.
|
NANDDir, // Where the emulated NAND is stored.
|
||||||
PlayTimeDir, // Where play time data is stored.
|
PlayTimeDir, // Where play time data is stored.
|
||||||
SaveDir, // Where save data is stored.
|
SaveDir, // Where save data is stored.
|
||||||
|
|||||||
@@ -388,6 +388,11 @@ struct Values {
|
|||||||
true,
|
true,
|
||||||
true};
|
true};
|
||||||
|
|
||||||
|
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
|
||||||
|
Specialization::Default, true, true};
|
||||||
|
SwitchableSetting<bool> frame_gen_dump_flow{linkage, false, "frame_gen_dump_flow",
|
||||||
|
Category::Renderer};
|
||||||
|
|
||||||
SwitchableSetting<bool> use_asynchronous_gpu_emulation{linkage,
|
SwitchableSetting<bool> use_asynchronous_gpu_emulation{linkage,
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ add_library(video_core STATIC
|
|||||||
engines/maxwell_dma.h
|
engines/maxwell_dma.h
|
||||||
engines/puller.cpp
|
engines/puller.cpp
|
||||||
engines/puller.h
|
engines/puller.h
|
||||||
|
frame_gen/lossless_dll.cpp
|
||||||
|
frame_gen/lossless_dll.h
|
||||||
|
frame_gen/lsfg_translate.cpp
|
||||||
|
frame_gen/lsfg_translate.h
|
||||||
framebuffer_config.cpp
|
framebuffer_config.cpp
|
||||||
framebuffer_config.h
|
framebuffer_config.h
|
||||||
fsr.cpp
|
fsr.cpp
|
||||||
@@ -121,6 +125,12 @@ add_library(video_core STATIC
|
|||||||
renderer_vulkan/present/anti_alias_pass.h
|
renderer_vulkan/present/anti_alias_pass.h
|
||||||
renderer_vulkan/present/filters.cpp
|
renderer_vulkan/present/filters.cpp
|
||||||
renderer_vulkan/present/filters.h
|
renderer_vulkan/present/filters.h
|
||||||
|
renderer_vulkan/present/frame_gen.cpp
|
||||||
|
renderer_vulkan/present/frame_gen.h
|
||||||
|
renderer_vulkan/present/lsfg_mipmaps.cpp
|
||||||
|
renderer_vulkan/present/lsfg_mipmaps.h
|
||||||
|
renderer_vulkan/present/lsfg_shaders.cpp
|
||||||
|
renderer_vulkan/present/lsfg_shaders.h
|
||||||
renderer_vulkan/present/fsr.cpp
|
renderer_vulkan/present/fsr.cpp
|
||||||
renderer_vulkan/present/fsr.h
|
renderer_vulkan/present/fsr.h
|
||||||
renderer_vulkan/present/fxaa.cpp
|
renderer_vulkan/present/fxaa.cpp
|
||||||
@@ -345,6 +355,7 @@ add_dependencies(video_core host_shaders)
|
|||||||
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
|
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
|
||||||
|
|
||||||
target_link_libraries(video_core PRIVATE sirit::sirit)
|
target_link_libraries(video_core PRIVATE sirit::sirit)
|
||||||
|
target_link_libraries(video_core PRIVATE dxbc)
|
||||||
|
|
||||||
# Header-only stuff needed by all dependent targets
|
# Header-only stuff needed by all dependent targets
|
||||||
target_link_libraries(video_core PUBLIC Vulkan::Headers Vulkan::UtilityHeaders GPUOpen::VulkanMemoryAllocator)
|
target_link_libraries(video_core PUBLIC Vulkan::Headers Vulkan::UtilityHeaders GPUOpen::VulkanMemoryAllocator)
|
||||||
|
|||||||
@@ -0,0 +1,515 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include "common/cityhash.h"
|
||||||
|
#include "common/fs/file.h"
|
||||||
|
#include "common/fs/fs.h"
|
||||||
|
#include "common/fs/fs_paths.h"
|
||||||
|
#include "common/fs/path_util.h"
|
||||||
|
#include "video_core/frame_gen/lossless_dll.h"
|
||||||
|
#include "video_core/frame_gen/lsfg_translate.h"
|
||||||
|
|
||||||
|
namespace VideoCore::FrameGen {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr u16 DOS_MAGIC = 0x5A4D;
|
||||||
|
constexpr u32 PE_SIGNATURE = 0x00004550;
|
||||||
|
constexpr u16 PE32_MAGIC = 0x010B;
|
||||||
|
constexpr u16 PE32_PLUS_MAGIC = 0x020B;
|
||||||
|
|
||||||
|
constexpr size_t DOS_LFANEW_OFFSET = 0x3C;
|
||||||
|
constexpr size_t COFF_HEADER_SIZE = 20;
|
||||||
|
constexpr size_t OPTIONAL_HEADER_SIZE_OFFSET = 16;
|
||||||
|
constexpr size_t SECTION_HEADER_SIZE = 40;
|
||||||
|
constexpr size_t DATA_DIRECTORY_ENTRY_SIZE = 8;
|
||||||
|
constexpr size_t DATA_DIRECTORY_OFFSET_PE32 = 96;
|
||||||
|
constexpr size_t DATA_DIRECTORY_OFFSET_PE32_PLUS = 112;
|
||||||
|
constexpr size_t RESOURCE_DATA_DIRECTORY_INDEX = 2;
|
||||||
|
|
||||||
|
constexpr size_t RESOURCE_DIRECTORY_SIZE = 16;
|
||||||
|
constexpr size_t RESOURCE_NAMED_COUNT_OFFSET = 12;
|
||||||
|
constexpr size_t RESOURCE_ID_COUNT_OFFSET = 14;
|
||||||
|
constexpr size_t RESOURCE_ENTRY_SIZE = 8;
|
||||||
|
constexpr u32 RESOURCE_SUBDIRECTORY_FLAG = 0x80000000;
|
||||||
|
constexpr u32 RESOURCE_TYPE_RCDATA = 10;
|
||||||
|
|
||||||
|
constexpr u32 MIPMAPS_SHADER_ID = 255;
|
||||||
|
constexpr u32 GENERATE_SHADER_ID = 256;
|
||||||
|
constexpr u32 PERFORMANCE_SHADER_ID_FIRST = 280;
|
||||||
|
constexpr u32 PERFORMANCE_SHADER_ID_LAST = 302;
|
||||||
|
|
||||||
|
constexpr u32 CACHE_MAGIC = 0x4746534C;
|
||||||
|
constexpr u32 CACHE_VERSION = 1;
|
||||||
|
|
||||||
|
struct CacheHeader {
|
||||||
|
u32 magic;
|
||||||
|
u32 version;
|
||||||
|
u64 source_size;
|
||||||
|
u64 source_hash;
|
||||||
|
u32 module_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Section {
|
||||||
|
u32 virtual_address;
|
||||||
|
u32 virtual_size;
|
||||||
|
u32 raw_address;
|
||||||
|
u32 raw_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ResourceEntry {
|
||||||
|
u32 id;
|
||||||
|
u32 offset;
|
||||||
|
bool is_directory;
|
||||||
|
bool is_named;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageReader {
|
||||||
|
public:
|
||||||
|
explicit ImageReader(std::span<const u8> image_) : image{image_} {}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] bool Read(size_t offset, T& out_value) const {
|
||||||
|
if (offset > image.size() || image.size() - offset < sizeof(T)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::memcpy(&out_value, image.data() + offset, sizeof(T));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool Slice(size_t offset, size_t size, std::span<const u8>& out_slice) const {
|
||||||
|
if (offset > image.size() || image.size() - offset < size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out_slice = image.subspan(offset, size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::span<const u8> image;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<size_t> FindPeHeader(const ImageReader& reader) {
|
||||||
|
u16 dos_magic{};
|
||||||
|
if (!reader.Read(0, dos_magic) || dos_magic != DOS_MAGIC) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 pe_offset{};
|
||||||
|
if (!reader.Read(DOS_LFANEW_OFFSET, pe_offset)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 pe_signature{};
|
||||||
|
if (!reader.Read(pe_offset, pe_signature) || pe_signature != PE_SIGNATURE) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<size_t>(pe_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<size_t> FindDataDirectory(const ImageReader& reader,
|
||||||
|
size_t optional_header_offset) {
|
||||||
|
u16 optional_magic{};
|
||||||
|
if (!reader.Read(optional_header_offset, optional_magic)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (optional_magic) {
|
||||||
|
case PE32_MAGIC:
|
||||||
|
return optional_header_offset + DATA_DIRECTORY_OFFSET_PE32;
|
||||||
|
case PE32_PLUS_MAGIC:
|
||||||
|
return optional_header_offset + DATA_DIRECTORY_OFFSET_PE32_PLUS;
|
||||||
|
default:
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool ReadSections(const ImageReader& reader, size_t pe_offset,
|
||||||
|
std::vector<Section>& out_sections) {
|
||||||
|
u16 section_count{};
|
||||||
|
u16 optional_header_size{};
|
||||||
|
if (!reader.Read(pe_offset + 4 + 2, section_count) ||
|
||||||
|
!reader.Read(pe_offset + 4 + OPTIONAL_HEADER_SIZE_OFFSET, optional_header_size)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t table_offset = pe_offset + 4 + COFF_HEADER_SIZE + optional_header_size;
|
||||||
|
out_sections.reserve(section_count);
|
||||||
|
for (size_t i = 0; i < section_count; ++i) {
|
||||||
|
const size_t offset = table_offset + i * SECTION_HEADER_SIZE;
|
||||||
|
Section section{};
|
||||||
|
if (!reader.Read(offset + 8, section.virtual_size) ||
|
||||||
|
!reader.Read(offset + 12, section.virtual_address) ||
|
||||||
|
!reader.Read(offset + 16, section.raw_size) ||
|
||||||
|
!reader.Read(offset + 20, section.raw_address)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out_sections.push_back(section);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<size_t> RvaToFileOffset(std::span<const Section> sections, u32 rva) {
|
||||||
|
for (const Section& section : sections) {
|
||||||
|
const u32 span = std::max(section.virtual_size, section.raw_size);
|
||||||
|
if (span == 0 || rva < section.virtual_address) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const u32 relative = rva - section.virtual_address;
|
||||||
|
if (relative < span) {
|
||||||
|
return static_cast<size_t>(section.raw_address) + relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool ReadResourceEntries(const ImageReader& reader, size_t directory_offset,
|
||||||
|
std::vector<ResourceEntry>& out_entries) {
|
||||||
|
u16 named_count{};
|
||||||
|
u16 id_count{};
|
||||||
|
if (!reader.Read(directory_offset + RESOURCE_NAMED_COUNT_OFFSET, named_count) ||
|
||||||
|
!reader.Read(directory_offset + RESOURCE_ID_COUNT_OFFSET, id_count)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t total = size_t{named_count} + size_t{id_count};
|
||||||
|
out_entries.clear();
|
||||||
|
out_entries.reserve(total);
|
||||||
|
for (size_t i = 0; i < total; ++i) {
|
||||||
|
const size_t offset = directory_offset + RESOURCE_DIRECTORY_SIZE + i * RESOURCE_ENTRY_SIZE;
|
||||||
|
u32 name{};
|
||||||
|
u32 data{};
|
||||||
|
if (!reader.Read(offset, name) || !reader.Read(offset + 4, data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out_entries.push_back(ResourceEntry{
|
||||||
|
.id = name & ~RESOURCE_SUBDIRECTORY_FLAG,
|
||||||
|
.offset = data & ~RESOURCE_SUBDIRECTORY_FLAG,
|
||||||
|
.is_directory = (data & RESOURCE_SUBDIRECTORY_FLAG) != 0,
|
||||||
|
.is_named = (name & RESOURCE_SUBDIRECTORY_FLAG) != 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool ReadResourceLeaf(const ImageReader& reader, std::span<const Section> sections,
|
||||||
|
size_t leaf_offset, std::span<const u8>& out_data) {
|
||||||
|
u32 data_rva{};
|
||||||
|
u32 data_size{};
|
||||||
|
if (!reader.Read(leaf_offset, data_rva) || !reader.Read(leaf_offset + 4, data_size) ||
|
||||||
|
data_size == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<size_t> data_offset = RvaToFileOffset(sections, data_rva);
|
||||||
|
if (!data_offset) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return reader.Slice(*data_offset, data_size, out_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
using ResourceSpans = std::map<u32, std::span<const u8>>;
|
||||||
|
|
||||||
|
[[nodiscard]] bool CollectRcData(const ImageReader& reader, std::span<const Section> sections,
|
||||||
|
size_t resource_base, ResourceSpans& out_resources) {
|
||||||
|
std::vector<ResourceEntry> type_entries;
|
||||||
|
if (!ReadResourceEntries(reader, resource_base, type_entries)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ResourceEntry& type_entry : type_entries) {
|
||||||
|
if (type_entry.is_named || type_entry.id != RESOURCE_TYPE_RCDATA ||
|
||||||
|
!type_entry.is_directory) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ResourceEntry> name_entries;
|
||||||
|
if (!ReadResourceEntries(reader, resource_base + type_entry.offset, name_entries)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ResourceEntry& name_entry : name_entries) {
|
||||||
|
if (name_entry.is_named || !name_entry.is_directory) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ResourceEntry> language_entries;
|
||||||
|
if (!ReadResourceEntries(reader, resource_base + name_entry.offset, language_entries)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ResourceEntry& language_entry : language_entries) {
|
||||||
|
if (language_entry.is_directory) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::span<const u8> data;
|
||||||
|
if (!ReadResourceLeaf(reader, sections, resource_base + language_entry.offset,
|
||||||
|
data)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out_resources.insert_or_assign(name_entry.id, data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<u32> PerformanceShaderIds() {
|
||||||
|
std::vector<u32> ids{MIPMAPS_SHADER_ID, GENERATE_SHADER_ID};
|
||||||
|
for (u32 id = PERFORMANCE_SHADER_ID_FIRST; id <= PERFORMANCE_SHADER_ID_LAST; ++id) {
|
||||||
|
ids.push_back(id);
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Map>
|
||||||
|
[[nodiscard]] bool HasPerformanceShaders(const Map& resources) {
|
||||||
|
const std::vector<u32> ids = PerformanceShaderIds();
|
||||||
|
return std::ranges::all_of(ids, [&](u32 id) { return resources.contains(id); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] LosslessStatus TranslateAll(const ResourceSpans& resources,
|
||||||
|
ShaderModules& out_modules) {
|
||||||
|
out_modules.clear();
|
||||||
|
for (const u32 id : PerformanceShaderIds()) {
|
||||||
|
const auto hit = resources.find(id);
|
||||||
|
if (hit == resources.end()) {
|
||||||
|
return LosslessStatus::MissingShaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u32> words = TranslateComputeShader(hit->second);
|
||||||
|
if (words.empty()) {
|
||||||
|
return LosslessStatus::TranslationFailed;
|
||||||
|
}
|
||||||
|
out_modules.emplace(id, std::move(words));
|
||||||
|
}
|
||||||
|
return LosslessStatus::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool WriteShaderCache(const std::filesystem::path& path, const CacheHeader& header,
|
||||||
|
const ShaderModules& modules) {
|
||||||
|
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write,
|
||||||
|
Common::FS::FileType::BinaryFile};
|
||||||
|
if (!file.IsOpen() || file.Write(header) != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [id, words] : modules) {
|
||||||
|
const u32 word_count = static_cast<u32>(words.size());
|
||||||
|
if (file.Write(id) != 1 || file.Write(word_count) != 1 ||
|
||||||
|
file.Write(words) != words.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool ReadShaderCache(const std::filesystem::path& path, u64 source_size,
|
||||||
|
u64 source_hash, ShaderModules& out_modules) {
|
||||||
|
if (!Common::FS::Exists(path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read,
|
||||||
|
Common::FS::FileType::BinaryFile};
|
||||||
|
CacheHeader header{};
|
||||||
|
if (!file.IsOpen() || file.Read(header) != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (header.magic != CACHE_MAGIC || header.version != CACHE_VERSION ||
|
||||||
|
header.source_size != source_size || header.source_hash != source_hash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_modules.clear();
|
||||||
|
for (u32 i = 0; i < header.module_count; ++i) {
|
||||||
|
u32 id{};
|
||||||
|
u32 word_count{};
|
||||||
|
if (file.Read(id) != 1 || file.Read(word_count) != 1 || word_count == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u32> words(word_count);
|
||||||
|
if (file.Read(words) != words.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out_modules.emplace(id, std::move(words));
|
||||||
|
}
|
||||||
|
|
||||||
|
return HasPerformanceShaders(out_modules);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] LosslessStatus ReadImageFile(const std::filesystem::path& path,
|
||||||
|
std::vector<u8>& out_image) {
|
||||||
|
if (!Common::FS::Exists(path)) {
|
||||||
|
return LosslessStatus::NotInstalled;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read,
|
||||||
|
Common::FS::FileType::BinaryFile};
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
return LosslessStatus::UnreadableFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_image.resize(static_cast<size_t>(file.GetSize()));
|
||||||
|
if (out_image.empty() || file.Read(out_image) != out_image.size()) {
|
||||||
|
return LosslessStatus::UnreadableFile;
|
||||||
|
}
|
||||||
|
return LosslessStatus::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] LosslessStatus ParseShaderSpans(std::span<const u8> image,
|
||||||
|
ResourceSpans& out_resources) {
|
||||||
|
const ImageReader reader{image};
|
||||||
|
const std::optional<size_t> pe_offset = FindPeHeader(reader);
|
||||||
|
if (!pe_offset) {
|
||||||
|
return LosslessStatus::NotPortableExecutable;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<size_t> data_directory =
|
||||||
|
FindDataDirectory(reader, *pe_offset + 4 + COFF_HEADER_SIZE);
|
||||||
|
if (!data_directory) {
|
||||||
|
return LosslessStatus::NotPortableExecutable;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Section> sections;
|
||||||
|
if (!ReadSections(reader, *pe_offset, sections)) {
|
||||||
|
return LosslessStatus::NotPortableExecutable;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 resource_rva{};
|
||||||
|
if (!reader.Read(*data_directory + RESOURCE_DATA_DIRECTORY_INDEX * DATA_DIRECTORY_ENTRY_SIZE,
|
||||||
|
resource_rva) ||
|
||||||
|
resource_rva == 0) {
|
||||||
|
return LosslessStatus::MissingShaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<size_t> resource_base = RvaToFileOffset(sections, resource_rva);
|
||||||
|
if (!resource_base) {
|
||||||
|
return LosslessStatus::NotPortableExecutable;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_resources.clear();
|
||||||
|
if (!CollectRcData(reader, sections, *resource_base, out_resources)) {
|
||||||
|
return LosslessStatus::MissingShaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HasPerformanceShaders(out_resources) ? LosslessStatus::Ok
|
||||||
|
: LosslessStatus::MissingShaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
std::filesystem::path GetLosslessDllPath() {
|
||||||
|
return Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / LOSSLESS_DLL_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path GetShaderCachePath() {
|
||||||
|
return Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / LOSSLESS_CACHE_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
LosslessStatus ReadShaderResources(const std::filesystem::path& path,
|
||||||
|
ShaderResources& out_resources) {
|
||||||
|
std::vector<u8> image;
|
||||||
|
const LosslessStatus read_status = ReadImageFile(path, image);
|
||||||
|
if (read_status != LosslessStatus::Ok) {
|
||||||
|
return read_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceSpans spans;
|
||||||
|
const LosslessStatus parse_status = ParseShaderSpans(image, spans);
|
||||||
|
if (parse_status != LosslessStatus::Ok) {
|
||||||
|
return parse_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_resources.clear();
|
||||||
|
for (const auto& [id, data] : spans) {
|
||||||
|
out_resources.emplace(id, std::vector<u8>{data.begin(), data.end()});
|
||||||
|
}
|
||||||
|
return LosslessStatus::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
LosslessStatus ValidateLosslessDll(const std::filesystem::path& path) {
|
||||||
|
std::vector<u8> image;
|
||||||
|
const LosslessStatus read_status = ReadImageFile(path, image);
|
||||||
|
if (read_status != LosslessStatus::Ok) {
|
||||||
|
return read_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceSpans spans;
|
||||||
|
return ParseShaderSpans(image, spans);
|
||||||
|
}
|
||||||
|
|
||||||
|
LosslessStatus GetInstalledLosslessStatus() {
|
||||||
|
return ValidateLosslessDll(GetLosslessDllPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
LosslessStatus LoadShaderModules(ShaderModules& out_modules) {
|
||||||
|
std::vector<u8> image;
|
||||||
|
const LosslessStatus read_status = ReadImageFile(GetLosslessDllPath(), image);
|
||||||
|
if (read_status != LosslessStatus::Ok) {
|
||||||
|
return read_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u64 source_size = image.size();
|
||||||
|
const u64 source_hash =
|
||||||
|
Common::CityHash64(reinterpret_cast<const char*>(image.data()), image.size());
|
||||||
|
const std::filesystem::path cache_path = GetShaderCachePath();
|
||||||
|
|
||||||
|
if (ReadShaderCache(cache_path, source_size, source_hash, out_modules)) {
|
||||||
|
return LosslessStatus::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceSpans spans;
|
||||||
|
const LosslessStatus parse_status = ParseShaderSpans(image, spans);
|
||||||
|
if (parse_status != LosslessStatus::Ok) {
|
||||||
|
return parse_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LosslessStatus translate_status = TranslateAll(spans, out_modules);
|
||||||
|
if (translate_status != LosslessStatus::Ok) {
|
||||||
|
return translate_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CacheHeader header{
|
||||||
|
.magic = CACHE_MAGIC,
|
||||||
|
.version = CACHE_VERSION,
|
||||||
|
.source_size = source_size,
|
||||||
|
.source_hash = source_hash,
|
||||||
|
.module_count = static_cast<u32>(out_modules.size()),
|
||||||
|
};
|
||||||
|
if (!WriteShaderCache(cache_path, header, out_modules)) {
|
||||||
|
void(Common::FS::RemoveFile(cache_path));
|
||||||
|
return LosslessStatus::CacheUnusable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LosslessStatus::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
LosslessStatus BuildShaderCache() {
|
||||||
|
ShaderModules modules;
|
||||||
|
return LoadShaderModules(modules);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoveInstalledLosslessDll() {
|
||||||
|
const std::filesystem::path cache_path = GetShaderCachePath();
|
||||||
|
if (Common::FS::Exists(cache_path)) {
|
||||||
|
void(Common::FS::RemoveFile(cache_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path path = GetLosslessDllPath();
|
||||||
|
if (!Common::FS::Exists(path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return Common::FS::RemoveFile(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace VideoCore::FrameGen
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace VideoCore::FrameGen {
|
||||||
|
|
||||||
|
enum class LosslessStatus : u32 {
|
||||||
|
Ok,
|
||||||
|
NotInstalled,
|
||||||
|
UnreadableFile,
|
||||||
|
NotPortableExecutable,
|
||||||
|
MissingShaders,
|
||||||
|
TranslationFailed,
|
||||||
|
CacheUnusable,
|
||||||
|
};
|
||||||
|
|
||||||
|
using ShaderResources = std::map<u32, std::vector<u8>>;
|
||||||
|
using ShaderModules = std::map<u32, std::vector<u32>>;
|
||||||
|
|
||||||
|
namespace PerformanceShader {
|
||||||
|
constexpr u32 MIPMAPS = 255;
|
||||||
|
constexpr u32 GENERATE = 256;
|
||||||
|
constexpr std::array<u32, 4> ALPHA{290, 291, 292, 293};
|
||||||
|
constexpr std::array<u32, 5> BETA{298, 299, 300, 301, 302};
|
||||||
|
constexpr std::array<u32, 5> GAMMA{280, 282, 283, 284, 285};
|
||||||
|
constexpr std::array<u32, 10> DELTA{280, 286, 287, 288, 289, 281, 294, 295, 296, 297};
|
||||||
|
} // namespace PerformanceShader
|
||||||
|
|
||||||
|
[[nodiscard]] std::filesystem::path GetLosslessDllPath();
|
||||||
|
|
||||||
|
[[nodiscard]] std::filesystem::path GetShaderCachePath();
|
||||||
|
|
||||||
|
[[nodiscard]] LosslessStatus ReadShaderResources(const std::filesystem::path& path,
|
||||||
|
ShaderResources& out_resources);
|
||||||
|
|
||||||
|
[[nodiscard]] LosslessStatus ValidateLosslessDll(const std::filesystem::path& path);
|
||||||
|
|
||||||
|
[[nodiscard]] LosslessStatus GetInstalledLosslessStatus();
|
||||||
|
|
||||||
|
[[nodiscard]] LosslessStatus BuildShaderCache();
|
||||||
|
|
||||||
|
[[nodiscard]] LosslessStatus LoadShaderModules(ShaderModules& out_modules);
|
||||||
|
|
||||||
|
bool RemoveInstalledLosslessDll();
|
||||||
|
|
||||||
|
} // namespace VideoCore::FrameGen
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <dxbc_modinfo.h>
|
||||||
|
#include <dxbc_module.h>
|
||||||
|
#include <dxbc_reader.h>
|
||||||
|
#include <thirdparty/spirv.hpp>
|
||||||
|
|
||||||
|
#include "video_core/frame_gen/lsfg_translate.h"
|
||||||
|
|
||||||
|
namespace VideoCore::FrameGen {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr u32 DECORATION_LITERAL_WORD = 3;
|
||||||
|
|
||||||
|
void RenumberBindings(dxvk::SpirvCodeBuffer& code) {
|
||||||
|
std::vector<u32> literal_offsets;
|
||||||
|
for (const auto instruction : code) {
|
||||||
|
if (instruction.opCode() == spv::OpFunction) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (instruction.opCode() == spv::OpDecorate &&
|
||||||
|
instruction.arg(2) == spv::DecorationBinding) {
|
||||||
|
literal_offsets.push_back(instruction.offset() + DECORATION_LITERAL_WORD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < literal_offsets.size(); ++i) {
|
||||||
|
code.data()[literal_offsets[i]] = static_cast<u32>(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
std::vector<u32> TranslateComputeShader(std::span<const u8> dxbc) {
|
||||||
|
if (dxbc.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
dxvk::DxbcReader reader{reinterpret_cast<const char*>(dxbc.data()), dxbc.size()};
|
||||||
|
dxvk::DxbcModule module{reader};
|
||||||
|
|
||||||
|
const dxvk::DxbcModuleInfo module_info{};
|
||||||
|
dxvk::SpirvCodeBuffer code = module.compile(module_info, "CS");
|
||||||
|
if (code.dwords() == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
RenumberBindings(code);
|
||||||
|
return std::vector<u32>{code.data(), code.data() + code.dwords()};
|
||||||
|
} catch (...) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace VideoCore::FrameGen
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace VideoCore::FrameGen {
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<u32> TranslateComputeShader(std::span<const u8> dxbc);
|
||||||
|
|
||||||
|
} // namespace VideoCore::FrameGen
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "common/fs/file.h"
|
||||||
|
#include "common/fs/fs.h"
|
||||||
|
#include "common/fs/path_util.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/frame_gen.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/util.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_present_manager.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_device.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr f32 LSFG_FLOW_SCALE = 1.0f;
|
||||||
|
|
||||||
|
void WriteGrayscalePgm(const std::filesystem::path& path, VkExtent2D extent,
|
||||||
|
std::span<const u8> pixels) {
|
||||||
|
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write,
|
||||||
|
Common::FS::FileType::BinaryFile};
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string header =
|
||||||
|
"P5\n" + std::to_string(extent.width) + " " + std::to_string(extent.height) + "\n255\n";
|
||||||
|
if (file.Write(header) != header.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t expected = static_cast<size_t>(extent.width) * extent.height;
|
||||||
|
void(file.Write(pixels.subspan(0, std::min(expected, pixels.size()))));
|
||||||
|
void(file.Flush());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
FrameGen::FrameGen(MemoryAllocator& memory_allocator_, Scheduler& scheduler_)
|
||||||
|
: memory_allocator{memory_allocator_}, scheduler{scheduler_} {}
|
||||||
|
|
||||||
|
FrameGen::~FrameGen() = default;
|
||||||
|
|
||||||
|
void FrameGen::Process(const Device& device, Frame* frame) {
|
||||||
|
if (unavailable || !Settings::values.frame_gen.GetValue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shaders) {
|
||||||
|
shaders.emplace(device);
|
||||||
|
if (!shaders->IsValid()) {
|
||||||
|
unavailable = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkExtent2D extent{.width = frame->width, .height = frame->height};
|
||||||
|
if (!mipmaps || built_extent.width != extent.width || built_extent.height != extent.height) {
|
||||||
|
Rebuild(device, extent);
|
||||||
|
}
|
||||||
|
|
||||||
|
mipmaps->Dispatch(device, scheduler, *frame->image_view, frame_count);
|
||||||
|
++frame_count;
|
||||||
|
|
||||||
|
const bool dump_requested = Settings::values.frame_gen_dump_flow.GetValue();
|
||||||
|
if (!dump_requested) {
|
||||||
|
dumped = false;
|
||||||
|
} else if (!dumped) {
|
||||||
|
DumpFlowPyramid(device);
|
||||||
|
dumped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameGen::Rebuild(const Device& device, VkExtent2D extent) {
|
||||||
|
scheduler.Finish();
|
||||||
|
mipmaps.emplace(device, memory_allocator, *shaders, extent, LSFG_FLOW_SCALE);
|
||||||
|
built_extent = extent;
|
||||||
|
frame_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameGen::DumpFlowPyramid(const Device& device) {
|
||||||
|
const std::filesystem::path directory =
|
||||||
|
Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / "debug";
|
||||||
|
if (!Common::FS::CreateDirs(directory)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t level = 0; level < LSFG_MIP_LEVELS; ++level) {
|
||||||
|
const VkExtent2D extent = mipmaps->GetLevelExtent(level);
|
||||||
|
const VkDeviceSize size = static_cast<VkDeviceSize>(extent.width) * extent.height;
|
||||||
|
vk::Buffer readback = CreateWrappedBuffer(memory_allocator, size, MemoryUsage::Download);
|
||||||
|
|
||||||
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
|
scheduler.Record([image = mipmaps->GetLevelImage(level), dst = *readback,
|
||||||
|
extent](vk::CommandBuffer cmdbuf) {
|
||||||
|
DownloadColorImage(cmdbuf, image, dst,
|
||||||
|
VkExtent3D{.width = extent.width, .height = extent.height,
|
||||||
|
.depth = 1});
|
||||||
|
});
|
||||||
|
scheduler.Finish();
|
||||||
|
|
||||||
|
readback.Invalidate();
|
||||||
|
WriteGrayscalePgm(directory / ("flow_mip" + std::to_string(level) + ".pgm"), extent,
|
||||||
|
readback.Mapped());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/lsfg_mipmaps.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
class Scheduler;
|
||||||
|
struct Frame;
|
||||||
|
|
||||||
|
class FrameGen {
|
||||||
|
public:
|
||||||
|
explicit FrameGen(MemoryAllocator& memory_allocator, Scheduler& scheduler);
|
||||||
|
~FrameGen();
|
||||||
|
|
||||||
|
void Process(const Device& device, Frame* frame);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Rebuild(const Device& device, VkExtent2D extent);
|
||||||
|
void DumpFlowPyramid(const Device& device);
|
||||||
|
|
||||||
|
MemoryAllocator& memory_allocator;
|
||||||
|
Scheduler& scheduler;
|
||||||
|
|
||||||
|
std::optional<LsfgShaders> shaders;
|
||||||
|
std::optional<LsfgMipmaps> mipmaps;
|
||||||
|
VkExtent2D built_extent{};
|
||||||
|
u64 frame_count{};
|
||||||
|
bool unavailable{};
|
||||||
|
bool dumped{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "video_core/frame_gen/lossless_dll.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/lsfg_mipmaps.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/util.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_device.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr VkFormat FLOW_FORMAT = VK_FORMAT_R8_UNORM;
|
||||||
|
constexpr u32 DISPATCH_TILE_SHIFT = 6;
|
||||||
|
constexpr size_t DESCRIPTOR_SET_COUNT = 2;
|
||||||
|
|
||||||
|
struct LsfgConstants {
|
||||||
|
std::array<u32, 2> input_offset;
|
||||||
|
u32 first_iter;
|
||||||
|
u32 first_iter_s;
|
||||||
|
u32 advanced_color_kind;
|
||||||
|
u32 hdr_support;
|
||||||
|
f32 resolution_inv_scale;
|
||||||
|
f32 timestamp;
|
||||||
|
f32 ui_threshold;
|
||||||
|
std::array<u32, 3> padding;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(LsfgConstants) == 48);
|
||||||
|
|
||||||
|
constexpr std::array<VkDescriptorType, 3 + LSFG_MIP_LEVELS> MIPMAPS_BINDINGS{
|
||||||
|
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_SAMPLER,
|
||||||
|
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
||||||
|
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
||||||
|
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
||||||
|
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
||||||
|
};
|
||||||
|
|
||||||
|
vk::Sampler CreateFlowSampler(const Device& device) {
|
||||||
|
return device.GetLogical().CreateSampler(VkSamplerCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.magFilter = VK_FILTER_LINEAR,
|
||||||
|
.minFilter = VK_FILTER_LINEAR,
|
||||||
|
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||||
|
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||||
|
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||||
|
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||||
|
.mipLodBias = 0.0f,
|
||||||
|
.anisotropyEnable = VK_FALSE,
|
||||||
|
.maxAnisotropy = 0.0f,
|
||||||
|
.compareEnable = VK_FALSE,
|
||||||
|
.compareOp = VK_COMPARE_OP_NEVER,
|
||||||
|
.minLod = 0.0f,
|
||||||
|
.maxLod = VK_LOD_CLAMP_NONE,
|
||||||
|
.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
|
||||||
|
.unnormalizedCoordinates = VK_FALSE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
LsfgMipmaps::LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator_,
|
||||||
|
const LsfgShaders& shaders, VkExtent2D input_extent, f32 flow_scale)
|
||||||
|
: memory_allocator{memory_allocator_} {
|
||||||
|
flow_extent = VkExtent2D{
|
||||||
|
.width = std::max(1u, static_cast<u32>(static_cast<f32>(input_extent.width) * flow_scale)),
|
||||||
|
.height = std::max(1u, static_cast<u32>(static_cast<f32>(input_extent.height) * flow_scale)),
|
||||||
|
};
|
||||||
|
|
||||||
|
CreateImages(device);
|
||||||
|
CreateUniformBuffer(flow_scale);
|
||||||
|
|
||||||
|
sampler = CreateFlowSampler(device);
|
||||||
|
descriptor_pool = CreateWrappedDescriptorPool(
|
||||||
|
device, DESCRIPTOR_SET_COUNT * MIPMAPS_BINDINGS.size(), DESCRIPTOR_SET_COUNT,
|
||||||
|
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_SAMPLER,
|
||||||
|
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE});
|
||||||
|
descriptor_set_layout = CreateWrappedDescriptorSetLayout(
|
||||||
|
device, std::span<const VkDescriptorType>{MIPMAPS_BINDINGS}, VK_SHADER_STAGE_COMPUTE_BIT);
|
||||||
|
|
||||||
|
const std::vector<VkDescriptorSetLayout> layouts(DESCRIPTOR_SET_COUNT,
|
||||||
|
*descriptor_set_layout);
|
||||||
|
descriptor_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||||
|
|
||||||
|
pipeline_layout = CreateWrappedPipelineLayout(device, descriptor_set_layout);
|
||||||
|
pipeline = CreateWrappedComputePipeline(
|
||||||
|
device, pipeline_layout, shaders.Get(VideoCore::FrameGen::PerformanceShader::MIPMAPS));
|
||||||
|
}
|
||||||
|
|
||||||
|
VkExtent2D LsfgMipmaps::GetLevelExtent(size_t level) const {
|
||||||
|
return VkExtent2D{
|
||||||
|
.width = std::max(1u, flow_extent.width >> level),
|
||||||
|
.height = std::max(1u, flow_extent.height >> level),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void LsfgMipmaps::CreateImages(const Device& device) {
|
||||||
|
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||||
|
images[i] = CreateWrappedImage(memory_allocator, GetLevelExtent(i), FLOW_FORMAT);
|
||||||
|
image_views[i] = CreateWrappedImageView(device, images[i], FLOW_FORMAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LsfgMipmaps::CreateUniformBuffer(f32 flow_scale) {
|
||||||
|
uniform_buffer = CreateWrappedBuffer(memory_allocator, sizeof(LsfgConstants),
|
||||||
|
MemoryUsage::Upload);
|
||||||
|
|
||||||
|
const LsfgConstants constants{
|
||||||
|
.input_offset = {0, 0},
|
||||||
|
.first_iter = 0,
|
||||||
|
.first_iter_s = 0,
|
||||||
|
.advanced_color_kind = 0,
|
||||||
|
.hdr_support = 0,
|
||||||
|
.resolution_inv_scale = 1.0f / flow_scale,
|
||||||
|
.timestamp = 0.0f,
|
||||||
|
.ui_threshold = 0.5f,
|
||||||
|
.padding = {0, 0, 0},
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::span<u8> mapped = uniform_buffer.Mapped();
|
||||||
|
std::memcpy(mapped.data(), &constants, sizeof(constants));
|
||||||
|
uniform_buffer.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LsfgMipmaps::Dispatch(const Device& device, Scheduler& scheduler, VkImageView current_view,
|
||||||
|
u64 frame_count) {
|
||||||
|
const size_t set_index = frame_count % DESCRIPTOR_SET_COUNT;
|
||||||
|
const VkDescriptorSet set = descriptor_sets[set_index];
|
||||||
|
|
||||||
|
const VkDescriptorBufferInfo buffer_info{
|
||||||
|
.buffer = *uniform_buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.range = sizeof(LsfgConstants),
|
||||||
|
};
|
||||||
|
const VkDescriptorImageInfo sampler_info{
|
||||||
|
.sampler = *sampler,
|
||||||
|
.imageView = VK_NULL_HANDLE,
|
||||||
|
.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||||
|
};
|
||||||
|
const VkDescriptorImageInfo sampled_info{
|
||||||
|
.sampler = VK_NULL_HANDLE,
|
||||||
|
.imageView = current_view,
|
||||||
|
.imageLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
|
};
|
||||||
|
std::array<VkDescriptorImageInfo, LSFG_MIP_LEVELS> storage_infos{};
|
||||||
|
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||||
|
storage_infos[i] = VkDescriptorImageInfo{
|
||||||
|
.sampler = VK_NULL_HANDLE,
|
||||||
|
.imageView = *image_views[i],
|
||||||
|
.imageLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkWriteDescriptorSet> writes;
|
||||||
|
writes.reserve(MIPMAPS_BINDINGS.size());
|
||||||
|
const auto push = [&](u32 binding, VkDescriptorType type,
|
||||||
|
const VkDescriptorImageInfo* image_info,
|
||||||
|
const VkDescriptorBufferInfo* buf_info) {
|
||||||
|
writes.push_back(VkWriteDescriptorSet{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.dstSet = set,
|
||||||
|
.dstBinding = binding,
|
||||||
|
.dstArrayElement = 0,
|
||||||
|
.descriptorCount = 1,
|
||||||
|
.descriptorType = type,
|
||||||
|
.pImageInfo = image_info,
|
||||||
|
.pBufferInfo = buf_info,
|
||||||
|
.pTexelBufferView = nullptr,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
push(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, nullptr, &buffer_info);
|
||||||
|
push(1, VK_DESCRIPTOR_TYPE_SAMPLER, &sampler_info, nullptr);
|
||||||
|
push(2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, &sampled_info, nullptr);
|
||||||
|
for (u32 i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||||
|
push(3 + i, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, &storage_infos[i], nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
device.GetLogical().UpdateDescriptorSets(writes, {});
|
||||||
|
|
||||||
|
const u32 groups_x = (flow_extent.width + (1u << DISPATCH_TILE_SHIFT) - 1) >>
|
||||||
|
DISPATCH_TILE_SHIFT;
|
||||||
|
const u32 groups_y = (flow_extent.height + (1u << DISPATCH_TILE_SHIFT) - 1) >>
|
||||||
|
DISPATCH_TILE_SHIFT;
|
||||||
|
|
||||||
|
std::array<VkImage, LSFG_MIP_LEVELS> raw_images{};
|
||||||
|
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||||
|
raw_images[i] = *images[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
|
scheduler.Record([raw_images, set, groups_x, groups_y, layout = *pipeline_layout,
|
||||||
|
compute_pipeline = *pipeline](vk::CommandBuffer cmdbuf) {
|
||||||
|
for (const VkImage image : raw_images) {
|
||||||
|
TransitionImageLayout(cmdbuf, image, VK_IMAGE_LAYOUT_GENERAL,
|
||||||
|
VK_IMAGE_LAYOUT_UNDEFINED);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, compute_pipeline);
|
||||||
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, set, {});
|
||||||
|
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
class LsfgShaders;
|
||||||
|
class Scheduler;
|
||||||
|
|
||||||
|
constexpr size_t LSFG_MIP_LEVELS = 7;
|
||||||
|
|
||||||
|
class LsfgMipmaps {
|
||||||
|
public:
|
||||||
|
explicit LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator,
|
||||||
|
const LsfgShaders& shaders, VkExtent2D input_extent, f32 flow_scale);
|
||||||
|
|
||||||
|
void Dispatch(const Device& device, Scheduler& scheduler, VkImageView current_view,
|
||||||
|
u64 frame_count);
|
||||||
|
|
||||||
|
[[nodiscard]] VkImageView GetLevelView(size_t level) const {
|
||||||
|
return *image_views[level];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] VkImage GetLevelImage(size_t level) const {
|
||||||
|
return *images[level];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] VkExtent2D GetLevelExtent(size_t level) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void CreateImages(const Device& device);
|
||||||
|
void CreateUniformBuffer(f32 flow_scale);
|
||||||
|
|
||||||
|
MemoryAllocator& memory_allocator;
|
||||||
|
VkExtent2D flow_extent{};
|
||||||
|
|
||||||
|
vk::Buffer uniform_buffer;
|
||||||
|
vk::Sampler sampler;
|
||||||
|
vk::DescriptorPool descriptor_pool;
|
||||||
|
vk::DescriptorSetLayout descriptor_set_layout;
|
||||||
|
vk::DescriptorSets descriptor_sets;
|
||||||
|
vk::PipelineLayout pipeline_layout;
|
||||||
|
vk::Pipeline pipeline;
|
||||||
|
|
||||||
|
std::array<vk::Image, LSFG_MIP_LEVELS> images;
|
||||||
|
std::array<vk::ImageView, LSFG_MIP_LEVELS> image_views;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "video_core/frame_gen/lossless_dll.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/util.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_device.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
LsfgShaders::LsfgShaders(const Device& device) {
|
||||||
|
if (!device.IsVulkanMemoryModelSupported()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoCore::FrameGen::ShaderModules code;
|
||||||
|
if (VideoCore::FrameGen::LoadShaderModules(code) != VideoCore::FrameGen::LosslessStatus::Ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [id, words] : code) {
|
||||||
|
modules.emplace(id, CreateWrappedShaderModule(device, words));
|
||||||
|
}
|
||||||
|
valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkShaderModule LsfgShaders::Get(u32 shader_id) const {
|
||||||
|
const auto hit = modules.find(shader_id);
|
||||||
|
return hit == modules.end() ? VK_NULL_HANDLE : *hit->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
|
||||||
|
class LsfgShaders {
|
||||||
|
public:
|
||||||
|
explicit LsfgShaders(const Device& device);
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsValid() const {
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] VkShaderModule Get(u32 shader_id) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<u32, vk::ShaderModule> modules;
|
||||||
|
bool valid{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
@@ -320,15 +320,16 @@ vk::DescriptorPool CreateWrappedDescriptorPool(const Device& device, size_t max_
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(
|
vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(const Device& device,
|
||||||
const Device& device, std::initializer_list<VkDescriptorType> types) {
|
std::span<const VkDescriptorType> types,
|
||||||
|
VkShaderStageFlags stages) {
|
||||||
std::vector<VkDescriptorSetLayoutBinding> bindings(types.size());
|
std::vector<VkDescriptorSetLayoutBinding> bindings(types.size());
|
||||||
for (size_t i = 0; i < types.size(); i++) {
|
for (size_t i = 0; i < types.size(); i++) {
|
||||||
bindings[i] = {
|
bindings[i] = {
|
||||||
.binding = static_cast<u32>(i),
|
.binding = static_cast<u32>(i),
|
||||||
.descriptorType = std::data(types)[i],
|
.descriptorType = types[i],
|
||||||
.descriptorCount = 1,
|
.descriptorCount = 1,
|
||||||
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
.stageFlags = stages,
|
||||||
.pImmutableSamplers = nullptr,
|
.pImmutableSamplers = nullptr,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -342,6 +343,13 @@ vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(
|
||||||
|
const Device& device, std::initializer_list<VkDescriptorType> types,
|
||||||
|
VkShaderStageFlags stages) {
|
||||||
|
return CreateWrappedDescriptorSetLayout(
|
||||||
|
device, std::span<const VkDescriptorType>{std::data(types), types.size()}, stages);
|
||||||
|
}
|
||||||
|
|
||||||
vk::DescriptorSets CreateWrappedDescriptorSets(vk::DescriptorPool& pool,
|
vk::DescriptorSets CreateWrappedDescriptorSets(vk::DescriptorPool& pool,
|
||||||
vk::Span<VkDescriptorSetLayout> layouts) {
|
vk::Span<VkDescriptorSetLayout> layouts) {
|
||||||
return pool.Allocate(VkDescriptorSetAllocateInfo{
|
return pool.Allocate(VkDescriptorSetAllocateInfo{
|
||||||
@@ -353,6 +361,28 @@ vk::DescriptorSets CreateWrappedDescriptorSets(vk::DescriptorPool& pool,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vk::Pipeline CreateWrappedComputePipeline(const Device& device, vk::PipelineLayout& layout,
|
||||||
|
VkShaderModule shader) {
|
||||||
|
return device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.stage =
|
||||||
|
{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||||
|
.module = shader,
|
||||||
|
.pName = "main",
|
||||||
|
.pSpecializationInfo = nullptr,
|
||||||
|
},
|
||||||
|
.layout = *layout,
|
||||||
|
.basePipelineHandle = VK_NULL_HANDLE,
|
||||||
|
.basePipelineIndex = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device,
|
vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device,
|
||||||
vk::DescriptorSetLayout& layout) {
|
vk::DescriptorSetLayout& layout) {
|
||||||
return device.GetLogical().CreatePipelineLayout(VkPipelineLayoutCreateInfo{
|
return device.GetLogical().CreatePipelineLayout(VkPipelineLayoutCreateInfo{
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ vk::DescriptorPool CreateWrappedDescriptorPool(const Device& device, size_t max_
|
|||||||
std::initializer_list<VkDescriptorType> types = {
|
std::initializer_list<VkDescriptorType> types = {
|
||||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER});
|
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER});
|
||||||
vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(
|
vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(
|
||||||
const Device& device, std::initializer_list<VkDescriptorType> types);
|
const Device& device, std::initializer_list<VkDescriptorType> types,
|
||||||
|
VkShaderStageFlags stages = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||||
|
vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(const Device& device,
|
||||||
|
std::span<const VkDescriptorType> types,
|
||||||
|
VkShaderStageFlags stages);
|
||||||
vk::DescriptorSets CreateWrappedDescriptorSets(vk::DescriptorPool& pool,
|
vk::DescriptorSets CreateWrappedDescriptorSets(vk::DescriptorPool& pool,
|
||||||
vk::Span<VkDescriptorSetLayout> layouts);
|
vk::Span<VkDescriptorSetLayout> layouts);
|
||||||
vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device,
|
vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device,
|
||||||
@@ -46,6 +50,8 @@ vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device,
|
|||||||
vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
|
vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
|
||||||
vk::PipelineLayout& layout,
|
vk::PipelineLayout& layout,
|
||||||
std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
|
std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
|
||||||
|
vk::Pipeline CreateWrappedComputePipeline(const Device& device, vk::PipelineLayout& layout,
|
||||||
|
VkShaderModule shader);
|
||||||
vk::Pipeline CreateWrappedPremultipliedBlendingPipeline(
|
vk::Pipeline CreateWrappedPremultipliedBlendingPipeline(
|
||||||
const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
|
const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
|
||||||
std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
|
std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
|
||||||
|
|||||||
@@ -155,7 +155,8 @@ try
|
|||||||
present_manager,
|
present_manager,
|
||||||
scheduler,
|
scheduler,
|
||||||
PresentFiltersForAppletCapture)
|
PresentFiltersForAppletCapture)
|
||||||
, rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker, scheduler) {
|
, rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker, scheduler)
|
||||||
|
, frame_gen(memory_allocator, scheduler) {
|
||||||
|
|
||||||
if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
|
if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
|
||||||
turbo_mode.emplace(instance, dld);
|
turbo_mode.emplace(instance, dld);
|
||||||
@@ -191,6 +192,9 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
|
|||||||
blit_swapchain.DrawToFrame(device, rasterizer, frame, framebuffers,
|
blit_swapchain.DrawToFrame(device, rasterizer, frame, framebuffers,
|
||||||
render_window.GetFramebufferLayout(), swapchain.GetImageCount(),
|
render_window.GetFramebufferLayout(), swapchain.GetImageCount(),
|
||||||
swapchain.GetImageViewFormat());
|
swapchain.GetImageViewFormat());
|
||||||
|
|
||||||
|
frame_gen.Process(device, frame);
|
||||||
|
|
||||||
scheduler.Flush(*frame->render_ready);
|
scheduler.Flush(*frame->render_ready);
|
||||||
|
|
||||||
present_manager.Present(frame);
|
present_manager.Present(frame);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "common/dynamic_library.h"
|
#include "common/dynamic_library.h"
|
||||||
#include "video_core/host1x/gpu_device_memory_manager.h"
|
#include "video_core/host1x/gpu_device_memory_manager.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/frame_gen.h"
|
||||||
#include "video_core/renderer_vulkan/vk_blit_screen.h"
|
#include "video_core/renderer_vulkan/vk_blit_screen.h"
|
||||||
#include "video_core/renderer_vulkan/vk_present_manager.h"
|
#include "video_core/renderer_vulkan/vk_present_manager.h"
|
||||||
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||||
@@ -95,6 +96,7 @@ private:
|
|||||||
BlitScreen blit_capture;
|
BlitScreen blit_capture;
|
||||||
BlitScreen blit_applet;
|
BlitScreen blit_applet;
|
||||||
RasterizerVulkan rasterizer;
|
RasterizerVulkan rasterizer;
|
||||||
|
FrameGen frame_gen;
|
||||||
std::optional<TurboMode> turbo_mode;
|
std::optional<TurboMode> turbo_mode;
|
||||||
|
|
||||||
Frame applet_frame;
|
Frame applet_frame;
|
||||||
|
|||||||
@@ -202,7 +202,8 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat
|
|||||||
.arrayLayers = 1,
|
.arrayLayers = 1,
|
||||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||||
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
|
||||||
|
VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||||
.queueFamilyIndexCount = 0,
|
.queueFamilyIndexCount = 0,
|
||||||
.pQueueFamilyIndices = nullptr,
|
.pQueueFamilyIndices = nullptr,
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
|||||||
FEATURE(EXT, HostQueryReset, HOST_QUERY_RESET, host_query_reset) \
|
FEATURE(EXT, HostQueryReset, HOST_QUERY_RESET, host_query_reset) \
|
||||||
FEATURE(KHR, 8BitStorage, 8BIT_STORAGE, bit8_storage) \
|
FEATURE(KHR, 8BitStorage, 8BIT_STORAGE, bit8_storage) \
|
||||||
FEATURE(KHR, BufferDeviceAddress, BUFFER_DEVICE_ADDRESS, buffer_device_address) \
|
FEATURE(KHR, BufferDeviceAddress, BUFFER_DEVICE_ADDRESS, buffer_device_address) \
|
||||||
FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore)
|
FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore) \
|
||||||
|
FEATURE(KHR, VulkanMemoryModel, VULKAN_MEMORY_MODEL, vulkan_memory_model)
|
||||||
|
|
||||||
#define FOR_EACH_VK_FEATURE_1_3(FEATURE) \
|
#define FOR_EACH_VK_FEATURE_1_3(FEATURE) \
|
||||||
FEATURE(EXT, ImageRobustness, IMAGE_ROBUSTNESS, robust_image_access) \
|
FEATURE(EXT, ImageRobustness, IMAGE_ROBUSTNESS, robust_image_access) \
|
||||||
@@ -416,6 +417,11 @@ FN_MAX_LIMIT_LIST
|
|||||||
return features.shader_float16_int8.shaderFloat16;
|
return features.shader_float16_int8.shaderFloat16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the device can run shaders built against the Vulkan memory model.
|
||||||
|
bool IsVulkanMemoryModelSupported() const {
|
||||||
|
return features.vulkan_memory_model.vulkanMemoryModel;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the device supports int8 natively.
|
/// Returns true if the device supports int8 natively.
|
||||||
bool IsInt8Supported() const {
|
bool IsInt8Supported() const {
|
||||||
return features.shader_float16_int8.shaderInt8;
|
return features.shader_float16_int8.shaderInt8;
|
||||||
|
|||||||
Reference in New Issue
Block a user