From 099547b3f4578bf1fc0b39988aefbe2d54b540cc Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Mon, 14 Sep 2026 02:53:30 +0200 Subject: [PATCH] [android] Add LSFG UI to quick settings (#4393) Adds UI of LSFG to QS UI, very self-explanatory. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4393 Reviewed-by: lizzie Reviewed-by: MaranBr --- .../yuzu/yuzu_emu/dialogs/QuickSettings.kt | 162 ++++++++++++++++++ .../features/settings/model/BooleanSetting.kt | 2 - .../settings/model/view/SettingsItem.kt | 18 +- .../settings/ui/SettingsFragmentPresenter.kt | 2 - .../yuzu_emu/fragments/EmulationFragment.kt | 6 + src/android/app/src/main/jni/native.cpp | 12 +- .../main/res/color/frame_gen_cell_text.xml | 5 + .../drawable/frame_gen_cell_background.xml | 31 ++++ .../layout/item_quick_settings_frame_gen.xml | 111 ++++++++++++ .../item_quick_settings_frame_gen_cell.xml | 13 ++ .../app/src/main/res/values-ar/strings.xml | 6 - .../app/src/main/res/values-de/strings.xml | 2 - .../app/src/main/res/values-es/strings.xml | 3 - .../app/src/main/res/values-ru/strings.xml | 4 - .../src/main/res/values-zh-rCN/strings.xml | 6 - .../src/main/res/values-zh-rTW/strings.xml | 6 - .../app/src/main/res/values/arrays.xml | 10 +- .../app/src/main/res/values/strings.xml | 17 +- src/common/settings.h | 5 +- src/video_core/frame_gen/lossless_dll.cpp | 59 ++----- src/video_core/frame_gen/lossless_dll.h | 10 +- .../renderer_vulkan/present/frame_gen.cpp | 15 +- .../renderer_vulkan/present/lsfg_shaders.cpp | 10 +- .../renderer_vulkan/vk_blit_screen.cpp | 18 +- .../renderer_vulkan/vk_present_manager.cpp | 19 +- .../renderer_vulkan/vk_present_manager.h | 5 +- 26 files changed, 410 insertions(+), 147 deletions(-) create mode 100644 src/android/app/src/main/res/color/frame_gen_cell_text.xml create mode 100644 src/android/app/src/main/res/drawable/frame_gen_cell_background.xml create mode 100644 src/android/app/src/main/res/layout/item_quick_settings_frame_gen.xml create mode 100644 src/android/app/src/main/res/layout/item_quick_settings_frame_gen_cell.xml diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt index 1136923fd0..c2ef683cfd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt @@ -9,6 +9,7 @@ import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.widget.LinearLayout import android.widget.RadioGroup import android.widget.TextView import androidx.drawerlayout.widget.DrawerLayout @@ -673,6 +674,167 @@ class QuickSettings(val emulationFragment: EmulationFragment) { } } + private fun addFrameGenCell(inflater: LayoutInflater, row: ViewGroup): TextView { + val cell = inflater.inflate(R.layout.item_quick_settings_frame_gen_cell, row, false) + as TextView + row.addView(cell) + return cell + } + + fun addFrameGen(container: ViewGroup) { + val inflater = LayoutInflater.from(emulationFragment.requireContext()) + val itemView = inflater.inflate(R.layout.item_quick_settings_frame_gen, container, false) + + val multiplierRow = itemView.findViewById(R.id.frame_gen_multipliers) + val targetRow = itemView.findViewById(R.id.frame_gen_targets) + val targetSection = itemView.findViewById(R.id.frame_gen_target_section) + val flowRow = itemView.findViewById(R.id.frame_gen_flow) + val flowSection = itemView.findViewById(R.id.frame_gen_flow_section) + + val multiplierNames = + emulationFragment.resources.getStringArray(R.array.frameGenMultiplierNames) + val multiplierValues = + emulationFragment.resources.getIntArray(R.array.frameGenMultiplierValues) + val targetValues = + emulationFragment.resources.getIntArray(R.array.frameGenTargetRateValues) + val flowValues = + emulationFragment.resources.getIntArray(R.array.frameGenFlowScaleValues) + + val columns = maxOf( + multiplierValues.size + 1, + targetValues.size, + flowValues.size + 1 + ).toFloat() + multiplierRow.weightSum = columns + targetRow.weightSum = columns + flowRow.weightSum = columns + + val powerCell = addFrameGenCell(inflater, multiplierRow) + + val multiplierCells = mutableListOf() + for (name in multiplierNames) { + val cell = addFrameGenCell(inflater, multiplierRow) + cell.text = name + multiplierCells.add(cell) + } + + val targetCells = mutableListOf() + for (value in targetValues) { + val cell = addFrameGenCell(inflater, targetRow) + if (value == 0) { + cell.setText(R.string.frame_gen_fixed) + } else { + cell.text = value.toString() + } + targetCells.add(cell) + } + + val autoCell = addFrameGenCell(inflater, flowRow) + autoCell.setText(R.string.frame_gen_flow_auto) + + val flowCells = mutableListOf() + for (value in flowValues) { + val cell = addFrameGenCell(inflater, flowRow) + cell.text = "$value%" + flowCells.add(cell) + } + + val inactiveAlpha = 0.38f + + fun refresh() { + val enabled = BooleanSetting.RENDERER_FRAME_GEN.getBoolean(needsGlobal = false) + val multiplier = IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.getInt(needsGlobal = false) + val target = IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.getInt(needsGlobal = false) + val fixed = target == 0 + val flowAuto = + BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean(needsGlobal = false) + val flowScale = IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.getInt(needsGlobal = false) + + var powerLabel = R.string.frame_gen_off + var rowAlpha = inactiveAlpha + if (enabled) { + powerLabel = R.string.frame_gen_on + rowAlpha = 1.0f + } + + var multiplierAlpha = inactiveAlpha + if (enabled && fixed) { + multiplierAlpha = 1.0f + } + + powerCell.setText(powerLabel) + powerCell.isSelected = enabled + + multiplierCells.forEachIndexed { index, cell -> + cell.isEnabled = enabled + cell.isSelected = enabled && fixed && multiplierValues[index] == multiplier + cell.alpha = multiplierAlpha + } + + targetSection.alpha = rowAlpha + targetCells.forEachIndexed { index, cell -> + cell.isEnabled = enabled + cell.isSelected = enabled && targetValues[index] == target + } + + flowSection.alpha = rowAlpha + autoCell.isEnabled = enabled + autoCell.isSelected = enabled && flowAuto + + flowCells.forEachIndexed { index, cell -> + cell.isEnabled = enabled + cell.isSelected = enabled && !flowAuto && flowValues[index] == flowScale + } + } + + powerCell.setOnClickListener { + if (BooleanSetting.RENDERER_FRAME_GEN.getBoolean(needsGlobal = false)) { + BooleanSetting.RENDERER_FRAME_GEN.setBoolean(false) + } else { + IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.setInt(multiplierValues.first()) + IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.setInt(0) + BooleanSetting.RENDERER_FRAME_GEN.setBoolean(true) + } + saveSettings() + refresh() + } + + multiplierCells.forEachIndexed { index, cell -> + cell.setOnClickListener { + IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.setInt(multiplierValues[index]) + IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.setInt(0) + saveSettings() + refresh() + } + } + + targetCells.forEachIndexed { index, cell -> + cell.setOnClickListener { + IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.setInt(targetValues[index]) + saveSettings() + refresh() + } + } + + autoCell.setOnClickListener { + BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.setBoolean(true) + saveSettings() + refresh() + } + + flowCells.forEachIndexed { index, cell -> + cell.setOnClickListener { + IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.setInt(flowValues[index]) + BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.setBoolean(false) + saveSettings() + refresh() + } + } + + refresh() + container.addView(itemView) + } + fun addDivider(container: ViewGroup) { val inflater = LayoutInflater.from(emulationFragment.requireContext()) val dividerView = inflater.inflate(R.layout.item_quick_settings_divider, container, false) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 5eaaaaecac..ca3309245e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -38,9 +38,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"), RENDERER_SAMPLE_SHADING("sample_shading"), RENDERER_FRAME_GEN("frame_gen"), - RENDERER_FRAME_GEN_FP16("frame_gen_fp16"), RENDERER_FRAME_GEN_FLOW_SCALE_AUTO("frame_gen_flow_scale_auto"), - RENDERER_FRAME_GEN_DUMP_FLOW("frame_gen_dump_flow"), GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"), PICTURE_IN_PICTURE("picture_in_picture"), USE_CUSTOM_RTC("custom_rtc_enabled"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index ab2ac7eb42..3f38831a17 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -123,9 +123,7 @@ abstract class SettingsItem( IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key, IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key, BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key, - IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key, - BooleanSetting.RENDERER_FRAME_GEN_FP16.key, - BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key + IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key ) const val TYPE_HEADER = 0 @@ -709,20 +707,6 @@ abstract class SettingsItem( units = "%" ) ) - put( - SwitchSetting( - BooleanSetting.RENDERER_FRAME_GEN_FP16, - titleId = R.string.frame_gen_fp16, - descriptionId = R.string.frame_gen_fp16_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( SingleChoiceSetting( IntSetting.RENDERER_SCREEN_LAYOUT, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 89e0084df0..ca282ba159 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -124,7 +124,6 @@ class SettingsFragmentPresenter( ) { add(IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key) } - add(BooleanSetting.RENDERER_FRAME_GEN_FP16.key) } } @@ -1553,7 +1552,6 @@ class SettingsFragmentPresenter( add(BooleanSetting.DUMP_GUEST_SHADERS.key) add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key) add(BooleanSetting.DUMP_MACROS.key) - add(BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key) add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key) add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key) add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 3f1f3da2ba..3323b3cf9a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -92,6 +92,7 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.Log +import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig import org.yuzu.yuzu_emu.utils.NativePostProcessing @@ -1188,6 +1189,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { quickSettings.addDivider(container) + if (LosslessScalingHelper.isInstalled() && LosslessScalingHelper.isSupportedByGpu()) { + quickSettings.addFrameGen(container) + quickSettings.addDivider(container) + } + quickSettings.addIntSetting( R.string.renderer_accuracy, container, diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 423edc2c1b..746b00ad11 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -1168,7 +1168,7 @@ VkPhysicalDeviceProperties GetVulkanDeviceProperties() { return physical_device.GetProperties(); } -bool GetVulkanMemoryModelSupport() { +bool GetFrameGenerationSupport() { Common::DynamicLibrary library; if (!library.Open("libvulkan.so")) { return false; @@ -1183,9 +1183,13 @@ bool GetVulkanMemoryModelSupport() { const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld); + VkPhysicalDeviceShaderFloat16Int8Features float16_int8{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES, + .pNext = nullptr, + }; VkPhysicalDeviceVulkanMemoryModelFeatures memory_model{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES, - .pNext = nullptr, + .pNext = &float16_int8, }; VkPhysicalDeviceFeatures2 features{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, @@ -1193,7 +1197,7 @@ bool GetVulkanMemoryModelSupport() { }; physical_device.GetFeatures2(features); - return memory_model.vulkanMemoryModel == VK_TRUE; + return memory_model.vulkanMemoryModel == VK_TRUE && float16_int8.shaderFloat16 == VK_TRUE; } } // namespace @@ -1272,7 +1276,7 @@ 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(GetVulkanMemoryModelSupport()); + return static_cast(GetFrameGenerationSupport()); } catch (...) { return static_cast(false); } diff --git a/src/android/app/src/main/res/color/frame_gen_cell_text.xml b/src/android/app/src/main/res/color/frame_gen_cell_text.xml new file mode 100644 index 0000000000..6e671681bf --- /dev/null +++ b/src/android/app/src/main/res/color/frame_gen_cell_text.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/android/app/src/main/res/drawable/frame_gen_cell_background.xml b/src/android/app/src/main/res/drawable/frame_gen_cell_background.xml new file mode 100644 index 0000000000..6f76a37718 --- /dev/null +++ b/src/android/app/src/main/res/drawable/frame_gen_cell_background.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/item_quick_settings_frame_gen.xml b/src/android/app/src/main/res/layout/item_quick_settings_frame_gen.xml new file mode 100644 index 0000000000..e5f8baa3d3 --- /dev/null +++ b/src/android/app/src/main/res/layout/item_quick_settings_frame_gen.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/item_quick_settings_frame_gen_cell.xml b/src/android/app/src/main/res/layout/item_quick_settings_frame_gen_cell.xml new file mode 100644 index 0000000000..0d4d0af3a8 --- /dev/null +++ b/src/android/app/src/main/res/layout/item_quick_settings_frame_gen_cell.xml @@ -0,0 +1,13 @@ + + diff --git a/src/android/app/src/main/res/values-ar/strings.xml b/src/android/app/src/main/res/values-ar/strings.xml index a3d7b2b515..9895ec011c 100644 --- a/src/android/app/src/main/res/values-ar/strings.xml +++ b/src/android/app/src/main/res/values-ar/strings.xml @@ -305,8 +305,6 @@ 60 إطارًا في الثانية 90 إطارًا في الثانية 120 إطارًا في الثانية - 144 إطارًا في الثانية - 165 إطارًا في الثانية هدف قائمة انتظار الإطارات كم عدد الإطارات المكتملة التي قد تنتظر قبل عرضها؟ تعمل قوائم الانتظار الأكبر حجمًا على امتصاص الارتفاعات المفاجئة في حمل وحدة معالجة الرسومات على حساب زمن انتقال الإدخال. أقل زمن انتقال (بدون تخزين مؤقت) @@ -316,10 +314,6 @@ قم بتقدير الحركة بناءً على الدقة التي تعرضها اللعبة فعليًّا، بدلاً من الإخراج الذي تم رفع دقته. ولا يؤثر ذلك على الدقة بأي شكل، لأن رفع الدقة لا يضيف أي تفاصيل تتعلق بالحركة. دقة تقدير الحركة دقة مسار التدفق البصري، كجزء من الناتج. ويُعد خفض هذه القيمة أرخص طريقة لاستعادة الأداء. - مُظلِّلات نصف الدقة - استخدم نسخة التظليل 16 بت. يتم التراجع تلقائيًا إلى الخيار البديل في حالة عدم توفرها في برنامج التشغيل أو الملف. - إفراغ الإطار الذي تم إنشاؤه - قم بكتابة مستويات MIP للتدفق البصري والإطار المُستكمل إلى مجلد lossless/debug مرة واحدة، لغرض استكشاف الأخطاء وإصلاحها توليد الإطار غير متاح لا يدعم برنامج تشغيل وحدة معالجة الرسومات هذا نموذج ذاكرة Vulkan، الذي تتطلبه برامج التظليل الخاصة بـ«Lossless Scaling». اختياري. قم بتوفير ملف Lossless.dll الخاص بك لتمكين إنشاء الإطارات لاحقًا diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index fe190325f6..42a428143a 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml @@ -304,8 +304,6 @@ 60 FPS 90 FPS 120 FPS - 144 FPS - 165 FPS Ausbalanciert (1 Bild) Flüssigste (2 Bilder) Frame-Generation nicht verfügbar diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 460ea1f0da..10031b7441 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -301,12 +301,9 @@ 60 FPS 90 FPS 120 FPS - 144 FPS - 165 FPS Latencia más baja (Sin búfer) Equilibrado (1 fotograma) Más suave (2 fotogramas) - Sombreadores de media precisión Generación de fotogramas no disponbile Instalar Lossless.dll Seleccionar una copia diferente de Lossless.dll diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index 630f61ffa4..9a3c97de9d 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml @@ -308,10 +308,6 @@ Оценивать движение в разрешении, которое игра действительно рендерит, вместо масштабированного вывода. Ничего не стоит в точности, так как масштабирование не добавляет деталей движения. Разрешение оценки движения Разрешение прохода оптического потока в долях от выходного разрешения. Его понижение — самый дешёвый способ вернуть производительность. - Шейдеры половинной точности - Использовать 16-битную версию шейдеров. Автоматически переключается на обычную, если драйвер или файл не поддерживают её. - Сохранить сгенерированный кадр - Однократно записать уровни мип-карт оптического потока и интерполированный кадр в папку lossless/debug для диагностики. Генерация кадров недоступна Драйвер ГПУ не поддерживает модель памяти Vulkan, требуемую шейдерами Lossless Scaling. Опционально. Укажите свой Lossless.dll для включения генерации кадров позже. diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index 930058b42d..03b730f1ed 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml @@ -305,8 +305,6 @@ 60 FPS 90 FPS 120 FPS - 144 FPS - 165 FPS 帧队列目标 在显示之前有多少已完成渲染的帧正在等待。较大的队列可以缓解 GPU 突发的压力,但会增加输入延迟。 最低延迟 (无缓冲) @@ -316,10 +314,6 @@ 在游戏实际渲染的分辨率下估算运动,而不是在放大后的输出上。不会影响精确性,因为放大并不会增加运动细节。 运动预估分辨率 光流通道的分辨率,以输出的比例表示。降低它是提升性能最经济的做法。 - 半精度着色器 - 使用 16 位着色器变体。如果驱动或文件不支持会自动回退。 - 转储已生成的帧 - 为了排查问题,把光流 mip 级别和插值帧写入 lossless/debug 文件夹一次 帧生成不可用 这个 GPU 驱动不支持无损缩放着色器所需的 Vulkan 内存模型。 作为可选项。请提供您自己的 Lossless.dll 以便在之后可以启用帧生成。 diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml index 28d0e93b4e..63eac94759 100644 --- a/src/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml @@ -305,8 +305,6 @@ 60 FPS 90 FPS 120 FPS - 144 FPS - 165 FPS 影格佇列目標 最多允許多少個已完成的影格在顯示器前等待,較大的佇列可以吸收 GPU 突發負載,但會增加輸入延遲 最低延遲(無緩衝) @@ -316,10 +314,6 @@ 以遊戲實際渲染的解析度而非升頻後的輸出進行運動預測。由於升頻後不會增加任何動態細節,因此不會影響準確度 運動預測解析度 光流處理的解析度,以輸出解析度的比例表示。降低此設定值是減少效能負載最有效的方法 - 半準確著色器 - 使用16位元著色器。如果驅動程式或著色器檔案不支援則會自動切換成其它版本 - 傾印生成的著色器 - 將光流的 MIP 層級與補間影格寫入 Eden 資料夾中的 lossless\debug 資料夾以便進行疑難排解 無法使用影格生成 Lossless Scaling 的著色器需要 Vulkan 記憶體模型,所選的驅動程式不支援該功能 可選擇安裝自己擁有的 Lossless.dll 以在之後啟用影格生成功能 diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 931cf2bfa7..b3aed5758c 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -174,8 +174,6 @@ @string/frame_gen_target_rate_60 @string/frame_gen_target_rate_90 @string/frame_gen_target_rate_120 - @string/frame_gen_target_rate_144 - @string/frame_gen_target_rate_165 @@ -183,8 +181,12 @@ 60 90 120 - 144 - 165 + + + + 50 + 75 + 100 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index ca7263ead7..e712ec4338 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -331,8 +331,13 @@ 60 FPS 90 FPS 120 FPS - 144 FPS - 165 FPS + On + Off + Fixed + Auto + Turn frame generation on or off and choose the frame multiplier. + Generate frames up to a target frame rate. The multiplier adjusts on its own. + Resolution used to estimate motion between frames. Lower values save GPU time. Frame queue target How many finished frames may wait ahead of the display. Larger queues absorb GPU spikes at the cost of input latency. Lowest latency (Unbuffered) @@ -342,19 +347,15 @@ Estimate motion at the resolution the game actually renders instead of the upscaled output. Costs nothing in accuracy, since upscaling adds no motion detail. Motion estimation resolution Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance. - Half precision shaders - Use the 16-bit shader variant. Falls back automatically if the driver or the file lacks it. - Dump generated frame - Write the optical flow mip levels and the interpolated frame to the lossless/debug folder once, for troubleshooting Frame generation unavailable - This GPU driver does not support the Vulkan memory model, which the Lossless Scaling shaders require. + This GPU driver lacks the Vulkan memory model or half precision (float16) support that the Lossless Scaling shaders require. Optional. Provide your own Lossless.dll to enable frame generation later Install Lossless.dll Frame generation needs your own legal copy of Lossless.dll from Lossless Scaling Select a different copy of Lossless.dll Frame generation Supported - Unsupported (no Vulkan memory model) + Unsupported (no Vulkan memory model or float16) Lossless Scaling Provide your own copy of Lossless.dll to enable frame generation Installed diff --git a/src/common/settings.h b/src/common/settings.h index 6b60d633a5..c1c2d4f0df 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -459,7 +459,7 @@ struct Values { &frame_gen}; SwitchableSetting frame_gen_queue_target{linkage, - 1, + 0, 0, 2, "frame_gen_queue_target", @@ -469,9 +469,6 @@ struct Values { false, &frame_gen}; - SwitchableSetting frame_gen_fp16{linkage, true, "frame_gen_fp16", Category::Renderer, - Specialization::Default, true, false, &frame_gen}; - SwitchableSetting frame_gen_dump_flow{linkage, false, "frame_gen_dump_flow", Category::Renderer}; diff --git a/src/video_core/frame_gen/lossless_dll.cpp b/src/video_core/frame_gen/lossless_dll.cpp index 5d2d08ffb3..6be1e5530f 100644 --- a/src/video_core/frame_gen/lossless_dll.cpp +++ b/src/video_core/frame_gen/lossless_dll.cpp @@ -45,7 +45,7 @@ constexpr u32 PERFORMANCE_SHADER_ID_FIRST = 280; constexpr u32 PERFORMANCE_SHADER_ID_LAST = 302; constexpr u32 CACHE_MAGIC = 0x4746534C; -constexpr u32 CACHE_VERSION = 2; +constexpr u32 CACHE_VERSION = 3; struct CacheHeader { u32 magic; @@ -53,7 +53,6 @@ struct CacheHeader { u64 source_size; u64 source_hash; u32 module_count; - u32 variant; }; struct Section { @@ -277,41 +276,19 @@ template return std::ranges::all_of(ids, [&](u32 id) { return resources.contains(id); }); } -[[nodiscard]] u32 VariantOffset(ShaderVariant variant) { - return variant == ShaderVariant::NativeFp16 ? PerformanceShader::NATIVE_FP16_OFFSET - : PerformanceShader::NATIVE_FP32_OFFSET; -} - template -[[nodiscard]] bool HasNativeVariant(const Map& resources, ShaderVariant variant) { - const u32 offset = VariantOffset(variant); +[[nodiscard]] bool HasNativeShaders(const Map& resources) { return std::ranges::all_of(PerformanceShaderIds(), [&](u32 id) { - const auto hit = resources.find(id + offset); + const auto hit = resources.find(id + PerformanceShader::NATIVE_FP16_OFFSET); return hit != resources.end() && IsSpirvModule(hit->second); }); } -[[nodiscard]] std::optional SelectVariant(const ResourceSpans& resources, - bool allow_fp16, bool prefer_fp16) { - if (prefer_fp16 && HasNativeVariant(resources, ShaderVariant::NativeFp16)) { - return ShaderVariant::NativeFp16; - } - if (HasNativeVariant(resources, ShaderVariant::NativeFp32)) { - return ShaderVariant::NativeFp32; - } - if (allow_fp16 && HasNativeVariant(resources, ShaderVariant::NativeFp16)) { - return ShaderVariant::NativeFp16; - } - return std::nullopt; -} - [[nodiscard]] LosslessStatus TranslateAll(const ResourceSpans& resources, - ShaderModules& out_modules, - ShaderVariant variant) { - const u32 offset = VariantOffset(variant); + ShaderModules& out_modules) { out_modules.clear(); for (const u32 id : PerformanceShaderIds()) { - const auto hit = resources.find(id + offset); + const auto hit = resources.find(id + PerformanceShader::NATIVE_FP16_OFFSET); if (hit == resources.end()) { return LosslessStatus::MissingShaders; } @@ -343,7 +320,7 @@ template } [[nodiscard]] bool ReadShaderCache(const std::filesystem::path& path, u64 source_size, - u64 source_hash, u32 variant, ShaderModules& out_modules) { + u64 source_hash, ShaderModules& out_modules) { if (!Common::FS::Exists(path)) { return false; } @@ -355,8 +332,7 @@ template return false; } if (header.magic != CACHE_MAGIC || header.version != CACHE_VERSION || - header.source_size != source_size || header.source_hash != source_hash || - header.variant != variant) { + header.source_size != source_size || header.source_hash != source_hash) { return false; } @@ -433,8 +409,10 @@ template return LosslessStatus::MissingShaders; } - return HasPerformanceShaders(out_resources) ? LosslessStatus::Ok - : LosslessStatus::MissingShaders; + if (!HasNativeShaders(out_resources)) { + return LosslessStatus::MissingShaders; + } + return LosslessStatus::Ok; } } // Anonymous namespace @@ -483,7 +461,7 @@ LosslessStatus GetInstalledLosslessStatus() { return ValidateLosslessDll(GetLosslessDllPath()); } -LosslessStatus LoadShaderModules(ShaderModules& out_modules, bool allow_fp16, bool prefer_fp16) { +LosslessStatus LoadShaderModules(ShaderModules& out_modules) { std::vector image; const LosslessStatus read_status = ReadImageFile(GetLosslessDllPath(), image); if (read_status != LosslessStatus::Ok) { @@ -501,17 +479,11 @@ LosslessStatus LoadShaderModules(ShaderModules& out_modules, bool allow_fp16, bo return parse_status; } - const std::optional variant = SelectVariant(spans, allow_fp16, prefer_fp16); - if (!variant) { - return LosslessStatus::MissingShaders; - } - - if (ReadShaderCache(cache_path, source_size, source_hash, static_cast(*variant), - out_modules)) { + if (ReadShaderCache(cache_path, source_size, source_hash, out_modules)) { return LosslessStatus::Ok; } - const LosslessStatus translate_status = TranslateAll(spans, out_modules, *variant); + const LosslessStatus translate_status = TranslateAll(spans, out_modules); if (translate_status != LosslessStatus::Ok) { return translate_status; } @@ -522,7 +494,6 @@ LosslessStatus LoadShaderModules(ShaderModules& out_modules, bool allow_fp16, bo .source_size = source_size, .source_hash = source_hash, .module_count = static_cast(out_modules.size()), - .variant = static_cast(*variant), }; if (!WriteShaderCache(cache_path, header, out_modules)) { void(Common::FS::RemoveFile(cache_path)); @@ -534,7 +505,7 @@ LosslessStatus LoadShaderModules(ShaderModules& out_modules, bool allow_fp16, bo LosslessStatus BuildShaderCache() { ShaderModules modules; - return LoadShaderModules(modules, true); + return LoadShaderModules(modules); } bool RemoveInstalledLosslessDll() { diff --git a/src/video_core/frame_gen/lossless_dll.h b/src/video_core/frame_gen/lossless_dll.h index 1d583d008f..53e4974521 100644 --- a/src/video_core/frame_gen/lossless_dll.h +++ b/src/video_core/frame_gen/lossless_dll.h @@ -28,11 +28,6 @@ enum class LosslessStatus : u32 { using ShaderResources = std::map>; using ShaderModules = std::map>; -enum class ShaderVariant : u32 { - NativeFp32 = 1, - NativeFp16 = 2, -}; - namespace PerformanceShader { constexpr u32 MIPMAPS = 255; constexpr u32 GENERATE = 256; @@ -42,7 +37,6 @@ constexpr std::array GAMMA{280, 282, 283, 284, 285}; constexpr std::array DELTA{280, 286, 287, 288, 289, 281, 294, 295, 296, 297}; constexpr u32 NATIVE_FP16_OFFSET = 49; -constexpr u32 NATIVE_FP32_OFFSET = 98; } // namespace PerformanceShader [[nodiscard]] std::filesystem::path GetLosslessDllPath(); @@ -58,9 +52,7 @@ constexpr u32 NATIVE_FP32_OFFSET = 98; [[nodiscard]] LosslessStatus BuildShaderCache(); -[[nodiscard]] LosslessStatus LoadShaderModules(ShaderModules& out_modules, - bool allow_fp16 = false, - bool prefer_fp16 = false); +[[nodiscard]] LosslessStatus LoadShaderModules(ShaderModules& out_modules); bool RemoveInstalledLosslessDll(); diff --git a/src/video_core/renderer_vulkan/present/frame_gen.cpp b/src/video_core/renderer_vulkan/present/frame_gen.cpp index b0d16819e5..e6d857323b 100644 --- a/src/video_core/renderer_vulkan/present/frame_gen.cpp +++ b/src/video_core/renderer_vulkan/present/frame_gen.cpp @@ -198,6 +198,10 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, VkExtent2D guest_extent) { generated = false; + if (!shaders) { + shaders.emplace(device); + } + if (unavailable || !Settings::values.frame_gen.GetValue()) { if (chain) { scheduler.Finish(); @@ -207,17 +211,14 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, return; } - if (!frame->storage_view) { + if (!shaders->IsValid()) { unavailable = true; return; } - if (!shaders) { - shaders.emplace(device); - if (!shaders->IsValid()) { - unavailable = true; - return; - } + if (!frame->storage_view) { + warm_streak = 0; + return; } peak_guest_extent.width = std::max(peak_guest_extent.width, guest_extent.width); diff --git a/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp b/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp index aafe0d6419..51c7e4c106 100644 --- a/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp +++ b/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "common/settings.h" #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" @@ -10,16 +9,13 @@ namespace Vulkan { LsfgShaders::LsfgShaders(const Device& device) { - if (!device.IsVulkanMemoryModelSupported() || !device.HasNullDescriptor()) { + if (!device.IsVulkanMemoryModelSupported() || !device.HasNullDescriptor() || + !device.IsFloat16Supported()) { return; } - const bool allow_fp16 = device.IsFloat16Supported(); - const bool prefer_fp16 = allow_fp16 && Settings::values.frame_gen_fp16.GetValue(); - VideoCore::FrameGen::ShaderModules code; - if (VideoCore::FrameGen::LoadShaderModules(code, allow_fp16, prefer_fp16) != - VideoCore::FrameGen::LosslessStatus::Ok) { + if (VideoCore::FrameGen::LoadShaderModules(code) != VideoCore::FrameGen::LosslessStatus::Ok) { return; } diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 96eeeb8045..205382f81a 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -8,6 +8,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include "common/settings.h" #include "video_core/framebuffer_config.h" #include "video_core/present.h" #include "video_core/renderer_vulkan/present/filters.h" @@ -87,13 +88,18 @@ void BlitScreen::SetWindowAdaptPass(const Device& device) { void BlitScreen::PrepareFrame(const Device& device, Frame* frame, const Layout::FramebufferLayout& layout) { - if (!window_adapt || (frame->width == layout.width && frame->height == layout.height)) { + if (!window_adapt) { + return; + } + + if (frame->width != layout.width || frame->height != layout.height) { + WaitIdle(device); + } else if (!present_manager.NeedsStorage(frame, true)) { return; } - WaitIdle(device); present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format, - window_adapt->GetRenderPass()); + window_adapt->GetRenderPass(), true); } void BlitScreen::DrawToFrame(const Device& device, RasterizerVulkan& rasterizer, Frame* frame, @@ -120,16 +126,20 @@ void BlitScreen::DrawToFrame(const Device& device, RasterizerVulkan& rasterizer, swapchain_view_format = current_swapchain_view_format; } + const bool storage_required = Settings::values.frame_gen.GetValue(); if (resource_update_required) { WaitIdle(device); SetWindowAdaptPass(device); if (presentation_recreate_required) { present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format, - window_adapt->GetRenderPass()); + window_adapt->GetRenderPass(), storage_required); } image_index = 0; + } else if (present_manager.NeedsStorage(frame, storage_required)) { + present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format, + window_adapt->GetRenderPass(), true); } const VkExtent2D window_size{ diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp index 3491f8fb75..d173a3ba5f 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -29,9 +29,6 @@ static_assert(MAX_FRAMES_IN_FLIGHT <= LSFG_MAX_TARGETS); bool CanStoreToFrame(const vk::PhysicalDevice& physical_device, VkFormat format) { #ifdef HAS_LSFG - if (!Settings::values.frame_gen.GetValue()) { - return false; - } const VkFormatProperties props{physical_device.GetFormatProperties(format)}; return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0; #else @@ -160,6 +157,7 @@ PresentManager::PresentManager(const vk::Instance& instance_, .pNext = nullptr, .flags = VK_FENCE_CREATE_SIGNALED_BIT, }); + frame.storage_capable = storage_supported; free_queue.push_back(&frame); } @@ -205,15 +203,22 @@ size_t PresentManager::MaxExtraFrames() const { return image_count - 1; } +bool PresentManager::NeedsStorage(const Frame* frame, bool required) const { + return required && frame->storage_capable && !frame->storage_view; +} + void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format, - VkRenderPass rd) { + VkRenderPass rd, bool storage) { auto& dld = device.GetLogical(); frame->width = width; frame->height = height; - const VkImageUsageFlags storage_usage = - storage_supported ? static_cast(VK_IMAGE_USAGE_STORAGE_BIT) : 0; + const bool with_storage = storage && frame->storage_capable; + VkImageUsageFlags storage_usage = 0; + if (with_storage) { + storage_usage = VK_IMAGE_USAGE_STORAGE_BIT; + } frame->image = memory_allocator.CreateImage({ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, @@ -264,7 +269,7 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat }); frame->storage_view = vk::ImageView{}; - if (storage_supported) { + if (with_storage) { frame->storage_view = dld.CreateImageView({ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = nullptr, diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h index 577a82098a..4741c55e61 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.h +++ b/src/video_core/renderer_vulkan/vk_present_manager.h @@ -36,6 +36,7 @@ struct Frame { vk::CommandBuffer cmdbuf; vk::Semaphore render_ready; vk::Fence present_done; + bool storage_capable{}; }; class PresentManager { @@ -57,7 +58,9 @@ public: /// Recreates the present frame to match the provided parameters void RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format, - VkRenderPass rd); + VkRenderPass rd, bool storage); + + [[nodiscard]] bool NeedsStorage(const Frame* frame, bool required) const; /// Waits for the present thread to finish presenting all queued frames. void WaitPresent();