From 52ffc8a7cedc995fade574d9076ba018eb5a89c6 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 13 Aug 2026 03:16:13 -0400 Subject: [PATCH] Let's try FP16 --- .../features/settings/model/BooleanSetting.kt | 1 + .../settings/model/view/SettingsItem.kt | 7 ++ .../settings/ui/SettingsFragmentPresenter.kt | 1 + .../app/src/main/res/values/strings.xml | 2 + src/common/settings.h | 3 + src/video_core/frame_gen/lossless_dll.cpp | 82 ++++++++++++++++--- src/video_core/frame_gen/lossless_dll.h | 14 +++- src/video_core/frame_gen/lsfg_translate.cpp | 71 ++++++++++++++++ src/video_core/frame_gen/lsfg_translate.h | 4 + .../renderer_vulkan/present/lsfg_shaders.cpp | 7 +- 10 files changed, 179 insertions(+), 13 deletions(-) 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 +#include + #include #include #include @@ -13,6 +18,7 @@ namespace VideoCore::FrameGen { namespace { constexpr u32 DECORATION_LITERAL_WORD = 3; +constexpr size_t SPIRV_HEADER_WORDS = 5; void RenumberBindings(dxvk::SpirvCodeBuffer& code) { std::vector literal_offsets; @@ -31,8 +37,73 @@ void RenumberBindings(dxvk::SpirvCodeBuffer& code) { } } +void RenumberBindingsInOrder(std::vector& words) { + struct Slot { + u32 set; + u32 binding; + size_t literal_offset; + }; + + std::map sets; + std::vector slots; + + size_t offset = SPIRV_HEADER_WORDS; + while (offset + 1 <= words.size()) { + const u32 length = words[offset] >> spv::WordCountShift; + const u32 opcode = words[offset] & spv::OpCodeMask; + if (length == 0 || offset + length > words.size()) { + return; + } + if (opcode == spv::OpFunction) { + break; + } + if (opcode == spv::OpDecorate && length >= 4) { + if (words[offset + 2] == spv::DecorationDescriptorSet) { + sets[words[offset + 1]] = words[offset + 3]; + } else if (words[offset + 2] == spv::DecorationBinding) { + slots.push_back(Slot{0, words[offset + 3], offset + DECORATION_LITERAL_WORD}); + } + } + offset += length; + } + + for (Slot& slot : slots) { + const auto hit = sets.find(words[slot.literal_offset - 2]); + slot.set = hit == sets.end() ? 0 : hit->second; + } + + std::ranges::stable_sort(slots, [](const Slot& lhs, const Slot& rhs) { + return std::tie(lhs.set, lhs.binding) < std::tie(rhs.set, rhs.binding); + }); + + for (size_t i = 0; i < slots.size(); ++i) { + words[slots[i].literal_offset] = static_cast(i); + } +} + } // Anonymous namespace +bool IsSpirvModule(std::span blob) { + if (blob.size() < SPIRV_HEADER_WORDS * sizeof(u32) || blob.size() % sizeof(u32) != 0) { + return false; + } + u32 magic{}; + std::memcpy(&magic, blob.data(), sizeof(magic)); + return magic == spv::MagicNumber; +} + +std::vector AdoptSpirvModule(std::span blob) { + if (!IsSpirvModule(blob)) { + return {}; + } + + std::vector words(blob.size() / sizeof(u32)); + std::memcpy(words.data(), blob.data(), blob.size()); + + RenumberBindingsInOrder(words); + return words; +} + std::vector TranslateComputeShader(std::span dxbc) { if (dxbc.empty()) { return {}; diff --git a/src/video_core/frame_gen/lsfg_translate.h b/src/video_core/frame_gen/lsfg_translate.h index 60dd104439..291db1c4f5 100644 --- a/src/video_core/frame_gen/lsfg_translate.h +++ b/src/video_core/frame_gen/lsfg_translate.h @@ -10,6 +10,10 @@ namespace VideoCore::FrameGen { +[[nodiscard]] bool IsSpirvModule(std::span blob); + +[[nodiscard]] std::vector AdoptSpirvModule(std::span blob); + [[nodiscard]] std::vector TranslateComputeShader(std::span dxbc); } // namespace VideoCore::FrameGen diff --git a/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp b/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp index 59a8de8613..0955a9aa54 100644 --- a/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp +++ b/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp @@ -1,6 +1,7 @@ // 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" @@ -13,8 +14,12 @@ LsfgShaders::LsfgShaders(const Device& device) { return; } + const bool prefer_fp16 = + Settings::values.frame_gen_fp16.GetValue() && device.IsFloat16Supported(); + VideoCore::FrameGen::ShaderModules code; - if (VideoCore::FrameGen::LoadShaderModules(code) != VideoCore::FrameGen::LosslessStatus::Ok) { + if (VideoCore::FrameGen::LoadShaderModules(code, prefer_fp16) != + VideoCore::FrameGen::LosslessStatus::Ok) { return; }