mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-14 04:24:31 +00:00
Compare commits
15 Commits
4f4f4a3586
...
81f396e70b
| Author | SHA1 | Date | |
|---|---|---|---|
| 81f396e70b | |||
| 3725b4d46d | |||
| 42b8ca5f9d | |||
| 52ffc8a7ce | |||
| e26bf22d36 | |||
| 42c5a12577 | |||
| a18d2b5c05 | |||
| 30be8a5e18 | |||
| 44e3211d1c | |||
| 52e0b2a005 | |||
| 9a1258c59d | |||
| 232656df4e | |||
| 367e5084da | |||
| 7467506cfa | |||
| 61f3a1d135 |
+18
-5
@@ -339,13 +339,26 @@ if (CXX_GCC OR CXX_CLANG)
|
||||
endif()
|
||||
elseif(ARCHITECTURE_arm64)
|
||||
# See https://gcc.gnu.org/onlinedocs/gcc/AArch64-Options.html
|
||||
set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, generic, armv9, native")
|
||||
set(mtune generic)
|
||||
set(armv8_2_target armv8.2-a+fp16+dotprod)
|
||||
|
||||
if (${YUZU_BUILD_PRESET} STREQUAL "generic")
|
||||
set(march armv8-a)
|
||||
elseif (${YUZU_BUILD_PRESET} STREQUAL "armv9")
|
||||
set(march armv9-a)
|
||||
if (ANDROID)
|
||||
set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, armv8.2, armv9, native")
|
||||
set(march ${armv8_2_target})
|
||||
|
||||
if (${YUZU_BUILD_PRESET} STREQUAL "armv9")
|
||||
set(march armv9-a)
|
||||
endif()
|
||||
else()
|
||||
set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, generic, armv8.2, armv9, native")
|
||||
|
||||
if (${YUZU_BUILD_PRESET} STREQUAL "generic")
|
||||
set(march armv8-a)
|
||||
elseif (${YUZU_BUILD_PRESET} STREQUAL "armv8.2")
|
||||
set(march ${armv8_2_target})
|
||||
elseif (${YUZU_BUILD_PRESET} STREQUAL "armv9")
|
||||
set(march armv9-a)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
+1
@@ -38,6 +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_DUMP_FLOW("frame_gen_dump_flow"),
|
||||
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
|
||||
PICTURE_IN_PICTURE("picture_in_picture"),
|
||||
|
||||
@@ -19,6 +19,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
RENDERER_ASTC_DECODE_METHOD("accelerate_astc"),
|
||||
RENDERER_ACCURACY("gpu_accuracy"),
|
||||
RENDERER_RESOLUTION("resolution_setup"),
|
||||
RENDERER_FRAME_GEN_MULTIPLIER("frame_gen_multiplier"),
|
||||
RENDERER_FRAME_GEN_FLOW_SCALE("frame_gen_flow_scale"),
|
||||
RENDERER_VSYNC("use_vsync"),
|
||||
RENDERER_SCALING_FILTER("scaling_filter"),
|
||||
RENDERER_ANTI_ALIASING("anti_aliasing"),
|
||||
|
||||
+26
@@ -614,6 +614,32 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.frame_gen_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_MULTIPLIER,
|
||||
titleId = R.string.frame_gen_multiplier,
|
||||
descriptionId = R.string.frame_gen_multiplier_description,
|
||||
choicesId = R.array.frameGenMultiplierNames,
|
||||
valuesId = R.array.frameGenMultiplierValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SliderSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE,
|
||||
titleId = R.string.frame_gen_flow_scale,
|
||||
descriptionId = R.string.frame_gen_flow_scale_description,
|
||||
min = 25,
|
||||
max = 100,
|
||||
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,
|
||||
|
||||
+3
@@ -116,6 +116,9 @@ 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_DUMP_FLOW.key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,9 +124,18 @@ float EmuWindow_Android::GetFrameTimeVerifiedHint() const {
|
||||
return QuantizeFrameRateHint(verified_rate);
|
||||
}
|
||||
|
||||
float EmuWindow_Android::GetPresentedFrameMultiplier() {
|
||||
if (!Settings::values.frame_gen.GetValue()) {
|
||||
return 1.0f;
|
||||
}
|
||||
return static_cast<float>(std::clamp<u32>(Settings::values.frame_gen_multiplier.GetValue(), 2, 4));
|
||||
}
|
||||
|
||||
float EmuWindow_Android::GetFrameRateHint() const {
|
||||
const float observed_rate = std::clamp(m_smoothed_present_rate, 0.0f, 240.0f);
|
||||
const float frame_time_verified_hint = GetFrameTimeVerifiedHint();
|
||||
const float presented_multiplier = GetPresentedFrameMultiplier();
|
||||
const float observed_rate =
|
||||
std::clamp(m_smoothed_present_rate * presented_multiplier, 0.0f, 240.0f);
|
||||
const float frame_time_verified_hint = GetFrameTimeVerifiedHint() * presented_multiplier;
|
||||
|
||||
if (m_last_frame_rate_hint > 0.0f && observed_rate > 0.0f) {
|
||||
const float tolerance = std::max(m_last_frame_rate_hint * 0.12f, 4.0f);
|
||||
@@ -150,9 +159,9 @@ float EmuWindow_Android::GetFrameRateHint() const {
|
||||
return frame_time_verified_hint;
|
||||
}
|
||||
|
||||
constexpr float NominalFrameRate = 60.0f;
|
||||
const float nominal_rate = 60.0f * presented_multiplier;
|
||||
if (!Settings::values.use_speed_limit.GetValue()) {
|
||||
return NominalFrameRate;
|
||||
return QuantizeFrameRateHint(nominal_rate);
|
||||
}
|
||||
|
||||
const u16 speed_limit = Settings::SpeedLimit();
|
||||
@@ -161,7 +170,7 @@ float EmuWindow_Android::GetFrameRateHint() const {
|
||||
}
|
||||
|
||||
const float speed_limited_rate =
|
||||
NominalFrameRate * (static_cast<float>(std::min<u16>(speed_limit, 100)) / 100.0f);
|
||||
nominal_rate * (static_cast<float>(std::min<u16>(speed_limit, 100)) / 100.0f);
|
||||
return QuantizeFrameRateHint(speed_limited_rate);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ private:
|
||||
void UpdateObservedFrameRate();
|
||||
[[nodiscard]] float GetFrameRateHint() const;
|
||||
[[nodiscard]] float GetFrameTimeVerifiedHint() const;
|
||||
[[nodiscard]] static float GetPresentedFrameMultiplier();
|
||||
[[nodiscard]] static float QuantizeFrameRateHint(float frame_rate);
|
||||
|
||||
float m_window_width{};
|
||||
|
||||
@@ -162,6 +162,18 @@
|
||||
<item>@string/resolution_four</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="frameGenMultiplierNames">
|
||||
<item>@string/frame_gen_multiplier_2x</item>
|
||||
<item>@string/frame_gen_multiplier_3x</item>
|
||||
<item>@string/frame_gen_multiplier_4x</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="frameGenMultiplierValues">
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="rendererVSyncNames">
|
||||
<item>@string/renderer_vsync_immediate</item>
|
||||
<item>@string/renderer_vsync_mailbox</item>
|
||||
|
||||
@@ -299,9 +299,18 @@
|
||||
<string name="gpu_driver_manager">GPU driver manager</string>
|
||||
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
||||
<string name="frame_gen">Frame generation</string>
|
||||
<string name="frame_gen_description">Insert an interpolated frame between every pair of real frames using Lossless Scaling. Adds about one frame of input latency.</string>
|
||||
<string name="frame_gen_dump_flow">Dump flow pyramid</string>
|
||||
<string name="frame_gen_dump_flow_description">Write the optical flow mip levels to the lossless/debug folder once, for troubleshooting</string>
|
||||
<string name="frame_gen_description">Insert interpolated frames between rendered ones using Lossless Scaling. Forces FIFO presentation while enabled, since other modes discard the generated frames. Adds about one frame of latency.</string>
|
||||
<string name="frame_gen_multiplier">Frame multiplier</string>
|
||||
<string name="frame_gen_multiplier_description">How many frames to display for each rendered frame. Higher values cost proportionally more GPU time.</string>
|
||||
<string name="frame_gen_multiplier_2x">2x (one generated frame)</string>
|
||||
<string name="frame_gen_multiplier_3x">3x (two generated frames)</string>
|
||||
<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_dump_flow">Dump generated frame</string>
|
||||
<string name="frame_gen_dump_flow_description">Write the optical flow mip levels and the interpolated frame to the lossless/debug folder once, for troubleshooting</string>
|
||||
<string name="frame_gen_unsupported">Frame generation unavailable</string>
|
||||
<string name="frame_gen_unsupported_description">This GPU driver does not support the Vulkan memory model, which the Lossless Scaling shaders require.</string>
|
||||
<string name="lossless_scaling_setup_description">Optional. Provide your own Lossless.dll to enable frame generation later</string>
|
||||
|
||||
@@ -390,6 +390,33 @@ struct Values {
|
||||
|
||||
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
|
||||
Specialization::Default, true, true};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_multiplier{linkage,
|
||||
2,
|
||||
2,
|
||||
4,
|
||||
"frame_gen_multiplier",
|
||||
Category::Renderer,
|
||||
Specialization::Countable,
|
||||
true,
|
||||
true,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_flow_scale{linkage,
|
||||
100,
|
||||
25,
|
||||
100,
|
||||
"frame_gen_flow_scale",
|
||||
Category::Renderer,
|
||||
Specialization::Countable |
|
||||
Specialization::Percentage,
|
||||
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};
|
||||
|
||||
|
||||
@@ -127,6 +127,20 @@ add_library(video_core STATIC
|
||||
renderer_vulkan/present/filters.h
|
||||
renderer_vulkan/present/frame_gen.cpp
|
||||
renderer_vulkan/present/frame_gen.h
|
||||
renderer_vulkan/present/lsfg_alpha.cpp
|
||||
renderer_vulkan/present/lsfg_alpha.h
|
||||
renderer_vulkan/present/lsfg_beta.cpp
|
||||
renderer_vulkan/present/lsfg_beta.h
|
||||
renderer_vulkan/present/lsfg_chain.cpp
|
||||
renderer_vulkan/present/lsfg_chain.h
|
||||
renderer_vulkan/present/lsfg_common.cpp
|
||||
renderer_vulkan/present/lsfg_common.h
|
||||
renderer_vulkan/present/lsfg_delta.cpp
|
||||
renderer_vulkan/present/lsfg_delta.h
|
||||
renderer_vulkan/present/lsfg_gamma.cpp
|
||||
renderer_vulkan/present/lsfg_gamma.h
|
||||
renderer_vulkan/present/lsfg_generate.cpp
|
||||
renderer_vulkan/present/lsfg_generate.h
|
||||
renderer_vulkan/present/lsfg_mipmaps.cpp
|
||||
renderer_vulkan/present/lsfg_mipmaps.h
|
||||
renderer_vulkan/present/lsfg_shaders.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 <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));
|
||||
|
||||
@@ -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,7 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
@@ -17,27 +19,162 @@ namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr f32 LSFG_FLOW_SCALE = 1.0f;
|
||||
constexpr size_t COLOR_CHANNELS = 4;
|
||||
constexpr u64 LSFG_REQUIRED_FRAMES = 2;
|
||||
|
||||
void WriteGrayscalePgm(const std::filesystem::path& path, VkExtent2D extent,
|
||||
std::span<const u8> pixels) {
|
||||
[[nodiscard]] f32 ConfiguredFlowScale() {
|
||||
return static_cast<f32>(Settings::values.frame_gen_flow_scale.GetValue()) / 100.0f;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t ConfiguredGenerations() {
|
||||
const u32 multiplier = std::clamp<u32>(Settings::values.frame_gen_multiplier.GetValue(),
|
||||
LSFG_MIN_MULTIPLIER, LSFG_MAX_MULTIPLIER);
|
||||
return multiplier - 1;
|
||||
}
|
||||
|
||||
bool IsBlueFirst(VkFormat format) {
|
||||
return format == VK_FORMAT_B8G8R8A8_UNORM || format == VK_FORMAT_B8G8R8A8_SRGB;
|
||||
}
|
||||
|
||||
VkDeviceSize BytesPerTexel(VkFormat format) {
|
||||
switch (format) {
|
||||
case VK_FORMAT_R8_UNORM:
|
||||
return 1;
|
||||
case VK_FORMAT_R16G16B16A16_SFLOAT:
|
||||
return 8;
|
||||
default:
|
||||
return COLOR_CHANNELS;
|
||||
}
|
||||
}
|
||||
|
||||
void WritePortablePixmap(const std::filesystem::path& path, const std::string& magic,
|
||||
VkExtent2D extent, std::span<const u8> pixels) {
|
||||
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write,
|
||||
Common::FS::FileType::BinaryFile};
|
||||
if (!file.IsOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string header =
|
||||
"P5\n" + std::to_string(extent.width) + " " + std::to_string(extent.height) + "\n255\n";
|
||||
const std::string header = magic + "\n" + std::to_string(extent.width) + " " +
|
||||
std::to_string(extent.height) + "\n255\n";
|
||||
if (file.Write(header) != header.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t expected = static_cast<size_t>(extent.width) * extent.height;
|
||||
void(file.Write(pixels.subspan(0, std::min(expected, pixels.size()))));
|
||||
void(file.Write(pixels));
|
||||
void(file.Flush());
|
||||
}
|
||||
|
||||
void WriteGrayscalePgm(const std::filesystem::path& path, VkExtent2D extent,
|
||||
std::span<const u8> pixels) {
|
||||
const size_t expected = static_cast<size_t>(extent.width) * extent.height;
|
||||
WritePortablePixmap(path, "P5", extent, pixels.subspan(0, std::min(expected, pixels.size())));
|
||||
}
|
||||
|
||||
void WriteRaw(const std::filesystem::path& path, std::span<const u8> pixels) {
|
||||
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write,
|
||||
Common::FS::FileType::BinaryFile};
|
||||
if (!file.IsOpen()) {
|
||||
return;
|
||||
}
|
||||
void(file.Write(pixels));
|
||||
void(file.Flush());
|
||||
}
|
||||
|
||||
void WriteColorPpm(const std::filesystem::path& path, VkExtent2D extent,
|
||||
std::span<const u8> pixels, bool blue_first) {
|
||||
const size_t pixel_count = static_cast<size_t>(extent.width) * extent.height;
|
||||
if (pixels.size() < pixel_count * COLOR_CHANNELS) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<u8> rgb(pixel_count * 3);
|
||||
for (size_t i = 0; i < pixel_count; ++i) {
|
||||
const u8 first = pixels[i * COLOR_CHANNELS];
|
||||
const u8 green = pixels[i * COLOR_CHANNELS + 1];
|
||||
const u8 third = pixels[i * COLOR_CHANNELS + 2];
|
||||
rgb[i * 3] = blue_first ? third : first;
|
||||
rgb[i * 3 + 1] = green;
|
||||
rgb[i * 3 + 2] = blue_first ? first : third;
|
||||
}
|
||||
|
||||
WritePortablePixmap(path, "P6", extent, rgb);
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier MakeTransitionBarrier(VkImage image, VkAccessFlags src_access,
|
||||
VkAccessFlags dst_access, VkImageLayout old_layout,
|
||||
VkImageLayout new_layout) {
|
||||
return VkImageMemoryBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = src_access,
|
||||
.dstAccessMask = dst_access,
|
||||
.oldLayout = old_layout,
|
||||
.newLayout = new_layout,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image,
|
||||
.subresourceRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
VkImageCopy MakeCopyRegion(VkExtent2D extent) {
|
||||
return VkImageCopy{
|
||||
.srcSubresource{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
.srcOffset = {},
|
||||
.dstSubresource{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
.dstOffset = {},
|
||||
.extent = {.width = extent.width, .height = extent.height, .depth = 1},
|
||||
};
|
||||
}
|
||||
|
||||
void CopyPresentedFrame(vk::CommandBuffer cmdbuf, VkImage source, LsfgImage& destination,
|
||||
VkExtent2D extent) {
|
||||
const auto make_barrier = MakeTransitionBarrier;
|
||||
|
||||
const std::array before{
|
||||
make_barrier(source, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL),
|
||||
make_barrier(destination.Handle(), VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
destination.Layout(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL),
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, {}, before);
|
||||
|
||||
cmdbuf.CopyImage(source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, destination.Handle(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, MakeCopyRegion(extent));
|
||||
|
||||
const std::array after{
|
||||
make_barrier(source, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL),
|
||||
make_barrier(destination.Handle(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL),
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0, {}, {}, after);
|
||||
|
||||
destination.SetLayout(VK_IMAGE_LAYOUT_GENERAL);
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
FrameGen::FrameGen(MemoryAllocator& memory_allocator_, Scheduler& scheduler_)
|
||||
@@ -45,7 +182,9 @@ FrameGen::FrameGen(MemoryAllocator& memory_allocator_, Scheduler& scheduler_)
|
||||
|
||||
FrameGen::~FrameGen() = default;
|
||||
|
||||
void FrameGen::Process(const Device& device, Frame* frame) {
|
||||
void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, bool generate) {
|
||||
generated = false;
|
||||
|
||||
if (unavailable || !Settings::values.frame_gen.GetValue()) {
|
||||
return;
|
||||
}
|
||||
@@ -59,53 +198,159 @@ void FrameGen::Process(const Device& device, Frame* frame) {
|
||||
}
|
||||
|
||||
const VkExtent2D extent{.width = frame->width, .height = frame->height};
|
||||
if (!mipmaps || built_extent.width != extent.width || built_extent.height != extent.height) {
|
||||
Rebuild(device, extent);
|
||||
if (!chain || built_extent.width != extent.width || built_extent.height != extent.height ||
|
||||
built_format != format || built_flow_scale != ConfiguredFlowScale() ||
|
||||
built_generations != ConfiguredGenerations()) {
|
||||
Rebuild(device, extent, format);
|
||||
}
|
||||
|
||||
mipmaps->Dispatch(device, scheduler, *frame->image_view, frame_count);
|
||||
++frame_count;
|
||||
const u64 count = frame_count++;
|
||||
generated = generate && count + 1 >= LSFG_REQUIRED_FRAMES;
|
||||
|
||||
const bool dump_requested = Settings::values.frame_gen_dump_flow.GetValue();
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([this, source = *frame->image, extent, count,
|
||||
dispatch = generated](vk::CommandBuffer cmdbuf) {
|
||||
CopyPresentedFrame(cmdbuf, source, chain->Input(count), extent);
|
||||
if (dispatch) {
|
||||
chain->Dispatch(cmdbuf, count);
|
||||
}
|
||||
});
|
||||
|
||||
const bool dump_requested = generated && Settings::values.frame_gen_dump_flow.GetValue();
|
||||
if (!dump_requested) {
|
||||
dumped = false;
|
||||
} else if (!dumped) {
|
||||
DumpFlowPyramid(device);
|
||||
DumpDebugImages(count);
|
||||
dumped = true;
|
||||
}
|
||||
}
|
||||
|
||||
void FrameGen::Rebuild(const Device& device, VkExtent2D extent) {
|
||||
scheduler.Finish();
|
||||
mipmaps.emplace(device, memory_allocator, *shaders, extent, LSFG_FLOW_SCALE);
|
||||
built_extent = extent;
|
||||
frame_count = 0;
|
||||
size_t FrameGen::WantedGenerations() const {
|
||||
if (unavailable || !Settings::values.frame_gen.GetValue()) {
|
||||
return 0;
|
||||
}
|
||||
return ConfiguredGenerations();
|
||||
}
|
||||
|
||||
void FrameGen::DumpFlowPyramid(const Device& device) {
|
||||
size_t FrameGen::GeneratedFrameCount() const {
|
||||
return generated && chain ? chain->GenerationCount() : 0;
|
||||
}
|
||||
|
||||
void FrameGen::CopyToFrame(Frame* destination, size_t generation) {
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([this, target = *destination->image,
|
||||
generation](vk::CommandBuffer cmdbuf) {
|
||||
LsfgImage& source = chain->Output(generation);
|
||||
const VkExtent2D extent = source.Extent();
|
||||
|
||||
const std::array before{
|
||||
MakeTransitionBarrier(source.Handle(), VK_ACCESS_SHADER_WRITE_BIT,
|
||||
VK_ACCESS_TRANSFER_READ_BIT, source.Layout(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL),
|
||||
MakeTransitionBarrier(target, 0, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL),
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, {}, before);
|
||||
|
||||
cmdbuf.CopyImage(source.Handle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, target,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, MakeCopyRegion(extent));
|
||||
|
||||
const std::array after{
|
||||
MakeTransitionBarrier(source.Handle(), VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_ACCESS_SHADER_WRITE_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_GENERAL),
|
||||
MakeTransitionBarrier(target, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_GENERAL),
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
0, {}, {}, after);
|
||||
|
||||
source.SetLayout(VK_IMAGE_LAYOUT_GENERAL);
|
||||
});
|
||||
}
|
||||
|
||||
void FrameGen::Rebuild(const Device& device, VkExtent2D extent, VkFormat format) {
|
||||
scheduler.Finish();
|
||||
chain.reset();
|
||||
|
||||
built_flow_scale = ConfiguredFlowScale();
|
||||
built_generations = ConfiguredGenerations();
|
||||
|
||||
chain.emplace(device, memory_allocator, *shaders, extent, format, built_flow_scale,
|
||||
built_generations);
|
||||
built_extent = extent;
|
||||
built_format = format;
|
||||
frame_count = 0;
|
||||
generated = false;
|
||||
}
|
||||
|
||||
void FrameGen::DumpDebugImages(u64 count) {
|
||||
const std::filesystem::path directory =
|
||||
Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / "debug";
|
||||
if (!Common::FS::CreateDirs(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t level = 0; level < LSFG_MIP_LEVELS; ++level) {
|
||||
const VkExtent2D extent = mipmaps->GetLevelExtent(level);
|
||||
const VkDeviceSize size = static_cast<VkDeviceSize>(extent.width) * extent.height;
|
||||
vk::Buffer readback = CreateWrappedBuffer(memory_allocator, size, MemoryUsage::Download);
|
||||
const auto dump = [&](const std::string& name, LsfgImage& image) {
|
||||
const VkExtent2D extent = image.Extent();
|
||||
const VkFormat format = image.Format();
|
||||
const VkDeviceSize texel_size = BytesPerTexel(format);
|
||||
const VkDeviceSize size =
|
||||
static_cast<VkDeviceSize>(extent.width) * extent.height * texel_size;
|
||||
|
||||
vk::Buffer buffer = CreateWrappedBuffer(memory_allocator, size, MemoryUsage::Download);
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([image = mipmaps->GetLevelImage(level), dst = *readback,
|
||||
extent](vk::CommandBuffer cmdbuf) {
|
||||
DownloadColorImage(cmdbuf, image, dst,
|
||||
VkExtent3D{.width = extent.width, .height = extent.height,
|
||||
.depth = 1});
|
||||
});
|
||||
scheduler.Record(
|
||||
[handle = image.Handle(), dst = *buffer, extent](vk::CommandBuffer cmdbuf) {
|
||||
DownloadColorImage(
|
||||
cmdbuf, handle, dst,
|
||||
VkExtent3D{.width = extent.width, .height = extent.height, .depth = 1});
|
||||
});
|
||||
scheduler.Finish();
|
||||
|
||||
readback.Invalidate();
|
||||
WriteGrayscalePgm(directory / ("flow_mip" + std::to_string(level) + ".pgm"), extent,
|
||||
readback.Mapped());
|
||||
buffer.Invalidate();
|
||||
const std::span<u8> mapped = buffer.Mapped();
|
||||
|
||||
if (format == LSFG_FLOW_FORMAT) {
|
||||
WriteGrayscalePgm(directory / (name + ".pgm"), extent, mapped);
|
||||
} else if (texel_size == COLOR_CHANNELS) {
|
||||
WriteColorPpm(directory / (name + ".ppm"), extent, mapped, IsBlueFirst(format));
|
||||
} else {
|
||||
WriteRaw(directory / (name + "_" + std::to_string(extent.width) + "x" +
|
||||
std::to_string(extent.height) + ".f16"),
|
||||
mapped.subspan(0, std::min<size_t>(size, mapped.size())));
|
||||
}
|
||||
};
|
||||
|
||||
dump("in0", chain->Input(0));
|
||||
dump("in1", chain->Input(1));
|
||||
|
||||
for (size_t level = 0; level < LSFG_MIP_LEVELS; ++level) {
|
||||
dump("flow_mip" + std::to_string(level), chain->FlowLevel(level));
|
||||
}
|
||||
for (size_t index = 0; index < 2; ++index) {
|
||||
dump("alpha0_" + std::to_string(index), chain->AlphaOutput(0, count, index));
|
||||
dump("alpha6_" + std::to_string(index), chain->AlphaOutput(LSFG_MIP_LEVELS - 1, count,
|
||||
index));
|
||||
}
|
||||
for (size_t level = 0; level < LSFG_BETA_OUTPUTS; ++level) {
|
||||
dump("beta_" + std::to_string(level), chain->BetaOutput(level));
|
||||
}
|
||||
|
||||
dump("gamma0", chain->GammaOutput(0));
|
||||
dump("gamma6", chain->GammaOutput(LSFG_MIP_LEVELS - 1));
|
||||
dump("delta2_out1", chain->DeltaOutput1(LSFG_DELTA_INSTANCES - 1));
|
||||
dump("delta2_out2", chain->DeltaOutput2(LSFG_DELTA_INSTANCES - 1));
|
||||
for (size_t generation = 0; generation < chain->GenerationCount(); ++generation) {
|
||||
dump("generated" + std::to_string(generation), chain->Output(generation));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <optional>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_mipmaps.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_chain.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
|
||||
@@ -21,19 +21,29 @@ public:
|
||||
explicit FrameGen(MemoryAllocator& memory_allocator, Scheduler& scheduler);
|
||||
~FrameGen();
|
||||
|
||||
void Process(const Device& device, Frame* frame);
|
||||
void Process(const Device& device, Frame* frame, VkFormat format, bool generate);
|
||||
|
||||
[[nodiscard]] size_t WantedGenerations() const;
|
||||
|
||||
[[nodiscard]] size_t GeneratedFrameCount() const;
|
||||
|
||||
void CopyToFrame(Frame* destination, size_t generation);
|
||||
|
||||
private:
|
||||
void Rebuild(const Device& device, VkExtent2D extent);
|
||||
void DumpFlowPyramid(const Device& device);
|
||||
void Rebuild(const Device& device, VkExtent2D extent, VkFormat format);
|
||||
void DumpDebugImages(u64 count);
|
||||
|
||||
MemoryAllocator& memory_allocator;
|
||||
Scheduler& scheduler;
|
||||
|
||||
std::optional<LsfgShaders> shaders;
|
||||
std::optional<LsfgMipmaps> mipmaps;
|
||||
std::optional<LsfgChain> chain;
|
||||
VkExtent2D built_extent{};
|
||||
VkFormat built_format{VK_FORMAT_UNDEFINED};
|
||||
f32 built_flow_scale{};
|
||||
size_t built_generations{};
|
||||
u64 frame_count{};
|
||||
bool generated{};
|
||||
bool unavailable{};
|
||||
bool dumped{};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_alpha.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DISPATCH_TILE_SHIFT = 3;
|
||||
|
||||
[[nodiscard]] u32 GroupCount(u32 size) {
|
||||
return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkExtent2D HalveExtent(VkExtent2D extent) {
|
||||
return VkExtent2D{
|
||||
.width = (extent.width + 1) >> 1,
|
||||
.height = (extent.height + 1) >> 1,
|
||||
};
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgAlpha::LsfgAlpha(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImage& input_)
|
||||
: input{&input_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
passes[0] = LsfgPass(device, shaders, ALPHA[0],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[1] = LsfgPass(device, shaders, ALPHA[1],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[2] = LsfgPass(device, shaders, ALPHA[2],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[3] = LsfgPass(device, shaders, ALPHA[3],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
|
||||
const VkExtent2D half_extent = HalveExtent(input->Extent());
|
||||
const VkExtent2D quarter_extent = HalveExtent(half_extent);
|
||||
|
||||
temp1 = LsfgImage(device, memory_allocator, half_extent);
|
||||
temp2 = LsfgImage(device, memory_allocator, half_extent);
|
||||
for (size_t i = 0; i < temp3.size(); ++i) {
|
||||
temp3[i] = LsfgImage(device, memory_allocator, quarter_extent);
|
||||
for (size_t j = 0; j < LSFG_HISTORY_SLOTS; ++j) {
|
||||
out_images[j][i] = LsfgImage(device, memory_allocator, quarter_extent);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<VkDescriptorSetLayout> layouts;
|
||||
for (size_t i = 0; i < LSFG_ALPHA_STAGES - 1; ++i) {
|
||||
layouts.push_back(passes[i].SetLayout());
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
layouts.push_back(passes[3].SetLayout());
|
||||
}
|
||||
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
for (size_t i = 0; i < LSFG_ALPHA_STAGES - 1; ++i) {
|
||||
descriptor_sets[i] = owned_sets[i];
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
last_descriptor_sets[i] = owned_sets[LSFG_ALPHA_STAGES - 1 + i];
|
||||
}
|
||||
|
||||
const VkSampler sampler = resources.GetSampler();
|
||||
|
||||
LsfgDescriptorWriter(descriptor_sets[0])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(*input)
|
||||
.AddStorageImage(temp1)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(descriptor_sets[1])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp1)
|
||||
.AddStorageImage(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(descriptor_sets[2])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp2)
|
||||
.AddStorageImages(temp3)
|
||||
.Build(device);
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
LsfgDescriptorWriter(last_descriptor_sets[i])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp3)
|
||||
.AddStorageImages(out_images[i])
|
||||
.Build(device);
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgAlpha::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
|
||||
const VkExtent2D half_extent = temp1.Extent();
|
||||
u32 groups_x = GroupCount(half_extent.width);
|
||||
u32 groups_y = GroupCount(half_extent.height);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToRead(*input).ReadToWrite(temp1).Build();
|
||||
passes[0].Bind(cmdbuf, descriptor_sets[0]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToRead(temp1).ReadToWrite(temp2).Build();
|
||||
passes[1].Bind(cmdbuf, descriptor_sets[1]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
const VkExtent2D quarter_extent = temp3[0].Extent();
|
||||
groups_x = GroupCount(quarter_extent.width);
|
||||
groups_y = GroupCount(quarter_extent.height);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToRead(temp2).ReadToWriteAll(temp3).Build();
|
||||
passes[2].Bind(cmdbuf, descriptor_sets[2]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
const size_t slot = frame_count % LSFG_HISTORY_SLOTS;
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp3).ReadToWriteAll(out_images[slot]).Build();
|
||||
passes[3].Bind(cmdbuf, last_descriptor_sets[slot]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,44 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
constexpr size_t LSFG_ALPHA_STAGES = 4;
|
||||
|
||||
class LsfgAlpha {
|
||||
public:
|
||||
LsfgAlpha() = default;
|
||||
LsfgAlpha(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
LsfgResources& resources, vk::DescriptorPool& descriptor_pool, LsfgImage& input);
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count);
|
||||
|
||||
[[nodiscard]] LsfgImageHistory& Outputs() {
|
||||
return out_images;
|
||||
}
|
||||
|
||||
private:
|
||||
LsfgImage* input{};
|
||||
|
||||
std::array<LsfgPass, LSFG_ALPHA_STAGES> passes;
|
||||
std::array<VkDescriptorSet, LSFG_ALPHA_STAGES - 1> descriptor_sets{};
|
||||
std::array<VkDescriptorSet, LSFG_HISTORY_SLOTS> last_descriptor_sets{};
|
||||
vk::DescriptorSets owned_sets;
|
||||
|
||||
LsfgImage temp1;
|
||||
LsfgImage temp2;
|
||||
LsfgImagePair temp3;
|
||||
LsfgImageHistory out_images;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,144 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_beta.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DISPATCH_TILE_SHIFT = 3;
|
||||
constexpr u32 OUTPUT_TILE_SHIFT = 5;
|
||||
|
||||
[[nodiscard]] u32 GroupCount(u32 size, u32 shift) {
|
||||
return (size + (1u << shift) - 1) >> shift;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgBeta::LsfgBeta(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImageHistory& inputs_)
|
||||
: inputs{&inputs_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
passes[0] = LsfgPass(device, shaders, BETA[0],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
for (size_t i = 1; i < LSFG_BETA_STAGES - 1; ++i) {
|
||||
passes[i] = LsfgPass(device, shaders, BETA[i],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
}
|
||||
passes[4] = LsfgPass(device, shaders, BETA[4],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
|
||||
const VkExtent2D extent = (*inputs)[0][0].Extent();
|
||||
for (size_t i = 0; i < temp1.size(); ++i) {
|
||||
temp1[i] = LsfgImage(device, memory_allocator, extent);
|
||||
temp2[i] = LsfgImage(device, memory_allocator, extent);
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_BETA_OUTPUTS; ++i) {
|
||||
const VkExtent2D level_extent{
|
||||
.width = extent.width >> i,
|
||||
.height = extent.height >> i,
|
||||
};
|
||||
out_images[i] = LsfgImage(device, memory_allocator, level_extent, LSFG_FLOW_FORMAT);
|
||||
}
|
||||
|
||||
std::vector<VkDescriptorSetLayout> layouts;
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
layouts.push_back(passes[0].SetLayout());
|
||||
}
|
||||
for (size_t i = 1; i < LSFG_BETA_STAGES; ++i) {
|
||||
layouts.push_back(passes[i].SetLayout());
|
||||
}
|
||||
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
first_descriptor_sets[i] = owned_sets[i];
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_BETA_STAGES - 1; ++i) {
|
||||
descriptor_sets[i] = owned_sets[LSFG_HISTORY_SLOTS + i];
|
||||
}
|
||||
|
||||
const VkSampler sampler = resources.GetSampler();
|
||||
const VkSampler border_sampler = resources.GetSampler(
|
||||
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
LsfgDescriptorWriter(first_descriptor_sets[i])
|
||||
.AddSampler(border_sampler)
|
||||
.AddSampledImages((*inputs)[(i + 1) % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS])
|
||||
.AddStorageImages(temp1)
|
||||
.Build(device);
|
||||
}
|
||||
LsfgDescriptorWriter(descriptor_sets[0])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp1)
|
||||
.AddStorageImages(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(descriptor_sets[1])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp2)
|
||||
.AddStorageImages(temp1)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(descriptor_sets[2])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp1)
|
||||
.AddStorageImages(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(descriptor_sets[3])
|
||||
.AddUniformBuffer(resources.GetBuffer(0.5f), LsfgResources::BufferSize())
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp2)
|
||||
.AddStorageImages(out_images)
|
||||
.Build(device);
|
||||
}
|
||||
|
||||
void LsfgBeta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
|
||||
const VkExtent2D extent = temp1[0].Extent();
|
||||
const u32 groups_x = GroupCount(extent.width, DISPATCH_TILE_SHIFT);
|
||||
const u32 groups_y = GroupCount(extent.height, DISPATCH_TILE_SHIFT);
|
||||
|
||||
LsfgBarriers barriers(cmdbuf);
|
||||
for (auto& slot : *inputs) {
|
||||
barriers.WriteToReadAll(slot);
|
||||
}
|
||||
barriers.ReadToWriteAll(temp1).Build();
|
||||
|
||||
passes[0].Bind(cmdbuf, first_descriptor_sets[frame_count % LSFG_HISTORY_SLOTS]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
|
||||
passes[1].Bind(cmdbuf, descriptor_sets[0]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(temp1).Build();
|
||||
passes[2].Bind(cmdbuf, descriptor_sets[1]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
|
||||
passes[3].Bind(cmdbuf, descriptor_sets[2]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(out_images).Build();
|
||||
passes[4].Bind(cmdbuf, descriptor_sets[3]);
|
||||
cmdbuf.Dispatch(GroupCount(extent.width, OUTPUT_TILE_SHIFT),
|
||||
GroupCount(extent.height, OUTPUT_TILE_SHIFT), 1);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
constexpr size_t LSFG_BETA_STAGES = 5;
|
||||
constexpr size_t LSFG_BETA_OUTPUTS = 6;
|
||||
|
||||
class LsfgBeta {
|
||||
public:
|
||||
LsfgBeta() = default;
|
||||
LsfgBeta(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
|
||||
LsfgImageHistory& inputs);
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count);
|
||||
|
||||
[[nodiscard]] LsfgImage& Output(size_t level) {
|
||||
return out_images[level];
|
||||
}
|
||||
|
||||
private:
|
||||
LsfgImageHistory* inputs{};
|
||||
|
||||
std::array<LsfgPass, LSFG_BETA_STAGES> passes;
|
||||
std::array<VkDescriptorSet, LSFG_HISTORY_SLOTS> first_descriptor_sets{};
|
||||
std::array<VkDescriptorSet, LSFG_BETA_STAGES - 1> descriptor_sets{};
|
||||
vk::DescriptorSets owned_sets;
|
||||
|
||||
LsfgImagePair temp1;
|
||||
LsfgImagePair temp2;
|
||||
std::array<LsfgImage, LSFG_BETA_OUTPUTS> out_images;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,88 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "video_core/renderer_vulkan/present/lsfg_chain.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 FIXED_DESCRIPTOR_SETS = 64;
|
||||
constexpr u32 DESCRIPTOR_SETS_PER_GENERATION = 96;
|
||||
constexpr size_t FIRST_DELTA_LEVEL = 4;
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgChain::LsfgChain(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, VkExtent2D extent, VkFormat format,
|
||||
f32 flow_scale, size_t generation_count_)
|
||||
: generation_count{generation_count_},
|
||||
resources{device, memory_allocator, flow_scale},
|
||||
descriptor_pool{CreateLsfgDescriptorPool(
|
||||
device, FIXED_DESCRIPTOR_SETS +
|
||||
DESCRIPTOR_SETS_PER_GENERATION * static_cast<u32>(generation_count))} {
|
||||
for (auto& image : frames) {
|
||||
image = LsfgImage(device, memory_allocator, extent, format);
|
||||
}
|
||||
|
||||
mipmaps = LsfgMipmaps(device, memory_allocator, shaders, resources, descriptor_pool, frames,
|
||||
flow_scale);
|
||||
|
||||
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||
alpha[i] = LsfgAlpha(device, memory_allocator, shaders, resources, descriptor_pool,
|
||||
mipmaps.Output(i));
|
||||
}
|
||||
|
||||
beta = LsfgBeta(device, memory_allocator, shaders, resources, descriptor_pool,
|
||||
alpha[0].Outputs());
|
||||
|
||||
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||
const size_t level = LSFG_MIP_LEVELS - 1 - i;
|
||||
gamma[i] = LsfgGamma(device, memory_allocator, shaders, resources, descriptor_pool,
|
||||
alpha[level].Outputs(),
|
||||
beta.Output(std::min(level, LSFG_BETA_OUTPUTS - 1)),
|
||||
i == 0 ? nullptr : &gamma[i - 1].Output(), generation_count);
|
||||
|
||||
if (i < FIRST_DELTA_LEVEL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t index = i - FIRST_DELTA_LEVEL;
|
||||
delta[index] = LsfgDelta(
|
||||
device, memory_allocator, shaders, resources, descriptor_pool, alpha[level].Outputs(),
|
||||
beta.Output(level), i == FIRST_DELTA_LEVEL ? nullptr : &gamma[i - 1].Output(),
|
||||
i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output1(),
|
||||
i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output2(), generation_count);
|
||||
}
|
||||
|
||||
generate = LsfgGenerate(device, memory_allocator, shaders, resources, descriptor_pool, frames,
|
||||
gamma[LSFG_MIP_LEVELS - 1].Output(),
|
||||
delta[LSFG_DELTA_INSTANCES - 1].Output1(),
|
||||
delta[LSFG_DELTA_INSTANCES - 1].Output2(), format, generation_count);
|
||||
}
|
||||
|
||||
void LsfgChain::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
|
||||
mipmaps.Dispatch(cmdbuf, frame_count);
|
||||
|
||||
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||
alpha[LSFG_MIP_LEVELS - 1 - i].Dispatch(cmdbuf, frame_count);
|
||||
}
|
||||
|
||||
beta.Dispatch(cmdbuf, frame_count);
|
||||
|
||||
for (size_t generation = 0; generation < generation_count; ++generation) {
|
||||
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||
gamma[i].Dispatch(cmdbuf, frame_count, generation);
|
||||
if (i >= FIRST_DELTA_LEVEL) {
|
||||
delta[i - FIRST_DELTA_LEVEL].Dispatch(cmdbuf, frame_count, generation);
|
||||
}
|
||||
}
|
||||
generate.Dispatch(cmdbuf, frame_count, generation);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,84 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_alpha.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_beta.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_delta.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_gamma.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_generate.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_mipmaps.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
constexpr size_t LSFG_DELTA_INSTANCES = 3;
|
||||
|
||||
class LsfgChain {
|
||||
public:
|
||||
LsfgChain(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
VkExtent2D extent, VkFormat format, f32 flow_scale, size_t generation_count_);
|
||||
|
||||
LsfgChain(const LsfgChain&) = delete;
|
||||
LsfgChain& operator=(const LsfgChain&) = delete;
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count);
|
||||
|
||||
[[nodiscard]] LsfgImage& Input(u64 frame_count) {
|
||||
return frames[frame_count % frames.size()];
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& Output(size_t generation) {
|
||||
return generate.Output(generation);
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t GenerationCount() const {
|
||||
return generation_count;
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& FlowLevel(size_t level) {
|
||||
return mipmaps.Output(level);
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& AlphaOutput(size_t level, u64 frame_count, size_t index) {
|
||||
return alpha[level].Outputs()[frame_count % LSFG_HISTORY_SLOTS][index];
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& BetaOutput(size_t level) {
|
||||
return beta.Output(level);
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& GammaOutput(size_t index) {
|
||||
return gamma[index].Output();
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& DeltaOutput1(size_t index) {
|
||||
return delta[index].Output1();
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& DeltaOutput2(size_t index) {
|
||||
return delta[index].Output2();
|
||||
}
|
||||
|
||||
private:
|
||||
size_t generation_count{};
|
||||
LsfgResources resources;
|
||||
vk::DescriptorPool descriptor_pool;
|
||||
|
||||
LsfgImagePair frames;
|
||||
LsfgMipmaps mipmaps;
|
||||
std::array<LsfgAlpha, LSFG_MIP_LEVELS> alpha;
|
||||
LsfgBeta beta;
|
||||
std::array<LsfgGamma, LSFG_MIP_LEVELS> gamma;
|
||||
std::array<LsfgDelta, LSFG_DELTA_INSTANCES> delta;
|
||||
LsfgGenerate generate;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,302 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DESCRIPTORS_PER_TYPE = 4096;
|
||||
|
||||
struct LsfgConstants {
|
||||
std::array<u32, 2> input_offset;
|
||||
u32 first_iter;
|
||||
u32 first_iter_s;
|
||||
u32 advanced_color_kind;
|
||||
u32 hdr_support;
|
||||
f32 resolution_inv_scale;
|
||||
f32 timestamp;
|
||||
f32 ui_threshold;
|
||||
std::array<u32, 3> padding;
|
||||
};
|
||||
static_assert(sizeof(LsfgConstants) == 48);
|
||||
|
||||
vk::Image CreateChainImage(MemoryAllocator& memory_allocator, VkExtent2D extent, VkFormat format) {
|
||||
const VkImageCreateInfo image_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = format,
|
||||
.extent = {.width = extent.width, .height = extent.height, .depth = 1},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
};
|
||||
return memory_allocator.CreateImage(image_ci);
|
||||
}
|
||||
|
||||
vk::Buffer CreateUniformBuffer(MemoryAllocator& memory_allocator, VkDeviceSize size) {
|
||||
const VkBufferCreateInfo buffer_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.size = size,
|
||||
.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
};
|
||||
return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::Upload);
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier MakeBarrier(const LsfgImage& image, VkAccessFlags src_access,
|
||||
VkAccessFlags dst_access) {
|
||||
return VkImageMemoryBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = src_access,
|
||||
.dstAccessMask = dst_access,
|
||||
.oldLayout = image.Layout(),
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image.Handle(),
|
||||
.subresourceRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgImage::LsfgImage(const Device& device, MemoryAllocator& memory_allocator, VkExtent2D extent_,
|
||||
VkFormat format_)
|
||||
: extent{std::max(1u, extent_.width), std::max(1u, extent_.height)}, format{format_} {
|
||||
image = CreateChainImage(memory_allocator, extent, format);
|
||||
view = CreateWrappedImageView(device, image, format);
|
||||
}
|
||||
|
||||
LsfgBarriers& LsfgBarriers::Push(LsfgImage& image, VkAccessFlags src_access,
|
||||
VkAccessFlags dst_access) {
|
||||
barriers.push_back(MakeBarrier(image, src_access, dst_access));
|
||||
image.SetLayout(VK_IMAGE_LAYOUT_GENERAL);
|
||||
return *this;
|
||||
}
|
||||
|
||||
LsfgBarriers& LsfgBarriers::WriteToRead(LsfgImage& image) {
|
||||
return Push(image, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
|
||||
}
|
||||
|
||||
LsfgBarriers& LsfgBarriers::ReadToWrite(LsfgImage& image) {
|
||||
return Push(image, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT);
|
||||
}
|
||||
|
||||
LsfgBarriers& LsfgBarriers::WriteToRead(LsfgImage* image) {
|
||||
return image == nullptr ? *this : WriteToRead(*image);
|
||||
}
|
||||
|
||||
LsfgBarriers& LsfgBarriers::ReadToWrite(LsfgImage* image) {
|
||||
return image == nullptr ? *this : ReadToWrite(*image);
|
||||
}
|
||||
|
||||
VkDeviceSize LsfgResources::BufferSize() {
|
||||
return sizeof(LsfgConstants);
|
||||
}
|
||||
|
||||
VkSampler LsfgResources::GetSampler(VkSamplerAddressMode address_mode, VkCompareOp compare_op,
|
||||
bool white_border) {
|
||||
const u64 key = static_cast<u64>(address_mode) | (static_cast<u64>(compare_op) << 8) |
|
||||
(static_cast<u64>(white_border) << 16);
|
||||
|
||||
const auto it = samplers.find(key);
|
||||
if (it != samplers.end()) {
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
const auto [entry, inserted] =
|
||||
samplers.emplace(key, CreateLsfgSampler(*device, address_mode, compare_op, white_border));
|
||||
return *entry->second;
|
||||
}
|
||||
|
||||
VkBuffer LsfgResources::GetBuffer(f32 timestamp, bool first_iter, bool first_iter_s) {
|
||||
u32 timestamp_bits{};
|
||||
std::memcpy(×tamp_bits, ×tamp, sizeof(timestamp_bits));
|
||||
const u64 key = static_cast<u64>(timestamp_bits) | (static_cast<u64>(first_iter) << 32) |
|
||||
(static_cast<u64>(first_iter_s) << 33);
|
||||
|
||||
const auto it = buffers.find(key);
|
||||
if (it != buffers.end()) {
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
vk::Buffer buffer = CreateUniformBuffer(*memory_allocator, sizeof(LsfgConstants));
|
||||
|
||||
const LsfgConstants constants{
|
||||
.input_offset = {0, 0},
|
||||
.first_iter = first_iter ? 1u : 0u,
|
||||
.first_iter_s = first_iter_s ? 1u : 0u,
|
||||
.advanced_color_kind = 0,
|
||||
.hdr_support = 0,
|
||||
.resolution_inv_scale = 1.0f / flow_scale,
|
||||
.timestamp = timestamp,
|
||||
.ui_threshold = 0.5f,
|
||||
.padding = {0, 0, 0},
|
||||
};
|
||||
|
||||
const std::span<u8> mapped = buffer.Mapped();
|
||||
std::memcpy(mapped.data(), &constants, sizeof(constants));
|
||||
buffer.Flush();
|
||||
|
||||
const auto [entry, inserted] = buffers.emplace(key, std::move(buffer));
|
||||
return *entry->second;
|
||||
}
|
||||
|
||||
void LsfgBarriers::Build() {
|
||||
if (barriers.empty()) {
|
||||
return;
|
||||
}
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, {}, {}, barriers);
|
||||
barriers.clear();
|
||||
}
|
||||
|
||||
LsfgDescriptorWriter& LsfgDescriptorWriter::PushImage(VkDescriptorType type, VkSampler sampler,
|
||||
VkImageView view) {
|
||||
image_infos.push_back(VkDescriptorImageInfo{
|
||||
.sampler = sampler,
|
||||
.imageView = view,
|
||||
.imageLayout = view == VK_NULL_HANDLE ? VK_IMAGE_LAYOUT_UNDEFINED
|
||||
: VK_IMAGE_LAYOUT_GENERAL,
|
||||
});
|
||||
writes.push_back(VkWriteDescriptorSet{
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = set,
|
||||
.dstBinding = binding++,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = type,
|
||||
.pImageInfo = &image_infos.back(),
|
||||
.pBufferInfo = nullptr,
|
||||
.pTexelBufferView = nullptr,
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampler(VkSampler sampler) {
|
||||
return PushImage(VK_DESCRIPTOR_TYPE_SAMPLER, sampler, VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampledImage(const LsfgImage& image) {
|
||||
return PushImage(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_NULL_HANDLE, image.View());
|
||||
}
|
||||
|
||||
LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampledImage(const LsfgImage* image) {
|
||||
return PushImage(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_NULL_HANDLE,
|
||||
image == nullptr ? VK_NULL_HANDLE : image->View());
|
||||
}
|
||||
|
||||
LsfgDescriptorWriter& LsfgDescriptorWriter::AddStorageImage(const LsfgImage& image) {
|
||||
return PushImage(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_NULL_HANDLE, image.View());
|
||||
}
|
||||
|
||||
LsfgDescriptorWriter& LsfgDescriptorWriter::AddUniformBuffer(VkBuffer buffer, VkDeviceSize size) {
|
||||
buffer_infos.push_back(VkDescriptorBufferInfo{
|
||||
.buffer = buffer,
|
||||
.offset = 0,
|
||||
.range = size,
|
||||
});
|
||||
writes.push_back(VkWriteDescriptorSet{
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = set,
|
||||
.dstBinding = binding++,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
.pImageInfo = nullptr,
|
||||
.pBufferInfo = &buffer_infos.back(),
|
||||
.pTexelBufferView = nullptr,
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
void LsfgDescriptorWriter::Build(const Device& device) {
|
||||
if (writes.empty()) {
|
||||
return;
|
||||
}
|
||||
device.GetLogical().UpdateDescriptorSets(writes, {});
|
||||
writes.clear();
|
||||
}
|
||||
|
||||
LsfgPass::LsfgPass(const Device& device, const LsfgShaders& shaders, u32 shader_id,
|
||||
LsfgBindings bindings) {
|
||||
std::vector<VkDescriptorType> types;
|
||||
for (const auto& [count, type] : bindings) {
|
||||
types.insert(types.end(), count, type);
|
||||
}
|
||||
descriptor_count = static_cast<u32>(types.size());
|
||||
|
||||
descriptor_set_layout = CreateWrappedDescriptorSetLayout(
|
||||
device, std::span<const VkDescriptorType>{types}, VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
pipeline_layout = CreateWrappedPipelineLayout(device, descriptor_set_layout);
|
||||
pipeline = CreateWrappedComputePipeline(device, pipeline_layout, shaders.Get(shader_id));
|
||||
}
|
||||
|
||||
void LsfgPass::Bind(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const {
|
||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0, set, {});
|
||||
}
|
||||
|
||||
vk::DescriptorPool CreateLsfgDescriptorPool(const Device& device, u32 max_sets) {
|
||||
return CreateWrappedDescriptorPool(
|
||||
device, DESCRIPTORS_PER_TYPE, max_sets,
|
||||
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_SAMPLER,
|
||||
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE});
|
||||
}
|
||||
|
||||
vk::Sampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode,
|
||||
VkCompareOp compare_op, bool white_border) {
|
||||
return device.GetLogical().CreateSampler(VkSamplerCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.magFilter = VK_FILTER_LINEAR,
|
||||
.minFilter = VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
.addressModeU = address_mode,
|
||||
.addressModeV = address_mode,
|
||||
.addressModeW = address_mode,
|
||||
.mipLodBias = 0.0f,
|
||||
.anisotropyEnable = VK_FALSE,
|
||||
.maxAnisotropy = 0.0f,
|
||||
.compareEnable = VK_FALSE,
|
||||
.compareOp = compare_op,
|
||||
.minLod = 0.0f,
|
||||
.maxLod = VK_LOD_CLAMP_NONE,
|
||||
.borderColor = white_border ? VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE
|
||||
: VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
|
||||
.unnormalizedCoordinates = VK_FALSE,
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,202 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
constexpr VkFormat LSFG_DEFAULT_FORMAT = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
constexpr VkFormat LSFG_FLOW_FORMAT = VK_FORMAT_R8_UNORM;
|
||||
constexpr VkFormat LSFG_MOTION_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
|
||||
constexpr size_t LSFG_HISTORY_SLOTS = 3;
|
||||
constexpr size_t LSFG_MIN_MULTIPLIER = 2;
|
||||
constexpr size_t LSFG_MAX_MULTIPLIER = 4;
|
||||
constexpr size_t LSFG_MAX_GENERATIONS = LSFG_MAX_MULTIPLIER - 1;
|
||||
|
||||
[[nodiscard]] constexpr f32 LsfgTimestamp(size_t generation, size_t generation_count) {
|
||||
return static_cast<f32>(generation + 1) / static_cast<f32>(generation_count + 1);
|
||||
}
|
||||
|
||||
class LsfgImage {
|
||||
public:
|
||||
LsfgImage() = default;
|
||||
LsfgImage(const Device& device, MemoryAllocator& memory_allocator, VkExtent2D extent_,
|
||||
VkFormat format = LSFG_DEFAULT_FORMAT);
|
||||
|
||||
[[nodiscard]] VkImage Handle() const {
|
||||
return *image;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkImageView View() const {
|
||||
return *view;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkExtent2D Extent() const {
|
||||
return extent;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkFormat Format() const {
|
||||
return format;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkImageLayout Layout() const {
|
||||
return layout;
|
||||
}
|
||||
|
||||
void SetLayout(VkImageLayout new_layout) {
|
||||
layout = new_layout;
|
||||
}
|
||||
|
||||
private:
|
||||
vk::Image image;
|
||||
vk::ImageView view;
|
||||
VkExtent2D extent{};
|
||||
VkFormat format{VK_FORMAT_UNDEFINED};
|
||||
VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED};
|
||||
};
|
||||
|
||||
using LsfgImagePair = std::array<LsfgImage, 2>;
|
||||
using LsfgImageHistory = std::array<LsfgImagePair, LSFG_HISTORY_SLOTS>;
|
||||
|
||||
class LsfgResources {
|
||||
public:
|
||||
LsfgResources() = default;
|
||||
LsfgResources(const Device& device_, MemoryAllocator& memory_allocator_, f32 flow_scale_)
|
||||
: device{&device_}, memory_allocator{&memory_allocator_}, flow_scale{flow_scale_} {}
|
||||
|
||||
[[nodiscard]] VkSampler GetSampler(
|
||||
VkSamplerAddressMode address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||
VkCompareOp compare_op = VK_COMPARE_OP_NEVER, bool white_border = false);
|
||||
|
||||
[[nodiscard]] VkBuffer GetBuffer(f32 timestamp = 0.0f, bool first_iter = false,
|
||||
bool first_iter_s = false);
|
||||
|
||||
[[nodiscard]] static VkDeviceSize BufferSize();
|
||||
|
||||
private:
|
||||
const Device* device{};
|
||||
MemoryAllocator* memory_allocator{};
|
||||
f32 flow_scale{1.0f};
|
||||
|
||||
std::map<u64, vk::Sampler> samplers;
|
||||
std::map<u64, vk::Buffer> buffers;
|
||||
};
|
||||
|
||||
class LsfgBarriers {
|
||||
public:
|
||||
explicit LsfgBarriers(vk::CommandBuffer cmdbuf_) : cmdbuf{cmdbuf_} {}
|
||||
|
||||
LsfgBarriers& WriteToRead(LsfgImage& image);
|
||||
LsfgBarriers& ReadToWrite(LsfgImage& image);
|
||||
LsfgBarriers& WriteToRead(LsfgImage* image);
|
||||
LsfgBarriers& ReadToWrite(LsfgImage* image);
|
||||
|
||||
template <typename Range>
|
||||
LsfgBarriers& WriteToReadAll(Range& images) {
|
||||
for (auto& image : images) {
|
||||
WriteToRead(image);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
LsfgBarriers& ReadToWriteAll(Range& images) {
|
||||
for (auto& image : images) {
|
||||
ReadToWrite(image);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Build();
|
||||
|
||||
private:
|
||||
LsfgBarriers& Push(LsfgImage& image, VkAccessFlags src_access, VkAccessFlags dst_access);
|
||||
|
||||
vk::CommandBuffer cmdbuf;
|
||||
std::vector<VkImageMemoryBarrier> barriers;
|
||||
};
|
||||
|
||||
class LsfgDescriptorWriter {
|
||||
public:
|
||||
explicit LsfgDescriptorWriter(VkDescriptorSet set_) : set{set_} {}
|
||||
|
||||
LsfgDescriptorWriter& AddSampler(VkSampler sampler);
|
||||
LsfgDescriptorWriter& AddSampledImage(const LsfgImage& image);
|
||||
LsfgDescriptorWriter& AddSampledImage(const LsfgImage* image);
|
||||
LsfgDescriptorWriter& AddStorageImage(const LsfgImage& image);
|
||||
LsfgDescriptorWriter& AddUniformBuffer(VkBuffer buffer, VkDeviceSize size);
|
||||
|
||||
template <typename Range>
|
||||
LsfgDescriptorWriter& AddSampledImages(const Range& images) {
|
||||
for (const auto& image : images) {
|
||||
AddSampledImage(image);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
LsfgDescriptorWriter& AddStorageImages(const Range& images) {
|
||||
for (const auto& image : images) {
|
||||
AddStorageImage(image);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Build(const Device& device);
|
||||
|
||||
private:
|
||||
LsfgDescriptorWriter& PushImage(VkDescriptorType type, VkSampler sampler, VkImageView view);
|
||||
|
||||
VkDescriptorSet set;
|
||||
u32 binding{};
|
||||
std::deque<VkDescriptorImageInfo> image_infos;
|
||||
std::deque<VkDescriptorBufferInfo> buffer_infos;
|
||||
std::vector<VkWriteDescriptorSet> writes;
|
||||
};
|
||||
|
||||
using LsfgBindings = std::initializer_list<std::pair<u32, VkDescriptorType>>;
|
||||
|
||||
class LsfgPass {
|
||||
public:
|
||||
LsfgPass() = default;
|
||||
LsfgPass(const Device& device, const LsfgShaders& shaders, u32 shader_id,
|
||||
LsfgBindings bindings);
|
||||
|
||||
[[nodiscard]] VkDescriptorSetLayout SetLayout() const {
|
||||
return *descriptor_set_layout;
|
||||
}
|
||||
|
||||
[[nodiscard]] u32 DescriptorCount() const {
|
||||
return descriptor_count;
|
||||
}
|
||||
|
||||
void Bind(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const;
|
||||
|
||||
private:
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::Pipeline pipeline;
|
||||
u32 descriptor_count{};
|
||||
};
|
||||
|
||||
[[nodiscard]] vk::DescriptorPool CreateLsfgDescriptorPool(const Device& device, u32 max_sets);
|
||||
|
||||
[[nodiscard]] vk::Sampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode,
|
||||
VkCompareOp compare_op, bool white_border);
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,281 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_delta.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DISPATCH_TILE_SHIFT = 3;
|
||||
|
||||
[[nodiscard]] u32 GroupCount(u32 size) {
|
||||
return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgDelta::LsfgDelta(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImageHistory& inputs_,
|
||||
LsfgImage& flow_input_, LsfgImage* previous_gamma_, LsfgImage* previous1_,
|
||||
LsfgImage* previous2_, size_t generation_count)
|
||||
: inputs{&inputs_}, flow_input{&flow_input_}, previous_gamma{previous_gamma_},
|
||||
previous1{previous1_}, previous2{previous2_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
passes[0] = LsfgPass(device, shaders, DELTA[0],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[1] = LsfgPass(device, shaders, DELTA[1],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[2] = LsfgPass(device, shaders, DELTA[2],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[3] = LsfgPass(device, shaders, DELTA[3],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[4] = LsfgPass(device, shaders, DELTA[4],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[5] = LsfgPass(device, shaders, DELTA[5],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
for (size_t i = 6; i < LSFG_DELTA_STAGES - 1; ++i) {
|
||||
passes[i] = LsfgPass(device, shaders, DELTA[i],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
}
|
||||
passes[9] = LsfgPass(device, shaders, DELTA[9],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
|
||||
const VkExtent2D extent = (*inputs)[0][0].Extent();
|
||||
for (auto& image : temp1) {
|
||||
image = LsfgImage(device, memory_allocator, extent);
|
||||
}
|
||||
for (auto& image : temp2) {
|
||||
image = LsfgImage(device, memory_allocator, extent);
|
||||
}
|
||||
out_image1 = LsfgImage(device, memory_allocator, extent, LSFG_MOTION_FORMAT);
|
||||
out_image2 = LsfgImage(device, memory_allocator, extent, LSFG_MOTION_FORMAT);
|
||||
|
||||
std::vector<VkDescriptorSetLayout> layouts;
|
||||
for (size_t generation = 0; generation < generation_count; ++generation) {
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
layouts.push_back(passes[0].SetLayout());
|
||||
}
|
||||
for (size_t i = 1; i <= 4; ++i) {
|
||||
layouts.push_back(passes[i].SetLayout());
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
layouts.push_back(passes[5].SetLayout());
|
||||
}
|
||||
for (size_t i = 6; i < LSFG_DELTA_STAGES; ++i) {
|
||||
layouts.push_back(passes[i].SetLayout());
|
||||
}
|
||||
}
|
||||
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
const VkSampler sampler = resources.GetSampler();
|
||||
const VkSampler border_sampler = resources.GetSampler(
|
||||
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
|
||||
const VkSampler edge_sampler =
|
||||
resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);
|
||||
generations.resize(generation_count);
|
||||
size_t next = 0;
|
||||
for (size_t generation = 0; generation < generation_count; ++generation) {
|
||||
Generation& pass = generations[generation];
|
||||
const VkBuffer buffer = resources.GetBuffer(
|
||||
LsfgTimestamp(generation, generation_count), false, previous_gamma == nullptr);
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
pass.first_descriptor_sets[i] = owned_sets[next++];
|
||||
}
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
pass.descriptor_sets[i] = owned_sets[next++];
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
pass.sixth_descriptor_sets[i] = owned_sets[next++];
|
||||
}
|
||||
for (size_t i = 4; i < LSFG_DELTA_STAGES - 2; ++i) {
|
||||
pass.descriptor_sets[i] = owned_sets[next++];
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
LsfgDescriptorWriter(pass.first_descriptor_sets[i])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(border_sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImage(previous_gamma)
|
||||
.AddStorageImages(temp1)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.sixth_descriptor_sets[i])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(border_sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImage(previous_gamma)
|
||||
.AddSampledImage(previous1)
|
||||
.AddStorageImage(temp2[0])
|
||||
.Build(device);
|
||||
}
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[0])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp1)
|
||||
.AddStorageImages(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[1])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp2)
|
||||
.AddStorageImage(temp1[0])
|
||||
.AddStorageImage(temp1[1])
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[2])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp1[0])
|
||||
.AddSampledImage(temp1[1])
|
||||
.AddStorageImages(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[3])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImages(temp2)
|
||||
.AddSampledImage(previous_gamma)
|
||||
.AddSampledImage(*flow_input)
|
||||
.AddStorageImage(out_image1)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[4])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp2[0])
|
||||
.AddStorageImage(temp1[0])
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[5])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp1[0])
|
||||
.AddStorageImage(temp2[0])
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[6])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp2[0])
|
||||
.AddStorageImage(temp1[0])
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[7])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImage(temp1[0])
|
||||
.AddSampledImage(previous2)
|
||||
.AddStorageImage(out_image2)
|
||||
.Build(device);
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgDelta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation) {
|
||||
const Generation& pass = generations[generation];
|
||||
|
||||
const VkExtent2D extent = temp1[0].Extent();
|
||||
const u32 groups_x = GroupCount(extent.width);
|
||||
const u32 groups_y = GroupCount(extent.height);
|
||||
|
||||
const size_t slot = frame_count % LSFG_HISTORY_SLOTS;
|
||||
const size_t previous_slot = (frame_count + 2) % LSFG_HISTORY_SLOTS;
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll((*inputs)[previous_slot])
|
||||
.WriteToReadAll((*inputs)[slot])
|
||||
.WriteToRead(previous_gamma)
|
||||
.ReadToWriteAll(temp1)
|
||||
.Build();
|
||||
passes[0].Bind(cmdbuf, pass.first_descriptor_sets[slot]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
|
||||
passes[1].Bind(cmdbuf, pass.descriptor_sets[0]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(temp1).Build();
|
||||
passes[2].Bind(cmdbuf, pass.descriptor_sets[1]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
|
||||
passes[3].Bind(cmdbuf, pass.descriptor_sets[2]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll(temp2)
|
||||
.WriteToRead(previous_gamma)
|
||||
.WriteToRead(*flow_input)
|
||||
.ReadToWrite(out_image1)
|
||||
.Build();
|
||||
passes[4].Bind(cmdbuf, pass.descriptor_sets[3]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll((*inputs)[previous_slot])
|
||||
.WriteToReadAll((*inputs)[slot])
|
||||
.WriteToRead(previous_gamma)
|
||||
.WriteToRead(previous1)
|
||||
.ReadToWriteAll(temp2)
|
||||
.Build();
|
||||
passes[5].Bind(cmdbuf, pass.sixth_descriptor_sets[slot]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll(temp2)
|
||||
.ReadToWrite(temp1[0])
|
||||
.ReadToWrite(temp1[1])
|
||||
.Build();
|
||||
passes[6].Bind(cmdbuf, pass.descriptor_sets[4]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToRead(temp1[0])
|
||||
.WriteToRead(temp1[1])
|
||||
.ReadToWriteAll(temp2)
|
||||
.Build();
|
||||
passes[7].Bind(cmdbuf, pass.descriptor_sets[5]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll(temp2)
|
||||
.ReadToWrite(temp1[0])
|
||||
.ReadToWrite(temp1[1])
|
||||
.Build();
|
||||
passes[8].Bind(cmdbuf, pass.descriptor_sets[6]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToRead(temp1[0])
|
||||
.WriteToRead(temp1[1])
|
||||
.WriteToRead(previous2)
|
||||
.ReadToWrite(out_image2)
|
||||
.Build();
|
||||
passes[9].Bind(cmdbuf, pass.descriptor_sets[7]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,61 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
constexpr size_t LSFG_DELTA_STAGES = 10;
|
||||
constexpr size_t LSFG_DELTA_TEMPS = 3;
|
||||
|
||||
class LsfgDelta {
|
||||
public:
|
||||
LsfgDelta() = default;
|
||||
LsfgDelta(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
|
||||
LsfgImageHistory& inputs, LsfgImage& flow_input, LsfgImage* previous_gamma,
|
||||
LsfgImage* previous1, LsfgImage* previous2, size_t generation_count);
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation);
|
||||
|
||||
[[nodiscard]] LsfgImage& Output1() {
|
||||
return out_image1;
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& Output2() {
|
||||
return out_image2;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Generation {
|
||||
std::array<VkDescriptorSet, LSFG_HISTORY_SLOTS> first_descriptor_sets{};
|
||||
std::array<VkDescriptorSet, LSFG_HISTORY_SLOTS> sixth_descriptor_sets{};
|
||||
std::array<VkDescriptorSet, LSFG_DELTA_STAGES - 2> descriptor_sets{};
|
||||
};
|
||||
|
||||
LsfgImageHistory* inputs{};
|
||||
LsfgImage* flow_input{};
|
||||
LsfgImage* previous_gamma{};
|
||||
LsfgImage* previous1{};
|
||||
LsfgImage* previous2{};
|
||||
|
||||
std::array<LsfgPass, LSFG_DELTA_STAGES> passes;
|
||||
std::vector<Generation> generations;
|
||||
vk::DescriptorSets owned_sets;
|
||||
|
||||
std::array<LsfgImage, LSFG_DELTA_TEMPS> temp1;
|
||||
LsfgImagePair temp2;
|
||||
LsfgImage out_image1;
|
||||
LsfgImage out_image2;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,183 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_gamma.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DISPATCH_TILE_SHIFT = 3;
|
||||
|
||||
[[nodiscard]] u32 GroupCount(u32 size) {
|
||||
return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgGamma::LsfgGamma(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImageHistory& inputs_,
|
||||
LsfgImage& flow_input_, LsfgImage* previous_, size_t generation_count)
|
||||
: inputs{&inputs_}, flow_input{&flow_input_}, previous{previous_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
passes[0] = LsfgPass(device, shaders, GAMMA[0],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[1] = LsfgPass(device, shaders, GAMMA[1],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[2] = LsfgPass(device, shaders, GAMMA[2],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[3] = LsfgPass(device, shaders, GAMMA[3],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[4] = LsfgPass(device, shaders, GAMMA[4],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
|
||||
const VkExtent2D extent = (*inputs)[0][0].Extent();
|
||||
for (auto& image : temp1) {
|
||||
image = LsfgImage(device, memory_allocator, extent);
|
||||
}
|
||||
for (auto& image : temp2) {
|
||||
image = LsfgImage(device, memory_allocator, extent);
|
||||
}
|
||||
out_image = LsfgImage(device, memory_allocator, extent, LSFG_MOTION_FORMAT);
|
||||
|
||||
std::vector<VkDescriptorSetLayout> layouts;
|
||||
for (size_t generation = 0; generation < generation_count; ++generation) {
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
layouts.push_back(passes[0].SetLayout());
|
||||
}
|
||||
for (size_t i = 1; i < LSFG_GAMMA_STAGES; ++i) {
|
||||
layouts.push_back(passes[i].SetLayout());
|
||||
}
|
||||
}
|
||||
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
const VkSampler sampler = resources.GetSampler();
|
||||
const VkSampler border_sampler = resources.GetSampler(
|
||||
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
|
||||
const VkSampler edge_sampler =
|
||||
resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);
|
||||
|
||||
generations.resize(generation_count);
|
||||
size_t next = 0;
|
||||
for (size_t generation = 0; generation < generation_count; ++generation) {
|
||||
Generation& pass = generations[generation];
|
||||
const VkBuffer buffer = resources.GetBuffer(
|
||||
LsfgTimestamp(generation, generation_count), previous == nullptr);
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
pass.first_descriptor_sets[i] = owned_sets[next++];
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_GAMMA_STAGES - 1; ++i) {
|
||||
pass.descriptor_sets[i] = owned_sets[next++];
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
LsfgDescriptorWriter(pass.first_descriptor_sets[i])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(border_sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImage(previous)
|
||||
.AddStorageImages(temp1)
|
||||
.Build(device);
|
||||
}
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[0])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp1)
|
||||
.AddStorageImages(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[1])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp2)
|
||||
.AddStorageImage(temp1[0])
|
||||
.AddStorageImage(temp1[1])
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[2])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp1[0])
|
||||
.AddSampledImage(temp1[1])
|
||||
.AddStorageImages(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[3])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImages(temp2)
|
||||
.AddSampledImage(previous)
|
||||
.AddSampledImage(*flow_input)
|
||||
.AddStorageImage(out_image)
|
||||
.Build(device);
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgGamma::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation) {
|
||||
const Generation& pass = generations[generation];
|
||||
|
||||
const VkExtent2D extent = temp1[0].Extent();
|
||||
const u32 groups_x = GroupCount(extent.width);
|
||||
const u32 groups_y = GroupCount(extent.height);
|
||||
|
||||
const size_t slot = frame_count % LSFG_HISTORY_SLOTS;
|
||||
const size_t previous_slot = (frame_count + 2) % LSFG_HISTORY_SLOTS;
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll((*inputs)[previous_slot])
|
||||
.WriteToReadAll((*inputs)[slot])
|
||||
.WriteToRead(previous)
|
||||
.ReadToWriteAll(temp1)
|
||||
.Build();
|
||||
passes[0].Bind(cmdbuf, pass.first_descriptor_sets[slot]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
|
||||
passes[1].Bind(cmdbuf, pass.descriptor_sets[0]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll(temp2)
|
||||
.ReadToWrite(temp1[0])
|
||||
.ReadToWrite(temp1[1])
|
||||
.Build();
|
||||
passes[2].Bind(cmdbuf, pass.descriptor_sets[1]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToRead(temp1[0])
|
||||
.WriteToRead(temp1[1])
|
||||
.ReadToWriteAll(temp2)
|
||||
.Build();
|
||||
passes[3].Bind(cmdbuf, pass.descriptor_sets[2]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll(temp2)
|
||||
.WriteToRead(previous)
|
||||
.WriteToRead(*flow_input)
|
||||
.ReadToWrite(out_image)
|
||||
.Build();
|
||||
passes[4].Bind(cmdbuf, pass.descriptor_sets[3]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,53 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
constexpr size_t LSFG_GAMMA_STAGES = 5;
|
||||
constexpr size_t LSFG_GAMMA_TEMPS = 3;
|
||||
|
||||
class LsfgGamma {
|
||||
public:
|
||||
LsfgGamma() = default;
|
||||
LsfgGamma(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
|
||||
LsfgImageHistory& inputs, LsfgImage& flow_input, LsfgImage* previous,
|
||||
size_t generation_count);
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation);
|
||||
|
||||
[[nodiscard]] LsfgImage& Output() {
|
||||
return out_image;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Generation {
|
||||
std::array<VkDescriptorSet, LSFG_HISTORY_SLOTS> first_descriptor_sets{};
|
||||
std::array<VkDescriptorSet, LSFG_GAMMA_STAGES - 1> descriptor_sets{};
|
||||
};
|
||||
|
||||
LsfgImageHistory* inputs{};
|
||||
LsfgImage* flow_input{};
|
||||
LsfgImage* previous{};
|
||||
|
||||
std::array<LsfgPass, LSFG_GAMMA_STAGES> passes;
|
||||
std::vector<Generation> generations;
|
||||
vk::DescriptorSets owned_sets;
|
||||
|
||||
std::array<LsfgImage, LSFG_GAMMA_TEMPS> temp1;
|
||||
LsfgImagePair temp2;
|
||||
LsfgImage out_image;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,89 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_generate.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DISPATCH_TILE_SHIFT = 4;
|
||||
|
||||
[[nodiscard]] u32 GroupCount(u32 size) {
|
||||
return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgGenerate::LsfgGenerate(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImagePair& frames_,
|
||||
LsfgImage& motion_, LsfgImage& detail1_, LsfgImage& detail2_,
|
||||
VkFormat format, size_t generation_count)
|
||||
: frames{&frames_}, motion{&motion_}, detail1{&detail1_}, detail2{&detail2_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
pass = LsfgPass(device, shaders, GENERATE,
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
|
||||
generations.resize(generation_count);
|
||||
|
||||
const std::vector<VkDescriptorSetLayout> layouts(
|
||||
generation_count * generations[0].descriptor_sets.size(), pass.SetLayout());
|
||||
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
const VkSampler sampler = resources.GetSampler();
|
||||
const VkSampler edge_sampler =
|
||||
resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);
|
||||
|
||||
size_t next = 0;
|
||||
for (size_t generation = 0; generation < generation_count; ++generation) {
|
||||
Generation& target = generations[generation];
|
||||
target.out_image = LsfgImage(device, memory_allocator, (*frames)[0].Extent(), format);
|
||||
|
||||
const VkBuffer buffer =
|
||||
resources.GetBuffer(LsfgTimestamp(generation, generation_count));
|
||||
|
||||
for (size_t i = 0; i < target.descriptor_sets.size(); ++i) {
|
||||
target.descriptor_sets[i] = owned_sets[next++];
|
||||
LsfgDescriptorWriter(target.descriptor_sets[i])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImage((*frames)[1 - i])
|
||||
.AddSampledImage((*frames)[i])
|
||||
.AddSampledImage(*motion)
|
||||
.AddSampledImage(*detail1)
|
||||
.AddSampledImage(*detail2)
|
||||
.AddStorageImage(target.out_image)
|
||||
.Build(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgGenerate::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation) {
|
||||
Generation& target = generations[generation];
|
||||
const VkExtent2D extent = target.out_image.Extent();
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll(*frames)
|
||||
.WriteToRead(*motion)
|
||||
.WriteToRead(*detail1)
|
||||
.WriteToRead(*detail2)
|
||||
.ReadToWrite(target.out_image)
|
||||
.Build();
|
||||
|
||||
pass.Bind(cmdbuf, target.descriptor_sets[frame_count % target.descriptor_sets.size()]);
|
||||
cmdbuf.Dispatch(GroupCount(extent.width), GroupCount(extent.height), 1);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
class LsfgGenerate {
|
||||
public:
|
||||
LsfgGenerate() = default;
|
||||
LsfgGenerate(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImagePair& frames, LsfgImage& motion,
|
||||
LsfgImage& detail1, LsfgImage& detail2, VkFormat format,
|
||||
size_t generation_count);
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation);
|
||||
|
||||
[[nodiscard]] LsfgImage& Output(size_t generation) {
|
||||
return generations[generation].out_image;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Generation {
|
||||
std::array<VkDescriptorSet, 2> descriptor_sets{};
|
||||
LsfgImage out_image;
|
||||
};
|
||||
|
||||
LsfgImagePair* frames{};
|
||||
LsfgImage* motion{};
|
||||
LsfgImage* detail1{};
|
||||
LsfgImage* detail2{};
|
||||
|
||||
LsfgPass pass;
|
||||
std::vector<Generation> generations;
|
||||
vk::DescriptorSets owned_sets;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -2,211 +2,77 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_mipmaps.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr VkFormat FLOW_FORMAT = VK_FORMAT_R8_UNORM;
|
||||
constexpr u32 DISPATCH_TILE_SHIFT = 6;
|
||||
constexpr size_t DESCRIPTOR_SET_COUNT = 2;
|
||||
|
||||
struct LsfgConstants {
|
||||
std::array<u32, 2> input_offset;
|
||||
u32 first_iter;
|
||||
u32 first_iter_s;
|
||||
u32 advanced_color_kind;
|
||||
u32 hdr_support;
|
||||
f32 resolution_inv_scale;
|
||||
f32 timestamp;
|
||||
f32 ui_threshold;
|
||||
std::array<u32, 3> padding;
|
||||
};
|
||||
static_assert(sizeof(LsfgConstants) == 48);
|
||||
|
||||
constexpr std::array<VkDescriptorType, 3 + LSFG_MIP_LEVELS> MIPMAPS_BINDINGS{
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_SAMPLER,
|
||||
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
||||
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
||||
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
||||
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
||||
};
|
||||
|
||||
vk::Sampler CreateFlowSampler(const Device& device) {
|
||||
return device.GetLogical().CreateSampler(VkSamplerCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.magFilter = VK_FILTER_LINEAR,
|
||||
.minFilter = VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||
.mipLodBias = 0.0f,
|
||||
.anisotropyEnable = VK_FALSE,
|
||||
.maxAnisotropy = 0.0f,
|
||||
.compareEnable = VK_FALSE,
|
||||
.compareOp = VK_COMPARE_OP_NEVER,
|
||||
.minLod = 0.0f,
|
||||
.maxLod = VK_LOD_CLAMP_NONE,
|
||||
.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
|
||||
.unnormalizedCoordinates = VK_FALSE,
|
||||
});
|
||||
[[nodiscard]] u32 GroupCount(u32 size) {
|
||||
return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgMipmaps::LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator_,
|
||||
const LsfgShaders& shaders, VkExtent2D input_extent, f32 flow_scale)
|
||||
: memory_allocator{memory_allocator_} {
|
||||
LsfgMipmaps::LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImagePair& frames_,
|
||||
f32 flow_scale)
|
||||
: frames{&frames_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
pass = LsfgPass(device, shaders, MIPMAPS,
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{LSFG_MIP_LEVELS, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
|
||||
const VkExtent2D input_extent = (*frames)[0].Extent();
|
||||
flow_extent = VkExtent2D{
|
||||
.width = std::max(1u, static_cast<u32>(static_cast<f32>(input_extent.width) * flow_scale)),
|
||||
.height = std::max(1u, static_cast<u32>(static_cast<f32>(input_extent.height) * flow_scale)),
|
||||
};
|
||||
|
||||
CreateImages(device);
|
||||
CreateUniformBuffer(flow_scale);
|
||||
|
||||
sampler = CreateFlowSampler(device);
|
||||
descriptor_pool = CreateWrappedDescriptorPool(
|
||||
device, DESCRIPTOR_SET_COUNT * MIPMAPS_BINDINGS.size(), DESCRIPTOR_SET_COUNT,
|
||||
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_SAMPLER,
|
||||
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE});
|
||||
descriptor_set_layout = CreateWrappedDescriptorSetLayout(
|
||||
device, std::span<const VkDescriptorType>{MIPMAPS_BINDINGS}, VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
|
||||
const std::vector<VkDescriptorSetLayout> layouts(DESCRIPTOR_SET_COUNT,
|
||||
*descriptor_set_layout);
|
||||
descriptor_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
pipeline_layout = CreateWrappedPipelineLayout(device, descriptor_set_layout);
|
||||
pipeline = CreateWrappedComputePipeline(
|
||||
device, pipeline_layout, shaders.Get(VideoCore::FrameGen::PerformanceShader::MIPMAPS));
|
||||
}
|
||||
|
||||
VkExtent2D LsfgMipmaps::GetLevelExtent(size_t level) const {
|
||||
return VkExtent2D{
|
||||
.width = std::max(1u, flow_extent.width >> level),
|
||||
.height = std::max(1u, flow_extent.height >> level),
|
||||
};
|
||||
}
|
||||
|
||||
void LsfgMipmaps::CreateImages(const Device& device) {
|
||||
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||
images[i] = CreateWrappedImage(memory_allocator, GetLevelExtent(i), FLOW_FORMAT);
|
||||
image_views[i] = CreateWrappedImageView(device, images[i], FLOW_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgMipmaps::CreateUniformBuffer(f32 flow_scale) {
|
||||
uniform_buffer = CreateWrappedBuffer(memory_allocator, sizeof(LsfgConstants),
|
||||
MemoryUsage::Upload);
|
||||
|
||||
const LsfgConstants constants{
|
||||
.input_offset = {0, 0},
|
||||
.first_iter = 0,
|
||||
.first_iter_s = 0,
|
||||
.advanced_color_kind = 0,
|
||||
.hdr_support = 0,
|
||||
.resolution_inv_scale = 1.0f / flow_scale,
|
||||
.timestamp = 0.0f,
|
||||
.ui_threshold = 0.5f,
|
||||
.padding = {0, 0, 0},
|
||||
};
|
||||
|
||||
const std::span<u8> mapped = uniform_buffer.Mapped();
|
||||
std::memcpy(mapped.data(), &constants, sizeof(constants));
|
||||
uniform_buffer.Flush();
|
||||
}
|
||||
|
||||
void LsfgMipmaps::Dispatch(const Device& device, Scheduler& scheduler, VkImageView current_view,
|
||||
u64 frame_count) {
|
||||
const size_t set_index = frame_count % DESCRIPTOR_SET_COUNT;
|
||||
const VkDescriptorSet set = descriptor_sets[set_index];
|
||||
|
||||
const VkDescriptorBufferInfo buffer_info{
|
||||
.buffer = *uniform_buffer,
|
||||
.offset = 0,
|
||||
.range = sizeof(LsfgConstants),
|
||||
};
|
||||
const VkDescriptorImageInfo sampler_info{
|
||||
.sampler = *sampler,
|
||||
.imageView = VK_NULL_HANDLE,
|
||||
.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
};
|
||||
const VkDescriptorImageInfo sampled_info{
|
||||
.sampler = VK_NULL_HANDLE,
|
||||
.imageView = current_view,
|
||||
.imageLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
};
|
||||
std::array<VkDescriptorImageInfo, LSFG_MIP_LEVELS> storage_infos{};
|
||||
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||
storage_infos[i] = VkDescriptorImageInfo{
|
||||
.sampler = VK_NULL_HANDLE,
|
||||
.imageView = *image_views[i],
|
||||
.imageLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
const VkExtent2D level_extent{
|
||||
.width = flow_extent.width >> i,
|
||||
.height = flow_extent.height >> i,
|
||||
};
|
||||
out_images[i] = LsfgImage(device, memory_allocator, level_extent, LSFG_FLOW_FORMAT);
|
||||
}
|
||||
|
||||
std::vector<VkWriteDescriptorSet> writes;
|
||||
writes.reserve(MIPMAPS_BINDINGS.size());
|
||||
const auto push = [&](u32 binding, VkDescriptorType type,
|
||||
const VkDescriptorImageInfo* image_info,
|
||||
const VkDescriptorBufferInfo* buf_info) {
|
||||
writes.push_back(VkWriteDescriptorSet{
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = set,
|
||||
.dstBinding = binding,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = type,
|
||||
.pImageInfo = image_info,
|
||||
.pBufferInfo = buf_info,
|
||||
.pTexelBufferView = nullptr,
|
||||
});
|
||||
};
|
||||
const std::vector<VkDescriptorSetLayout> layouts(descriptor_sets.size(), pass.SetLayout());
|
||||
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
push(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, nullptr, &buffer_info);
|
||||
push(1, VK_DESCRIPTOR_TYPE_SAMPLER, &sampler_info, nullptr);
|
||||
push(2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, &sampled_info, nullptr);
|
||||
for (u32 i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||
push(3 + i, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, &storage_infos[i], nullptr);
|
||||
const VkSampler sampler = resources.GetSampler();
|
||||
const VkBuffer buffer = resources.GetBuffer();
|
||||
|
||||
for (size_t i = 0; i < descriptor_sets.size(); ++i) {
|
||||
descriptor_sets[i] = owned_sets[i];
|
||||
LsfgDescriptorWriter(descriptor_sets[i])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage((*frames)[i])
|
||||
.AddStorageImages(out_images)
|
||||
.Build(device);
|
||||
}
|
||||
}
|
||||
|
||||
device.GetLogical().UpdateDescriptorSets(writes, {});
|
||||
void LsfgMipmaps::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
|
||||
const size_t slot = frame_count % descriptor_sets.size();
|
||||
|
||||
const u32 groups_x = (flow_extent.width + (1u << DISPATCH_TILE_SHIFT) - 1) >>
|
||||
DISPATCH_TILE_SHIFT;
|
||||
const u32 groups_y = (flow_extent.height + (1u << DISPATCH_TILE_SHIFT) - 1) >>
|
||||
DISPATCH_TILE_SHIFT;
|
||||
LsfgBarriers(cmdbuf).WriteToRead((*frames)[slot]).ReadToWriteAll(out_images).Build();
|
||||
|
||||
std::array<VkImage, LSFG_MIP_LEVELS> raw_images{};
|
||||
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||
raw_images[i] = *images[i];
|
||||
}
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([raw_images, set, groups_x, groups_y, layout = *pipeline_layout,
|
||||
compute_pipeline = *pipeline](vk::CommandBuffer cmdbuf) {
|
||||
for (const VkImage image : raw_images) {
|
||||
TransitionImageLayout(cmdbuf, image, VK_IMAGE_LAYOUT_GENERAL,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED);
|
||||
}
|
||||
|
||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, compute_pipeline);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, set, {});
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
});
|
||||
pass.Bind(cmdbuf, descriptor_sets[slot]);
|
||||
cmdbuf.Dispatch(GroupCount(flow_extent.width), GroupCount(flow_extent.height), 1);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -6,52 +6,37 @@
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
class Scheduler;
|
||||
|
||||
constexpr size_t LSFG_MIP_LEVELS = 7;
|
||||
|
||||
class LsfgMipmaps {
|
||||
public:
|
||||
explicit LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, VkExtent2D input_extent, f32 flow_scale);
|
||||
LsfgMipmaps() = default;
|
||||
LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
|
||||
LsfgImagePair& frames, f32 flow_scale);
|
||||
|
||||
void Dispatch(const Device& device, Scheduler& scheduler, VkImageView current_view,
|
||||
u64 frame_count);
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count);
|
||||
|
||||
[[nodiscard]] VkImageView GetLevelView(size_t level) const {
|
||||
return *image_views[level];
|
||||
[[nodiscard]] LsfgImage& Output(size_t level) {
|
||||
return out_images[level];
|
||||
}
|
||||
|
||||
[[nodiscard]] VkImage GetLevelImage(size_t level) const {
|
||||
return *images[level];
|
||||
}
|
||||
|
||||
[[nodiscard]] VkExtent2D GetLevelExtent(size_t level) const;
|
||||
|
||||
private:
|
||||
void CreateImages(const Device& device);
|
||||
void CreateUniformBuffer(f32 flow_scale);
|
||||
LsfgImagePair* frames{};
|
||||
|
||||
LsfgPass pass;
|
||||
std::array<VkDescriptorSet, 2> descriptor_sets{};
|
||||
vk::DescriptorSets owned_sets;
|
||||
|
||||
MemoryAllocator& memory_allocator;
|
||||
VkExtent2D flow_extent{};
|
||||
|
||||
vk::Buffer uniform_buffer;
|
||||
vk::Sampler sampler;
|
||||
vk::DescriptorPool descriptor_pool;
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
vk::DescriptorSets descriptor_sets;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::Pipeline pipeline;
|
||||
|
||||
std::array<vk::Image, LSFG_MIP_LEVELS> images;
|
||||
std::array<vk::ImageView, LSFG_MIP_LEVELS> image_views;
|
||||
std::array<LsfgImage, LSFG_MIP_LEVELS> out_images;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -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"
|
||||
@@ -9,12 +10,16 @@
|
||||
namespace Vulkan {
|
||||
|
||||
LsfgShaders::LsfgShaders(const Device& device) {
|
||||
if (!device.IsVulkanMemoryModelSupported()) {
|
||||
if (!device.IsVulkanMemoryModelSupported() || !device.HasNullDescriptor()) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -193,7 +193,19 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
|
||||
render_window.GetFramebufferLayout(), swapchain.GetImageCount(),
|
||||
swapchain.GetImageViewFormat());
|
||||
|
||||
frame_gen.Process(device, frame);
|
||||
const size_t wanted = frame_gen.WantedGenerations();
|
||||
const bool can_present_all = wanted > 0 && present_manager.AvailableExtraFrames() >= wanted;
|
||||
|
||||
frame_gen.Process(device, frame, swapchain.GetImageFormat(), can_present_all);
|
||||
|
||||
const size_t generated_frames = frame_gen.GeneratedFrameCount();
|
||||
for (size_t generation = 0; generation < generated_frames; ++generation) {
|
||||
Frame* generated = present_manager.GetRenderFrame();
|
||||
blit_swapchain.PrepareFrame(device, generated, render_window.GetFramebufferLayout());
|
||||
frame_gen.CopyToFrame(generated, generation);
|
||||
scheduler.Flush(*generated->render_ready);
|
||||
present_manager.Present(generated);
|
||||
}
|
||||
|
||||
scheduler.Flush(*frame->render_ready);
|
||||
|
||||
|
||||
@@ -85,6 +85,17 @@ 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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
WaitIdle(device);
|
||||
present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format,
|
||||
window_adapt->GetRenderPass());
|
||||
}
|
||||
|
||||
void BlitScreen::DrawToFrame(const Device& device, RasterizerVulkan& rasterizer, Frame* frame,
|
||||
std::span<const Tegra::FramebufferConfig> framebuffers,
|
||||
const Layout::FramebufferLayout& layout,
|
||||
|
||||
@@ -60,6 +60,8 @@ public:
|
||||
const Layout::FramebufferLayout& layout, size_t current_swapchain_image_count,
|
||||
VkFormat current_swapchain_view_format);
|
||||
|
||||
void PrepareFrame(const Device& device, Frame* frame, const Layout::FramebufferLayout& layout);
|
||||
|
||||
[[nodiscard]] vk::Framebuffer CreateFramebuffer(const Device& device, const Layout::FramebufferLayout& layout,
|
||||
VkImageView image_view,
|
||||
VkFormat current_view_format);
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t FRAME_GEN_EXTRA_FRAMES = 2;
|
||||
|
||||
bool CanBlitToSwapchain(const vk::PhysicalDevice& physical_device, VkFormat format) {
|
||||
const VkFormatProperties props{physical_device.GetFormatProperties(format)};
|
||||
return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT);
|
||||
@@ -179,6 +181,11 @@ void PresentManager::Present(Frame* frame) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t PresentManager::AvailableExtraFrames() {
|
||||
std::scoped_lock lock{free_mutex};
|
||||
return free_queue.size();
|
||||
}
|
||||
|
||||
void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format,
|
||||
VkRenderPass rd) {
|
||||
auto& dld = device.GetLogical();
|
||||
@@ -202,8 +209,8 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
|
||||
VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
@@ -302,7 +309,11 @@ void PresentManager::SetImageCount() {
|
||||
// We cannot have more than 7 images in flight at any given time.
|
||||
// FRAMES_IN_FLIGHT is 8, and the cache TICKS_TO_DESTROY is 8.
|
||||
// Mali drivers will give us 6.
|
||||
image_count = std::min<size_t>(swapchain.GetImageCount(), 7);
|
||||
const size_t generated =
|
||||
Settings::values.frame_gen.GetValue()
|
||||
? Settings::values.frame_gen_multiplier.GetValue() * FRAME_GEN_EXTRA_FRAMES
|
||||
: 0;
|
||||
image_count = std::min<size_t>(swapchain.GetImageCount() + generated, 7);
|
||||
}
|
||||
|
||||
void PresentManager::CopyToSwapchain(Frame* frame) {
|
||||
|
||||
@@ -60,6 +60,9 @@ public:
|
||||
/// Waits for the present thread to finish presenting all queued frames.
|
||||
void WaitPresent();
|
||||
|
||||
/// How many additional frames can be queued without stalling the render thread
|
||||
size_t AvailableExtraFrames();
|
||||
|
||||
private:
|
||||
void PresentThread(std::stop_token token);
|
||||
|
||||
|
||||
@@ -146,9 +146,10 @@ public:
|
||||
frame_counter++;
|
||||
auto target_time = start_time + frame_interval * frame_counter;
|
||||
if (target_time >= now) {
|
||||
constexpr auto spin_tail = std::chrono::milliseconds(1);
|
||||
auto sleep_time = target_time - now;
|
||||
if (sleep_time > std::chrono::milliseconds(15)) {
|
||||
std::this_thread::sleep_for(sleep_time - std::chrono::milliseconds(1));
|
||||
if (sleep_time > spin_tail * 2) {
|
||||
std::this_thread::sleep_for(sleep_time - spin_tail);
|
||||
}
|
||||
while (std::chrono::steady_clock::now() < target_time) {
|
||||
std::this_thread::yield();
|
||||
|
||||
@@ -48,6 +48,9 @@ static VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox,
|
||||
Settings::VSyncMode setting = [has_imm, has_mailbox]() {
|
||||
// Choose Mailbox or Immediate if unlocked and those modes are supported
|
||||
const auto mode = Settings::values.vsync_mode.GetValue();
|
||||
if (Settings::values.frame_gen.GetValue()) {
|
||||
return mode == Settings::VSyncMode::FifoRelaxed ? mode : Settings::VSyncMode::Fifo;
|
||||
}
|
||||
if (Settings::values.use_speed_limit.GetValue() &&
|
||||
Settings::values.current_speed_mode.GetValue() != Settings::SpeedMode::Turbo) {
|
||||
return mode;
|
||||
|
||||
Reference in New Issue
Block a user