Wire the full lsfg chain into the present path

This commit is contained in:
CamilleLaVey
2026-08-12 23:56:52 -04:00
parent 52e0b2a005
commit 44e3211d1c
9 changed files with 374 additions and 256 deletions
+2
View File
@@ -131,6 +131,8 @@ add_library(video_core STATIC
renderer_vulkan/present/lsfg_alpha.h
renderer_vulkan/present/lsfg_beta.cpp
renderer_vulkan/present/lsfg_beta.h
renderer_vulkan/present/lsfg_chain.cpp
renderer_vulkan/present/lsfg_chain.h
renderer_vulkan/present/lsfg_common.cpp
renderer_vulkan/present/lsfg_common.h
renderer_vulkan/present/lsfg_delta.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include <string>
#include <vector>
#include "common/fs/file.h"
#include "common/fs/fs.h"
@@ -18,26 +19,124 @@ namespace Vulkan {
namespace {
constexpr f32 LSFG_FLOW_SCALE = 1.0f;
constexpr size_t COLOR_CHANNELS = 4;
void WriteGrayscalePgm(const std::filesystem::path& path, VkExtent2D extent,
std::span<const u8> pixels) {
bool IsBlueFirst(VkFormat format) {
return format == VK_FORMAT_B8G8R8A8_UNORM || format == VK_FORMAT_B8G8R8A8_SRGB;
}
void WritePortablePixmap(const std::filesystem::path& path, const std::string& magic,
VkExtent2D extent, std::span<const u8> pixels) {
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write,
Common::FS::FileType::BinaryFile};
if (!file.IsOpen()) {
return;
}
const std::string header =
"P5\n" + std::to_string(extent.width) + " " + std::to_string(extent.height) + "\n255\n";
const std::string header = magic + "\n" + std::to_string(extent.width) + " " +
std::to_string(extent.height) + "\n255\n";
if (file.Write(header) != header.size()) {
return;
}
const size_t expected = static_cast<size_t>(extent.width) * extent.height;
void(file.Write(pixels.subspan(0, std::min(expected, pixels.size()))));
void(file.Write(pixels));
void(file.Flush());
}
void WriteGrayscalePgm(const std::filesystem::path& path, VkExtent2D extent,
std::span<const u8> pixels) {
const size_t expected = static_cast<size_t>(extent.width) * extent.height;
WritePortablePixmap(path, "P5", extent, pixels.subspan(0, std::min(expected, pixels.size())));
}
void WriteColorPpm(const std::filesystem::path& path, VkExtent2D extent,
std::span<const u8> pixels, bool blue_first) {
const size_t pixel_count = static_cast<size_t>(extent.width) * extent.height;
if (pixels.size() < pixel_count * COLOR_CHANNELS) {
return;
}
std::vector<u8> rgb(pixel_count * 3);
for (size_t i = 0; i < pixel_count; ++i) {
const u8 first = pixels[i * COLOR_CHANNELS];
const u8 green = pixels[i * COLOR_CHANNELS + 1];
const u8 third = pixels[i * COLOR_CHANNELS + 2];
rgb[i * 3] = blue_first ? third : first;
rgb[i * 3 + 1] = green;
rgb[i * 3 + 2] = blue_first ? first : third;
}
WritePortablePixmap(path, "P6", extent, rgb);
}
void CopyPresentedFrame(vk::CommandBuffer cmdbuf, VkImage source, LsfgImage& destination,
VkExtent2D extent) {
const auto make_barrier = [](VkImage image, VkAccessFlags src_access, VkAccessFlags dst_access,
VkImageLayout old_layout, VkImageLayout new_layout) {
return VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = src_access,
.dstAccessMask = dst_access,
.oldLayout = old_layout,
.newLayout = new_layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image,
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
};
const std::array before{
make_barrier(source, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL),
make_barrier(destination.Handle(), VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
destination.Layout(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL),
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, {}, before);
const VkImageCopy region{
.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},
};
cmdbuf.CopyImage(source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, destination.Handle(),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, region);
const std::array after{
make_barrier(source, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL),
make_barrier(destination.Handle(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL),
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, {}, {}, after);
destination.SetLayout(VK_IMAGE_LAYOUT_GENERAL);
}
} // Anonymous namespace
FrameGen::FrameGen(MemoryAllocator& memory_allocator_, Scheduler& scheduler_)
@@ -45,7 +144,7 @@ FrameGen::FrameGen(MemoryAllocator& memory_allocator_, Scheduler& scheduler_)
FrameGen::~FrameGen() = default;
void FrameGen::Process(const Device& device, Frame* frame) {
void FrameGen::Process(const Device& device, Frame* frame, VkFormat format) {
if (unavailable || !Settings::values.frame_gen.GetValue()) {
return;
}
@@ -59,54 +158,77 @@ void FrameGen::Process(const Device& device, Frame* frame) {
}
const VkExtent2D extent{.width = frame->width, .height = frame->height};
if (!mipmaps || built_extent.width != extent.width || built_extent.height != extent.height) {
Rebuild(device, extent);
if (!chain || built_extent.width != extent.width || built_extent.height != extent.height ||
built_format != format) {
Rebuild(device, extent, format);
}
mipmaps->Dispatch(device, scheduler, *frame->image, *frame->image_view, frame_count);
++frame_count;
const u64 count = frame_count++;
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, source = *frame->image, extent, count](vk::CommandBuffer cmdbuf) {
CopyPresentedFrame(cmdbuf, source, chain->Input(count), extent);
chain->Dispatch(cmdbuf, count);
});
const bool dump_requested = Settings::values.frame_gen_dump_flow.GetValue();
if (!dump_requested) {
dumped = false;
} else if (!dumped) {
DumpFlowPyramid(device);
DumpDebugImages();
dumped = true;
}
}
void FrameGen::Rebuild(const Device& device, VkExtent2D extent) {
void FrameGen::Rebuild(const Device& device, VkExtent2D extent, VkFormat format) {
scheduler.Finish();
mipmaps.emplace(device, memory_allocator, *shaders, extent, LSFG_FLOW_SCALE);
chain.reset();
chain.emplace(device, memory_allocator, *shaders, extent, format, LSFG_FLOW_SCALE);
built_extent = extent;
built_format = format;
frame_count = 0;
}
void FrameGen::DumpFlowPyramid(const Device& device) {
void FrameGen::DumpDebugImages() {
const std::filesystem::path directory =
Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / "debug";
if (!Common::FS::CreateDirs(directory)) {
return;
}
for (size_t level = 0; level < LSFG_MIP_LEVELS; ++level) {
const VkExtent2D extent = mipmaps->GetLevelExtent(level);
const VkDeviceSize size = static_cast<VkDeviceSize>(extent.width) * extent.height;
vk::Buffer readback = CreateWrappedBuffer(memory_allocator, size, MemoryUsage::Download);
const auto readback = [&](LsfgImage& image, VkDeviceSize size) {
vk::Buffer buffer = CreateWrappedBuffer(memory_allocator, size, MemoryUsage::Download);
const VkExtent2D extent = image.Extent();
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([image = mipmaps->GetLevelImage(level), dst = *readback,
scheduler.Record([handle = image.Handle(), dst = *buffer,
extent](vk::CommandBuffer cmdbuf) {
DownloadColorImage(cmdbuf, image, dst,
VkExtent3D{.width = extent.width, .height = extent.height,
DownloadColorImage(cmdbuf, handle, dst,
VkExtent3D{.width = extent.width,
.height = extent.height,
.depth = 1});
});
scheduler.Finish();
readback.Invalidate();
buffer.Invalidate();
return buffer;
};
for (size_t level = 0; level < LSFG_MIP_LEVELS; ++level) {
LsfgImage& image = chain->FlowLevel(level);
const VkExtent2D extent = image.Extent();
const VkDeviceSize size = static_cast<VkDeviceSize>(extent.width) * extent.height;
vk::Buffer buffer = readback(image, size);
WriteGrayscalePgm(directory / ("flow_mip" + std::to_string(level) + ".pgm"), extent,
readback.Mapped());
buffer.Mapped());
}
LsfgImage& output = chain->Output();
const VkExtent2D extent = output.Extent();
const VkDeviceSize size =
static_cast<VkDeviceSize>(extent.width) * extent.height * COLOR_CHANNELS;
vk::Buffer buffer = readback(output, size);
WriteColorPpm(directory / "generated.ppm", extent, buffer.Mapped(), IsBlueFirst(built_format));
}
} // namespace Vulkan
@@ -6,7 +6,7 @@
#include <optional>
#include "common/common_types.h"
#include "video_core/renderer_vulkan/present/lsfg_mipmaps.h"
#include "video_core/renderer_vulkan/present/lsfg_chain.h"
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
@@ -21,18 +21,19 @@ public:
explicit FrameGen(MemoryAllocator& memory_allocator, Scheduler& scheduler);
~FrameGen();
void Process(const Device& device, Frame* frame);
void Process(const Device& device, Frame* frame, VkFormat format);
private:
void Rebuild(const Device& device, VkExtent2D extent);
void DumpFlowPyramid(const Device& device);
void Rebuild(const Device& device, VkExtent2D extent, VkFormat format);
void DumpDebugImages();
MemoryAllocator& memory_allocator;
Scheduler& scheduler;
std::optional<LsfgShaders> shaders;
std::optional<LsfgMipmaps> mipmaps;
std::optional<LsfgChain> chain;
VkExtent2D built_extent{};
VkFormat built_format{VK_FORMAT_UNDEFINED};
u64 frame_count{};
bool unavailable{};
bool dumped{};
@@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include "video_core/renderer_vulkan/present/lsfg_chain.h"
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
namespace {
constexpr u32 DESCRIPTOR_SET_COUNT = 256;
constexpr size_t FIRST_DELTA_LEVEL = 4;
} // Anonymous namespace
LsfgChain::LsfgChain(const Device& device, MemoryAllocator& memory_allocator,
const LsfgShaders& shaders, VkExtent2D extent, VkFormat format,
f32 flow_scale)
: resources{device, memory_allocator, flow_scale},
descriptor_pool{CreateLsfgDescriptorPool(device, DESCRIPTOR_SET_COUNT)} {
for (auto& image : frames) {
image = LsfgImage(device, memory_allocator, extent, format);
}
mipmaps = LsfgMipmaps(device, memory_allocator, shaders, resources, descriptor_pool, frames,
flow_scale);
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
alpha[i] = LsfgAlpha(device, memory_allocator, shaders, resources, descriptor_pool,
mipmaps.Output(i));
}
beta = LsfgBeta(device, memory_allocator, shaders, resources, descriptor_pool,
alpha[0].Outputs());
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
const size_t level = LSFG_MIP_LEVELS - 1 - i;
gamma[i] = LsfgGamma(device, memory_allocator, shaders, resources, descriptor_pool,
alpha[level].Outputs(),
beta.Output(std::min(level, LSFG_BETA_OUTPUTS - 1)),
i == 0 ? nullptr : &gamma[i - 1].Output());
if (i < FIRST_DELTA_LEVEL) {
continue;
}
const size_t index = i - FIRST_DELTA_LEVEL;
delta[index] = LsfgDelta(
device, memory_allocator, shaders, resources, descriptor_pool, alpha[level].Outputs(),
beta.Output(level), i == FIRST_DELTA_LEVEL ? nullptr : &gamma[i - 1].Output(),
i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output1(),
i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output2());
}
generate = LsfgGenerate(device, memory_allocator, shaders, resources, descriptor_pool, frames,
gamma[LSFG_MIP_LEVELS - 1].Output(),
delta[LSFG_DELTA_INSTANCES - 1].Output1(),
delta[LSFG_DELTA_INSTANCES - 1].Output2(), format);
}
void LsfgChain::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
mipmaps.Dispatch(cmdbuf, frame_count);
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
alpha[LSFG_MIP_LEVELS - 1 - i].Dispatch(cmdbuf, frame_count);
}
beta.Dispatch(cmdbuf, frame_count);
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
gamma[i].Dispatch(cmdbuf, frame_count);
if (i >= FIRST_DELTA_LEVEL) {
delta[i - FIRST_DELTA_LEVEL].Dispatch(cmdbuf, frame_count);
}
}
generate.Dispatch(cmdbuf, frame_count);
}
} // namespace Vulkan
@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include "common/common_types.h"
#include "video_core/renderer_vulkan/present/lsfg_alpha.h"
#include "video_core/renderer_vulkan/present/lsfg_beta.h"
#include "video_core/renderer_vulkan/present/lsfg_common.h"
#include "video_core/renderer_vulkan/present/lsfg_delta.h"
#include "video_core/renderer_vulkan/present/lsfg_gamma.h"
#include "video_core/renderer_vulkan/present/lsfg_generate.h"
#include "video_core/renderer_vulkan/present/lsfg_mipmaps.h"
namespace Vulkan {
class Device;
class LsfgShaders;
constexpr size_t LSFG_DELTA_INSTANCES = 3;
class LsfgChain {
public:
LsfgChain(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
VkExtent2D extent, VkFormat format, f32 flow_scale);
LsfgChain(const LsfgChain&) = delete;
LsfgChain& operator=(const LsfgChain&) = delete;
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count);
[[nodiscard]] LsfgImage& Input(u64 frame_count) {
return frames[frame_count % frames.size()];
}
[[nodiscard]] LsfgImage& Output() {
return generate.Output();
}
[[nodiscard]] LsfgImage& FlowLevel(size_t level) {
return mipmaps.Output(level);
}
private:
LsfgResources resources;
vk::DescriptorPool descriptor_pool;
LsfgImagePair frames;
LsfgMipmaps mipmaps;
std::array<LsfgAlpha, LSFG_MIP_LEVELS> alpha;
LsfgBeta beta;
std::array<LsfgGamma, LSFG_MIP_LEVELS> gamma;
std::array<LsfgDelta, LSFG_DELTA_INSTANCES> delta;
LsfgGenerate generate;
};
} // namespace Vulkan
@@ -28,6 +28,28 @@ struct LsfgConstants {
};
static_assert(sizeof(LsfgConstants) == 48);
vk::Image CreateChainImage(MemoryAllocator& memory_allocator, VkExtent2D extent, VkFormat format) {
const VkImageCreateInfo image_ci{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = {.width = extent.width, .height = extent.height, .depth = 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
return memory_allocator.CreateImage(image_ci);
}
vk::Buffer CreateUniformBuffer(MemoryAllocator& memory_allocator, VkDeviceSize size) {
const VkBufferCreateInfo buffer_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
@@ -69,7 +91,7 @@ VkImageMemoryBarrier MakeBarrier(const LsfgImage& image, VkAccessFlags src_acces
LsfgImage::LsfgImage(const Device& device, MemoryAllocator& memory_allocator, VkExtent2D extent_,
VkFormat format)
: extent{std::max(1u, extent_.width), std::max(1u, extent_.height)} {
image = CreateWrappedImage(memory_allocator, extent, format);
image = CreateChainImage(memory_allocator, extent, format);
view = CreateWrappedImageView(device, image, format);
}
@@ -2,233 +2,77 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cstring>
#include <vector>
#include "video_core/frame_gen/lossless_dll.h"
#include "video_core/renderer_vulkan/present/lsfg_mipmaps.h"
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
#include "video_core/renderer_vulkan/present/util.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
namespace {
constexpr VkFormat FLOW_FORMAT = VK_FORMAT_R8_UNORM;
constexpr u32 DISPATCH_TILE_SHIFT = 6;
constexpr size_t DESCRIPTOR_SET_COUNT = 2;
struct LsfgConstants {
std::array<u32, 2> input_offset;
u32 first_iter;
u32 first_iter_s;
u32 advanced_color_kind;
u32 hdr_support;
f32 resolution_inv_scale;
f32 timestamp;
f32 ui_threshold;
std::array<u32, 3> padding;
};
static_assert(sizeof(LsfgConstants) == 48);
constexpr std::array<VkDescriptorType, 3 + LSFG_MIP_LEVELS> MIPMAPS_BINDINGS{
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_SAMPLER,
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
};
vk::Sampler CreateFlowSampler(const Device& device) {
return device.GetLogical().CreateSampler(VkSamplerCreateInfo{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.magFilter = VK_FILTER_LINEAR,
.minFilter = VK_FILTER_LINEAR,
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
.mipLodBias = 0.0f,
.anisotropyEnable = VK_FALSE,
.maxAnisotropy = 0.0f,
.compareEnable = VK_FALSE,
.compareOp = VK_COMPARE_OP_NEVER,
.minLod = 0.0f,
.maxLod = VK_LOD_CLAMP_NONE,
.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
.unnormalizedCoordinates = VK_FALSE,
});
[[nodiscard]] u32 GroupCount(u32 size) {
return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT;
}
} // Anonymous namespace
LsfgMipmaps::LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator_,
const LsfgShaders& shaders, VkExtent2D input_extent, f32 flow_scale)
: memory_allocator{memory_allocator_} {
LsfgMipmaps::LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator,
const LsfgShaders& shaders, LsfgResources& resources,
vk::DescriptorPool& descriptor_pool, LsfgImagePair& frames_,
f32 flow_scale)
: frames{&frames_} {
using namespace VideoCore::FrameGen::PerformanceShader;
pass = LsfgPass(device, shaders, MIPMAPS,
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
{1, VK_DESCRIPTOR_TYPE_SAMPLER},
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
{LSFG_MIP_LEVELS, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
const VkExtent2D input_extent = (*frames)[0].Extent();
flow_extent = VkExtent2D{
.width = std::max(1u, static_cast<u32>(static_cast<f32>(input_extent.width) * flow_scale)),
.height = std::max(1u, static_cast<u32>(static_cast<f32>(input_extent.height) * flow_scale)),
};
CreateImages(device);
CreateUniformBuffer(flow_scale);
sampler = CreateFlowSampler(device);
descriptor_pool = CreateWrappedDescriptorPool(
device, DESCRIPTOR_SET_COUNT * MIPMAPS_BINDINGS.size(), DESCRIPTOR_SET_COUNT,
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_SAMPLER,
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE});
descriptor_set_layout = CreateWrappedDescriptorSetLayout(
device, std::span<const VkDescriptorType>{MIPMAPS_BINDINGS}, VK_SHADER_STAGE_COMPUTE_BIT);
const std::vector<VkDescriptorSetLayout> layouts(DESCRIPTOR_SET_COUNT,
*descriptor_set_layout);
descriptor_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
pipeline_layout = CreateWrappedPipelineLayout(device, descriptor_set_layout);
pipeline = CreateWrappedComputePipeline(
device, pipeline_layout, shaders.Get(VideoCore::FrameGen::PerformanceShader::MIPMAPS));
}
VkExtent2D LsfgMipmaps::GetLevelExtent(size_t level) const {
return VkExtent2D{
.width = std::max(1u, flow_extent.width >> level),
.height = std::max(1u, flow_extent.height >> level),
};
}
void LsfgMipmaps::CreateImages(const Device& device) {
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
images[i] = CreateWrappedImage(memory_allocator, GetLevelExtent(i), FLOW_FORMAT);
image_views[i] = CreateWrappedImageView(device, images[i], FLOW_FORMAT);
}
}
void LsfgMipmaps::CreateUniformBuffer(f32 flow_scale) {
uniform_buffer = CreateWrappedBuffer(memory_allocator, sizeof(LsfgConstants),
MemoryUsage::Upload);
const LsfgConstants constants{
.input_offset = {0, 0},
.first_iter = 0,
.first_iter_s = 0,
.advanced_color_kind = 0,
.hdr_support = 0,
.resolution_inv_scale = 1.0f / flow_scale,
.timestamp = 0.0f,
.ui_threshold = 0.5f,
.padding = {0, 0, 0},
};
const std::span<u8> mapped = uniform_buffer.Mapped();
std::memcpy(mapped.data(), &constants, sizeof(constants));
uniform_buffer.Flush();
}
void LsfgMipmaps::Dispatch(const Device& device, Scheduler& scheduler, VkImage current_image,
VkImageView current_view, u64 frame_count) {
const size_t set_index = frame_count % DESCRIPTOR_SET_COUNT;
const VkDescriptorSet set = descriptor_sets[set_index];
const VkDescriptorBufferInfo buffer_info{
.buffer = *uniform_buffer,
.offset = 0,
.range = sizeof(LsfgConstants),
};
const VkDescriptorImageInfo sampler_info{
.sampler = *sampler,
.imageView = VK_NULL_HANDLE,
.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
const VkDescriptorImageInfo sampled_info{
.sampler = VK_NULL_HANDLE,
.imageView = current_view,
.imageLayout = VK_IMAGE_LAYOUT_GENERAL,
};
std::array<VkDescriptorImageInfo, LSFG_MIP_LEVELS> storage_infos{};
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
storage_infos[i] = VkDescriptorImageInfo{
.sampler = VK_NULL_HANDLE,
.imageView = *image_views[i],
.imageLayout = VK_IMAGE_LAYOUT_GENERAL,
const VkExtent2D level_extent{
.width = flow_extent.width >> i,
.height = flow_extent.height >> i,
};
out_images[i] = LsfgImage(device, memory_allocator, level_extent, LSFG_FLOW_FORMAT);
}
std::vector<VkWriteDescriptorSet> writes;
writes.reserve(MIPMAPS_BINDINGS.size());
const auto push = [&](u32 binding, VkDescriptorType type,
const VkDescriptorImageInfo* image_info,
const VkDescriptorBufferInfo* buf_info) {
writes.push_back(VkWriteDescriptorSet{
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = set,
.dstBinding = binding,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = type,
.pImageInfo = image_info,
.pBufferInfo = buf_info,
.pTexelBufferView = nullptr,
});
};
const std::vector<VkDescriptorSetLayout> layouts(descriptor_sets.size(), pass.SetLayout());
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
push(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, nullptr, &buffer_info);
push(1, VK_DESCRIPTOR_TYPE_SAMPLER, &sampler_info, nullptr);
push(2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, &sampled_info, nullptr);
for (u32 i = 0; i < LSFG_MIP_LEVELS; ++i) {
push(3 + i, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, &storage_infos[i], nullptr);
const VkSampler sampler = resources.GetSampler();
const VkBuffer buffer = resources.GetBuffer();
for (size_t i = 0; i < descriptor_sets.size(); ++i) {
descriptor_sets[i] = owned_sets[i];
LsfgDescriptorWriter(descriptor_sets[i])
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
.AddSampler(sampler)
.AddSampledImage((*frames)[i])
.AddStorageImages(out_images)
.Build(device);
}
}
device.GetLogical().UpdateDescriptorSets(writes, {});
void LsfgMipmaps::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
const size_t slot = frame_count % descriptor_sets.size();
const u32 groups_x = (flow_extent.width + (1u << DISPATCH_TILE_SHIFT) - 1) >>
DISPATCH_TILE_SHIFT;
const u32 groups_y = (flow_extent.height + (1u << DISPATCH_TILE_SHIFT) - 1) >>
DISPATCH_TILE_SHIFT;
LsfgBarriers(cmdbuf).WriteToRead((*frames)[slot]).ReadToWriteAll(out_images).Build();
std::array<VkImage, LSFG_MIP_LEVELS> raw_images{};
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
raw_images[i] = *images[i];
}
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([raw_images, current_image, set, groups_x, groups_y,
layout = *pipeline_layout,
compute_pipeline = *pipeline](vk::CommandBuffer cmdbuf) {
const VkImageMemoryBarrier input_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = current_image,
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, {}, {}, input_barrier);
for (const VkImage image : raw_images) {
TransitionImageLayout(cmdbuf, image, VK_IMAGE_LAYOUT_GENERAL,
VK_IMAGE_LAYOUT_UNDEFINED);
}
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, compute_pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, set, {});
cmdbuf.Dispatch(groups_x, groups_y, 1);
});
pass.Bind(cmdbuf, descriptor_sets[slot]);
cmdbuf.Dispatch(GroupCount(flow_extent.width), GroupCount(flow_extent.height), 1);
}
} // namespace Vulkan
@@ -6,52 +6,37 @@
#include <array>
#include "common/common_types.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/renderer_vulkan/present/lsfg_common.h"
namespace Vulkan {
class Device;
class LsfgShaders;
class Scheduler;
constexpr size_t LSFG_MIP_LEVELS = 7;
class LsfgMipmaps {
public:
explicit LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator,
const LsfgShaders& shaders, VkExtent2D input_extent, f32 flow_scale);
LsfgMipmaps() = default;
LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
LsfgImagePair& frames, f32 flow_scale);
void Dispatch(const Device& device, Scheduler& scheduler, VkImage current_image,
VkImageView current_view, u64 frame_count);
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count);
[[nodiscard]] VkImageView GetLevelView(size_t level) const {
return *image_views[level];
[[nodiscard]] LsfgImage& Output(size_t level) {
return out_images[level];
}
[[nodiscard]] VkImage GetLevelImage(size_t level) const {
return *images[level];
}
[[nodiscard]] VkExtent2D GetLevelExtent(size_t level) const;
private:
void CreateImages(const Device& device);
void CreateUniformBuffer(f32 flow_scale);
LsfgImagePair* frames{};
LsfgPass pass;
std::array<VkDescriptorSet, 2> descriptor_sets{};
vk::DescriptorSets owned_sets;
MemoryAllocator& memory_allocator;
VkExtent2D flow_extent{};
vk::Buffer uniform_buffer;
vk::Sampler sampler;
vk::DescriptorPool descriptor_pool;
vk::DescriptorSetLayout descriptor_set_layout;
vk::DescriptorSets descriptor_sets;
vk::PipelineLayout pipeline_layout;
vk::Pipeline pipeline;
std::array<vk::Image, LSFG_MIP_LEVELS> images;
std::array<vk::ImageView, LSFG_MIP_LEVELS> image_views;
std::array<LsfgImage, LSFG_MIP_LEVELS> out_images;
};
} // namespace Vulkan
@@ -193,7 +193,7 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
render_window.GetFramebufferLayout(), swapchain.GetImageCount(),
swapchain.GetImageViewFormat());
frame_gen.Process(device, frame);
frame_gen.Process(device, frame, swapchain.GetImageFormat());
scheduler.Flush(*frame->render_ready);