diff --git a/CMakeLists.txt b/CMakeLists.txt
index f647204a4b..91a77b717b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -82,8 +82,8 @@ cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSV
option(ENABLE_DEBUG_TOOLS "Enable debugging tools (maxwell disassembler, SPIRV translator, etc)" OFF)
option(ENABLE_WERROR "Enable -Werror diagnostics" ON)
-# Lossless Scaling frame generation. Only Android.
-cmake_dependent_option(ENABLE_LSFG "Enable Lossless Scaling frame generation" ON "ANDROID" OFF)
+# Lossless Scaling frame generation.
+option(ENABLE_LSFG "Enable Lossless Scaling frame generation" ON)
option(ENABLE_RESHADE "Enable ReShade FX post-processing effects" ON)
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 2238a9ec4c..3453e22190 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -346,7 +346,7 @@
Match motion estimation to the game
Estimate motion at the resolution the game actually renders instead of the upscaled output. Costs nothing in accuracy, since upscaling adds no motion detail.
Motion estimation resolution
- Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance.
+ Resolution of the optical flow pass, as a fraction of the game\'s render resolution. Lowering it is the cheapest way to reclaim performance.
Frame generation unavailable
This GPU driver lacks the Vulkan memory model or half precision (float16) support that the Lossless Scaling shaders require.
Optional. Provide your own Lossless.dll to enable frame generation later
diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp
index 1f6792f86c..2c459855e8 100644
--- a/src/qt_common/config/shared_translation.cpp
+++ b/src/qt_common/config/shared_translation.cpp
@@ -154,6 +154,25 @@ std::unique_ptr InitializeTranslations(QObject* parent) {
INSERT(Settings, post_shader_preset, QString(), QString());
INSERT(Settings, post_shader_enabled, tr("Enable post-processing effects"),
tr("Applies post-processing effects to the final image."));
+ INSERT(Settings, frame_gen, tr("Frame generation (Lossless Scaling)"),
+ tr("Generates intermediate frames with the Lossless Scaling frame generation "
+ "shaders.\nRequires your own legal copy of Lossless.dll and forces VSync (FIFO)."));
+ INSERT(Settings, frame_gen_multiplier, tr("Frame generation multiplier:"),
+ tr("How many frames are shown for each frame the game renders.\nOnly used when the "
+ "target frame rate is 0."));
+ INSERT(Settings, frame_gen_target_rate, tr("Frame generation target rate:"),
+ tr("Pick the rate your display can actually show. The multiplier then rises or falls "
+ "on its own to hold it.\n0 uses the fixed multiplier."));
+ INSERT(Settings, frame_gen_flow_scale_auto, tr("Match motion estimation to the game"),
+ tr("Estimate motion at the resolution the game actually renders instead of the "
+ "upscaled output."));
+ INSERT(Settings, frame_gen_flow_scale, tr("Motion estimation resolution:"),
+ tr("Resolution of the optical flow pass, as a fraction of the game's render "
+ "resolution.\nLowering it is the cheapest way to reclaim performance."));
+ INSERT(Settings, frame_gen_queue_target, tr("Frame generation queue target:"),
+ tr("How many finished frames may wait ahead of the display.\nLarger queues absorb GPU "
+ "spikes at the cost of input latency."));
+ INSERT(Settings, frame_gen_dump_flow, QString(), QString());
INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"),
tr("The method used to render the window in fullscreen.\nBorderless offers the best "
"compatibility with the on-screen keyboard that some games request for "
diff --git a/src/video_core/renderer_vulkan/present/frame_gen.cpp b/src/video_core/renderer_vulkan/present/frame_gen.cpp
index 38981448de..21bd0d06b8 100644
--- a/src/video_core/renderer_vulkan/present/frame_gen.cpp
+++ b/src/video_core/renderer_vulkan/present/frame_gen.cpp
@@ -11,7 +11,6 @@
#include "common/settings.h"
#include "video_core/renderer_vulkan/present/frame_gen.h"
#include "video_core/renderer_vulkan/present/util.h"
-#include "video_core/renderer_vulkan/vk_present_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/vulkan_common/vulkan_device.h"
@@ -23,25 +22,11 @@ constexpr size_t COLOR_CHANNELS = 4;
constexpr u64 LSFG_REQUIRED_FRAMES = 2;
constexpr u32 LSFG_RECURRENCE_FRAMES = 2;
-[[nodiscard]] f32 ManualFlowScale() {
- return static_cast(Settings::values.frame_gen_flow_scale.GetValue()) / 100.0f;
-}
-
-[[nodiscard]] f32 ConfiguredFlowScale(VkExtent2D guest_extent, VkExtent2D presented_extent) {
- if (!Settings::values.frame_gen_flow_scale_auto.GetValue()) {
- return ManualFlowScale();
- }
- if (guest_extent.width == 0 || presented_extent.width == 0) {
+[[nodiscard]] f32 ConfiguredFlowScale() {
+ if (Settings::values.frame_gen_flow_scale_auto.GetValue()) {
return 1.0f;
}
-
- const f32 rendered_width = static_cast(guest_extent.width) *
- Settings::values.resolution_info.up_factor;
- const f32 ratio = rendered_width / static_cast(presented_extent.width);
-
- constexpr f32 FLOW_SCALE_STEPS = 20.0f;
- const f32 stepped = std::ceil(ratio * FLOW_SCALE_STEPS) / FLOW_SCALE_STEPS;
- return std::clamp(stepped, 0.25f, 1.0f);
+ return static_cast(Settings::values.frame_gen_flow_scale.GetValue()) / 100.0f;
}
bool IsBlueFirst(VkFormat format) {
@@ -140,55 +125,52 @@ VkImageMemoryBarrier2 MakeTransitionBarrier(VkImage image, VkPipelineStageFlags2
};
}
-VkImageCopy2 MakeCopyRegion(VkExtent2D extent) {
- return VkImageCopy2{
- .sType = VK_STRUCTURE_TYPE_IMAGE_COPY_2,
+VkImageBlit2 MakeBlitRegion(VkExtent2D extent) {
+ const VkImageSubresourceLayers layers{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ };
+ const VkOffset3D end{
+ .x = static_cast(extent.width),
+ .y = static_cast(extent.height),
+ .z = 1,
+ };
+ return VkImageBlit2{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2,
.pNext = nullptr,
- .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},
+ .srcSubresource = layers,
+ .srcOffsets = {VkOffset3D{}, end},
+ .dstSubresource = layers,
+ .dstOffsets = {VkOffset3D{}, end},
};
}
-void CopyPresentedFrame(vk::CommandBuffer cmdbuf, VkImage source, LsfgImage& destination,
- VkExtent2D extent) {
- const auto make_barrier = MakeTransitionBarrier;
-
- static constexpr VkPipelineStageFlags2 present_stage =
- VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
-
+void CopySourceFrame(vk::CommandBuffer cmdbuf, VkImage source, LsfgImage& destination,
+ VkExtent2D extent) {
const std::array before{
- make_barrier(source, present_stage, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
- VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_READ_BIT,
- VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL),
- make_barrier(destination.Handle(), present_stage, VK_ACCESS_2_SHADER_READ_BIT,
- VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT,
- destination.Layout(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL),
+ MakeTransitionBarrier(source, vk::PIPELINE_STAGE_IMAGE_USERS, vk::ACCESS_IMAGE_WRITES,
+ VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_READ_BIT,
+ VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL),
+ MakeTransitionBarrier(destination.Handle(), VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
+ VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_TRANSFER_BIT,
+ VK_ACCESS_2_TRANSFER_WRITE_BIT, destination.Layout(),
+ VK_IMAGE_LAYOUT_GENERAL),
};
cmdbuf.PipelineBarrier(0, {}, {}, before);
- cmdbuf.CopyImage(source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, destination.Handle(),
- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, MakeCopyRegion(extent));
+ cmdbuf.BlitImage(source, VK_IMAGE_LAYOUT_GENERAL, destination.Handle(),
+ VK_IMAGE_LAYOUT_GENERAL, MakeBlitRegion(extent), VK_FILTER_NEAREST);
const std::array after{
- make_barrier(source, VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_READ_BIT,
- present_stage, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
- VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL),
- make_barrier(destination.Handle(), VK_PIPELINE_STAGE_2_TRANSFER_BIT,
- VK_ACCESS_2_TRANSFER_WRITE_BIT, present_stage, VK_ACCESS_2_SHADER_READ_BIT,
- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL),
+ MakeTransitionBarrier(source, VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_NONE,
+ vk::PIPELINE_STAGE_IMAGE_USERS, VK_ACCESS_2_NONE,
+ VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL),
+ MakeTransitionBarrier(destination.Handle(), VK_PIPELINE_STAGE_2_TRANSFER_BIT,
+ VK_ACCESS_2_TRANSFER_WRITE_BIT,
+ VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_READ_BIT,
+ VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL),
};
cmdbuf.PipelineBarrier(0, {}, {}, after);
@@ -202,8 +184,7 @@ FrameGen::FrameGen(MemoryAllocator& memory_allocator_, Scheduler& scheduler_)
FrameGen::~FrameGen() = default;
-void FrameGen::Process(const Device& device, Frame* frame, VkFormat format,
- VkExtent2D guest_extent) {
+void FrameGen::Process(const Device& device, VkImage source, VkExtent2D extent) {
generated = false;
if (!shaders) {
@@ -214,6 +195,7 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format,
if (chain) {
scheduler.Finish();
chain.reset();
+ outputs = {};
}
warm_streak = 0;
return;
@@ -224,19 +206,15 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format,
return;
}
- if (!frame->storage_view) {
+ if (source == VK_NULL_HANDLE) {
warm_streak = 0;
return;
}
- peak_guest_extent.width = std::max(peak_guest_extent.width, guest_extent.width);
- peak_guest_extent.height = std::max(peak_guest_extent.height, guest_extent.height);
-
- const VkExtent2D extent{.width = frame->width, .height = frame->height};
- const f32 flow_scale = ConfiguredFlowScale(peak_guest_extent, extent);
+ const f32 flow_scale = ConfiguredFlowScale();
if (!chain || built_extent.width != extent.width || built_extent.height != extent.height ||
- built_format != format || built_flow_scale != flow_scale) {
- Rebuild(device, extent, format, flow_scale);
+ built_flow_scale != flow_scale) {
+ Rebuild(device, extent, flow_scale);
}
const u64 count = frame_count++;
@@ -247,14 +225,13 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format,
warm_streak = warm ? warm_streak + 1 : 0;
generated = warm && warm_streak >= LSFG_RECURRENCE_FRAMES && plan.generations > 0;
- scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([this, source = *frame->image, extent, count,
- dispatch = warm](vk::CommandBuffer cmdbuf) {
- CopyPresentedFrame(cmdbuf, source, chain->Input(count), extent);
- if (dispatch) {
+ if (warm) {
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([this, source, extent, count](vk::CommandBuffer cmdbuf) {
+ CopySourceFrame(cmdbuf, source, chain->Input(count), extent);
chain->DispatchShared(cmdbuf, count);
- }
- });
+ });
+ }
const bool dump_requested = generated && Settings::values.frame_gen_dump_flow.GetValue();
if (!dump_requested) {
@@ -278,30 +255,30 @@ size_t FrameGen::GeneratedFrameCount() const {
return generated ? last_generations : 0;
}
-void FrameGen::GenerateInto(const Device& device, Frame* destination, size_t generation) {
- chain->SetTarget(device, last_generations, generation, destination->index,
- *destination->storage_view);
-
- const VkExtent2D extent{.width = destination->width, .height = destination->height};
+const LsfgImage& FrameGen::Generate(const Device& device, size_t generation) {
+ LsfgImage& output = outputs[generation];
+ chain->SetTarget(device, last_generations, generation, output.View());
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, count = last_count, generation_count = last_generations, generation,
- target = destination->index, image = *destination->image,
- extent](vk::CommandBuffer cmdbuf) {
- chain->DispatchGeneration(cmdbuf, count, generation_count, generation, target, image,
- extent);
+ image = output.Handle(), extent = built_extent](vk::CommandBuffer cmdbuf) {
+ chain->DispatchGeneration(cmdbuf, count, generation_count, generation, image, extent);
});
+ return output;
}
-void FrameGen::Rebuild(const Device& device, VkExtent2D extent, VkFormat format, f32 flow_scale) {
+void FrameGen::Rebuild(const Device& device, VkExtent2D extent, f32 flow_scale) {
scheduler.Finish();
chain.reset();
built_flow_scale = flow_scale;
- chain.emplace(device, memory_allocator, *shaders, extent, format, built_flow_scale);
+ chain.emplace(device, memory_allocator, *shaders, extent, LSFG_DEFAULT_FORMAT,
+ built_flow_scale);
+ for (LsfgImage& output : outputs) {
+ output = LsfgImage(device, memory_allocator, extent, LSFG_DEFAULT_FORMAT);
+ }
built_extent = extent;
- built_format = format;
frame_count = 0;
warm_streak = 0;
generated = false;
diff --git a/src/video_core/renderer_vulkan/present/frame_gen.h b/src/video_core/renderer_vulkan/present/frame_gen.h
index 19c6d04289..cf82fee004 100644
--- a/src/video_core/renderer_vulkan/present/frame_gen.h
+++ b/src/video_core/renderer_vulkan/present/frame_gen.h
@@ -3,6 +3,7 @@
#pragma once
+#include
#include
#include "common/common_types.h"
@@ -15,23 +16,22 @@ namespace Vulkan {
class Device;
class Scheduler;
-struct Frame;
class FrameGen {
public:
explicit FrameGen(MemoryAllocator& memory_allocator, Scheduler& scheduler);
~FrameGen();
- void Process(const Device& device, Frame* frame, VkFormat format, VkExtent2D guest_extent);
+ void Process(const Device& device, VkImage source, VkExtent2D extent);
[[nodiscard]] size_t WantedGenerations(size_t capacity);
[[nodiscard]] size_t GeneratedFrameCount() const;
- void GenerateInto(const Device& device, Frame* destination, size_t generation);
+ [[nodiscard]] const LsfgImage& Generate(const Device& device, size_t generation);
private:
- void Rebuild(const Device& device, VkExtent2D extent, VkFormat format, f32 flow_scale);
+ void Rebuild(const Device& device, VkExtent2D extent, f32 flow_scale);
void DumpDebugImages(u64 count);
MemoryAllocator& memory_allocator;
@@ -39,11 +39,10 @@ private:
std::optional shaders;
std::optional chain;
+ std::array outputs;
FrameGenPacer pacer;
FrameGenPlan plan{};
- VkExtent2D peak_guest_extent{};
VkExtent2D built_extent{};
- VkFormat built_format{VK_FORMAT_UNDEFINED};
f32 built_flow_scale{};
u64 frame_count{};
u64 last_count{};
diff --git a/src/video_core/renderer_vulkan/present/layer.cpp b/src/video_core/renderer_vulkan/present/layer.cpp
index d0411df705..2ce6a5f845 100644
--- a/src/video_core/renderer_vulkan/present/layer.cpp
+++ b/src/video_core/renderer_vulkan/present/layer.cpp
@@ -60,25 +60,34 @@ VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
} // Anonymous namespace
-Layer::Layer(const Device& device, MemoryAllocator& memory_allocator_, Scheduler& scheduler_, Tegra::MaxwellDeviceMemoryManager& device_memory_, size_t image_count_, VkExtent2D output_size, VkDescriptorSetLayout layout, const PresentFilters& filters_)
+Layer::Layer(const Device& device, MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
+ Tegra::MaxwellDeviceMemoryManager& device_memory_, size_t image_count_,
+ size_t generation_count_, VkExtent2D output_size, VkDescriptorSetLayout layout,
+ const PresentFilters& filters_)
: memory_allocator(memory_allocator_)
, scheduler(scheduler_)
, device_memory(device_memory_)
, filters(filters_)
, image_count(image_count_)
+ , generation_count(generation_count_)
+ , generation_ticks(generation_count_)
{
CreateDescriptorPool(device);
CreateDescriptorSets(device, layout);
+ const size_t slot_count = image_count + generation_count;
if (filters.get_scaling_filter() == Settings::ScalingFilter::Fsr) {
- sr_filter.emplace(device, memory_allocator, image_count, output_size);
+ sr_filter.emplace(device, memory_allocator, slot_count, output_size);
} else if (filters.get_scaling_filter() == Settings::ScalingFilter::Sgsr) {
- sr_filter.emplace(device, memory_allocator, image_count, output_size, false);
+ sr_filter.emplace(device, memory_allocator, slot_count, output_size, false);
} else if (filters.get_scaling_filter() == Settings::ScalingFilter::SgsrEdge) {
- sr_filter.emplace(device, memory_allocator, image_count, output_size, true);
+ sr_filter.emplace(device, memory_allocator, slot_count, output_size, true);
}
}
Layer::~Layer() {
+ for (const u64 tick : generation_ticks) {
+ scheduler.Wait(tick);
+ }
ReleaseRawImages();
}
@@ -115,24 +124,36 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
VkImage source_image = texture_info ? texture_info->image : *raw_images[image_index];
VkImageView source_image_view =
texture_info ? texture_info->image_view : *raw_image_views[image_index];
+ const VkExtent2D render_extent{
+ .width = scaled_width,
+ .height = scaled_height,
+ };
+ const VkExtent2D processed_extent{
+ .width = Settings::values.resolution_info.ScaleUp(raw_width),
+ .height = Settings::values.resolution_info.ScaleUp(raw_height),
+ };
+ VkExtent2D source_extent = render_extent;
if (auto* fxaa = std::get_if(&anti_alias)) {
fxaa->Draw(device, scheduler, image_index, &source_image, &source_image_view);
+ source_extent = processed_extent;
} else if (auto* smaa = std::get_if(&anti_alias)) {
smaa->Draw(device, scheduler, image_index, &source_image, &source_image_view);
+ source_extent = processed_extent;
}
#ifdef HAS_RESHADE
if (post_process.has_value()) {
post_process->Draw(device, scheduler, image_index, &source_image, &source_image_view);
+ source_extent = processed_extent;
}
#endif
auto crop_rect = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height);
- const VkExtent2D render_extent{
- .width = scaled_width,
- .height = scaled_height,
- };
+ generation_source = source_image;
+ generation_extent = source_extent;
+ generation_render_extent = render_extent;
+ generation_crop = crop_rect;
if (auto* fsr = std::get_if(&sr_filter)) {
source_image_view = fsr->Draw(device, scheduler, image_index, source_image, source_image_view, render_extent, crop_rect);
@@ -149,12 +170,43 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
*out_descriptor_set = descriptor_sets[image_index];
}
+void Layer::ConfigureGenerated(const Device& device, PresentPushConstants* out_push_constants,
+ VkDescriptorSet* out_descriptor_set, VkSampler sampler,
+ size_t generation, VkImage image, VkImageView view,
+ const Layout::FramebufferLayout& layout) {
+ const size_t slot = image_count + generation;
+ generation_ticks[generation] = scheduler.CurrentTick();
+
+ VkImageView source_image_view = view;
+ Common::Rectangle crop_rect = generation_crop;
+ if (auto* fsr = std::get_if(&sr_filter)) {
+ source_image_view =
+ fsr->Draw(device, scheduler, slot, image, view, generation_render_extent, crop_rect);
+ crop_rect = {0, 0, 1, 1};
+ } else if (auto* sgsr = std::get_if(&sr_filter)) {
+ source_image_view =
+ sgsr->Draw(device, scheduler, slot, image, view, generation_render_extent, crop_rect);
+ crop_rect = {0, 0, 1, 1};
+ }
+
+ SetMatrixData(device, *out_push_constants, layout);
+ SetVertexData(device, *out_push_constants, layout, crop_rect);
+
+ UpdateDescriptorSet(device, source_image_view, sampler, slot);
+ *out_descriptor_set = descriptor_sets[slot];
+}
+
+bool Layer::IsGenerationFree(size_t generation) const {
+ return generation < generation_count && scheduler.IsFree(generation_ticks[generation]);
+}
+
void Layer::CreateDescriptorPool(const Device& device) {
- descriptor_pool = CreateWrappedDescriptorPool(device, image_count, image_count);
+ const size_t slot_count = image_count + generation_count;
+ descriptor_pool = CreateWrappedDescriptorPool(device, slot_count, slot_count);
}
void Layer::CreateDescriptorSets(const Device& device, VkDescriptorSetLayout layout) {
- const std::vector layouts(image_count, layout);
+ const std::vector layouts(image_count + generation_count, layout);
descriptor_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
}
diff --git a/src/video_core/renderer_vulkan/present/layer.h b/src/video_core/renderer_vulkan/present/layer.h
index f290ba5bb4..47b23d2596 100644
--- a/src/video_core/renderer_vulkan/present/layer.h
+++ b/src/video_core/renderer_vulkan/present/layer.h
@@ -51,7 +51,7 @@ class Layer final {
public:
explicit Layer(const Device& device, MemoryAllocator& memory_allocator, Scheduler& scheduler,
Tegra::MaxwellDeviceMemoryManager& device_memory, size_t image_count,
- VkExtent2D output_size, VkDescriptorSetLayout layout,
+ size_t generation_count, VkExtent2D output_size, VkDescriptorSetLayout layout,
const PresentFilters& filters);
~Layer();
@@ -61,6 +61,21 @@ public:
const Tegra::FramebufferConfig& framebuffer,
const Layout::FramebufferLayout& layout);
+ void ConfigureGenerated(const Device& device, PresentPushConstants* out_push_constants,
+ VkDescriptorSet* out_descriptor_set, VkSampler sampler,
+ size_t generation, VkImage image, VkImageView view,
+ const Layout::FramebufferLayout& layout);
+
+ [[nodiscard]] bool IsGenerationFree(size_t generation) const;
+
+ [[nodiscard]] VkImage GenerationSource() const {
+ return generation_source;
+ }
+
+ [[nodiscard]] VkExtent2D GenerationExtent() const {
+ return generation_extent;
+ }
+
private:
void CreateDescriptorPool(const Device& device);
void CreateDescriptorSets(const Device& device, VkDescriptorSetLayout layout);
@@ -88,6 +103,7 @@ private:
Tegra::MaxwellDeviceMemoryManager& device_memory;
const PresentFilters& filters;
const size_t image_count{};
+ const size_t generation_count{};
vk::DescriptorPool descriptor_pool{};
vk::DescriptorSets descriptor_sets{};
@@ -108,6 +124,11 @@ private:
VkExtent2D post_process_extent{};
#endif
std::vector resource_ticks{};
+ std::vector generation_ticks{};
+ VkImage generation_source{};
+ VkExtent2D generation_extent{};
+ VkExtent2D generation_render_extent{};
+ Common::Rectangle generation_crop{};
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/present/lsfg_chain.cpp b/src/video_core/renderer_vulkan/present/lsfg_chain.cpp
index 972b13a1c4..e52b2fa5fc 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_chain.cpp
+++ b/src/video_core/renderer_vulkan/present/lsfg_chain.cpp
@@ -15,7 +15,7 @@ namespace Vulkan {
namespace {
constexpr u32 FIXED_DESCRIPTOR_SETS = 64;
-constexpr u32 DESCRIPTOR_SETS_PER_SLOT = 112;
+constexpr u32 DESCRIPTOR_SETS_PER_SLOT = 100;
constexpr size_t FIRST_DELTA_LEVEL = 4;
} // Anonymous namespace
@@ -88,8 +88,8 @@ void LsfgChain::DispatchShared(vk::CommandBuffer cmdbuf, u64 frame_count) {
}
void LsfgChain::DispatchGeneration(vk::CommandBuffer cmdbuf, u64 frame_count,
- size_t generation_count, size_t generation, u32 target,
- VkImage image, VkExtent2D extent) {
+ size_t generation_count, size_t generation, VkImage image,
+ VkExtent2D extent) {
const size_t slot = LsfgGenerationSlot(generation_count, generation);
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
gamma[i].Dispatch(cmdbuf, frame_count, slot);
@@ -97,7 +97,7 @@ void LsfgChain::DispatchGeneration(vk::CommandBuffer cmdbuf, u64 frame_count,
delta[i - FIRST_DELTA_LEVEL].Dispatch(cmdbuf, frame_count, slot);
}
}
- generate.Dispatch(cmdbuf, frame_count, slot, target, image, extent);
+ generate.Dispatch(cmdbuf, frame_count, slot, image, extent);
}
} // 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 c8879f74a9..4561d3a092 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_chain.h
+++ b/src/video_core/renderer_vulkan/present/lsfg_chain.h
@@ -35,11 +35,11 @@ public:
void DispatchShared(vk::CommandBuffer cmdbuf, u64 frame_count);
void DispatchGeneration(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation_count,
- size_t generation, u32 target, VkImage image, VkExtent2D extent);
+ size_t generation, VkImage image, VkExtent2D extent);
- void SetTarget(const Device& device, size_t generation_count, size_t generation, u32 target,
+ void SetTarget(const Device& device, size_t generation_count, size_t generation,
VkImageView view) {
- generate.SetTarget(device, LsfgGenerationSlot(generation_count, generation), target, view);
+ generate.SetTarget(device, LsfgGenerationSlot(generation_count, generation), view);
}
[[nodiscard]] LsfgImage& Input(u64 frame_count) {
diff --git a/src/video_core/renderer_vulkan/present/lsfg_common.cpp b/src/video_core/renderer_vulkan/present/lsfg_common.cpp
index e05c7a8b02..68ef254633 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_common.cpp
+++ b/src/video_core/renderer_vulkan/present/lsfg_common.cpp
@@ -127,7 +127,7 @@ LsfgBarriers& LsfgBarriers::DiscardToWrite(VkImage image) {
barriers.push_back(VkImageMemoryBarrier2{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.pNext = nullptr,
- .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
+ .srcStageMask = vk::PIPELINE_STAGE_IMAGE_USERS,
.srcAccessMask = VK_ACCESS_2_NONE,
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.dstAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT,
diff --git a/src/video_core/renderer_vulkan/present/lsfg_common.h b/src/video_core/renderer_vulkan/present/lsfg_common.h
index 33547b1346..68e4d16f57 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_common.h
+++ b/src/video_core/renderer_vulkan/present/lsfg_common.h
@@ -27,7 +27,6 @@ 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_MAX_TARGETS = 7;
constexpr size_t LSFG_MAX_GENERATIONS = 3;
constexpr size_t LSFG_GENERATION_SLOTS = LSFG_MAX_GENERATIONS * (LSFG_MAX_GENERATIONS + 1) / 2;
diff --git a/src/video_core/renderer_vulkan/present/lsfg_generate.cpp b/src/video_core/renderer_vulkan/present/lsfg_generate.cpp
index 2aa470bdb0..f26c27c92c 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_generate.cpp
+++ b/src/video_core/renderer_vulkan/present/lsfg_generate.cpp
@@ -66,25 +66,21 @@ LsfgGenerate::LsfgGenerate(const Device& device, const LsfgShaders& shaders,
edge_sampler =
resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);
- const std::vector layouts(
- LSFG_GENERATION_SLOTS * LSFG_MAX_TARGETS * 2, pass.SetLayout());
+ const std::vector layouts(LSFG_GENERATION_SLOTS * 2, pass.SetLayout());
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
size_t next = 0;
for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) {
- Generation& target = generations[slot];
- target.buffer = resources.GetBuffer(LsfgSlotTimestamp(slot));
-
- for (auto& entry : target.targets) {
- for (auto& set : entry.descriptor_sets) {
- set = owned_sets[next++];
- }
+ Generation& entry = generations[slot];
+ entry.buffer = resources.GetBuffer(LsfgSlotTimestamp(slot));
+ for (auto& set : entry.descriptor_sets) {
+ set = owned_sets[next++];
}
}
}
-void LsfgGenerate::SetTarget(const Device& device, size_t slot, u32 target, VkImageView view) {
- Target& entry = generations[slot].targets[target];
+void LsfgGenerate::SetTarget(const Device& device, size_t slot, VkImageView view) {
+ Generation& entry = generations[slot];
if (entry.view == view) {
return;
}
@@ -92,7 +88,7 @@ void LsfgGenerate::SetTarget(const Device& device, size_t slot, u32 target, VkIm
for (size_t i = 0; i < entry.descriptor_sets.size(); ++i) {
LsfgDescriptorWriter(entry.descriptor_sets[i])
- .AddUniformBuffer(generations[slot].buffer, LsfgResources::BufferSize())
+ .AddUniformBuffer(entry.buffer, LsfgResources::BufferSize())
.AddSampler(sampler)
.AddSampler(edge_sampler)
.AddSampledImage((*frames)[1 - i])
@@ -105,9 +101,9 @@ void LsfgGenerate::SetTarget(const Device& device, size_t slot, u32 target, VkIm
}
}
-void LsfgGenerate::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot, u32 target,
+void LsfgGenerate::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot,
VkImage image, VkExtent2D extent) {
- const Target& entry = generations[slot].targets[target];
+ const Generation& entry = generations[slot];
LsfgBarriers(cmdbuf)
.WriteToReadAll(*frames)
@@ -120,11 +116,10 @@ void LsfgGenerate::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t sl
pass.Bind(cmdbuf, entry.descriptor_sets[frame_count % entry.descriptor_sets.size()]);
cmdbuf.Dispatch(GroupCount(extent.width), GroupCount(extent.height), 1);
- const std::array after{MakeTargetBarrier(
- image, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_WRITE_BIT,
- VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_2_TRANSFER_BIT,
- VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_TRANSFER_READ_BIT,
- VK_IMAGE_LAYOUT_GENERAL)};
+ const std::array after{MakeTargetBarrier(image, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
+ VK_ACCESS_2_SHADER_WRITE_BIT,
+ vk::PIPELINE_STAGE_IMAGE_USERS,
+ vk::ACCESS_IMAGE_USERS, VK_IMAGE_LAYOUT_GENERAL)};
cmdbuf.PipelineBarrier(0, {}, {}, after);
}
diff --git a/src/video_core/renderer_vulkan/present/lsfg_generate.h b/src/video_core/renderer_vulkan/present/lsfg_generate.h
index 75cbd8d2e4..fedad97bdd 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_generate.h
+++ b/src/video_core/renderer_vulkan/present/lsfg_generate.h
@@ -23,19 +23,15 @@ public:
vk::DescriptorPool& descriptor_pool, LsfgImagePair& frames, LsfgImage& motion,
LsfgImage& detail1, LsfgImage& detail2);
- void SetTarget(const Device& device, size_t slot, u32 target, VkImageView view);
+ void SetTarget(const Device& device, size_t slot, VkImageView view);
- void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot, u32 target,
- VkImage image, VkExtent2D extent);
+ void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot, VkImage image,
+ VkExtent2D extent);
private:
- struct Target {
+ struct Generation {
std::array descriptor_sets{};
VkImageView view{};
- };
-
- struct Generation {
- std::array targets{};
VkBuffer buffer{};
};
diff --git a/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp b/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp
index 51c7e4c106..9aebbc3cd9 100644
--- a/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp
+++ b/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp
@@ -10,7 +10,7 @@ namespace Vulkan {
LsfgShaders::LsfgShaders(const Device& device) {
if (!device.IsVulkanMemoryModelSupported() || !device.HasNullDescriptor() ||
- !device.IsFloat16Supported()) {
+ !device.IsNativeFloat16Enabled()) {
return;
}
diff --git a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp
index c7718bc688..9cd99206c7 100644
--- a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp
+++ b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp
@@ -34,7 +34,47 @@ void WindowAdaptPass::Draw(const Device& device, RasterizerVulkan& rasterizer, S
std::list& layers,
std::span configs,
const Layout::FramebufferLayout& layout, Frame* dst) {
+ const size_t layer_count = configs.size();
+ layer_push_constants.resize(layer_count);
+ layer_descriptor_sets.resize(layer_count);
+ layer_pipelines.resize(layer_count);
+ auto layer_it = layers.begin();
+ for (size_t i = 0; i < layer_count; i++) {
+ switch (configs[i].blending) {
+ case Tegra::BlendMode::Opaque:
+ default:
+ layer_pipelines[i] = *opaque_pipeline;
+ break;
+ case Tegra::BlendMode::Premultiplied:
+ layer_pipelines[i] = *premultiplied_pipeline;
+ break;
+ case Tegra::BlendMode::Coverage:
+ layer_pipelines[i] = *coverage_pipeline;
+ break;
+ }
+
+ layer_it->ConfigureDraw(device, &layer_push_constants[i], &layer_descriptor_sets[i],
+ rasterizer, *sampler, image_index, configs[i], layout);
+ layer_it++;
+ }
+
+ Record(scheduler, dst, layer_push_constants, layer_descriptor_sets);
+}
+
+void WindowAdaptPass::DrawGenerated(const Device& device, Scheduler& scheduler, Layer& layer,
+ size_t generation, VkImage image, VkImageView view,
+ const Layout::FramebufferLayout& layout, Frame* dst) {
+ std::vector push_constants = layer_push_constants;
+ std::vector descriptor_sets = layer_descriptor_sets;
+ layer.ConfigureGenerated(device, &push_constants.front(), &descriptor_sets.front(), *sampler,
+ generation, image, view, layout);
+ Record(scheduler, dst, std::move(push_constants), std::move(descriptor_sets));
+}
+
+void WindowAdaptPass::Record(Scheduler& scheduler, Frame* dst,
+ std::vector push_constants,
+ std::vector descriptor_sets) const {
const VkImage host_image{*dst->image};
const VkImageView host_view{*dst->image_view};
const VkPipelineLayout graphics_pipeline_layout{*pipeline_layout};
@@ -42,33 +82,10 @@ void WindowAdaptPass::Draw(const Device& device, RasterizerVulkan& rasterizer, S
.width = dst->width,
.height = dst->height,
};
-
- const size_t layer_count = configs.size();
- std::vector push_constants(layer_count);
- std::vector descriptor_sets(layer_count);
- std::vector graphics_pipelines(layer_count);
-
- auto layer_it = layers.begin();
- for (size_t i = 0; i < layer_count; i++) {
- switch (configs[i].blending) {
- case Tegra::BlendMode::Opaque:
- default:
- graphics_pipelines[i] = *opaque_pipeline;
- break;
- case Tegra::BlendMode::Premultiplied:
- graphics_pipelines[i] = *premultiplied_pipeline;
- break;
- case Tegra::BlendMode::Coverage:
- graphics_pipelines[i] = *coverage_pipeline;
- break;
- }
-
- layer_it->ConfigureDraw(device, &push_constants[i], &descriptor_sets[i], rasterizer, *sampler,
- image_index, configs[i], layout);
- layer_it++;
- }
-
- scheduler.Record([=](vk::CommandBuffer cmdbuf) {
+ scheduler.Record([host_image, host_view, graphics_pipeline_layout, render_area,
+ graphics_pipelines = layer_pipelines,
+ push_constants = std::move(push_constants),
+ descriptor_sets = std::move(descriptor_sets)](vk::CommandBuffer cmdbuf) {
const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f;
const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f;
const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f;
@@ -80,7 +97,7 @@ void WindowAdaptPass::Draw(const Device& device, RasterizerVulkan& rasterizer, S
VK_IMAGE_LAYOUT_UNDEFINED);
BeginRendering(cmdbuf, host_view, render_area, VK_ATTACHMENT_LOAD_OP_CLEAR, clear_value);
- for (size_t i = 0; i < layer_count; i++) {
+ for (size_t i = 0; i < graphics_pipelines.size(); i++) {
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipelines[i]);
cmdbuf.PushConstants(graphics_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT,
push_constants[i]);
diff --git a/src/video_core/renderer_vulkan/present/window_adapt_pass.h b/src/video_core/renderer_vulkan/present/window_adapt_pass.h
index 4a9ec6934a..d33cacaee9 100644
--- a/src/video_core/renderer_vulkan/present/window_adapt_pass.h
+++ b/src/video_core/renderer_vulkan/present/window_adapt_pass.h
@@ -7,8 +7,10 @@
#pragma once
#include
+#include
#include "common/math_util.h"
+#include "video_core/renderer_vulkan/present/present_push_constants.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Layout {
@@ -37,9 +39,16 @@ public:
std::list& layers, std::span configs,
const Layout::FramebufferLayout& layout, Frame* dst);
+ void DrawGenerated(const Device& device, Scheduler& scheduler, Layer& layer, size_t generation,
+ VkImage image, VkImageView view, const Layout::FramebufferLayout& layout,
+ Frame* dst);
+
VkDescriptorSetLayout GetDescriptorSetLayout();
private:
+ void Record(Scheduler& scheduler, Frame* dst, std::vector push_constants,
+ std::vector descriptor_sets) const;
+
void CreateDescriptorSetLayout(const Device& device);
void CreatePipelineLayout(const Device& device);
void CreateVertexShader(const Device& device);
@@ -53,6 +62,9 @@ private:
vk::Pipeline opaque_pipeline;
vk::Pipeline premultiplied_pipeline;
vk::Pipeline coverage_pipeline;
+ std::vector layer_pipelines;
+ std::vector layer_push_constants;
+ std::vector layer_descriptor_sets;
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 12c9e3a14b..759ae3db32 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -52,23 +52,6 @@ constexpr VkExtent2D CaptureImageSize{
.height = VideoCore::Capture::LinearHeight,
};
-#ifdef HAS_LSFG
-[[nodiscard]] VkExtent2D GuestExtent(std::span framebuffers) {
- if (framebuffers.empty()) {
- return VkExtent2D{};
- }
-
- const auto& framebuffer = framebuffers.front();
- if (framebuffer.crop_rect.IsEmpty()) {
- return VkExtent2D{.width = framebuffer.width, .height = framebuffer.height};
- }
- return VkExtent2D{
- .width = static_cast(framebuffer.crop_rect.GetWidth()),
- .height = static_cast(framebuffer.crop_rect.GetHeight()),
- };
-}
-#endif
-
constexpr VkExtent3D CaptureImageExtent{
.width = VideoCore::Capture::LinearWidth,
.height = VideoCore::Capture::LinearHeight,
@@ -224,14 +207,23 @@ void RendererVulkan::Composite(std::span framebu
#ifdef HAS_LSFG
void(frame_gen.WantedGenerations(present_manager.MaxExtraFrames()));
- frame_gen.Process(device, frame, present_manager.SwapchainImageFormat(),
- GuestExtent(framebuffers));
+ const FrameGenSource source = blit_swapchain.GenerationSource();
+ frame_gen.Process(device, source.image, source.extent);
+ const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
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.GenerateInto(device, generated, generation);
+ if (!blit_swapchain.IsGenerationFree(generation)) {
+ break;
+ }
+ Frame* const generated = present_manager.TryGetRenderFrame();
+ if (generated == nullptr) {
+ break;
+ }
+ blit_swapchain.PrepareFrame(device, generated, layout);
+ const LsfgImage& output = frame_gen.Generate(device, generation);
+ blit_swapchain.DrawGenerated(device, generated, layout, generation, output.Handle(),
+ output.View());
scheduler.Flush(*generated->render_ready);
present_manager.Present(generated);
}
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 1ca8bf7938..19b9a64344 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -92,13 +92,33 @@ void BlitScreen::PrepareFrame(const Device& device, Frame* frame,
return;
}
- if (frame->width != layout.width || frame->height != layout.height) {
- WaitIdle(device);
- } else if (!present_manager.NeedsStorage(frame, true)) {
+ if (frame->width == layout.width && frame->height == layout.height) {
return;
}
- present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format, true);
+ WaitIdle(device);
+ present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format);
+}
+
+FrameGenSource BlitScreen::GenerationSource() const {
+ if (layers.empty()) {
+ return {};
+ }
+ return FrameGenSource{
+ .image = layers.front().GenerationSource(),
+ .extent = layers.front().GenerationExtent(),
+ };
+}
+
+bool BlitScreen::IsGenerationFree(size_t generation) const {
+ return !layers.empty() && layers.front().IsGenerationFree(generation);
+}
+
+void BlitScreen::DrawGenerated(const Device& device, Frame* frame,
+ const Layout::FramebufferLayout& layout, size_t generation,
+ VkImage image, VkImageView view) {
+ window_adapt->DrawGenerated(device, scheduler, layers.front(), generation, image, view, layout,
+ frame);
}
void BlitScreen::DrawToFrame(const Device& device, RasterizerVulkan& rasterizer, Frame* frame,
@@ -125,20 +145,16 @@ void BlitScreen::DrawToFrame(const Device& device, RasterizerVulkan& rasterizer,
swapchain_view_format = current_swapchain_view_format;
}
- const bool storage_required = Settings::values.frame_gen.GetValue();
if (resource_update_required) {
WaitIdle(device);
SetWindowAdaptPass(device);
if (presentation_recreate_required) {
- present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format,
- storage_required);
+ present_manager.RecreateFrame(frame, layout.width, layout.height,
+ swapchain_view_format);
}
image_index = 0;
- } else if (present_manager.NeedsStorage(frame, storage_required)) {
- present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format,
- true);
}
const VkExtent2D window_size{
@@ -146,11 +162,14 @@ void BlitScreen::DrawToFrame(const Device& device, RasterizerVulkan& rasterizer,
.height = layout.screen.GetHeight(),
};
- if (layers.size() != framebuffers.size()) {
+ const size_t generations = Settings::FrameGenMaxGenerations();
+ if (layers.size() != framebuffers.size() || generation_count != generations) {
layers.clear();
+ generation_count = generations;
for (size_t i = 0; i < framebuffers.size(); ++i) {
layers.emplace_back(device, memory_allocator, scheduler, device_memory, image_count,
- window_size, window_adapt->GetDescriptorSetLayout(), filters);
+ generation_count, window_size,
+ window_adapt->GetDescriptorSetLayout(), filters);
}
}
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index 46d42d3a73..4f1a7c78d5 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -48,6 +48,11 @@ struct FramebufferTextureInfo {
u32 scaled_height{};
};
+struct FrameGenSource {
+ VkImage image{};
+ VkExtent2D extent{};
+};
+
class BlitScreen {
public:
explicit BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory, const Device& device,
@@ -62,6 +67,13 @@ public:
void PrepareFrame(const Device& device, Frame* frame, const Layout::FramebufferLayout& layout);
+ [[nodiscard]] FrameGenSource GenerationSource() const;
+
+ [[nodiscard]] bool IsGenerationFree(size_t generation) const;
+
+ void DrawGenerated(const Device& device, Frame* frame, const Layout::FramebufferLayout& layout,
+ size_t generation, VkImage image, VkImageView view);
+
private:
void WaitIdle(const Device& device);
void SetWindowAdaptPass(const Device& device);
@@ -73,6 +85,7 @@ private:
const PresentFilters& filters;
std::size_t image_count{};
std::size_t image_index{};
+ std::size_t generation_count{};
VkFormat swapchain_view_format{};
Settings::ScalingFilter scaling_filter{};
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp
index 144a23b4b8..ec26f4dc9a 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp
@@ -7,9 +7,6 @@
#include "common/settings.h"
#include "common/thread.h"
#include "core/frontend/emu_window.h"
-#ifdef HAS_LSFG
-#include "video_core/renderer_vulkan/present/lsfg_common.h"
-#endif
#include "video_core/renderer_vulkan/vk_present_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
@@ -24,18 +21,6 @@ namespace {
constexpr size_t MAX_FRAMES_IN_FLIGHT = 7;
constexpr u32 MAX_PRESENT_ATTEMPTS = 3;
-#ifdef HAS_LSFG
-static_assert(MAX_FRAMES_IN_FLIGHT <= LSFG_MAX_TARGETS);
-#endif
-
-bool CanStoreToFrame(const vk::PhysicalDevice& physical_device, VkFormat format) {
-#ifdef HAS_LSFG
- const VkFormatProperties props{physical_device.GetFormatProperties(format)};
- return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
-#else
- return false;
-#endif
-}
bool CanBlitToSwapchain(const vk::PhysicalDevice& physical_device, VkFormat format) {
const VkFormatProperties props{physical_device.GetFormatProperties(format)};
@@ -132,7 +117,6 @@ PresentManager::PresentManager(const vk::Instance& instance_,
, swapchain{swapchain_}
, surface{surface_}
, blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())}
- , storage_supported{CanStoreToFrame(device.GetPhysical(), swapchain.GetImageFormat())}
, use_present_thread{Settings::values.async_presentation.GetValue()}
{
SetImageCount();
@@ -162,7 +146,6 @@ PresentManager::PresentManager(const vk::Instance& instance_,
.pNext = nullptr,
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
});
- frame.storage_capable = storage_supported;
free_queue.push_back(&frame);
}
@@ -190,6 +173,17 @@ Frame* PresentManager::GetRenderFrame() {
return frame;
}
+Frame* PresentManager::TryGetRenderFrame() {
+ std::scoped_lock lock{free_mutex};
+ if (free_queue.empty() || free_queue.front()->present_done.GetStatus() != VK_SUCCESS) {
+ return nullptr;
+ }
+ Frame* const frame = free_queue.front();
+ free_queue.pop_front();
+ frame->present_done.Reset();
+ return frame;
+}
+
void PresentManager::Present(Frame* frame) {
if (use_present_thread) {
scheduler.Record([this, frame](vk::CommandBuffer) {
@@ -208,23 +202,13 @@ size_t PresentManager::MaxExtraFrames() const {
return image_count - 1;
}
-bool PresentManager::NeedsStorage(const Frame* frame, bool required) const {
- return required && frame->storage_capable && !frame->storage_view;
-}
-
-void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format,
- bool storage) {
+void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height,
+ VkFormat image_view_format) {
auto& dld = device.GetLogical();
frame->width = width;
frame->height = height;
- const bool with_storage = storage && frame->storage_capable;
- VkImageUsageFlags storage_usage = 0;
- if (with_storage) {
- storage_usage = VK_IMAGE_USAGE_STORAGE_BIT;
- }
-
frame->image = memory_allocator.CreateImage({
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
@@ -242,7 +226,7 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat
.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_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | storage_usage,
+ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
@@ -272,33 +256,6 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat
.layerCount = 1,
},
});
-
- frame->storage_view = vk::ImageView{};
- if (with_storage) {
- frame->storage_view = dld.CreateImageView({
- .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .image = *frame->image,
- .viewType = VK_IMAGE_VIEW_TYPE_2D,
- .format = swapchain.GetImageFormat(),
- .components =
- {
- .r = VK_COMPONENT_SWIZZLE_IDENTITY,
- .g = VK_COMPONENT_SWIZZLE_IDENTITY,
- .b = VK_COMPONENT_SWIZZLE_IDENTITY,
- .a = VK_COMPONENT_SWIZZLE_IDENTITY,
- },
- .subresourceRange =
- {
- .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
- .baseMipLevel = 0,
- .levelCount = 1,
- .baseArrayLayer = 0,
- .layerCount = 1,
- },
- });
- }
}
void PresentManager::WaitPresent() {
@@ -358,9 +315,9 @@ void PresentManager::SetImageCount() {
#ifdef HAS_LSFG
const size_t generations = Settings::FrameGenMaxGenerations();
const size_t queued_composites = Settings::values.frame_gen_queue_target.GetValue() + 1;
- image_count =
- std::clamp((generations + 1) * queued_composites, swapchain.GetImageCount(),
- MAX_FRAMES_IN_FLIGHT);
+ const size_t baseline = swapchain.GetImageCount() + generations;
+ image_count = std::min(std::max((generations + 1) * queued_composites, baseline),
+ MAX_FRAMES_IN_FLIGHT);
#else
image_count = std::min(swapchain.GetImageCount(), MAX_FRAMES_IN_FLIGHT);
#endif
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h
index bde5bb1d5d..a59d18f420 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.h
+++ b/src/video_core/renderer_vulkan/vk_present_manager.h
@@ -32,11 +32,9 @@ struct Frame {
u32 index;
vk::Image image;
vk::ImageView image_view;
- vk::ImageView storage_view;
vk::CommandBuffer cmdbuf;
vk::Semaphore render_ready;
vk::Fence present_done;
- bool storage_capable{};
};
class PresentManager {
@@ -53,14 +51,13 @@ public:
/// Returns the last used presentation frame
Frame* GetRenderFrame();
+ [[nodiscard]] Frame* TryGetRenderFrame();
+
/// Pushes a frame for presentation
void Present(Frame* frame);
/// Recreates the present frame to match the provided parameters
- void RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format,
- bool storage);
-
- [[nodiscard]] bool NeedsStorage(const Frame* frame, bool required) const;
+ void RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format);
/// Waits for the present thread to finish presenting all queued frames.
void WaitPresent();
@@ -103,7 +100,6 @@ private:
std::mutex free_mutex;
std::jthread present_thread;
bool blit_supported;
- bool storage_supported;
bool use_present_thread;
std::atomic image_count{};
std::atomic swapchain_image_count{};
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 6c52137e08..58218a91fb 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -564,7 +564,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
const auto arch = GetNvidiaArch();
if (arch >= NvidiaArchitecture::Arch_AmpereOrNewer) {
LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
- features.shader_float16_int8.shaderFloat16 = false;
+ has_broken_float16_math = true;
}
// Use hardware depth/stencil blits instead when available
@@ -596,13 +596,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
"AMD GCN4 and earlier have broken VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT");
has_broken_cube_compatibility = true;
}
-
- // AMD drivers (2026+) have broken float16 math on DKCR
- if (features.shader_float16_int8.shaderFloat16) {
- LOG_WARNING(Render_Vulkan,
- "AMD drivers (2026+) have broken float16 math");
- features.shader_float16_int8.shaderFloat16 = false;
- }
}
if (extensions.sampler_filter_minmax && is_amd) {
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 460a3b702e..88504926c8 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -434,6 +434,10 @@ FN_MAX_LIMIT_LIST
/// Returns true if the device supports float16 natively.
bool IsFloat16Supported() const {
+ return features.shader_float16_int8.shaderFloat16 && !has_broken_float16_math;
+ }
+
+ bool IsNativeFloat16Enabled() const {
return features.shader_float16_int8.shaderFloat16;
}
@@ -1196,6 +1200,7 @@ private:
bool is_integrated{}; ///< Is GPU an iGPU.
bool has_broken_compute{}; ///< Compute shaders can cause crashes
bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit
+ bool has_broken_float16_math{};
bool has_broken_descriptor_aliasing{}; ///< Miscompiles descriptors aliased on one binding
bool has_broken_parallel_compiling{}; ///< Has broken parallel shader compiling.
bool has_renderdoc{}; ///< Has RenderDoc attached
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 641f65d82a..4d89a53be0 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -41,6 +41,37 @@
#include "yuzu/configuration/configure_post_processing.h"
#endif
+#ifdef HAS_LSFG
+#include
+#include
+#include
+#include
+#include
+#include
+#include "common/string_util.h"
+#include "video_core/frame_gen/lossless_dll.h"
+
+namespace {
+
+QString LosslessSearchDirectory() {
+ const QString suffix = QStringLiteral("/steamapps/common/Lossless Scaling");
+ const std::array roots{
+ QDir::homePath() + QStringLiteral("/.local/share/Steam"),
+ QDir::homePath() + QStringLiteral("/.steam/steam"),
+ QDir::homePath() + QStringLiteral("/.var/app/com.valvesoftware.Steam/.local/share/Steam"),
+ QStringLiteral("C:/Program Files (x86)/Steam"),
+ };
+ for (const QString& root : roots) {
+ if (QDir(root + suffix).exists()) {
+ return root + suffix;
+ }
+ }
+ return QDir::homePath();
+}
+
+} // Anonymous namespace
+#endif
+
ConfigureGraphics::ConfigureGraphics(
const Core::System& system_, std::vector& records_,
const std::function& expose_compute_option_,
@@ -322,11 +353,27 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) {
hold_graphics.emplace(setting->Id(), widget);
#endif
+#ifdef HAS_LSFG
+ } else if (setting->Id() == Settings::values.frame_gen.Id()) {
+ SetupFrameGen(widget);
+ hold_graphics.emplace(setting->Id(), widget);
+ } else if (setting->PairedSetting() == &Settings::values.frame_gen) {
+ frame_gen_widgets.push_back(widget);
+ hold_graphics.emplace(setting->Id(), widget);
+#endif
} else {
hold_graphics.emplace(setting->Id(), widget);
}
}
+#ifdef HAS_LSFG
+ if (frame_gen_checkbox != nullptr) {
+ for (QWidget* frame_gen_widget : frame_gen_widgets) {
+ frame_gen_widget->setVisible(frame_gen_checkbox->isChecked());
+ }
+ }
+#endif
+
for (const auto& [id, widget] : hold_graphics) {
graphics_layout.addWidget(widget);
}
@@ -511,3 +558,84 @@ Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
return Settings::RendererBackend::OpenGL_GLSL;
return selected_backend;
}
+
+#ifdef HAS_LSFG
+void ConfigureGraphics::SetupFrameGen(ConfigurationShared::Widget* widget) {
+ frame_gen_checkbox = widget->checkbox;
+ lossless_button = new QPushButton(widget);
+ lossless_button->setEnabled(!system.IsPoweredOn());
+ UpdateLosslessButton();
+
+ connect(lossless_button, &QAbstractButton::clicked, this, [this] { InstallLosslessDll(); });
+ connect(frame_gen_checkbox, &QCheckBox::toggled, this, [this](bool checked) {
+ for (QWidget* frame_gen_widget : frame_gen_widgets) {
+ frame_gen_widget->setVisible(checked);
+ }
+ });
+
+ QBoxLayout* row = qobject_cast(widget->layout());
+ row->insertWidget(1, lossless_button);
+}
+
+void ConfigureGraphics::UpdateLosslessButton() {
+ using VideoCore::FrameGen::LosslessStatus;
+ if (VideoCore::FrameGen::GetInstalledLosslessStatus() == LosslessStatus::Ok) {
+ lossless_button->setText(tr("Replace Lossless.dll..."));
+ } else {
+ lossless_button->setText(tr("Install Lossless.dll..."));
+ }
+}
+
+void ConfigureGraphics::InstallLosslessDll() {
+ using VideoCore::FrameGen::LosslessStatus;
+ const auto status_text = [this](LosslessStatus status) {
+ switch (status) {
+ case LosslessStatus::NotPortableExecutable:
+ return tr("The selected file is not a Windows library. Select Lossless.dll from your "
+ "Lossless Scaling installation.");
+ case LosslessStatus::MissingShaders:
+ return tr("This copy of Lossless.dll does not contain the frame generation shaders. "
+ "Update Lossless Scaling and try again.");
+ case LosslessStatus::TranslationFailed:
+ return tr("The frame generation shaders could not be translated. This version of "
+ "Lossless Scaling is not supported yet.");
+ case LosslessStatus::CacheUnusable:
+ return tr("The translated shaders could not be written to storage. Check that there "
+ "is free space available.");
+ default:
+ return tr("The selected file could not be read.");
+ }
+ };
+
+ const QString file =
+ QFileDialog::getOpenFileName(this, tr("Select Lossless.dll"), LosslessSearchDirectory(),
+ tr("Lossless Scaling library (Lossless.dll)"));
+ if (file.isEmpty()) {
+ return;
+ }
+
+ const std::filesystem::path source{Common::U16StringFromBuffer(file.utf16(), file.size())};
+ LosslessStatus status = VideoCore::FrameGen::ValidateLosslessDll(source);
+ if (status == LosslessStatus::Ok) {
+ const std::filesystem::path target = VideoCore::FrameGen::GetLosslessDllPath();
+ std::error_code error;
+ std::filesystem::create_directories(target.parent_path(), error);
+ std::filesystem::copy_file(source, target,
+ std::filesystem::copy_options::overwrite_existing, error);
+ if (error) {
+ QMessageBox::warning(this, tr("Could not install Lossless.dll"),
+ tr("The selected file could not be copied."));
+ return;
+ }
+ status = VideoCore::FrameGen::BuildShaderCache();
+ }
+
+ UpdateLosslessButton();
+ if (status == LosslessStatus::Ok) {
+ QMessageBox::information(this, tr("Lossless Scaling"),
+ tr("Lossless.dll installed successfully."));
+ return;
+ }
+ QMessageBox::warning(this, tr("Could not install Lossless.dll"), status_text(status));
+}
+#endif
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index 9002af437b..95fe1995cb 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -22,6 +22,7 @@
#include "qt_common/util/vk.h"
#include "yuzu/configuration/configuration_shared.h"
+class QCheckBox;
class QPushButton;
class QEvent;
class QObject;
@@ -42,6 +43,7 @@ class ConfigureGraphics;
namespace ConfigurationShared {
class Builder;
+class Widget;
}
class ConfigureGraphics : public ConfigurationShared::Tab {
@@ -83,6 +85,12 @@ private:
int FindIndex(u32 enumeration, int value) const;
+#ifdef HAS_LSFG
+ void SetupFrameGen(ConfigurationShared::Widget* widget);
+ void InstallLosslessDll();
+ void UpdateLosslessButton();
+#endif
+
std::unique_ptr ui;
QColor bg_color;
@@ -110,4 +118,9 @@ private:
QWidget* api_widget;
QComboBox* aspect_ratio_combobox;
QComboBox* resolution_combobox;
+#ifdef HAS_LSFG
+ QCheckBox* frame_gen_checkbox{};
+ QPushButton* lossless_button{};
+ std::vector frame_gen_widgets;
+#endif
};
diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp
index 0f59ee23a6..5dd6094b96 100644
--- a/src/yuzu/configuration/shared_widget.cpp
+++ b/src/yuzu/configuration/shared_widget.cpp
@@ -511,7 +511,12 @@ void Widget::SetupComponent(const QString& label, std::function& load_fu
layout->setContentsMargins(0, 0, 0, 0);
if (other_setting == nullptr) {
- other_setting = setting.PairedSetting();
+ Settings::BasicSetting* const paired = setting.PairedSetting();
+ const bool paired_standalone = paired != nullptr && translations.contains(paired->Id()) &&
+ !translations.at(paired->Id()).first.isEmpty();
+ if (!paired_standalone) {
+ other_setting = paired;
+ }
}
const bool require_checkbox = other_setting != nullptr && other_setting->TypeId() == "bool";
@@ -774,7 +779,12 @@ Builder::~Builder() = default;
static bool IsAndroidOnly(const Settings::BasicSetting& setting) {
const std::string& label = setting.GetLabel();
- return label.starts_with("frame_gen") || label == "emulate_bgr565";
+#ifndef HAS_LSFG
+ if (label.starts_with("frame_gen")) {
+ return true;
+ }
+#endif
+ return label == "emulate_bgr565";
}
Widget* Builder::BuildWidget(Settings::BasicSetting* setting,