[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 <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
This commit is contained in:
CamilleLaVey
2026-09-14 02:53:30 +02:00
committed by crueter
parent 93318ef697
commit 099547b3f4
26 changed files with 410 additions and 147 deletions
+15 -44
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 = 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 <typename Map>
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 <typename Map>
[[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<ShaderVariant> 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 <typename Map>
}
[[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 <typename Map>
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 <typename Map>
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<u8> 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<ShaderVariant> variant = SelectVariant(spans, allow_fp16, prefer_fp16);
if (!variant) {
return LosslessStatus::MissingShaders;
}
if (ReadShaderCache(cache_path, source_size, source_hash, static_cast<u32>(*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<u32>(out_modules.size()),
.variant = static_cast<u32>(*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() {
+1 -9
View File
@@ -28,11 +28,6 @@ enum class LosslessStatus : u32 {
using ShaderResources = std::map<u32, std::vector<u8>>;
using ShaderModules = std::map<u32, std::vector<u32>>;
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<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();
@@ -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();
@@ -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);
@@ -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;
}
@@ -8,6 +8,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <vulkan/vulkan_core.h>
#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{
@@ -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<VkImageUsageFlags>(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,
@@ -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();