Let's try FP16

This commit is contained in:
CamilleLaVey
2026-08-13 03:16:13 -04:00
parent e26bf22d36
commit 52ffc8a7ce
10 changed files with 179 additions and 13 deletions
@@ -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"),
@@ -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,
@@ -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)
}
@@ -307,6 +307,8 @@
<string name="frame_gen_multiplier_4x">4x (three generated frames)</string>
<string name="frame_gen_flow_scale">Motion estimation resolution</string>
<string name="frame_gen_flow_scale_description">Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance.</string>
<string name="frame_gen_fp16">Half precision shaders</string>
<string name="frame_gen_fp16_description">Use the 16-bit shader variant shipped in Lossless.dll. Much faster on Adreno. Falls back automatically if the driver or the file lacks it.</string>
<string name="frame_gen_hdr">HDR interpolation</string>
<string name="frame_gen_hdr_description">Treat frames as HDR when interpolating. Only useful on an HDR output.</string>
<string name="frame_gen_dump_flow">Dump generated frame</string>
+3
View File
@@ -417,6 +417,9 @@ struct Values {
SwitchableSetting<bool> frame_gen_hdr{linkage, false, "frame_gen_hdr", Category::Renderer,
Specialization::Default, true, true, &frame_gen};
SwitchableSetting<bool> frame_gen_fp16{linkage, true, "frame_gen_fp16", Category::Renderer,
Specialization::Default, true, true, &frame_gen};
SwitchableSetting<bool> frame_gen_dump_flow{linkage, false, "frame_gen_dump_flow",
Category::Renderer};
+71 -11
View File
@@ -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 <typename Map>
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 <typename Map>
[[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<u32> adopted = AdoptSpirvModule(hit->second);
if (adopted.empty()) {
return LosslessStatus::TranslationFailed;
}
out_modules.emplace(id, std::move(adopted));
continue;
}
std::vector<u32> words = TranslateComputeShader(hit->second);
if (words.empty()) {
@@ -313,7 +354,7 @@ template <typename Map>
}
[[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 <typename Map>
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<u8> 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<u8> 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<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);
const ShaderVariant variant = SelectVariant(spans, prefer_fp16);
if (ReadShaderCache(cache_path, source_size, source_hash, static_cast<u32>(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<u32>(out_modules.size()),
.variant = static_cast<u32>(variant),
};
if (!WriteShaderCache(cache_path, header, out_modules)) {
void(Common::FS::RemoveFile(cache_path));
+13 -1
View File
@@ -25,6 +25,12 @@ enum class LosslessStatus : u32 {
using ShaderResources = std::map<u32, std::vector<u8>>;
using ShaderModules = std::map<u32, std::vector<u32>>;
enum class ShaderVariant : u32 {
TranslatedDxbc,
NativeFp32,
NativeFp16,
};
namespace PerformanceShader {
constexpr u32 MIPMAPS = 255;
constexpr u32 GENERATE = 256;
@@ -32,6 +38,9 @@ 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};
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<u32, 10> 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();
@@ -1,6 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cstring>
#include <map>
#include <tuple>
#include <dxbc_modinfo.h>
#include <dxbc_module.h>
#include <dxbc_reader.h>
@@ -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<u32> literal_offsets;
@@ -31,8 +37,73 @@ void RenumberBindings(dxvk::SpirvCodeBuffer& code) {
}
}
void RenumberBindingsInOrder(std::vector<u32>& words) {
struct Slot {
u32 set;
u32 binding;
size_t literal_offset;
};
std::map<u32, u32> sets;
std::vector<Slot> 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<u32>(i);
}
}
} // Anonymous namespace
bool IsSpirvModule(std::span<const u8> 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<u32> AdoptSpirvModule(std::span<const u8> blob) {
if (!IsSpirvModule(blob)) {
return {};
}
std::vector<u32> words(blob.size() / sizeof(u32));
std::memcpy(words.data(), blob.data(), blob.size());
RenumberBindingsInOrder(words);
return words;
}
std::vector<u32> TranslateComputeShader(std::span<const u8> dxbc) {
if (dxbc.empty()) {
return {};
@@ -10,6 +10,10 @@
namespace VideoCore::FrameGen {
[[nodiscard]] bool IsSpirvModule(std::span<const u8> blob);
[[nodiscard]] std::vector<u32> AdoptSpirvModule(std::span<const u8> blob);
[[nodiscard]] std::vector<u32> TranslateComputeShader(std::span<const u8> dxbc);
} // namespace VideoCore::FrameGen
@@ -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;
}