Experiments with polishing LSFG impl

This commit is contained in:
CamilleLaVey
2026-09-30 14:14:57 -04:00
parent bad10039ae
commit 5714c3e477
26 changed files with 495 additions and 282 deletions
+2 -2
View File
@@ -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)
@@ -346,7 +346,7 @@
<string name="frame_gen_flow_scale_auto">Match motion estimation to the game</string>
<string name="frame_gen_flow_scale_auto_description">Estimate motion at the resolution the game actually renders instead of the upscaled output. Costs nothing in accuracy, since upscaling adds no motion detail.</string>
<string name="frame_gen_flow_scale">Motion estimation resolution</string>
<string name="frame_gen_flow_scale_description">Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance.</string>
<string name="frame_gen_flow_scale_description">Resolution of the optical flow pass, as a fraction of the game\'s render resolution. Lowering it is the cheapest way to reclaim performance.</string>
<string name="frame_gen_unsupported">Frame generation unavailable</string>
<string name="frame_gen_unsupported_description">This GPU driver lacks the Vulkan memory model or half precision (float16) support that the Lossless Scaling shaders require.</string>
<string name="lossless_scaling_setup_description">Optional. Provide your own Lossless.dll to enable frame generation later</string>
@@ -154,6 +154,25 @@ std::unique_ptr<TranslationMap> 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 "
@@ -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<f32>(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<f32>(guest_extent.width) *
Settings::values.resolution_info.up_factor;
const f32 ratio = rendered_width / static_cast<f32>(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<f32>(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<s32>(extent.width),
.y = static_cast<s32>(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;
@@ -3,6 +3,7 @@
#pragma once
#include <array>
#include <optional>
#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<LsfgShaders> shaders;
std::optional<LsfgChain> chain;
std::array<LsfgImage, LSFG_MAX_GENERATIONS> 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{};
@@ -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<FSR>(device, memory_allocator, image_count, output_size);
sr_filter.emplace<FSR>(device, memory_allocator, slot_count, output_size);
} else if (filters.get_scaling_filter() == Settings::ScalingFilter::Sgsr) {
sr_filter.emplace<SGSR>(device, memory_allocator, image_count, output_size, false);
sr_filter.emplace<SGSR>(device, memory_allocator, slot_count, output_size, false);
} else if (filters.get_scaling_filter() == Settings::ScalingFilter::SgsrEdge) {
sr_filter.emplace<SGSR>(device, memory_allocator, image_count, output_size, true);
sr_filter.emplace<SGSR>(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<FXAA>(&anti_alias)) {
fxaa->Draw(device, scheduler, image_index, &source_image, &source_image_view);
source_extent = processed_extent;
} else if (auto* smaa = std::get_if<SMAA>(&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<FSR>(&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<f32> crop_rect = generation_crop;
if (auto* fsr = std::get_if<FSR>(&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<SGSR>(&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);
}
+22 -1
View File
@@ -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<u64> resource_ticks{};
std::vector<u64> generation_ticks{};
VkImage generation_source{};
VkExtent2D generation_extent{};
VkExtent2D generation_render_extent{};
Common::Rectangle<f32> generation_crop{};
};
} // namespace Vulkan
@@ -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
@@ -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) {
@@ -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,
@@ -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;
@@ -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<VkDescriptorSetLayout> layouts(
LSFG_GENERATION_SLOTS * LSFG_MAX_TARGETS * 2, pass.SetLayout());
const std::vector<VkDescriptorSetLayout> 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);
}
@@ -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<VkDescriptorSet, 2> descriptor_sets{};
VkImageView view{};
};
struct Generation {
std::array<Target, LSFG_MAX_TARGETS> targets{};
VkBuffer buffer{};
};
@@ -10,7 +10,7 @@ namespace Vulkan {
LsfgShaders::LsfgShaders(const Device& device) {
if (!device.IsVulkanMemoryModelSupported() || !device.HasNullDescriptor() ||
!device.IsFloat16Supported()) {
!device.IsNativeFloat16Enabled()) {
return;
}
@@ -34,7 +34,47 @@ void WindowAdaptPass::Draw(const Device& device, RasterizerVulkan& rasterizer, S
std::list<Layer>& layers,
std::span<const Tegra::FramebufferConfig> 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<PresentPushConstants> push_constants = layer_push_constants;
std::vector<VkDescriptorSet> 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<PresentPushConstants> push_constants,
std::vector<VkDescriptorSet> 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<PresentPushConstants> push_constants(layer_count);
std::vector<VkDescriptorSet> descriptor_sets(layer_count);
std::vector<VkPipeline> 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]);
@@ -7,8 +7,10 @@
#pragma once
#include <list>
#include <vector>
#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<Layer>& layers, std::span<const Tegra::FramebufferConfig> 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<PresentPushConstants> push_constants,
std::vector<VkDescriptorSet> 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<VkPipeline> layer_pipelines;
std::vector<PresentPushConstants> layer_push_constants;
std::vector<VkDescriptorSet> layer_descriptor_sets;
};
} // namespace Vulkan
@@ -52,23 +52,6 @@ constexpr VkExtent2D CaptureImageSize{
.height = VideoCore::Capture::LinearHeight,
};
#ifdef HAS_LSFG
[[nodiscard]] VkExtent2D GuestExtent(std::span<const Tegra::FramebufferConfig> 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<u32>(framebuffer.crop_rect.GetWidth()),
.height = static_cast<u32>(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<const Tegra::FramebufferConfig> 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);
}
@@ -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);
}
}
@@ -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{};
@@ -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<size_t>((generations + 1) * queued_composites, swapchain.GetImageCount(),
MAX_FRAMES_IN_FLIGHT);
const size_t baseline = swapchain.GetImageCount() + generations;
image_count = std::min<size_t>(std::max((generations + 1) * queued_composites, baseline),
MAX_FRAMES_IN_FLIGHT);
#else
image_count = std::min<size_t>(swapchain.GetImageCount(), MAX_FRAMES_IN_FLIGHT);
#endif
@@ -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<std::size_t> image_count{};
std::atomic<std::size_t> swapchain_image_count{};
@@ -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) {
@@ -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
@@ -41,6 +41,37 @@
#include "yuzu/configuration/configure_post_processing.h"
#endif
#ifdef HAS_LSFG
#include <array>
#include <filesystem>
#include <system_error>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
#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<VkDeviceInfo::Record>& records_,
const std::function<void()>& 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<QBoxLayout*>(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
@@ -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::ConfigureGraphics> 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<QWidget*> frame_gen_widgets;
#endif
};
+12 -2
View File
@@ -511,7 +511,12 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& 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,