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 7d094b56fd..db562848fd 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
@@ -39,6 +39,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_SAMPLE_SHADING("sample_shading"),
RENDERER_FRAME_GEN("frame_gen"),
RENDERER_FRAME_GEN_HDR("frame_gen_hdr"),
+ RENDERER_FRAME_GEN_FP16("frame_gen_fp16"),
RENDERER_FRAME_GEN_DUMP_FLOW("frame_gen_dump_flow"),
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
PICTURE_IN_PICTURE("picture_in_picture"),
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 94d9f3441c..1811045d63 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
@@ -640,6 +640,13 @@ abstract class SettingsItem(
descriptionId = R.string.frame_gen_hdr_description
)
)
+ 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,
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 56e60529ca..2f8fd9474d 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
@@ -118,6 +118,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.RENDERER_FRAME_GEN.key)
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key)
add(IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key)
+ add(BooleanSetting.RENDERER_FRAME_GEN_FP16.key)
add(BooleanSetting.RENDERER_FRAME_GEN_HDR.key)
add(BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key)
}
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index b6f0d90af7..225912ec87 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -307,6 +307,8 @@
4x (three generated frames)
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 shipped in Lossless.dll. Much faster on Adreno. Falls back automatically if the driver or the file lacks it.
HDR interpolation
Treat frames as HDR when interpolating. Only useful on an HDR output.
Dump generated frame
diff --git a/src/common/settings.h b/src/common/settings.h
index aa3514f137..3c7172e855 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -417,6 +417,9 @@ struct Values {
SwitchableSetting frame_gen_hdr{linkage, false, "frame_gen_hdr", Category::Renderer,
Specialization::Default, true, true, &frame_gen};
+ SwitchableSetting frame_gen_fp16{linkage, true, "frame_gen_fp16", Category::Renderer,
+ Specialization::Default, true, true, &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 84acbdd5d3..03228b8669 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 = 1;
+constexpr u32 CACHE_VERSION = 2;
struct CacheHeader {
u32 magic;
@@ -53,6 +53,7 @@ struct CacheHeader {
u64 source_size;
u64 source_hash;
u32 module_count;
+ u32 variant;
};
struct Section {
@@ -276,14 +277,54 @@ template
return std::ranges::all_of(ids, [&](u32 id) { return resources.contains(id); });
}
+[[nodiscard]] u32 VariantOffset(ShaderVariant variant) {
+ switch (variant) {
+ case ShaderVariant::NativeFp16:
+ return PerformanceShader::NATIVE_FP16_OFFSET;
+ case ShaderVariant::NativeFp32:
+ return PerformanceShader::NATIVE_FP32_OFFSET;
+ default:
+ return 0;
+ }
+}
+
+template
+[[nodiscard]] bool HasNativeVariant(const Map& resources, ShaderVariant variant) {
+ const u32 offset = VariantOffset(variant);
+ return std::ranges::all_of(PerformanceShaderIds(), [&](u32 id) {
+ const auto hit = resources.find(id + offset);
+ return hit != resources.end() && IsSpirvModule(hit->second);
+ });
+}
+
+[[nodiscard]] ShaderVariant SelectVariant(const ResourceSpans& resources, bool prefer_fp16) {
+ if (prefer_fp16 && HasNativeVariant(resources, ShaderVariant::NativeFp16)) {
+ return ShaderVariant::NativeFp16;
+ }
+ if (HasNativeVariant(resources, ShaderVariant::NativeFp32)) {
+ return ShaderVariant::NativeFp32;
+ }
+ return ShaderVariant::TranslatedDxbc;
+}
+
[[nodiscard]] LosslessStatus TranslateAll(const ResourceSpans& resources,
- ShaderModules& out_modules) {
+ ShaderModules& out_modules,
+ ShaderVariant variant) {
+ const u32 offset = VariantOffset(variant);
out_modules.clear();
for (const u32 id : PerformanceShaderIds()) {
- const auto hit = resources.find(id);
+ const auto hit = resources.find(id + offset);
if (hit == resources.end()) {
return LosslessStatus::MissingShaders;
}
+ if (variant != ShaderVariant::TranslatedDxbc) {
+ std::vector adopted = AdoptSpirvModule(hit->second);
+ if (adopted.empty()) {
+ return LosslessStatus::TranslationFailed;
+ }
+ out_modules.emplace(id, std::move(adopted));
+ continue;
+ }
std::vector words = TranslateComputeShader(hit->second);
if (words.empty()) {
@@ -313,7 +354,7 @@ template
}
[[nodiscard]] bool ReadShaderCache(const std::filesystem::path& path, u64 source_size,
- u64 source_hash, ShaderModules& out_modules) {
+ u64 source_hash, u32 variant, ShaderModules& out_modules) {
if (!Common::FS::Exists(path)) {
return false;
}
@@ -325,7 +366,8 @@ template
return false;
}
if (header.magic != CACHE_MAGIC || header.version != CACHE_VERSION ||
- header.source_size != source_size || header.source_hash != source_hash) {
+ header.source_size != source_size || header.source_hash != source_hash ||
+ header.variant != variant) {
return false;
}
@@ -452,7 +494,21 @@ LosslessStatus GetInstalledLosslessStatus() {
return ValidateLosslessDll(GetLosslessDllPath());
}
-LosslessStatus LoadShaderModules(ShaderModules& out_modules) {
+ShaderVariant GetAvailableVariant(bool prefer_fp16) {
+ std::vector image;
+ if (ReadImageFile(GetLosslessDllPath(), image) != LosslessStatus::Ok) {
+ return ShaderVariant::TranslatedDxbc;
+ }
+
+ ResourceSpans spans;
+ if (ParseShaderSpans(image, spans) != LosslessStatus::Ok) {
+ return ShaderVariant::TranslatedDxbc;
+ }
+
+ return SelectVariant(spans, prefer_fp16);
+}
+
+LosslessStatus LoadShaderModules(ShaderModules& out_modules, bool prefer_fp16) {
std::vector image;
const LosslessStatus read_status = ReadImageFile(GetLosslessDllPath(), image);
if (read_status != LosslessStatus::Ok) {
@@ -464,17 +520,20 @@ LosslessStatus LoadShaderModules(ShaderModules& out_modules) {
Common::CityHash64(reinterpret_cast(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);
+ const ShaderVariant variant = SelectVariant(spans, prefer_fp16);
+
+ if (ReadShaderCache(cache_path, source_size, source_hash, static_cast(variant),
+ out_modules)) {
+ return LosslessStatus::Ok;
+ }
+
+ const LosslessStatus translate_status = TranslateAll(spans, out_modules, variant);
if (translate_status != LosslessStatus::Ok) {
return translate_status;
}
@@ -485,6 +544,7 @@ LosslessStatus LoadShaderModules(ShaderModules& out_modules) {
.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));
diff --git a/src/video_core/frame_gen/lossless_dll.h b/src/video_core/frame_gen/lossless_dll.h
index 5c87a1afb6..f37572f369 100644
--- a/src/video_core/frame_gen/lossless_dll.h
+++ b/src/video_core/frame_gen/lossless_dll.h
@@ -25,6 +25,12 @@ enum class LosslessStatus : u32 {
using ShaderResources = std::map>;
using ShaderModules = std::map>;
+enum class ShaderVariant : u32 {
+ TranslatedDxbc,
+ NativeFp32,
+ NativeFp16,
+};
+
namespace PerformanceShader {
constexpr u32 MIPMAPS = 255;
constexpr u32 GENERATE = 256;
@@ -32,6 +38,9 @@ constexpr std::array ALPHA{290, 291, 292, 293};
constexpr std::array BETA{298, 299, 300, 301, 302};
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();
@@ -47,7 +56,10 @@ constexpr std::array DELTA{280, 286, 287, 288, 289, 281, 294, 295, 296,
[[nodiscard]] LosslessStatus BuildShaderCache();
-[[nodiscard]] LosslessStatus LoadShaderModules(ShaderModules& out_modules);
+[[nodiscard]] ShaderVariant GetAvailableVariant(bool prefer_fp16);
+
+[[nodiscard]] LosslessStatus LoadShaderModules(ShaderModules& out_modules,
+ bool prefer_fp16 = false);
bool RemoveInstalledLosslessDll();
diff --git a/src/video_core/frame_gen/lsfg_translate.cpp b/src/video_core/frame_gen/lsfg_translate.cpp
index 0221553b7c..758711ed5d 100644
--- a/src/video_core/frame_gen/lsfg_translate.cpp
+++ b/src/video_core/frame_gen/lsfg_translate.cpp
@@ -1,6 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
+#include
+#include
+#include