diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index e29b7bc8c4..7d094b56fd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -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_HDR("frame_gen_hdr"),
RENDERER_FRAME_GEN_DUMP_FLOW("frame_gen_dump_flow"),
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
PICTURE_IN_PICTURE("picture_in_picture"),
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
index 117336ab64..ea41fb6cbd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
@@ -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"),
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 22d09769c1..94d9f3441c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -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_HDR,
+ titleId = R.string.frame_gen_hdr,
+ descriptionId = R.string.frame_gen_hdr_description
+ )
+ )
put(
SwitchSetting(
BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index 83746f8051..56e60529ca 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -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_HDR.key)
add(BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key)
}
}
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 341d89d6b4..2778e76ee7 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -162,6 +162,18 @@
- @string/resolution_four
+
+ - @string/frame_gen_multiplier_2x
+ - @string/frame_gen_multiplier_3x
+ - @string/frame_gen_multiplier_4x
+
+
+
+ - 2
+ - 3
+ - 4
+
+
- @string/renderer_vsync_immediate
- @string/renderer_vsync_mailbox
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 99252cedb7..b6f0d90af7 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -300,6 +300,15 @@
Install alternative drivers for potentially better performance or accuracy
Frame generation
Run the Lossless Scaling interpolation shaders on every frame. Incomplete: the interpolated frame is computed but not yet displayed, so this only costs performance for now.
+ Frame multiplier
+ How many frames to display for each rendered frame. Higher values cost proportionally more GPU time.
+ 2x (one generated frame)
+ 3x (two generated frames)
+ 4x (three generated frames)
+ Motion estimation resolution
+ Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance.
+ HDR interpolation
+ Treat frames as HDR when interpolating. Only useful on an HDR output.
Dump generated frame
Write the optical flow mip levels and the interpolated frame to the lossless/debug folder once, for troubleshooting
Frame generation unavailable
diff --git a/src/common/settings.h b/src/common/settings.h
index b1b6cec526..aa3514f137 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -390,6 +390,33 @@ struct Values {
SwitchableSetting frame_gen{linkage, false, "frame_gen", Category::Renderer,
Specialization::Default, true, true};
+
+ SwitchableSetting frame_gen_multiplier{linkage,
+ 2,
+ 2,
+ 4,
+ "frame_gen_multiplier",
+ Category::Renderer,
+ Specialization::Countable,
+ true,
+ true,
+ &frame_gen};
+
+ SwitchableSetting frame_gen_flow_scale{linkage,
+ 100,
+ 25,
+ 100,
+ "frame_gen_flow_scale",
+ Category::Renderer,
+ Specialization::Countable |
+ Specialization::Percentage,
+ true,
+ true,
+ &frame_gen};
+
+ SwitchableSetting frame_gen_hdr{linkage, false, "frame_gen_hdr", Category::Renderer,
+ Specialization::Default, true, true, &frame_gen};
+
SwitchableSetting frame_gen_dump_flow{linkage, false, "frame_gen_dump_flow",
Category::Renderer};
diff --git a/src/video_core/renderer_vulkan/present/frame_gen.cpp b/src/video_core/renderer_vulkan/present/frame_gen.cpp
index c6ab83e018..7ec2364083 100644
--- a/src/video_core/renderer_vulkan/present/frame_gen.cpp
+++ b/src/video_core/renderer_vulkan/present/frame_gen.cpp
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
+#include
#include
#include
@@ -18,10 +19,19 @@ namespace Vulkan {
namespace {
-constexpr f32 LSFG_FLOW_SCALE = 1.0f;
constexpr size_t COLOR_CHANNELS = 4;
constexpr u64 LSFG_REQUIRED_FRAMES = 2;
+[[nodiscard]] f32 ConfiguredFlowScale() {
+ return static_cast(Settings::values.frame_gen_flow_scale.GetValue()) / 100.0f;
+}
+
+[[nodiscard]] size_t ConfiguredGenerations() {
+ const u32 multiplier = std::clamp(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;
}
@@ -187,7 +197,9 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format) {
const VkExtent2D extent{.width = frame->width, .height = frame->height};
if (!chain || built_extent.width != extent.width || built_extent.height != extent.height ||
- built_format != format) {
+ built_format != format || built_flow_scale != ConfiguredFlowScale() ||
+ built_hdr != Settings::values.frame_gen_hdr.GetValue() ||
+ built_generations != ConfiguredGenerations()) {
Rebuild(device, extent, format);
}
@@ -208,15 +220,19 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format) {
}
}
-bool FrameGen::HasGeneratedFrame() const {
- return chain.has_value() && frame_count >= LSFG_REQUIRED_FRAMES &&
- Settings::values.frame_gen.GetValue();
+size_t FrameGen::GeneratedFrameCount() const {
+ if (!chain || frame_count < LSFG_REQUIRED_FRAMES ||
+ !Settings::values.frame_gen.GetValue()) {
+ return 0;
+ }
+ return chain->GenerationCount();
}
-void FrameGen::CopyToFrame(Frame* destination) {
+void FrameGen::CopyToFrame(Frame* destination, size_t generation) {
scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([this, target = *destination->image](vk::CommandBuffer cmdbuf) {
- LsfgImage& source = chain->Output();
+ scheduler.Record([this, target = *destination->image,
+ generation](vk::CommandBuffer cmdbuf) {
+ LsfgImage& source = chain->Output(generation);
const VkExtent2D extent = source.Extent();
const std::array before{
@@ -255,7 +271,13 @@ void FrameGen::CopyToFrame(Frame* destination) {
void FrameGen::Rebuild(const Device& device, VkExtent2D extent, VkFormat format) {
scheduler.Finish();
chain.reset();
- chain.emplace(device, memory_allocator, *shaders, extent, format, LSFG_FLOW_SCALE);
+
+ built_flow_scale = ConfiguredFlowScale();
+ built_hdr = Settings::values.frame_gen_hdr.GetValue();
+ built_generations = ConfiguredGenerations();
+
+ chain.emplace(device, memory_allocator, *shaders, extent, format, built_flow_scale, built_hdr,
+ built_generations);
built_extent = extent;
built_format = format;
frame_count = 0;
@@ -319,7 +341,9 @@ void FrameGen::DumpDebugImages(u64 count) {
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));
- dump("generated", chain->Output());
+ for (size_t generation = 0; generation < chain->GenerationCount(); ++generation) {
+ dump("generated" + std::to_string(generation), chain->Output(generation));
+ }
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/present/frame_gen.h b/src/video_core/renderer_vulkan/present/frame_gen.h
index 35bb5bb5b0..42991fbf23 100644
--- a/src/video_core/renderer_vulkan/present/frame_gen.h
+++ b/src/video_core/renderer_vulkan/present/frame_gen.h
@@ -23,9 +23,9 @@ public:
void Process(const Device& device, Frame* frame, VkFormat format);
- [[nodiscard]] bool HasGeneratedFrame() const;
+ [[nodiscard]] size_t GeneratedFrameCount() const;
- void CopyToFrame(Frame* destination);
+ void CopyToFrame(Frame* destination, size_t generation);
private:
void Rebuild(const Device& device, VkExtent2D extent, VkFormat format);
@@ -38,6 +38,9 @@ private:
std::optional chain;
VkExtent2D built_extent{};
VkFormat built_format{VK_FORMAT_UNDEFINED};
+ f32 built_flow_scale{};
+ bool built_hdr{};
+ size_t built_generations{};
u64 frame_count{};
bool unavailable{};
bool dumped{};
diff --git a/src/video_core/renderer_vulkan/present/lsfg_beta.cpp b/src/video_core/renderer_vulkan/present/lsfg_beta.cpp
index 049948e761..7b0ff37666 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_beta.cpp
+++ b/src/video_core/renderer_vulkan/present/lsfg_beta.cpp
@@ -102,7 +102,7 @@ LsfgBeta::LsfgBeta(const Device& device, MemoryAllocator& memory_allocator,
.AddStorageImages(temp2)
.Build(device);
LsfgDescriptorWriter(descriptor_sets[3])
- .AddUniformBuffer(resources.GetBuffer(LSFG_TIMESTAMP), LsfgResources::BufferSize())
+ .AddUniformBuffer(resources.GetBuffer(0.5f), LsfgResources::BufferSize())
.AddSampler(sampler)
.AddSampledImages(temp2)
.AddStorageImages(out_images)
diff --git a/src/video_core/renderer_vulkan/present/lsfg_chain.cpp b/src/video_core/renderer_vulkan/present/lsfg_chain.cpp
index a242fda7f7..886f9eb16c 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_chain.cpp
+++ b/src/video_core/renderer_vulkan/present/lsfg_chain.cpp
@@ -11,16 +11,20 @@ namespace Vulkan {
namespace {
-constexpr u32 DESCRIPTOR_SET_COUNT = 256;
+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)
- : resources{device, memory_allocator, flow_scale},
- descriptor_pool{CreateLsfgDescriptorPool(device, DESCRIPTOR_SET_COUNT)} {
+ f32 flow_scale, bool is_hdr, size_t generation_count_)
+ : generation_count{generation_count_},
+ resources{device, memory_allocator, flow_scale, is_hdr},
+ descriptor_pool{CreateLsfgDescriptorPool(
+ device, FIXED_DESCRIPTOR_SETS +
+ DESCRIPTOR_SETS_PER_GENERATION * static_cast(generation_count))} {
for (auto& image : frames) {
image = LsfgImage(device, memory_allocator, extent, format);
}
@@ -41,7 +45,7 @@ LsfgChain::LsfgChain(const Device& device, MemoryAllocator& memory_allocator,
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());
+ i == 0 ? nullptr : &gamma[i - 1].Output(), generation_count);
if (i < FIRST_DELTA_LEVEL) {
continue;
@@ -52,13 +56,13 @@ LsfgChain::LsfgChain(const Device& device, MemoryAllocator& memory_allocator,
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());
+ 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);
+ delta[LSFG_DELTA_INSTANCES - 1].Output2(), format, generation_count);
}
void LsfgChain::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
@@ -70,14 +74,15 @@ void LsfgChain::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
beta.Dispatch(cmdbuf, frame_count);
- for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
- gamma[i].Dispatch(cmdbuf, frame_count);
- if (i >= FIRST_DELTA_LEVEL) {
- delta[i - FIRST_DELTA_LEVEL].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);
}
-
- generate.Dispatch(cmdbuf, frame_count);
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/present/lsfg_chain.h b/src/video_core/renderer_vulkan/present/lsfg_chain.h
index 91f58ed13a..36dc9fff32 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_chain.h
+++ b/src/video_core/renderer_vulkan/present/lsfg_chain.h
@@ -24,7 +24,8 @@ 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);
+ VkExtent2D extent, VkFormat format, f32 flow_scale, bool is_hdr,
+ size_t generation_count_);
LsfgChain(const LsfgChain&) = delete;
LsfgChain& operator=(const LsfgChain&) = delete;
@@ -35,8 +36,12 @@ public:
return frames[frame_count % frames.size()];
}
- [[nodiscard]] LsfgImage& Output() {
- return generate.Output();
+ [[nodiscard]] LsfgImage& Output(size_t generation) {
+ return generate.Output(generation);
+ }
+
+ [[nodiscard]] size_t GenerationCount() const {
+ return generation_count;
}
[[nodiscard]] LsfgImage& FlowLevel(size_t level) {
@@ -64,6 +69,7 @@ public:
}
private:
+ size_t generation_count{};
LsfgResources resources;
vk::DescriptorPool descriptor_pool;
diff --git a/src/video_core/renderer_vulkan/present/lsfg_common.cpp b/src/video_core/renderer_vulkan/present/lsfg_common.cpp
index c84bf544c5..b882a10263 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_common.cpp
+++ b/src/video_core/renderer_vulkan/present/lsfg_common.cpp
@@ -154,8 +154,8 @@ VkBuffer LsfgResources::GetBuffer(f32 timestamp, bool first_iter, bool first_ite
.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,
+ .advanced_color_kind = is_hdr ? 2u : 0u,
+ .hdr_support = is_hdr ? 1u : 0u,
.resolution_inv_scale = 1.0f / flow_scale,
.timestamp = timestamp,
.ui_threshold = 0.5f,
diff --git a/src/video_core/renderer_vulkan/present/lsfg_common.h b/src/video_core/renderer_vulkan/present/lsfg_common.h
index d53150d35b..9a2cd3b608 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_common.h
+++ b/src/video_core/renderer_vulkan/present/lsfg_common.h
@@ -24,8 +24,13 @@ 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_GENERATION_COUNT = 1;
-constexpr f32 LSFG_TIMESTAMP = 1.0f / static_cast(LSFG_GENERATION_COUNT + 1);
+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(generation + 1) / static_cast(generation_count + 1);
+}
class LsfgImage {
public:
@@ -71,8 +76,10 @@ using LsfgImageHistory = std::array;
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_} {}
+ LsfgResources(const Device& device_, MemoryAllocator& memory_allocator_, f32 flow_scale_,
+ bool is_hdr_)
+ : device{&device_}, memory_allocator{&memory_allocator_}, flow_scale{flow_scale_},
+ is_hdr{is_hdr_} {}
[[nodiscard]] VkSampler GetSampler(
VkSamplerAddressMode address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
@@ -87,6 +94,7 @@ private:
const Device* device{};
MemoryAllocator* memory_allocator{};
f32 flow_scale{1.0f};
+ bool is_hdr{};
std::map samplers;
std::map buffers;
diff --git a/src/video_core/renderer_vulkan/present/lsfg_delta.cpp b/src/video_core/renderer_vulkan/present/lsfg_delta.cpp
index e903e47757..f0a48c49b7 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_delta.cpp
+++ b/src/video_core/renderer_vulkan/present/lsfg_delta.cpp
@@ -25,7 +25,7 @@ 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_)
+ 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;
@@ -80,115 +80,123 @@ LsfgDelta::LsfgDelta(const Device& device, MemoryAllocator& memory_allocator,
out_image2 = LsfgImage(device, memory_allocator, extent, LSFG_MOTION_FORMAT);
std::vector layouts;
- 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());
+ 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);
- size_t next = 0;
- for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
- first_descriptor_sets[i] = owned_sets[next++];
- }
- for (size_t i = 0; i < 4; ++i) {
- descriptor_sets[i] = owned_sets[next++];
- }
- for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
- sixth_descriptor_sets[i] = owned_sets[next++];
- }
- for (size_t i = 4; i < LSFG_DELTA_STAGES - 2; ++i) {
- descriptor_sets[i] = owned_sets[next++];
- }
-
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);
- const VkBuffer buffer =
- resources.GetBuffer(LSFG_TIMESTAMP, false, previous_gamma == nullptr);
+ 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) {
- LsfgDescriptorWriter(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)
+ 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(sixth_descriptor_sets[i])
+ 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(border_sampler)
+ .AddSampler(sampler)
.AddSampler(edge_sampler)
- .AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS])
- .AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS])
+ .AddSampledImages(temp2)
.AddSampledImage(previous_gamma)
- .AddSampledImage(previous1)
+ .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);
}
- LsfgDescriptorWriter(descriptor_sets[0])
- .AddSampler(sampler)
- .AddSampledImages(temp1)
- .AddStorageImages(temp2)
- .Build(device);
- LsfgDescriptorWriter(descriptor_sets[1])
- .AddSampler(sampler)
- .AddSampledImages(temp2)
- .AddStorageImage(temp1[0])
- .AddStorageImage(temp1[1])
- .Build(device);
- LsfgDescriptorWriter(descriptor_sets[2])
- .AddSampler(sampler)
- .AddSampledImage(temp1[0])
- .AddSampledImage(temp1[1])
- .AddStorageImages(temp2)
- .Build(device);
- LsfgDescriptorWriter(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(descriptor_sets[4])
- .AddSampler(sampler)
- .AddSampledImage(temp2[0])
- .AddStorageImage(temp1[0])
- .Build(device);
- LsfgDescriptorWriter(descriptor_sets[5])
- .AddSampler(sampler)
- .AddSampledImage(temp1[0])
- .AddStorageImage(temp2[0])
- .Build(device);
- LsfgDescriptorWriter(descriptor_sets[6])
- .AddSampler(sampler)
- .AddSampledImage(temp2[0])
- .AddStorageImage(temp1[0])
- .Build(device);
- LsfgDescriptorWriter(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) {
+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);
@@ -202,19 +210,19 @@ void LsfgDelta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
.WriteToRead(previous_gamma)
.ReadToWriteAll(temp1)
.Build();
- passes[0].Bind(cmdbuf, first_descriptor_sets[slot]);
+ 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, descriptor_sets[0]);
+ 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, descriptor_sets[1]);
+ 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, descriptor_sets[2]);
+ passes[3].Bind(cmdbuf, pass.descriptor_sets[2]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
LsfgBarriers(cmdbuf)
@@ -223,7 +231,7 @@ void LsfgDelta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
.WriteToRead(*flow_input)
.ReadToWrite(out_image1)
.Build();
- passes[4].Bind(cmdbuf, descriptor_sets[3]);
+ passes[4].Bind(cmdbuf, pass.descriptor_sets[3]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
LsfgBarriers(cmdbuf)
@@ -233,7 +241,7 @@ void LsfgDelta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
.WriteToRead(previous1)
.ReadToWriteAll(temp2)
.Build();
- passes[5].Bind(cmdbuf, sixth_descriptor_sets[slot]);
+ passes[5].Bind(cmdbuf, pass.sixth_descriptor_sets[slot]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
LsfgBarriers(cmdbuf)
@@ -241,7 +249,7 @@ void LsfgDelta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
.ReadToWrite(temp1[0])
.ReadToWrite(temp1[1])
.Build();
- passes[6].Bind(cmdbuf, descriptor_sets[4]);
+ passes[6].Bind(cmdbuf, pass.descriptor_sets[4]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
LsfgBarriers(cmdbuf)
@@ -249,7 +257,7 @@ void LsfgDelta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
.WriteToRead(temp1[1])
.ReadToWriteAll(temp2)
.Build();
- passes[7].Bind(cmdbuf, descriptor_sets[5]);
+ passes[7].Bind(cmdbuf, pass.descriptor_sets[5]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
LsfgBarriers(cmdbuf)
@@ -257,7 +265,7 @@ void LsfgDelta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
.ReadToWrite(temp1[0])
.ReadToWrite(temp1[1])
.Build();
- passes[8].Bind(cmdbuf, descriptor_sets[6]);
+ passes[8].Bind(cmdbuf, pass.descriptor_sets[6]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
LsfgBarriers(cmdbuf)
@@ -266,7 +274,7 @@ void LsfgDelta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
.WriteToRead(previous2)
.ReadToWrite(out_image2)
.Build();
- passes[9].Bind(cmdbuf, descriptor_sets[7]);
+ passes[9].Bind(cmdbuf, pass.descriptor_sets[7]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
}
diff --git a/src/video_core/renderer_vulkan/present/lsfg_delta.h b/src/video_core/renderer_vulkan/present/lsfg_delta.h
index 2681ca2933..f4a014b016 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_delta.h
+++ b/src/video_core/renderer_vulkan/present/lsfg_delta.h
@@ -4,6 +4,7 @@
#pragma once
#include
+#include
#include "common/common_types.h"
#include "video_core/renderer_vulkan/present/lsfg_common.h"
@@ -22,9 +23,9 @@ public:
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);
+ LsfgImage* previous1, LsfgImage* previous2, size_t generation_count);
- void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count);
+ void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation);
[[nodiscard]] LsfgImage& Output1() {
return out_image1;
@@ -35,6 +36,12 @@ public:
}
private:
+ struct Generation {
+ std::array first_descriptor_sets{};
+ std::array sixth_descriptor_sets{};
+ std::array descriptor_sets{};
+ };
+
LsfgImageHistory* inputs{};
LsfgImage* flow_input{};
LsfgImage* previous_gamma{};
@@ -42,9 +49,7 @@ private:
LsfgImage* previous2{};
std::array passes;
- std::array first_descriptor_sets{};
- std::array sixth_descriptor_sets{};
- std::array descriptor_sets{};
+ std::vector generations;
vk::DescriptorSets owned_sets;
std::array temp1;
diff --git a/src/video_core/renderer_vulkan/present/lsfg_gamma.cpp b/src/video_core/renderer_vulkan/present/lsfg_gamma.cpp
index 0fb6d547b5..bd55b2d1da 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_gamma.cpp
+++ b/src/video_core/renderer_vulkan/present/lsfg_gamma.cpp
@@ -24,7 +24,7 @@ constexpr u32 DISPATCH_TILE_SHIFT = 3;
LsfgGamma::LsfgGamma(const Device& device, MemoryAllocator& memory_allocator,
const LsfgShaders& shaders, LsfgResources& resources,
vk::DescriptorPool& descriptor_pool, LsfgImageHistory& inputs_,
- LsfgImage& flow_input_, LsfgImage* previous_)
+ LsfgImage& flow_input_, LsfgImage* previous_, size_t generation_count)
: inputs{&inputs_}, flow_input{&flow_input_}, previous{previous_} {
using namespace VideoCore::FrameGen::PerformanceShader;
@@ -61,68 +61,79 @@ LsfgGamma::LsfgGamma(const Device& device, MemoryAllocator& memory_allocator,
out_image = LsfgImage(device, memory_allocator, extent, LSFG_MOTION_FORMAT);
std::vector layouts;
- 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());
+ 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);
- for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
- first_descriptor_sets[i] = owned_sets[i];
- }
- for (size_t i = 0; i < LSFG_GAMMA_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);
const VkSampler edge_sampler =
resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);
- const VkBuffer buffer = resources.GetBuffer(LSFG_TIMESTAMP, previous == nullptr);
- for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
- LsfgDescriptorWriter(first_descriptor_sets[i])
+ 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(border_sampler)
+ .AddSampler(sampler)
.AddSampler(edge_sampler)
- .AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS])
- .AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS])
+ .AddSampledImages(temp2)
.AddSampledImage(previous)
- .AddStorageImages(temp1)
+ .AddSampledImage(*flow_input)
+ .AddStorageImage(out_image)
.Build(device);
}
- LsfgDescriptorWriter(descriptor_sets[0])
- .AddSampler(sampler)
- .AddSampledImages(temp1)
- .AddStorageImages(temp2)
- .Build(device);
- LsfgDescriptorWriter(descriptor_sets[1])
- .AddSampler(sampler)
- .AddSampledImages(temp2)
- .AddStorageImage(temp1[0])
- .AddStorageImage(temp1[1])
- .Build(device);
- LsfgDescriptorWriter(descriptor_sets[2])
- .AddSampler(sampler)
- .AddSampledImage(temp1[0])
- .AddSampledImage(temp1[1])
- .AddStorageImages(temp2)
- .Build(device);
- LsfgDescriptorWriter(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) {
+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);
@@ -136,11 +147,11 @@ void LsfgGamma::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
.WriteToRead(previous)
.ReadToWriteAll(temp1)
.Build();
- passes[0].Bind(cmdbuf, first_descriptor_sets[slot]);
+ 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, descriptor_sets[0]);
+ passes[1].Bind(cmdbuf, pass.descriptor_sets[0]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
LsfgBarriers(cmdbuf)
@@ -148,7 +159,7 @@ void LsfgGamma::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
.ReadToWrite(temp1[0])
.ReadToWrite(temp1[1])
.Build();
- passes[2].Bind(cmdbuf, descriptor_sets[1]);
+ passes[2].Bind(cmdbuf, pass.descriptor_sets[1]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
LsfgBarriers(cmdbuf)
@@ -156,7 +167,7 @@ void LsfgGamma::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
.WriteToRead(temp1[1])
.ReadToWriteAll(temp2)
.Build();
- passes[3].Bind(cmdbuf, descriptor_sets[2]);
+ passes[3].Bind(cmdbuf, pass.descriptor_sets[2]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
LsfgBarriers(cmdbuf)
@@ -165,7 +176,7 @@ void LsfgGamma::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
.WriteToRead(*flow_input)
.ReadToWrite(out_image)
.Build();
- passes[4].Bind(cmdbuf, descriptor_sets[3]);
+ passes[4].Bind(cmdbuf, pass.descriptor_sets[3]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
}
diff --git a/src/video_core/renderer_vulkan/present/lsfg_gamma.h b/src/video_core/renderer_vulkan/present/lsfg_gamma.h
index 2a2a36d4c2..7acf021669 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_gamma.h
+++ b/src/video_core/renderer_vulkan/present/lsfg_gamma.h
@@ -4,6 +4,7 @@
#pragma once
#include
+#include
#include "common/common_types.h"
#include "video_core/renderer_vulkan/present/lsfg_common.h"
@@ -21,22 +22,27 @@ 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);
+ LsfgImageHistory& inputs, LsfgImage& flow_input, LsfgImage* previous,
+ size_t generation_count);
- void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count);
+ void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation);
[[nodiscard]] LsfgImage& Output() {
return out_image;
}
private:
+ struct Generation {
+ std::array first_descriptor_sets{};
+ std::array descriptor_sets{};
+ };
+
LsfgImageHistory* inputs{};
LsfgImage* flow_input{};
LsfgImage* previous{};
std::array passes;
- std::array first_descriptor_sets{};
- std::array descriptor_sets{};
+ std::vector generations;
vk::DescriptorSets owned_sets;
std::array temp1;
diff --git a/src/video_core/renderer_vulkan/present/lsfg_generate.cpp b/src/video_core/renderer_vulkan/present/lsfg_generate.cpp
index 8498f2487d..d6c7adca10 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_generate.cpp
+++ b/src/video_core/renderer_vulkan/present/lsfg_generate.cpp
@@ -25,7 +25,7 @@ LsfgGenerate::LsfgGenerate(const Device& device, MemoryAllocator& memory_allocat
const LsfgShaders& shaders, LsfgResources& resources,
vk::DescriptorPool& descriptor_pool, LsfgImagePair& frames_,
LsfgImage& motion_, LsfgImage& detail1_, LsfgImage& detail2_,
- VkFormat format)
+ VkFormat format, size_t generation_count)
: frames{&frames_}, motion{&motion_}, detail1{&detail1_}, detail2{&detail2_} {
using namespace VideoCore::FrameGen::PerformanceShader;
@@ -35,44 +35,54 @@ LsfgGenerate::LsfgGenerate(const Device& device, MemoryAllocator& memory_allocat
{5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
- out_image = LsfgImage(device, memory_allocator, (*frames)[0].Extent(), format);
+ generations.resize(generation_count);
- const std::vector layouts(descriptor_sets.size(), pass.SetLayout());
+ const std::vector 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);
- const VkBuffer buffer = resources.GetBuffer(LSFG_TIMESTAMP);
- 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)
- .AddSampler(edge_sampler)
- .AddSampledImage((*frames)[1 - i])
- .AddSampledImage((*frames)[i])
- .AddSampledImage(*motion)
- .AddSampledImage(*detail1)
- .AddSampledImage(*detail2)
- .AddStorageImage(out_image)
- .Build(device);
+ 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) {
- const VkExtent2D extent = out_image.Extent();
+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(out_image)
+ .ReadToWrite(target.out_image)
.Build();
- pass.Bind(cmdbuf, descriptor_sets[frame_count % descriptor_sets.size()]);
+ pass.Bind(cmdbuf, target.descriptor_sets[frame_count % target.descriptor_sets.size()]);
cmdbuf.Dispatch(GroupCount(extent.width), GroupCount(extent.height), 1);
}
diff --git a/src/video_core/renderer_vulkan/present/lsfg_generate.h b/src/video_core/renderer_vulkan/present/lsfg_generate.h
index 0ee90b4b71..67a315801b 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_generate.h
+++ b/src/video_core/renderer_vulkan/present/lsfg_generate.h
@@ -4,6 +4,7 @@
#pragma once
#include
+#include
#include "common/common_types.h"
#include "video_core/renderer_vulkan/present/lsfg_common.h"
@@ -19,25 +20,29 @@ public:
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);
+ LsfgImage& detail1, LsfgImage& detail2, VkFormat format,
+ size_t generation_count);
- void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count);
+ void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation);
- [[nodiscard]] LsfgImage& Output() {
- return out_image;
+ [[nodiscard]] LsfgImage& Output(size_t generation) {
+ return generations[generation].out_image;
}
private:
+ struct Generation {
+ std::array descriptor_sets{};
+ LsfgImage out_image;
+ };
+
LsfgImagePair* frames{};
LsfgImage* motion{};
LsfgImage* detail1{};
LsfgImage* detail2{};
LsfgPass pass;
- std::array descriptor_sets{};
+ std::vector generations;
vk::DescriptorSets owned_sets;
-
- LsfgImage out_image;
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 01b4269a6d..7dc0043640 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -195,10 +195,15 @@ void RendererVulkan::Composite(std::span framebu
frame_gen.Process(device, frame, swapchain.GetImageFormat());
- if (frame_gen.HasGeneratedFrame() && present_manager.CanQueueExtraFrame()) {
+ const size_t generated_frames = frame_gen.GeneratedFrameCount();
+ for (size_t generation = 0; generation < generated_frames; ++generation) {
+ if (!present_manager.CanQueueExtraFrame()) {
+ break;
+ }
+
Frame* generated = present_manager.GetRenderFrame();
blit_swapchain.PrepareFrame(device, generated, render_window.GetFramebufferLayout());
- frame_gen.CopyToFrame(generated);
+ frame_gen.CopyToFrame(generated, generation);
scheduler.Flush(*generated->render_ready);
present_manager.Present(generated);
}
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp
index 98ba048783..bbef7b6e9e 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp
@@ -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);
@@ -180,12 +182,8 @@ void PresentManager::Present(Frame* frame) {
}
bool PresentManager::CanQueueExtraFrame() {
- if (!use_present_thread) {
- return false;
- }
-
- std::scoped_lock lock{queue_mutex, free_mutex};
- return present_queue.empty() && !free_queue.empty();
+ std::scoped_lock lock{free_mutex};
+ return !free_queue.empty();
}
void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format,
@@ -311,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(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(swapchain.GetImageCount() + generated, 7);
}
void PresentManager::CopyToSwapchain(Frame* frame) {