mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-17 13:56:06 +00:00
Wire the full lsfg chain into the present path
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user