mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-10-06 14:09:58 +00:00
Experiments with polishing LSFG impl
This commit is contained in:
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user