feat(bindless): Write backend base

This commit is contained in:
PancakeTAS 2026-04-25 21:03:11 +02:00
parent 3211d8f9e8
commit 93816ef4ab
No known key found for this signature in database
35 changed files with 249 additions and 2864 deletions

View file

@ -1,143 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include <exception>
#include <filesystem>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace lsfgvk::backend {
class [[gnu::visibility("default")]] ContextImpl;
class [[gnu::visibility("default")]] InstanceImpl;
using Context = ContextImpl;
///
/// Primitive exception class that deliveres a detailed error message
///
class [[gnu::visibility("default")]] error : public std::runtime_error {
public:
///
/// Construct an error
///
/// @param msg Error message.
/// @param inner Inner exception.
///
explicit error(const std::string &msg, const std::exception &inner);
///
/// Construct an error
///
/// @param msg Error message.
///
explicit error(const std::string &msg);
error(const error &) = default;
error &operator=(const error &) = default;
error(error &&) = default;
error &operator=(error &&) = default;
~error() override;
};
/// Function type for picking a device based on its name and IDs
using DevicePicker = std::function<bool(
const std::string& deviceName,
std::pair<const std::string&, const std::string&> ids, // (vendor ID, device ID) 0xXXXX format
const std::optional<std::string>& pci // (bus:slot.func) if available, no padded zeros
)>;
///
/// Main entry point of the library
///
class [[gnu::visibility("default")]] Instance {
public:
///
/// Create a lsfg-vk instance
///
/// @param devicePicker Function that picks a physical device based on some identifiers.
/// @param shaderDllPath Path to the Lossless.dll file to load shaders from.
/// @param allowLowPrecision Whether to load low-precision (FP16) shaders if supported.
///
/// @throws backend::error on failure
///
Instance(
const DevicePicker& devicePicker,
const std::filesystem::path& shaderDllPath,
bool allowLowPrecision
);
///
/// Open a frame generation context.
///
/// The VkFormat of the exchanged images is inferred from whether hdr is true or false:
/// - false: VK_FORMAT_R8G8B8A8_UNORM
/// - true: VK_FORMAT_R16G16B16A16_SFLOAT
///
/// The application and library must keep track of the frame index. When the next frame
/// is ready, signal the syncFd with one increment (with the first trigger being 1).
/// Each generated frame will increment the semaphore by one:
/// - Application signals 1 -> Start generating with (curr, next) source images
/// - Library signals 1 -> First frame between (curr, next) is ready
/// - Library signals N -> N-th frame between (curr, next) is ready
/// - Application signals N+1 -> Start generating with (next, curr) source images
///
/// @param sourceFds Pair of file descriptors for the source images alternated between.
/// @param destFds Vector with file descriptors to import output images from.
/// @param syncFd File descriptor for the timeline semaphore used for synchronization.
/// @param width Width of the images.
/// @param height Height of the images.
/// @param hdr Whether the images are HDR.
/// @param flow Motion flow factor.
/// @param perf Whether to enable performance mode.
///
/// @throws backend::error on failure
///
Context& openContext(
std::pair<int, int> sourceFds,
const std::vector<int>& destFds,
int syncFd,
uint32_t width, uint32_t height,
bool hdr, float flow, bool perf
);
///
/// Schedule a new set of generated frames.
///
/// @param context Context to use.
/// @throws backend::error on failure
///
void scheduleFrames(Context& context);
///
/// Close a frame generation context
///
/// @param context Context to close.
///
void closeContext(const Context& context);
// Non-copyable and non-movable
Instance(const Instance&) = delete;
Instance& operator=(const Instance&) = delete;
Instance(Instance&&) = delete;
Instance& operator=(Instance&&) = delete;
virtual ~Instance();
private:
std::unique_ptr<InstanceImpl> m_impl;
std::vector<std::unique_ptr<Context>> m_contexts;
};
///
/// Make all lsfg-vk instances leaking.
/// This is to workaround a bug in the Vulkan loader, which
/// makes it impossible to destroy Vulkan instances and devices.
///
void makeLeaking();
}

View file

@ -0,0 +1,138 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include <cstdint>
#include <filesystem>
#include <memory>
#include <string>
namespace lsfgvk {
/// Forward declaration of implementation classes
namespace priv {
struct [[gnu::visibility("default")]] Instance;
struct [[gnu::visibility("default")]] Context;
}
///
/// Main entrypoint of the library
///
class [[gnu::visibility("default")]] Instance {
friend class Context;
public:
///
/// Create a lsfg-vk instance
///
/// The device identifier may be one of:
/// - Device name (e.g. "NVIDIA GeForce RTX 5080")
/// - Vendor ID + Device ID in lowercase hexadecimal (e.g. "10de:2c02")
/// - PCI bus ID with padded zeroes (e.g. "0000:01:00.0")
///
/// @param deviceId Device identifier (see above)
/// @param lsfgvkDllPath Path to the lsfg-vk DLL file
/// @param allowFP16 Whether to allow usage of fp16 shader variants
/// @throws std::runtime_error on failure
///
Instance(
const std::string& deviceId,
const std::filesystem::path& lsfgvkDllPath,
bool allowFP16
);
// Non-copyable, non-movable
Instance(const Instance&) = delete;
Instance& operator=(const Instance&) = delete;
Instance(Instance&&) = delete;
Instance& operator=(Instance&&) = delete;
~Instance();
private:
std::unique_ptr<priv::Instance> m_priv;
};
///
/// File descriptors exported from a context, the user must close them after use.
///
struct FileDescriptors {
///
/// File descriptor for a Vulkan memory allocation containing
/// a 2D array of RGBA8 pixels with length 2 and optimal allocation.
///
/// Starting at iteration 0, the next frame for which frames should be interpolated
/// inbetween should be placed in image `iteration % 2`.
///
int sourceFd;
///
/// File descriptor for a Vulkan memory allocation containing a single RGBA8
/// image into which each generated frame will be written to.
///
int destinationFd;
///
/// File descriptor for a timeline semaphore. When scheduling frames for generation,
/// a specific value is waited for and signaled on return. It is up to the user to ensure
/// the destination image is not overwritten before it is read.
///
int syncFd;
};
/// A context for generating frames
///
class [[gnu::visibility("default")]] Context {
public:
///
/// Create a frame generation context
///
/// @param instance Parent instance
/// @param width Image width
/// @param height Image height
/// @param flowScale Flow estimation scale factor
/// @param performanceMode Whether to enable performance mode
/// @throws std::runtime_error on failure
///
Context(
const Instance& instance,
uint32_t width,
uint32_t height,
float flowScale,
bool performanceMode
);
///
/// Export the internal resources
///
/// @return File descriptors for internal resources
/// @throws std::runtime_error on failure
///
[[nodiscard]] FileDescriptors exportFds() const;
///
/// Dispatch frame generation
///
/// Let `so - 1` be the current value of the timeline semaphore, starting at 0.
/// The user must signal `so` to start the generation of the next frame, after
/// which lsfg-vk will signal `so + 1`. The user must ensure the previously
/// generated frame is read before signaling the next one (at `so + 2` and so on).
///
/// @param total Total number of frames to generate
/// @throws std::runtime_error on failure
///
void dispatch(uint32_t total);
///
/// Wait for the device to be idle
///
void idle() const;
// Non-copyable, non-movable
Context(const Context&) = delete;
Context& operator=(const Context&) = delete;
Context(Context&&) = delete;
Context& operator=(Context&&) = delete;
~Context();
private:
std::unique_ptr<priv::Context> m_priv;
};
}

View file

@ -1,213 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "dll_reader.hpp"
#include "lsfg-vk-common/helpers/errors.hpp"
#include <ios>
#include <unordered_map>
#include <filesystem>
#include <algorithm>
#include <iostream>
#include <optional>
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <utility>
#include <vector>
#include <array>
#include <span>
using namespace lsfgvk;
using namespace lsfgvk::backend;
namespace {
/// DOS file header
struct DOSHeader {
uint16_t magic; // 0x5A4D
std::array<uint16_t, 29> pad;
int32_t pe_offset; // file offset
};
/// PE header
struct PEHeader {
uint32_t signature; // "PE\0\0"
std::array<uint16_t, 1> pad1;
uint16_t sect_count;
std::array<uint16_t, 6> pad2;
uint16_t opt_hdr_size;
std::array<uint16_t, 1> pad3;
};
/// (partial!) PE optional header
struct PEOptionalHeader {
uint16_t magic; // 0x20B
std::array<uint16_t, 63> pad4;
std::pair<uint32_t, uint32_t> resource_table; // file offset/size
};
/// Section header
struct SectionHeader {
std::array<uint16_t, 4> pad1;
uint32_t vsize; // virtual
uint32_t vaddress;
uint32_t fsize; // raw
uint32_t foffset;
std::array<uint16_t, 8> pad2;
};
/// Resource directory
struct ResourceDirectory {
std::array<uint16_t, 6> pad;
uint16_t name_count;
uint16_t id_count;
};
/// Resource directory entry
struct ResourceDirectoryEntry {
uint32_t id;
uint32_t offset; // high bit = directory
};
/// Resource data entry
struct ResourceDataEntry {
uint32_t offset;
uint32_t size;
std::array<uint32_t, 2> pad;
};
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-container"
namespace {
/// Safely cast a vector to a pointer of type T
template<typename T>
const T* safe_cast(const std::vector<uint8_t>& data, size_t offset) {
const size_t end = offset + sizeof(T);
if (end > data.size() || end < offset)
throw ls::error("buffer overflow/underflow during safe cast");
return reinterpret_cast<const T*>(&data.at(offset));
}
/// Safely cast a vector to a span of T
template<typename T>
std::span<const T> span_cast(const std::vector<uint8_t>& data, size_t offset, size_t count) {
const size_t end = offset + (count * sizeof(T));
if (end > data.size() || end < offset)
throw ls::error("buffer overflow/underflow during safe cast");
return std::span<const T>(reinterpret_cast<const T*>(&data.at(offset)), count);
}
}
#pragma clang diagnostic pop
std::unordered_map<uint32_t, std::vector<uint8_t>> backend::extractResourcesFromDLL(
const std::filesystem::path& dll) {
std::ifstream file(dll, std::ios::binary | std::ios::ate);
if (!file.is_open())
throw ls::error("failed to open dll file");
const std::streamsize size = static_cast<std::streamsize>(file.tellg());
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(static_cast<size_t>(size));
if (!file.read(reinterpret_cast<char*>(data.data()), size))
throw ls::error("failed to read dll file");
// parse dos header
size_t fileOffset = 0;
const auto* dosHdr = safe_cast<const DOSHeader>(data, 0);
if (dosHdr->magic != 0x5A4D)
throw ls::error("dos header magic number is incorrect");
// parse pe header
fileOffset += static_cast<size_t>(dosHdr->pe_offset);
const auto* peHdr = safe_cast<const PEHeader>(data, fileOffset);
if (peHdr->signature != 0x00004550)
throw ls::error("pe header signature is incorrect");
// parse optional pe header
fileOffset += sizeof(PEHeader);
const auto* peOptHdr = safe_cast<const PEOptionalHeader>(data, fileOffset);
if (peOptHdr->magic != 0x20B)
throw ls::error("pe format is not PE32+");
const auto& [rsrc_rva, rsrc_size] = peOptHdr->resource_table;
// locate section containing resources
std::optional<size_t> rsrc_offset;
fileOffset += peHdr->opt_hdr_size;
const auto sectHdrs = span_cast<const SectionHeader>(data, fileOffset, peHdr->sect_count);
for (const auto& sectHdr : sectHdrs) {
if (rsrc_rva < sectHdr.vaddress || rsrc_rva > (sectHdr.vaddress + sectHdr.vsize))
continue;
rsrc_offset.emplace((rsrc_rva - sectHdr.vaddress) + sectHdr.foffset);
break;
}
if (!rsrc_offset)
throw ls::error("unable to locate resource section");
// parse resource directory
fileOffset = rsrc_offset.value();
const auto* rsrcDir = safe_cast<const ResourceDirectory>(data, fileOffset);
if (rsrcDir->id_count < 3)
throw ls::error("resource directory does not have enough entries");
// find resource table with data type
std::optional<size_t> rsrc_tbl_offset;
fileOffset = rsrc_offset.value() + sizeof(ResourceDirectory);
const auto rsrcDirEntries = span_cast<const ResourceDirectoryEntry>(
data, fileOffset, rsrcDir->name_count + rsrcDir->id_count);
for (const auto& rsrcDirEntry : rsrcDirEntries) {
if (rsrcDirEntry.id != 10) // RT_RCDATA
continue;
if ((rsrcDirEntry.offset & 0x80000000) == 0)
throw ls::error("expected resource directory, found data entry");
rsrc_tbl_offset.emplace(rsrcDirEntry.offset & 0x7FFFFFFF);
}
if (!rsrc_tbl_offset)
throw ls::error("unabele to locate RT_RCDATA directory");
// parse data type resource directory
fileOffset = rsrc_offset.value() + rsrc_tbl_offset.value();
const auto* rsrcTbl = safe_cast<const ResourceDirectory>(data, fileOffset);
if (rsrcTbl->id_count < 1)
throw ls::error("RT_RCDATA directory does not have enough entries");
// collect all resources
fileOffset += sizeof(ResourceDirectory);
const auto rsrcTblEntries = span_cast<const ResourceDirectoryEntry>(
data, fileOffset, rsrcTbl->name_count + rsrcTbl->id_count);
std::unordered_map<uint32_t, std::vector<uint8_t>> resources;
for (const auto& rsrcTblEntry : rsrcTblEntries) {
if ((rsrcTblEntry.offset & 0x80000000) == 0)
throw ls::error("expected resource directory, found data entry");
// skip over language directory
fileOffset = rsrc_offset.value() + (rsrcTblEntry.offset & 0x7FFFFFFF);
const auto* langDir = safe_cast<const ResourceDirectory>(data, fileOffset);
if (langDir->id_count < 1)
throw ls::error("Incorrect language directory");
fileOffset += sizeof(ResourceDirectory);
const auto* langDirEntry = safe_cast<const ResourceDirectoryEntry>(data, fileOffset);
if ((langDirEntry->offset & 0x80000000) != 0)
throw ls::error("expected resource data entry, but found directory");
// parse resource data entry
fileOffset = rsrc_offset.value() + (langDirEntry->offset & 0x7FFFFFFF);
const auto* entry = safe_cast<const ResourceDataEntry>(data, fileOffset);
if (entry->offset < rsrc_rva || entry->offset > (rsrc_rva + rsrc_size))
throw ls::error("resource data entry points outside resource section");
// extract resource
std::vector<uint8_t> resource(entry->size);
fileOffset = (entry->offset - rsrc_rva) + rsrc_offset.value();
if (fileOffset + entry->size > data.size())
throw ls::error("resource data entry points outside file");
std::copy_n(&data.at(fileOffset), entry->size, resource.data());
resources.emplace(rsrcTblEntry.id, std::move(resource));
}
return resources;
}

View file

@ -1,19 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include <cstdint>
#include <filesystem>
#include <unordered_map>
#include <vector>
namespace lsfgvk::backend {
/// extract all resources from a DLL file
/// @param dll path to the DLL file
/// @return map of resource IDs to their binary data
/// @throws ls::error on various failure points
std::unordered_map<uint32_t, std::vector<uint8_t>> extractResourcesFromDLL(
const std::filesystem::path& dll);
}

View file

@ -1,171 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "shader_registry.hpp"
#include "lsfg-vk-common/helpers/errors.hpp"
#include "lsfg-vk-common/vulkan/shader.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <cstdint>
#include <span>
#include <string>
#include <unordered_map>
#include <vector>
using namespace lsfgvk;
using namespace lsfgvk::backend;
namespace {
/// get the source code for a shader
const std::vector<uint8_t>& getShaderSource(uint32_t id, bool fp16, bool perf,
const std::unordered_map<uint32_t, std::vector<uint8_t>>& resources) {
const size_t BASE_OFFSET = 49;
const size_t OFFSET_PERF = 23;
const size_t OFFSET_FP32 = 49;
auto it = resources.find(BASE_OFFSET + id +
(perf ? OFFSET_PERF : 0) +
(fp16 ? 0 : OFFSET_FP32));
if (it == resources.end())
throw ls::error("unable to find shader with id: " + std::to_string(id));
return it->second;
}
/// patch the generate shader
void patchGenerateShader(std::vector<uint8_t>& data, bool hdr) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-container"
auto* _ptr = data.data();
const std::span<uint32_t> words(
reinterpret_cast<uint32_t*>(_ptr),
data.size() / sizeof(uint32_t)
);
#pragma clang diagnostic pop
const uint16_t SpvOpCapability = 17;
const uint16_t SpvOpTypeImage = 25;
const uint32_t SpvCapabilityStorageImageWriteWithoutFormat = 56;
const uint32_t SpvCapabilityShader = 1;
const uint32_t SpvImageFormatRgba16f = 2;
const uint32_t SpvImageFormatRgba8 = 4;
for (size_t i = 5; i < words.size();) {
const uint32_t& word = words[i]; // NOLINT ([]-usage)
const uint16_t wc = (word >> 16);
const uint16_t op = word & 0xFFFF;
// remove write without format capability
if (op == SpvOpCapability && wc >= 2) {
uint32_t& cap = words[i + 1]; // NOLINT ([]-usage)
if (cap == SpvCapabilityStorageImageWriteWithoutFormat)
cap = SpvCapabilityShader;
}
// patch format in image instructions
if (op == SpvOpTypeImage && wc >= 9) {
const uint32_t sampled = words[i + 7]; // NOLINT ([]-usage)
if (sampled == 2)
words[i + 8] = // NOLINT ([]-usage)
hdr ? SpvImageFormatRgba16f : SpvImageFormatRgba8;
}
i += wc ? wc : 1;
}
}
}
ShaderRegistry backend::buildShaderRegistry(const vk::Vulkan& vk, bool fp16,
const std::unordered_map<uint32_t, std::vector<uint8_t>>& resources) {
// patch the generate shader
std::vector<uint8_t> generate_data = getShaderSource(256, fp16, false, resources);
std::vector<uint8_t> generate_data_hdr = generate_data;
patchGenerateShader(generate_data, false);
patchGenerateShader(generate_data_hdr, true);
// load all other shaders
#define SHADER(id, p1, p2, p3, p4) \
vk::Shader(vk, getShaderSource(id, fp16, PERF, resources), \
p1, p2, p3, p4)
return {
#define PERF false
.mipmaps = SHADER(255, 1, 7, 1, 1),
.generate = vk::Shader(vk, generate_data, 5, 1, 1, 2),
.generate_hdr = vk::Shader(vk, generate_data_hdr, 5, 1, 1, 2),
.quality = {
.alpha = {
SHADER(267, 1, 2, 0, 1),
SHADER(268, 2, 2, 0, 1),
SHADER(269, 2, 4, 0, 1),
SHADER(270, 4, 4, 0, 1)
},
.beta = {
SHADER(275, 12, 2, 0, 1),
SHADER(276, 2, 2, 0, 1),
SHADER(277, 2, 2, 0, 1),
SHADER(278, 2, 2, 0, 1),
SHADER(279, 2, 6, 1, 1)
},
.gamma = {
SHADER(257, 9, 3, 1, 2),
SHADER(259, 3, 4, 0, 1),
SHADER(260, 4, 4, 0, 1),
SHADER(261, 4, 4, 0, 1),
SHADER(262, 6, 1, 1, 2)
},
.delta = {
SHADER(257, 9, 3, 1, 2),
SHADER(263, 3, 4, 0, 1),
SHADER(264, 4, 4, 0, 1),
SHADER(265, 4, 4, 0, 1),
SHADER(266, 6, 1, 1, 2),
SHADER(258, 10, 2, 1, 2),
SHADER(271, 2, 2, 0, 1),
SHADER(272, 2, 2, 0, 1),
SHADER(273, 2, 2, 0, 1),
SHADER(274, 3, 1, 1, 2)
}
},
#undef PERF
#define PERF true
.performance = {
.alpha = {
SHADER(267, 1, 1, 0, 1),
SHADER(268, 1, 1, 0, 1),
SHADER(269, 1, 2, 0, 1),
SHADER(270, 2, 2, 0, 1)
},
.beta = {
SHADER(275, 6, 2, 0, 1),
SHADER(276, 2, 2, 0, 1),
SHADER(277, 2, 2, 0, 1),
SHADER(278, 2, 2, 0, 1),
SHADER(279, 2, 6, 1, 1)
},
.gamma = {
SHADER(257, 5, 3, 1, 2),
SHADER(259, 3, 2, 0, 1),
SHADER(260, 2, 2, 0, 1),
SHADER(261, 2, 2, 0, 1),
SHADER(262, 4, 1, 1, 2)
},
.delta = {
SHADER(257, 5, 3, 1, 2),
SHADER(263, 3, 2, 0, 1),
SHADER(264, 2, 2, 0, 1),
SHADER(265, 2, 2, 0, 1),
SHADER(266, 4, 1, 1, 2),
SHADER(258, 6, 1, 1, 2),
SHADER(271, 1, 1, 0, 1),
SHADER(272, 1, 1, 0, 1),
SHADER(273, 1, 1, 0, 1),
SHADER(274, 2, 1, 1, 2)
}
},
#undef PERF
.is_fp16 = fp16
};
#undef SHADER
}

View file

@ -1,42 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "lsfg-vk-common/vulkan/shader.hpp"
#include <array>
#include <cstdint>
#include <unordered_map>
#include <vector>
namespace lsfgvk::backend {
/// shader collection struct
struct Shaders {
std::array<vk::Shader, 4> alpha;
std::array<vk::Shader, 5> beta;
std::array<vk::Shader, 5> gamma;
std::array<vk::Shader, 10> delta;
};
/// shader registry struct
struct ShaderRegistry {
vk::Shader mipmaps;
vk::Shader generate, generate_hdr;
Shaders quality;
Shaders performance;
bool is_fp16; //!< whether the fp16 shader variants were loaded
};
/// build a shader registry from resources
/// @param vk Vulkan instance
/// @param fp16 whether to load fp16 variants
/// @param resources map of resource IDs to their binary data
/// @return constructed shader registry
/// @throws ls::error if shaders are missing
/// @throws vk::vulkan_error on Vulkan errors
ShaderRegistry buildShaderRegistry(const vk::Vulkan& vk, bool fp16,
const std::unordered_map<uint32_t, std::vector<uint8_t>>& resources);
}

View file

@ -1,56 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "limits.hpp"
#include "lsfg-vk-common/vulkan/descriptor_pool.hpp"
#include <cstddef>
#include <cstdint>
using namespace lsfgvk;
using namespace lsfgvk::backend;
namespace {
const vk::Limits BASE_LIMITS{
.sets = 51,
.uniform_buffers = 3,
.samplers = 51,
.sampled_images = 165,
.storage_images = 172
};
const vk::Limits BASE_LIMITS_PERF{
.sampled_images = 91,
.storage_images = 102
};
const vk::Limits GEN_LIMITS{
.sets = 93,
.uniform_buffers = 54,
.samplers = 147,
.sampled_images = 567,
.storage_images = 261
};
const vk::Limits GEN_LIMITS_PERF{
.sampled_images = 339,
.storage_images = 183
};
}
vk::Limits backend::calculateDescriptorPoolLimits(size_t count, bool perf) {
const auto m = static_cast<uint16_t>(count);
vk::Limits a{BASE_LIMITS};
vk::Limits b{GEN_LIMITS};
if (perf) {
a.sampled_images = BASE_LIMITS_PERF.sampled_images;
b.sampled_images = GEN_LIMITS_PERF.sampled_images;
a.storage_images = BASE_LIMITS_PERF.storage_images;
b.storage_images = GEN_LIMITS_PERF.storage_images;
}
a.sets += b.sets * m;
a.uniform_buffers += b.uniform_buffers * m;
a.samplers += b.samplers * m;
a.sampled_images += b.sampled_images * m;
a.storage_images += b.storage_images * m;
return a;
}

View file

@ -1,15 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "lsfg-vk-common/vulkan/descriptor_pool.hpp"
#include <cstddef>
namespace lsfgvk::backend {
/// calculate limits for descriptor pools
/// @param count number of images
/// @param perf whether performance mode is enabled
/// @return calculated limits
vk::Limits calculateDescriptorPoolLimits(size_t count, bool perf);
}

View file

@ -1,128 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "managed_shader.hpp"
#include "lsfg-vk-common/vulkan/buffer.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/descriptor_pool.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/sampler.hpp"
#include "lsfg-vk-common/vulkan/shader.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <functional>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk;
using namespace lsfgvk::backend;
ManagedShaderBuilder& ManagedShaderBuilder::sampled(const vk::Image& image) {
this->sampledImages.push_back(std::ref(image));
return *this;
}
ManagedShaderBuilder& ManagedShaderBuilder::sampleds(
const std::vector<vk::Image>& images,
size_t offset, size_t count) {
if (count == 0 || offset + count > images.size())
count = images.size() - offset;
for (size_t i = 0; i < count; ++i)
this->sampledImages.push_back(std::ref(images.at(offset + i)));
return *this;
}
ManagedShaderBuilder& ManagedShaderBuilder::storage(const vk::Image& image) {
this->storageImages.push_back(std::ref(image));
return *this;
}
ManagedShaderBuilder& ManagedShaderBuilder::storages(
const std::vector<vk::Image>& images,
size_t offset, size_t count) {
if (count == 0 || offset + count > images.size())
count = images.size() - offset;
for (size_t i = 0; i < count; ++i)
this->storageImages.push_back(std::ref(images.at(offset + i)));
return *this;
}
ManagedShaderBuilder& ManagedShaderBuilder::sampler(const vk::Sampler& sampler) {
this->imageSamplers.push_back(std::ref(sampler));
return *this;
}
ManagedShaderBuilder& ManagedShaderBuilder::samplers(
const std::vector<vk::Sampler>& samplers) {
for (const auto& sampler : samplers)
this->imageSamplers.push_back(std::ref(sampler));
return *this;
}
ManagedShaderBuilder& ManagedShaderBuilder::buffer(const vk::Buffer& buffer) {
this->constantBuffers.push_back(std::ref(buffer));
return *this;
}
ManagedShader ManagedShaderBuilder::build(const vk::Vulkan& vk,
const vk::DescriptorPool& pool, const vk::Shader& shader) const {
std::vector<vk::Barrier> barriers;
barriers.reserve(this->storageImages.size() + this->sampledImages.size());
for (const auto& img : this->sampledImages)
barriers.push_back({
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_SHADER_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 = img.get().handle(),
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = 1,
.layerCount = 1
}
});
for (const auto& img : this->storageImages)
barriers.push_back({
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_SHADER_READ_BIT,
.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = img.get().handle(),
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = 1,
.layerCount = 1
}
});
return {
std::ref(shader),
std::move(barriers),
vk::DescriptorSet(vk, pool, shader,
this->sampledImages,
this->storageImages,
this->imageSamplers,
this->constantBuffers)
};
}
void ManagedShader::dispatch(const vk::Vulkan& vk, const vk::CommandBuffer& cmd,
VkExtent2D extent) const {
cmd.dispatch(vk, this->shader,
this->descriptorSet,
this->barriers,
extent.width, extent.height, 1
);
}

View file

@ -1,98 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/descriptor_pool.hpp"
#include "lsfg-vk-common/vulkan/descriptor_set.hpp"
#include "lsfg-vk-common/vulkan/shader.hpp"
#include <cstddef>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace lsfgvk::backend {
/// managed shader handling dispatch and barriers
/// this class is NOT memory-safe
class ManagedShader {
friend class ManagedShaderBuilder;
public:
/// dispatch the managed shader
/// @param vk the vulkan instance
/// @param cmd command buffer to use
/// @param extent dispatch size
/// @throws ls::vulkan_error on failure
void dispatch(const vk::Vulkan& vk,
const vk::CommandBuffer& cmd, VkExtent2D extent) const;
private:
ls::R<const vk::Shader> shader;
std::vector<vk::Barrier> barriers;
vk::DescriptorSet descriptorSet;
// simple move constructor
ManagedShader(ls::R<const vk::Shader> shader,
std::vector<vk::Barrier> barriers,
vk::DescriptorSet descriptorSet) :
shader(shader),
barriers(std::move(barriers)),
descriptorSet(std::move(descriptorSet)) {
}
};
/// class for building managed shaders
/// this class is NOT memory-safe
class ManagedShaderBuilder {
public:
/// default constructor
ManagedShaderBuilder() = default;
/// add a sampled image
/// @param image image to add
[[nodiscard]] ManagedShaderBuilder& sampled(const vk::Image& image);
/// add multiple sampled images
/// @param images images to add
/// @param offset offset into images
/// @param count number of images to add (0 = all)
[[nodiscard]] ManagedShaderBuilder& sampleds(const std::vector<vk::Image>& images,
size_t offset = 0, size_t count = 0);
/// add a storage image
/// @param image image to add
[[nodiscard]] ManagedShaderBuilder& storage(const vk::Image& image);
/// add multiple storage images
/// @param images images to add
/// @param offset offset into images
/// @param count number of images to add (0 = all)
[[nodiscard]] ManagedShaderBuilder& storages(const std::vector<vk::Image>& images,
size_t offset = 0, size_t count = 0);
/// add a sampler
/// @param sampler sampler to add
[[nodiscard]] ManagedShaderBuilder& sampler(const vk::Sampler& sampler);
/// add multiple samplers
/// @param samplers samplers to add
[[nodiscard]] ManagedShaderBuilder& samplers(const std::vector<vk::Sampler>& samplers);
/// add a buffer
/// @param buffer buffer to add
[[nodiscard]] ManagedShaderBuilder& buffer(const vk::Buffer& buffer);
/// build the managed shader
/// @param vk the vulkan instance
/// @param pool the descriptor pool to use
/// @param shader the shader to use
/// @returns the built managed shader
[[nodiscard]] ManagedShader build(const vk::Vulkan& vk,
const vk::DescriptorPool& pool, const vk::Shader& shader) const;
private:
std::vector<ls::R<const vk::Image>> sampledImages;
std::vector<ls::R<const vk::Image>> storageImages;
std::vector<ls::R<const vk::Sampler>> imageSamplers;
std::vector<ls::R<const vk::Buffer>> constantBuffers;
};
}

View file

@ -1,50 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "utils.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
#include <string>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk;
using namespace lsfgvk::backend;
ConstantBuffer backend::getDefaultConstantBuffer(
size_t index, size_t total,
bool hdr, float invFlow) {
return ConstantBuffer {
.advancedColorKind = hdr ? 2U : 0U,
.hdrSupport = hdr ? 1U : 0U,
.resolutionInvScale = invFlow,
.timestamp = static_cast<float>(index + 1) / static_cast<float>(total + 1),
.uiThreshold = 0.5F
};
}
VkExtent2D backend::shift_extent(VkExtent2D extent, uint32_t i) {
return VkExtent2D{
.width = extent.width >> i,
.height = extent.height >> i
};
}
VkExtent2D backend::add_shift_extent(VkExtent2D extent, uint32_t a, uint32_t i) {
return VkExtent2D{
.width = (extent.width + a) >> i,
.height = (extent.height + a) >> i
};
}
std::string backend::to_hex_id(uint32_t id) {
const std::array<char, 17> chars = std::to_array("0123456789ABCDEF");
std::string result = "0x";
result += chars.at((id >> 12) & 0xF);
result += chars.at((id >> 8) & 0xF);
result += chars.at((id >> 4) & 0xF);
result += chars.at(id & 0xF);
return result;
}

View file

@ -1,82 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "../extraction/shader_registry.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/buffer.hpp"
#include "lsfg-vk-common/vulkan/descriptor_pool.hpp"
#include "lsfg-vk-common/vulkan/sampler.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace lsfgvk::backend {
/// exposed context data
struct Ctx {
ls::R<const vk::Vulkan> vk; // safe back reference
ls::R<const ShaderRegistry> shaders; // safe back reference
vk::DescriptorPool pool;
vk::Buffer constantBuffer;
std::vector<vk::Buffer> constantBuffers;
vk::Sampler bnbSampler; //!< border, no compare, black
vk::Sampler bnwSampler; //!< border, no compare, white
vk::Sampler eabSampler; //!< edge, always compare, black
VkExtent2D sourceExtent;
VkExtent2D flowExtent;
bool hdr;
float flow;
bool perf;
size_t count;
};
/// constant buffer used in shaders
struct ConstantBuffer {
std::array<uint32_t, 2> inputOffset;
uint32_t firstIter;
uint32_t firstIterS;
uint32_t advancedColorKind;
uint32_t hdrSupport;
float resolutionInvScale;
float timestamp;
float uiThreshold;
std::array<uint32_t, 3> pad;
};
/// get a prefilled constant buffer
/// @param index timestamp index
/// @param total total amount of images
/// @param hdr whether HDR is enabled
/// @param invFlow inverted flow scale value
/// @return prefilled constant buffer
ConstantBuffer getDefaultConstantBuffer(
size_t index, size_t total,
bool hdr, float invFlow
);
/// round down a VkExtent2D
/// @param extent the extent to shift
/// @param i the amount to shift by
/// @return the shifted extent
VkExtent2D shift_extent(VkExtent2D extent, uint32_t i);
/// round up a VkExtent2D
/// @param extent the extent to shift
/// @param a the amount to add before shifting
/// @param i the amount to shift by
/// @return the shifted extent
VkExtent2D add_shift_extent(VkExtent2D extent, uint32_t a, uint32_t i);
/// convert a device/vendor id into a hex string
std::string to_hex_id(uint32_t id);
}

View file

@ -1,666 +1,7 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "lsfg-vk-backend/lsfgvk.hpp"
#include "extraction/dll_reader.hpp"
#include "extraction/shader_registry.hpp"
#include "helpers/limits.hpp"
#include "helpers/utils.hpp"
#include "lsfg-vk-common/helpers/errors.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/buffer.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/fence.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/timeline_semaphore.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include "shaderchains/alpha0.hpp"
#include "shaderchains/alpha1.hpp"
#include "shaderchains/beta0.hpp"
#include "shaderchains/beta1.hpp"
#include "shaderchains/delta0.hpp"
#include "shaderchains/delta1.hpp"
#include "shaderchains/gamma0.hpp"
#include "shaderchains/gamma1.hpp"
#include "shaderchains/generate.hpp"
#include "shaderchains/mipmaps.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <filesystem>
#include <functional>
#include <iostream>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
#ifdef LSFGVK_TESTING_RENDERDOC
#include <renderdoc_app.h>
#include <dlfcn.h>
#endif
#include "lsfg-vk/lsfgvk.hpp"
using namespace lsfgvk;
using namespace lsfgvk::backend;
namespace lsfgvk::backend {
error::error(const std::string& msg, const std::exception& inner)
: std::runtime_error(msg + "\n- " + inner.what()) {}
error::error(const std::string& msg)
: std::runtime_error(msg) {}
error::~error() = default;
/// instance class
class InstanceImpl {
public:
/// create an instance
/// (see lsfg-vk documentation)
InstanceImpl(vk::PhysicalDeviceSelector selectPhysicalDevice,
const std::filesystem::path& shaderDllPath,
bool allowLowPrecision);
/// get the Vulkan instance
/// @return the Vulkan instance
[[nodiscard]] const auto& getVulkan() const { return this->vk; }
/// get the shader registry
/// @return the shader registry
[[nodiscard]] const auto& getShaderRegistry() const { return this->shaders; }
#ifdef LSFGVK_TESTING_RENDERDOC
/// get the RenderDoc API
/// @return the RenderDoc API
[[nodiscard]] const auto& getRenderDocAPI() const { return this->renderdoc; }
#endif
// Movable, non-copyable, custom destructor
InstanceImpl(const InstanceImpl&) = delete;
InstanceImpl& operator=(const InstanceImpl&) = delete;
InstanceImpl(InstanceImpl&&) = default;
InstanceImpl& operator=(InstanceImpl&&) = default;
~InstanceImpl();
private:
vk::Vulkan vk;
ShaderRegistry shaders;
#ifdef LSFGVK_TESTING_RENDERDOC
std::optional<RENDERDOC_API_1_6_0> renderdoc;
#endif
};
/// context class
class ContextImpl {
public:
/// create a context
/// (see lsfg-vk documentation)
ContextImpl(const InstanceImpl& instance,
std::pair<int, int> sourceFds, const std::vector<int>& destFds, int syncFd,
VkExtent2D extent, bool hdr, float flow, bool perf);
/// schedule frames
/// (see lsfg-vk documentation)
void scheduleFrames();
private:
std::pair<vk::Image, vk::Image> sourceImages;
std::vector<vk::Image> destImages;
vk::Image blackImage;
vk::TimelineSemaphore syncSemaphore; // imported
vk::TimelineSemaphore prepassSemaphore;
size_t idx{1};
size_t fidx{0}; // real frame index
std::vector<vk::CommandBuffer> cmdbufs;
vk::Fence cmdbufFence;
Ctx ctx;
Mipmaps mipmaps;
std::array<Alpha0, 7> alpha0;
std::array<Alpha1, 7> alpha1;
Beta0 beta0;
Beta1 beta1;
struct Pass {
std::vector<Gamma0> gamma0;
std::vector<Gamma1> gamma1;
std::vector<Delta0> delta0;
std::vector<Delta1> delta1;
ls::lazy<Generate> generate;
};
std::vector<Pass> passes;
};
}
Instance::Instance(
const DevicePicker& devicePicker,
const std::filesystem::path& shaderDllPath,
bool allowLowPrecision) {
const auto selectFunc = [&devicePicker](const vk::VulkanInstanceFuncs funcs,
const std::vector<VkPhysicalDevice>& devices) {
for (const auto& device : devices) {
// check if the physical device supports VK_EXT_pci_bus_info
uint32_t ext_count{};
funcs.EnumerateDeviceExtensionProperties(device, nullptr, &ext_count, VK_NULL_HANDLE);
std::vector<VkExtensionProperties> extensions(ext_count);
funcs.EnumerateDeviceExtensionProperties(device, nullptr, &ext_count, extensions.data());
const bool has_pci_ext = std::ranges::find_if(extensions,
[](const VkExtensionProperties& ext) {
return std::string(std::to_array(ext.extensionName).data())
== VK_EXT_PCI_BUS_INFO_EXTENSION_NAME;
}) != extensions.end();
// then fetch all available properties
VkPhysicalDevicePCIBusInfoPropertiesEXT pciInfo{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PCI_BUS_INFO_PROPERTIES_EXT
};
VkPhysicalDeviceProperties2 props{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
.pNext = has_pci_ext ? &pciInfo : nullptr
};
funcs.GetPhysicalDeviceProperties2(device, &props);
std::array<char, 256> devname = std::to_array(props.properties.deviceName);
devname.at(255) = '\0'; // ensure null-termination
if (devicePicker(
std::string(devname.data()),
{ backend::to_hex_id(props.properties.vendorID),
backend::to_hex_id(props.properties.deviceID) },
has_pci_ext ? std::optional<std::string>{
std::to_string(pciInfo.pciBus) + ":" +
std::to_string(pciInfo.pciDevice) + "." +
std::to_string(pciInfo.pciFunction)
} : std::nullopt
))
return device;
}
throw ls::vulkan_error("no suitable physical device found");
};
this->m_impl = std::make_unique<InstanceImpl>(
selectFunc, shaderDllPath, allowLowPrecision
);
}
namespace {
/// find the cache file path
std::filesystem::path findCacheFilePath() {
const char* xdgCacheHome = std::getenv("XDG_CACHE_HOME");
if (xdgCacheHome && *xdgCacheHome != '\0')
return std::filesystem::path(xdgCacheHome) / "lsfg-vk_pipeline_cache.bin";
const char* home = std::getenv("HOME");
if (home && *home != '\0')
return std::filesystem::path(home) / ".cache" / "lsfg-vk_pipeline_cache.bin";
return{"/tmp/lsfg-vk_pipeline_cache.bin"};
}
/// create a Vulkan instance
vk::Vulkan createVulkanInstance(vk::PhysicalDeviceSelector selectPhysicalDevice) {
try {
return{
"lsfg-vk", vk::version{2, 0, 0},
"lsfg-vk-engine", vk::version{2, 0, 0},
selectPhysicalDevice,
false, std::nullopt,
findCacheFilePath()
};
} catch (const std::exception& e) {
throw backend::error("Unable to initialize Vulkan", e);
}
}
/// build a shader registry
ShaderRegistry createShaderRegistry(vk::Vulkan& vk,
const std::filesystem::path& shaderDllPath,
bool allowLowPrecision) {
std::unordered_map<uint32_t, std::vector<uint8_t>> resources{};
try {
resources = backend::extractResourcesFromDLL(shaderDllPath);
} catch (const std::exception& e) {
throw backend::error("Unable to parse Lossless Scaling DLL", e);
}
try {
return backend::buildShaderRegistry(
vk, allowLowPrecision && vk.supportsFP16(),
resources
);
} catch (const std::exception& e) {
throw backend::error("Unable to build shader registry", e);
}
}
#ifdef LSFGVK_TESTING_RENDERDOC
/// load RenderDoc integration
std::optional<RENDERDOC_API_1_6_0> loadRenderDocIntegration() {
void* module = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
if (!module)
return std::nullopt;
auto renderdocGetAPI = reinterpret_cast<pRENDERDOC_GetAPI>(
dlsym(module, "RENDERDOC_GetAPI"));
if (!renderdocGetAPI)
return std::nullopt;
RENDERDOC_API_1_6_0* api{};
renderdocGetAPI(eRENDERDOC_API_Version_1_6_0, reinterpret_cast<void**>(&api));
if (!api)
return std::nullopt;
return *api;
}
#endif
}
InstanceImpl::InstanceImpl(vk::PhysicalDeviceSelector selectPhysicalDevice,
const std::filesystem::path& shaderDllPath,
bool allowLowPrecision)
: vk(createVulkanInstance(selectPhysicalDevice)),
shaders(createShaderRegistry(this->vk, shaderDllPath,
allowLowPrecision && vk.supportsFP16())) {
#ifdef LSFGVK_TESTING_RENDERDOC
this->renderdoc = loadRenderDocIntegration();
#endif
vk.persistPipelineCache(); // will silently fail
}
Context& Instance::openContext(std::pair<int, int> sourceFds, const std::vector<int>& destFds,
int syncFd, uint32_t width, uint32_t height,
bool hdr, float flow, bool perf) {
const VkExtent2D extent{ width, height };
return *this->m_contexts.emplace_back(std::make_unique<ContextImpl>(*this->m_impl,
sourceFds, destFds, syncFd,
extent, hdr, flow, perf
)).get();
}
namespace {
/// import source images
std::pair<vk::Image, vk::Image> importImages(const vk::Vulkan& vk,
const std::pair<int, int>& sourceFds,
VkExtent2D extent, VkFormat format) {
try {
return {
vk::Image(vk, extent, format,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, sourceFds.first),
vk::Image(vk, extent, format,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, sourceFds.second)
};
} catch (const std::exception& e) {
throw backend::error("Unable to import destination images", e);
}
}
/// import destination images
std::vector<vk::Image> importImages(const vk::Vulkan& vk,
const std::vector<int>& destFds,
VkExtent2D extent, VkFormat format) {
try {
std::vector<vk::Image> destImages;
destImages.reserve(destFds.size());
for (const auto& fd : destFds)
destImages.emplace_back(vk, extent, format,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, fd);
return destImages;
} catch (const std::exception& e) {
throw backend::error("Unable to import destination images", e);
}
}
/// create a black image
vk::Image createBlackImage(const vk::Vulkan& vk) {
try {
return{vk,
{ .width = 4, .height = 4 }
};
} catch (const std::exception& e) {
throw backend::error("Unable to create black image", e);
}
}
/// import timeline semaphore
vk::TimelineSemaphore importTimelineSemaphore(const vk::Vulkan& vk, int syncFd) {
try {
return{vk, 0, syncFd};
} catch (const std::exception& e) {
throw backend::error("Unable to import timeline semaphore", e);
}
}
/// create prepass semaphores
vk::TimelineSemaphore createPrepassSemaphore(const vk::Vulkan& vk) {
try {
return{vk, 0};
} catch (const std::exception& e) {
throw backend::error("Unable to create prepass semaphore", e);
}
}
/// create command buffers
std::vector<vk::CommandBuffer> createCommandBuffers(const vk::Vulkan& vk, size_t count) {
try {
std::vector<vk::CommandBuffer> cmdbufs;
cmdbufs.reserve(count);
for (size_t i = 0; i < count; ++i)
cmdbufs.emplace_back(vk);
return cmdbufs;
} catch (const std::exception& e) {
throw backend::error("Unable to create command buffers", e);
}
}
/// create context data
Ctx createCtx(const InstanceImpl& instance, VkExtent2D extent,
bool hdr, float flow, bool perf, size_t count) {
const auto& vk = instance.getVulkan();
const auto& shaders = instance.getShaderRegistry();
try {
std::vector<vk::Buffer> constantBuffers{};
constantBuffers.reserve(count);
for (size_t i = 0; i < count; ++i)
constantBuffers.emplace_back(vk,
backend::getDefaultConstantBuffer(
i, count,
hdr, flow
)
);
return {
.vk = std::ref(vk),
.shaders = std::ref(shaders),
.pool{vk, backend::calculateDescriptorPoolLimits(count, perf)},
.constantBuffer{vk, backend::getDefaultConstantBuffer(0, 1, hdr, flow)},
.constantBuffers{std::move(constantBuffers)},
.bnbSampler{vk, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, false},
.bnwSampler{vk, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true},
.eabSampler{vk, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false},
.sourceExtent = extent,
.flowExtent = VkExtent2D {
.width = static_cast<uint32_t>(static_cast<float>(extent.width) / flow),
.height = static_cast<uint32_t>(static_cast<float>(extent.height) / flow)
},
.hdr = hdr,
.flow = flow,
.perf = perf,
.count = count
};
} catch (const std::exception& e) {
throw backend::error("Unable to create context", e);
}
}
}
ContextImpl::ContextImpl(const InstanceImpl& instance,
std::pair<int, int> sourceFds, const std::vector<int>& destFds, int syncFd,
VkExtent2D extent, bool hdr, float flow, bool perf) :
sourceImages(importImages(instance.getVulkan(), sourceFds,
extent, hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM)),
destImages(importImages(instance.getVulkan(), destFds,
extent, hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM)),
blackImage(createBlackImage(instance.getVulkan())),
syncSemaphore(importTimelineSemaphore(instance.getVulkan(), syncFd)),
prepassSemaphore(createPrepassSemaphore(instance.getVulkan())),
cmdbufs(createCommandBuffers(instance.getVulkan(), destFds.size() + 1)),
cmdbufFence(instance.getVulkan()),
ctx(createCtx(instance, extent, hdr, flow, perf, destFds.size())),
mipmaps(ctx, sourceImages),
alpha0{
Alpha0(ctx, mipmaps.getImages().at(0)),
Alpha0(ctx, mipmaps.getImages().at(1)),
Alpha0(ctx, mipmaps.getImages().at(2)),
Alpha0(ctx, mipmaps.getImages().at(3)),
Alpha0(ctx, mipmaps.getImages().at(4)),
Alpha0(ctx, mipmaps.getImages().at(5)),
Alpha0(ctx, mipmaps.getImages().at(6))
},
alpha1{
Alpha1(ctx, 3, alpha0.at(0).getImages()),
Alpha1(ctx, 2, alpha0.at(1).getImages()),
Alpha1(ctx, 2, alpha0.at(2).getImages()),
Alpha1(ctx, 2, alpha0.at(3).getImages()),
Alpha1(ctx, 2, alpha0.at(4).getImages()),
Alpha1(ctx, 2, alpha0.at(5).getImages()),
Alpha1(ctx, 2, alpha0.at(6).getImages())
},
beta0(ctx, alpha1.at(0).getImages()),
beta1(ctx, beta0.getImages()) {
// build main passes
for (size_t i = 0; i < destImages.size(); ++i) {
auto& pass = this->passes.emplace_back();
pass.gamma0.reserve(7);
pass.gamma1.reserve(7);
pass.delta0.reserve(3);
pass.delta1.reserve(3);
for (size_t j = 0; j < 7; j++) {
if (j == 0) { // first pass has no prior data
pass.gamma0.emplace_back(ctx, i,
this->alpha1.at(6 - j).getImages(),
this->blackImage
);
pass.gamma1.emplace_back(ctx, i,
pass.gamma0.at(j).getImages(),
this->blackImage,
this->beta1.getImages().at(5)
);
} else { // other passes use prior data
pass.gamma0.emplace_back(ctx, i,
this->alpha1.at(6 - j).getImages(),
pass.gamma1.at(j - 1).getImage()
);
pass.gamma1.emplace_back(ctx, i,
pass.gamma0.at(j).getImages(),
pass.gamma1.at(j - 1).getImage(),
this->beta1.getImages().at(6 - j)
);
}
if (j == 4) { // first special pass has no prior data
pass.delta0.emplace_back(ctx, i,
this->alpha1.at(6 - j).getImages(),
this->blackImage,
pass.gamma1.at(j - 1).getImage()
);
pass.delta1.emplace_back(ctx, i,
pass.delta0.at(j - 4).getImages0(),
pass.delta0.at(j - 4).getImages1(),
this->blackImage,
this->beta1.getImages().at(6 - j),
this->blackImage
);
} else if (j > 4) { // further passes do
pass.delta0.emplace_back(ctx, i,
this->alpha1.at(6 - j).getImages(),
pass.delta1.at(j - 5).getImage0(),
pass.gamma1.at(j - 1).getImage()
);
pass.delta1.emplace_back(ctx, i,
pass.delta0.at(j - 4).getImages0(),
pass.delta0.at(j - 4).getImages1(),
pass.delta1.at(j - 5).getImage0(),
this->beta1.getImages().at(6 - j),
pass.delta1.at(j - 5).getImage1()
);
}
}
pass.generate.emplace(ctx, i,
this->sourceImages,
pass.gamma1.at(6).getImage(),
pass.delta1.at(2).getImage0(),
pass.delta1.at(2).getImage1(),
this->destImages.at(i)
);
}
// initialize all images
std::vector<VkImage> images{};
images.push_back(this->blackImage.handle());
mipmaps.prepare(images);
for (size_t i = 0; i < 7; ++i) {
alpha0.at(i).prepare(images);
alpha1.at(i).prepare(images);
}
beta0.prepare(images);
beta1.prepare(images);
for (const auto& pass : this->passes) {
for (size_t i = 0; i < 7; ++i) {
pass.gamma0.at(i).prepare(images);
pass.gamma1.at(i).prepare(images);
if (i < 4) continue;
pass.delta0.at(i - 4).prepare(images);
pass.delta1.at(i - 4).prepare(images);
}
}
std::vector<vk::Barrier> barriers{};
barriers.reserve(images.size());
for (const auto& image : images) {
barriers.emplace_back(vk::Barrier {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = 1,
.layerCount = 1
}
});
}
const vk::CommandBuffer cmdbuf{ctx.vk};
cmdbuf.begin(ctx.vk);
cmdbuf.insertBarriers(ctx.vk, barriers);
cmdbuf.end(ctx.vk);
cmdbuf.submit(ctx.vk); // wait for completion
}
void Instance::scheduleFrames(Context& context) { // NOLINT (static)
#ifdef LSFGVK_TESTING_RENDERDOC
const auto& impl = this->m_impl;
if (impl->getRenderDocAPI()) {
impl->getRenderDocAPI()->StartFrameCapture(
RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(impl->getVulkan().inst()),
nullptr);
}
#endif
try {
context.scheduleFrames();
} catch (const std::exception& e) {
throw backend::error("Unable to schedule frames", e);
}
#ifdef LSFGVK_TESTING_RENDERDOC
if (impl->getRenderDocAPI()) {
impl->getVulkan().df().DeviceWaitIdle(impl->getVulkan().dev());
impl->getRenderDocAPI()->EndFrameCapture(
RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(impl->getVulkan().inst()),
nullptr);
}
#endif
}
void Context::scheduleFrames() {
// wait for previous pre-pass to complete
if (this->fidx && !this->cmdbufFence.wait(this->ctx.vk))
throw backend::error("Timeout waiting for previous frame to complete");
this->cmdbufFence.reset(this->ctx.vk);
// schedule pre-pass
const auto& cmdbuf = this->cmdbufs.at(0);
cmdbuf.begin(ctx.vk);
this->mipmaps.render(ctx.vk, cmdbuf, this->fidx);
for (size_t i = 0; i < 7; ++i) {
this->alpha0.at(6 - i).render(ctx.vk, cmdbuf);
this->alpha1.at(6 - i).render(ctx.vk, cmdbuf, this->fidx);
}
this->beta0.render(ctx.vk, cmdbuf, this->fidx);
this->beta1.render(ctx.vk, cmdbuf);
cmdbuf.end(ctx.vk);
cmdbuf.submit(this->ctx.vk,
{}, this->syncSemaphore.handle(), this->idx,
{}, this->prepassSemaphore.handle(), this->idx
);
this->idx++;
// schedule main passes
for (size_t i = 0; i < this->destImages.size(); i++) {
const auto& cmdbuf = this->cmdbufs.at(i + 1);
cmdbuf.begin(ctx.vk);
const auto& pass = this->passes.at(i);
for (size_t j = 0; j < 7; j++) {
pass.gamma0.at(j).render(ctx.vk, cmdbuf, this->fidx);
pass.gamma1.at(j).render(ctx.vk, cmdbuf);
if (j < 4) continue;
pass.delta0.at(j - 4).render(ctx.vk, cmdbuf, this->fidx);
pass.delta1.at(j - 4).render(ctx.vk, cmdbuf);
}
pass.generate->render(ctx.vk, cmdbuf, this->fidx);
cmdbuf.end(ctx.vk);
cmdbuf.submit(this->ctx.vk,
{}, this->prepassSemaphore.handle(), this->idx - 1,
{}, this->syncSemaphore.handle(), this->idx + i,
i == this->destImages.size() - 1 ? this->cmdbufFence.handle() : VK_NULL_HANDLE
);
}
this->idx += this->destImages.size();
this->fidx++;
}
void Instance::closeContext(const Context& context) {
auto it = std::ranges::find_if(this->m_contexts,
[context = &context](const std::unique_ptr<ContextImpl>& ctx) {
return ctx.get() == context;
});
if (it == this->m_contexts.end())
throw backend::error("attempted to close unknown context",
std::runtime_error("no such context"));
const auto& vk = this->m_impl->getVulkan();
vk.df().DeviceWaitIdle(vk.dev());
this->m_contexts.erase(it);
}
Instance::~Instance() = default;
// leaking shenanigans
namespace {
bool leaking{false}; // NOLINT (global variable)
}
InstanceImpl::~InstanceImpl() {
if (!leaking) return;
try {
new vk::Vulkan(std::move(this->vk));
} catch (...) {
std::cerr << "lsfg-vk: failed to leak Vulkan instance\n";
}
}
void backend::makeLeaking() {
leaking = true;
}
/* TODO */

View file

@ -1,73 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "alpha0.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <vector>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk::backend;
Alpha0::Alpha0(const Ctx& ctx,
const vk::Image& sourceImage) {
const size_t m = ctx.perf ? 1 : 2; // multiplier
const VkExtent2D halfExtent = backend::add_shift_extent(sourceImage.getExtent(), 1, 1);
const VkExtent2D quarterExtent = backend::add_shift_extent(halfExtent, 1, 1);
// create temporary & output images
this->tempImages0.reserve(m);
this->tempImages1.reserve(m);
for (size_t i = 0; i < m; i++) {
this->tempImages0.emplace_back(ctx.vk, halfExtent);
this->tempImages1.emplace_back(ctx.vk, halfExtent);
}
this->images.reserve(2 * m);
for (size_t i = 0; i < (2 * m); i++)
this->images.emplace_back(ctx.vk, quarterExtent);
// create descriptor sets
const auto& shaders = ctx.perf ? ctx.shaders.get().performance : ctx.shaders.get().quality;
this->sets.reserve(3);
this->sets.emplace_back(ManagedShaderBuilder()
.sampled(sourceImage)
.storages(this->tempImages0)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.alpha.at(0)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages0)
.storages(this->tempImages1)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.alpha.at(1)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages1)
.storages(this->images)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.alpha.at(2)));
// store dispatch extents
this->dispatchExtent0 = backend::add_shift_extent(halfExtent, 7, 3);
this->dispatchExtent1 = backend::add_shift_extent(quarterExtent, 7, 3);
}
void Alpha0::prepare(std::vector<VkImage>& images) const {
for (size_t i = 0; i < this->tempImages0.size(); i++) {
images.push_back(this->tempImages0.at(i).handle());
images.push_back(this->tempImages1.at(i).handle());
}
for (const auto& image : this->images)
images.push_back(image.handle());
}
void Alpha0::render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd) const {
this->sets.at(0).dispatch(vk, cmd, this->dispatchExtent0);
this->sets.at(1).dispatch(vk, cmd, this->dispatchExtent0);
this->sets.at(2).dispatch(vk, cmd, this->dispatchExtent1);
}

View file

@ -1,48 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "../helpers/managed_shader.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <vector>
#include <vulkan/vulkan_core.h>
namespace ctx { struct Ctx; }
namespace lsfgvk::backend {
/// pre-alpha shaderchain
class Alpha0 {
public:
/// create a pre-alpha shaderchain
/// @param ctx context
/// @param sourceImage source image
Alpha0(const Ctx& ctx,
const vk::Image& sourceImage);
/// prepare the shaderchain initially
/// @param images vector to fill with image handles
void prepare(std::vector<VkImage>& images) const;
/// render the pre-alpha shaderchain
/// @param vk the vulkan instance
/// @param cmd command buffer
void render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd) const;
/// get the generated images
/// @return vector of images
[[nodiscard]] const auto& getImages() const { return this->images; }
private:
std::vector<vk::Image> tempImages0;
std::vector<vk::Image> tempImages1;
std::vector<vk::Image> images;
std::vector<ManagedShader> sets;
VkExtent2D dispatchExtent0{};
VkExtent2D dispatchExtent1{};
};
}

View file

@ -1,54 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "alpha1.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <vector>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk::backend;
Alpha1::Alpha1(const Ctx& ctx, size_t temporal,
const std::vector<vk::Image>& sourceImages) {
const size_t m = ctx.perf ? 1 : 2; // multiplier
const VkExtent2D quarterExtent = sourceImages.at(0).getExtent();
// create output images for mod3
this->images.reserve(temporal);
for(size_t i = 0; i < temporal; i++) {
auto& vec = this->images.emplace_back();
vec.reserve(2 * m);
for (size_t j = 0; j < (2 * m); j++)
vec.emplace_back(ctx.vk, quarterExtent);
}
// create descriptor sets
const auto& shaders = ctx.perf ? ctx.shaders.get().performance : ctx.shaders.get().quality;
this->sets.reserve(temporal);
for (size_t i = 0; i < temporal; i++)
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(sourceImages)
.storages(this->images.at(i))
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.alpha.at(3)));
// store dispatch extents
this->dispatchExtent = backend::add_shift_extent(quarterExtent, 7, 3);
}
void Alpha1::prepare(std::vector<VkImage>& images) const {
for (const auto& vec : this->images)
for (const auto& img : vec)
images.push_back(img.handle());
}
void Alpha1::render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd, size_t idx) const {
this->sets.at(idx % this->sets.size()).dispatch(vk, cmd, dispatchExtent);
}

View file

@ -1,47 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "../helpers/managed_shader.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <vector>
#include <vulkan/vulkan_core.h>
namespace ctx { struct Ctx; }
namespace lsfgvk::backend {
/// alpha shaderchain
class Alpha1 {
public:
/// create a alpha shaderchain
/// @param ctx context
/// @param temporal temporal count
/// @param sourceImages source images
Alpha1(const Ctx& ctx, size_t temporal,
const std::vector<vk::Image>& sourceImages);
/// prepare the shaderchain initially
/// @param images vector to fill with image handles
void prepare(std::vector<VkImage>& images) const;
/// render the alpha shaderchain
/// @param vk the vulkan instance
/// @param cmd command buffer
/// @param idx frame index
void render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd, size_t idx) const;
/// get the generated images
/// @return vector of images
[[nodiscard]] const auto& getImages() const { return this->images; }
private:
std::vector<std::vector<vk::Image>> images;
std::vector<ManagedShader> sets;
VkExtent2D dispatchExtent{};
};
}

View file

@ -1,50 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "beta0.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <vector>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk::backend;
Beta0::Beta0(const Ctx& ctx,
const std::vector<std::vector<vk::Image>>& sourceImages) {
const VkExtent2D extent = sourceImages.at(0).at(0).getExtent();
// create output images
this->images.reserve(2);
for(size_t i = 0; i < 2; i++)
this->images.emplace_back(ctx.vk, extent);
// create descriptor sets
const auto& shader = (ctx.perf ?
ctx.shaders.get().performance : ctx.shaders.get().quality).beta.at(0);
this->sets.reserve(sourceImages.size());
for (size_t i = 0; i < sourceImages.size(); i++)
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(sourceImages.at((i + (sourceImages.size() - 2)) % sourceImages.size()))
.sampleds(sourceImages.at((i + (sourceImages.size() - 1)) % sourceImages.size()))
.sampleds(sourceImages.at(i % sourceImages.size()))
.storages(this->images)
.sampler(ctx.bnwSampler)
.build(ctx.vk, ctx.pool, shader));
// store dispatch extents
this->dispatchExtent = backend::add_shift_extent(extent, 7, 3);
}
void Beta0::prepare(std::vector<VkImage>& images) const {
for (const auto& img : this->images)
images.push_back(img.handle());
}
void Beta0::render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd, size_t idx) const {
this->sets.at(idx % this->sets.size()).dispatch(vk, cmd, dispatchExtent);
}

View file

@ -1,46 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "../helpers/managed_shader.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <vector>
#include <vulkan/vulkan_core.h>
namespace ctx { struct Ctx; }
namespace lsfgvk::backend {
/// beta shaderchain
class Beta0 {
public:
/// create a beta shaderchain
/// @param ctx context
/// @param sourceImages source images
Beta0(const Ctx& ctx,
const std::vector<std::vector<vk::Image>>& sourceImages);
/// prepare the shaderchain initially
/// @param images vector to fill with image handles
void prepare(std::vector<VkImage>& images) const;
/// render the beta shaderchain
/// @param vk vulkan instance
/// @param cmd command buffer
/// @param idx frame index
void render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd, size_t idx) const;
/// get the generated images
/// @return vector of images
[[nodiscard]] const auto& getImages() const { return this->images; }
private:
std::vector<vk::Image> images;
std::vector<ManagedShader> sets;
VkExtent2D dispatchExtent{};
};
}

View file

@ -1,81 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "beta1.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <cstdint>
#include <vector>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk::backend;
Beta1::Beta1(const Ctx& ctx,
const std::vector<vk::Image>& sourceImages) {
const VkExtent2D extent = sourceImages.at(0).getExtent();
// create temporary & output images
this->tempImages0.reserve(2);
this->tempImages1.reserve(2);
for(uint32_t i = 0; i < 2; i++) {
this->tempImages0.emplace_back(ctx.vk, extent);
this->tempImages1.emplace_back(ctx.vk, extent);
}
this->images.reserve(6);
for (uint32_t i = 0; i < 6; i++)
this->images.emplace_back(ctx.vk,
backend::shift_extent(extent, i),
VK_FORMAT_R8_UNORM);
// create descriptor sets
const auto& shaders = (ctx.perf ?
ctx.shaders.get().performance : ctx.shaders.get().quality).beta;
this->sets.reserve(4);
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(sourceImages)
.storages(this->tempImages0)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.at(1)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages0)
.storages(this->tempImages1)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.at(2)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages1)
.storages(this->tempImages0)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.at(3)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages0)
.storages(this->images)
.sampler(ctx.bnbSampler)
.buffer(ctx.constantBuffer)
.build(ctx.vk, ctx.pool, shaders.at(4)));
// store dispatch extents
this->dispatchExtent0 = backend::add_shift_extent(extent, 7, 3);
this->dispatchExtent1 = backend::add_shift_extent(extent, 31, 5);
}
void Beta1::prepare(std::vector<VkImage>& images) const {
for (size_t i = 0; i < 2; i++) {
images.push_back(this->tempImages0.at(i).handle());
images.push_back(this->tempImages1.at(i).handle());
}
for (const auto& img : this->images)
images.push_back(img.handle());
}
void Beta1::render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd) const {
this->sets.at(0).dispatch(vk, cmd, this->dispatchExtent0);
this->sets.at(1).dispatch(vk, cmd, this->dispatchExtent0);
this->sets.at(2).dispatch(vk, cmd, this->dispatchExtent0);
this->sets.at(3).dispatch(vk, cmd, this->dispatchExtent1);
}

View file

@ -1,48 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "../helpers/managed_shader.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <vector>
#include <vulkan/vulkan_core.h>
namespace ctx { struct Ctx; }
namespace lsfgvk::backend {
/// beta shaderchain
class Beta1 {
public:
/// create a beta shaderchain
/// @param ctx context
/// @param sourceImages source images
Beta1(const Ctx& ctx,
const std::vector<vk::Image>& sourceImages);
/// prepare the shaderchain initially
/// @param images vector to fill with image handles
void prepare(std::vector<VkImage>& images) const;
/// render the beta shaderchain
/// @param vk the vulkan instance
/// @param cmd command buffer
void render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd) const;
/// get the generated images
/// @return vector of images
[[nodiscard]] const auto& getImages() const { return this->images; }
private:
std::vector<vk::Image> tempImages0;
std::vector<vk::Image> tempImages1;
std::vector<vk::Image> images;
std::vector<ManagedShader> sets;
VkExtent2D dispatchExtent0{};
VkExtent2D dispatchExtent1{};
};
}

View file

@ -1,75 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "delta0.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <vector>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk::backend;
Delta0::Delta0(const Ctx& ctx, size_t idx,
const std::vector<std::vector<vk::Image>>& sourceImages,
const vk::Image& additionalInput0,
const vk::Image& additionalInput1) {
const size_t m = ctx.perf ? 1 : 2; // multiplier
const VkExtent2D extent = sourceImages.at(0).at(0).getExtent();
// create output images
this->images0.reserve(3);
for(size_t i = 0; i < 3; i++)
this->images0.emplace_back(ctx.vk, extent);
this->images1.reserve(m);
for (size_t i = 0; i < m; i++)
this->images1.emplace_back(ctx.vk, extent);
// create descriptor sets
const auto& shaders = (ctx.perf ?
ctx.shaders.get().performance : ctx.shaders.get().quality).delta;
this->sets0.reserve(sourceImages.size());
for (size_t i = 0; i < sourceImages.size(); i++)
this->sets0.emplace_back(ManagedShaderBuilder()
.sampleds(sourceImages.at((i + (sourceImages.size() - 1)) % sourceImages.size()))
.sampleds(sourceImages.at(i % sourceImages.size()))
.sampled(additionalInput0)
.storages(this->images0)
.sampler(ctx.bnwSampler)
.sampler(ctx.eabSampler)
.buffer(ctx.constantBuffers.at(idx))
.build(ctx.vk, ctx.pool, shaders.at(0)));
this->sets1.reserve(sourceImages.size());
for (size_t i = 0; i < sourceImages.size(); i++)
this->sets1.emplace_back(ManagedShaderBuilder()
.sampleds(sourceImages.at((i + (sourceImages.size() - 1)) % sourceImages.size()))
.sampleds(sourceImages.at(i % sourceImages.size()))
.sampled(additionalInput1)
.sampled(additionalInput0)
.storages(this->images1)
.sampler(ctx.bnwSampler)
.sampler(ctx.eabSampler)
.buffer(ctx.constantBuffers.at(idx))
.build(ctx.vk, ctx.pool, shaders.at(5)));
// store dispatch extents
this->dispatchExtent = backend::add_shift_extent(extent, 7, 3);
}
void Delta0::prepare(std::vector<VkImage>& images) const {
for (const auto& img : this->images0)
images.push_back(img.handle());
for (const auto& img : this->images1)
images.push_back(img.handle());
}
void Delta0::render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd, size_t idx) const {
this->sets0.at(idx % this->sets0.size()).dispatch(vk, cmd, dispatchExtent);
this->sets1.at(idx % this->sets1.size()).dispatch(vk, cmd, dispatchExtent);
}

View file

@ -1,57 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "../helpers/managed_shader.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <vector>
#include <vulkan/vulkan_core.h>
namespace ctx { struct Ctx; }
namespace lsfgvk::backend {
/// delta shaderchain
class Delta0 {
public:
/// create a delta shaderchain
/// @param ctx context
/// @param idx generated frame index
/// @param sourceImages source images
/// @param additionalInput0 additional input image
/// @param additionalInput1 additional input image
Delta0(const Ctx& ctx, size_t idx,
const std::vector<std::vector<vk::Image>>& sourceImages,
const vk::Image& additionalInput0,
const vk::Image& additionalInput1);
/// prepare the shaderchain initially
/// @param images vector to fill with image handles
void prepare(std::vector<VkImage>& images) const;
/// render the delta shaderchain
/// @param vk the vulkan instance
/// @param cmd command buffer
/// @param idx frame index
void render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd, size_t idx) const;
/// get the generated images
/// @return vector of images
[[nodiscard]] const auto& getImages0() const { return this->images0; }
/// get the other generated images
/// @return vector of images
[[nodiscard]] const auto& getImages1() const { return this->images1; }
private:
std::vector<vk::Image> images0;
std::vector<vk::Image> images1;
std::vector<ManagedShader> sets0;
std::vector<ManagedShader> sets1;
VkExtent2D dispatchExtent{};
};
}

View file

@ -1,110 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "delta1.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <vector>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk::backend;
Delta1::Delta1(const Ctx& ctx, size_t idx,
const std::vector<vk::Image>& sourceImages0,
const std::vector<vk::Image>& sourceImages1,
const vk::Image& additionalInput0,
const vk::Image& additionalInput1,
const vk::Image& additionalInput2) {
const size_t m = ctx.perf ? 1 : 2; // multiplier
const VkExtent2D extent = sourceImages0.at(0).getExtent();
// create temporary & output images
for (size_t i = 0; i < (2 * m); i++) {
this->tempImages0.emplace_back(ctx.vk, extent);
this->tempImages1.emplace_back(ctx.vk, extent);
}
this->image0.emplace(ctx.vk,
VkExtent2D { extent.width, extent.height },
VK_FORMAT_R16G16B16A16_SFLOAT
);
this->image1.emplace(ctx.vk,
VkExtent2D { extent.width, extent.height },
VK_FORMAT_R16G16B16A16_SFLOAT
);
// create descriptor sets
const auto& shaders = (ctx.perf ?
ctx.shaders.get().performance : ctx.shaders.get().quality).delta;
this->sets.reserve(4 + 4);
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(sourceImages0)
.storages(this->tempImages0)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.at(1)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages0)
.storages(this->tempImages1)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.at(2)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages1)
.storages(this->tempImages0)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.at(3)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages0)
.sampled(additionalInput0)
.sampled(additionalInput1)
.storage(*this->image0)
.sampler(ctx.bnbSampler)
.sampler(ctx.eabSampler)
.buffer(ctx.constantBuffers.at(idx))
.build(ctx.vk, ctx.pool, shaders.at(4)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(sourceImages1)
.storages(this->tempImages0, 0, m)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.at(6)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages0, 0, m)
.storages(this->tempImages1, 0, m)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.at(7)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages1, 0, m)
.storages(this->tempImages0, 0, m)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.at(8)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages0, 0, m)
.sampled(additionalInput2)
.storage(*this->image1)
.sampler(ctx.bnbSampler)
.sampler(ctx.eabSampler)
.buffer(ctx.constantBuffers.at(idx))
.build(ctx.vk, ctx.pool, shaders.at(9)));
// store dispatch extents
this->dispatchExtent = backend::add_shift_extent(extent, 7, 3);
}
void Delta1::prepare(std::vector<VkImage>& images) const {
for (size_t i = 0; i < this->tempImages0.size(); i++) {
images.push_back(this->tempImages0.at(i).handle());
images.push_back(this->tempImages1.at(i).handle());
}
images.push_back(this->image0->handle());
images.push_back(this->image1->handle());
}
void Delta1::render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd) const {
for (const auto& set : this->sets)
set.dispatch(vk, cmd, dispatchExtent);
}

View file

@ -1,62 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "../helpers/managed_shader.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <vector>
#include <vulkan/vulkan_core.h>
namespace ctx { struct Ctx; }
namespace lsfgvk::backend {
/// gamma shaderchain
class Delta1 {
public:
/// create a gamma shaderchain
/// @param ctx context
/// @param idx generated frame index
/// @param sourceImages0 source images
/// @param sourceImages1 source images
/// @param additionalInput0 additional input image
/// @param additionalInput1 additional input image
/// @param additionalInput2 additional input image
Delta1(const Ctx& ctx, size_t idx,
const std::vector<vk::Image>& sourceImages0,
const std::vector<vk::Image>& sourceImages1,
const vk::Image& additionalInput0,
const vk::Image& additionalInput1,
const vk::Image& additionalInput2);
/// prepare the shaderchain initially
/// @param images vector to fill with image handles
void prepare(std::vector<VkImage>& images) const;
/// render the gamma shaderchain
/// @param vk the vulkan instance
/// @param cmd command buffer
void render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd) const;
/// get the first generated image
/// @return image
[[nodiscard]] const auto& getImage0() const { return *this->image0; }
/// get the second generated image
/// @return image
[[nodiscard]] const auto& getImage1() const { return *this->image1; }
private:
std::vector<vk::Image> tempImages0;
std::vector<vk::Image> tempImages1;
ls::lazy<vk::Image> image0;
ls::lazy<vk::Image> image1;
std::vector<ManagedShader> sets;
VkExtent2D dispatchExtent{};
};
}

View file

@ -1,53 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "gamma0.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <vector>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk::backend;
Gamma0::Gamma0(const Ctx& ctx, size_t idx,
const std::vector<std::vector<vk::Image>>& sourceImages,
const vk::Image& additionalInput) {
const VkExtent2D extent = sourceImages.at(0).at(0).getExtent();
// create output images
this->images.reserve(3);
for(size_t i = 0; i < 3; i++)
this->images.emplace_back(ctx.vk, extent);
// create descriptor sets
const auto& shader = (ctx.perf ?
ctx.shaders.get().performance : ctx.shaders.get().quality).gamma.at(0);
this->sets.reserve(sourceImages.size());
for (size_t i = 0; i < sourceImages.size(); i++)
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(sourceImages.at((i + (sourceImages.size() - 1)) % sourceImages.size()))
.sampleds(sourceImages.at(i % sourceImages.size()))
.sampled(additionalInput)
.storages(this->images)
.sampler(ctx.bnwSampler)
.sampler(ctx.eabSampler)
.buffer(ctx.constantBuffers.at(idx))
.build(ctx.vk, ctx.pool, shader));
// store dispatch extents
this->dispatchExtent = backend::add_shift_extent(extent, 7, 3);
}
void Gamma0::prepare(std::vector<VkImage>& images) const {
for (const auto& img : this->images)
images.push_back(img.handle());
}
void Gamma0::render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd, size_t idx) const {
this->sets.at(idx % this->sets.size()).dispatch(vk, cmd, dispatchExtent);
}

View file

@ -1,49 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "../helpers/managed_shader.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <vector>
#include <vulkan/vulkan_core.h>
namespace ctx { struct Ctx; }
namespace lsfgvk::backend {
/// gamma shaderchain
class Gamma0 {
public:
/// create a gamma shaderchain
/// @param ctx context
/// @param idx generated frame index
/// @param sourceImages source images
/// @param additionalInput additional input image
Gamma0(const Ctx& ctx, size_t idx,
const std::vector<std::vector<vk::Image>>& sourceImages,
const vk::Image& additionalInput);
/// prepare the shaderchain initially
/// @param images vector to fill with image handles
void prepare(std::vector<VkImage>& images) const;
/// render the gamma shaderchain
/// @param vk the vulkan instance
/// @param cmd command buffer
/// @param idx frame index
void render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd, size_t idx) const;
/// get the generated images
/// @return vector of images
[[nodiscard]] const auto& getImages() const { return this->images; }
private:
std::vector<vk::Image> images;
std::vector<ManagedShader> sets;
VkExtent2D dispatchExtent{};
};
}

View file

@ -1,78 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "gamma1.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <vector>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk::backend;
Gamma1::Gamma1(const Ctx& ctx, size_t idx,
const std::vector<vk::Image>& sourceImages,
const vk::Image& additionalInput0,
const vk::Image& additionalInput1) {
const size_t m = ctx.perf ? 1 : 2; // multiplier
const VkExtent2D extent = sourceImages.at(0).getExtent();
// create temporary & output images
for (size_t i = 0; i < (2 * m); i++) {
this->tempImages0.emplace_back(ctx.vk, extent);
this->tempImages1.emplace_back(ctx.vk, extent);
}
this->image.emplace(ctx.vk,
VkExtent2D { extent.width, extent.height },
VK_FORMAT_R16G16B16A16_SFLOAT
);
// create descriptor sets
const auto& shaders = (ctx.perf ?
ctx.shaders.get().performance : ctx.shaders.get().quality).gamma;
this->sets.reserve(4);
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(sourceImages)
.storages(this->tempImages0)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.at(1)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages0)
.storages(this->tempImages1)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.at(2)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages1)
.storages(this->tempImages0)
.sampler(ctx.bnbSampler)
.build(ctx.vk, ctx.pool, shaders.at(3)));
this->sets.emplace_back(ManagedShaderBuilder()
.sampleds(this->tempImages0)
.sampled(additionalInput0)
.sampled(additionalInput1)
.storage(*this->image)
.sampler(ctx.bnbSampler)
.sampler(ctx.eabSampler)
.buffer(ctx.constantBuffers.at(idx))
.build(ctx.vk, ctx.pool, shaders.at(4)));
// store dispatch extents
this->dispatchExtent = backend::add_shift_extent(extent, 7, 3);
}
void Gamma1::prepare(std::vector<VkImage>& images) const {
for (size_t i = 0; i < this->tempImages0.size(); i++) {
images.push_back(this->tempImages0.at(i).handle());
images.push_back(this->tempImages1.at(i).handle());
}
images.push_back(this->image->handle());
}
void Gamma1::render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd) const {
for (const auto& set : this->sets)
set.dispatch(vk, cmd, dispatchExtent);
}

View file

@ -1,53 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "../helpers/managed_shader.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <vector>
#include <vulkan/vulkan_core.h>
namespace ctx { struct Ctx; }
namespace lsfgvk::backend {
/// gamma shaderchain
class Gamma1 {
public:
/// create a gamma shaderchain
/// @param ctx context
/// @param idx generated frame index
/// @param sourceImages source images
/// @param additionalInput0 additional input image
/// @param additionalInput1 additional input image
Gamma1(const Ctx& ctx, size_t idx,
const std::vector<vk::Image>& sourceImages,
const vk::Image& additionalInput0,
const vk::Image& additionalInput1);
/// prepare the shaderchain initially
/// @param images vector to fill with image handles
void prepare(std::vector<VkImage>& images) const;
/// render the gamma shaderchain
/// @param vk the vulkan instance
/// @param cmd command buffer
void render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd) const;
/// get the generated image
/// @return image
[[nodiscard]] const auto& getImage() const { return *this->image; }
private:
std::vector<vk::Image> tempImages0;
std::vector<vk::Image> tempImages1;
ls::lazy<vk::Image> image;
std::vector<ManagedShader> sets;
VkExtent2D dispatchExtent{};
};
}

View file

@ -1,57 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "generate.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk::backend;
Generate::Generate(const Ctx& ctx, size_t idx,
const std::pair<vk::Image, vk::Image>& sourceImages,
const vk::Image& inputImage1,
const vk::Image& inputImage2,
const vk::Image& inputImage3,
const vk::Image& outputImage) {
// create descriptor sets
const auto& shader = ctx.hdr ?
ctx.shaders.get().generate_hdr : ctx.shaders.get().generate;
this->sets.reserve(2);
this->sets.emplace_back(ManagedShaderBuilder()
.sampled(sourceImages.second)
.sampled(sourceImages.first)
.sampled(inputImage1)
.sampled(inputImage2)
.sampled(inputImage3)
.storage(outputImage)
.sampler(ctx.bnbSampler)
.sampler(ctx.eabSampler)
.buffer(ctx.constantBuffers.at(idx))
.build(ctx.vk, ctx.pool, shader));
this->sets.emplace_back(ManagedShaderBuilder()
.sampled(sourceImages.first)
.sampled(sourceImages.second)
.sampled(inputImage1)
.sampled(inputImage2)
.sampled(inputImage3)
.storage(outputImage)
.sampler(ctx.bnbSampler)
.sampler(ctx.eabSampler)
.buffer(ctx.constantBuffers.at(idx))
.build(ctx.vk, ctx.pool, shader));
// store dispatch extent
this->dispatchExtent = backend::add_shift_extent(ctx.sourceExtent, 15, 4);
}
void Generate::render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd, size_t idx) const {
this->sets.at(idx % 2).dispatch(vk, cmd, this->dispatchExtent);
}

View file

@ -1,45 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "../helpers/managed_shader.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace ctx { struct Ctx; }
namespace lsfgvk::backend {
/// generate shaderchain
class Generate {
public:
/// create a generate shaderchain
/// @param ctx context
/// @param idx generated frame index
/// @param sourceImages pair of source images
/// @param inputImage1 input image 1
/// @param inputImage2 input image 2
/// @param inputImage3 input image 3
Generate(const Ctx& ctx, size_t idx,
const std::pair<vk::Image, vk::Image>& sourceImages,
const vk::Image& inputImage1,
const vk::Image& inputImage2,
const vk::Image& inputImage3,
const vk::Image& outputImage);
/// render the generate shaderchain
/// @param vk the vulkan instance
/// @param cmd command buffer
/// @param idx frame index
void render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd, size_t idx) const;
private:
std::vector<ManagedShader> sets;
VkExtent2D dispatchExtent{};
};
}

View file

@ -1,53 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "mipmaps.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <cstdint>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk::backend;
Mipmaps::Mipmaps(const Ctx& ctx,
const std::pair<vk::Image, vk::Image>& sourceImages) {
// create output images for base and 6 mips
this->images.reserve(7);
for (uint32_t i = 0; i < 7; i++)
this->images.emplace_back(ctx.vk,
backend::shift_extent(ctx.flowExtent, i), VK_FORMAT_R8_UNORM);
// create descriptor sets for both input images
this->sets.reserve(2);
this->sets.emplace_back(ManagedShaderBuilder()
.sampled(sourceImages.first)
.storages(this->images)
.sampler(ctx.bnbSampler)
.buffer(ctx.constantBuffer)
.build(ctx.vk, ctx.pool, ctx.shaders.get().mipmaps));
this->sets.emplace_back(ManagedShaderBuilder()
.sampled(sourceImages.second)
.storages(this->images)
.sampler(ctx.bnbSampler)
.buffer(ctx.constantBuffer)
.build(ctx.vk, ctx.pool, ctx.shaders.get().mipmaps));
// store dispatch extent
this->dispatchExtent = backend::add_shift_extent(ctx.flowExtent, 63, 6);
}
void Mipmaps::prepare(std::vector<VkImage>& images) const {
for (const auto& img : this->images)
images.push_back(img.handle());
}
void Mipmaps::render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd, size_t idx) const {
this->sets.at(idx % 2).dispatch(vk, cmd, this->dispatchExtent);
}

View file

@ -1,47 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "../helpers/managed_shader.hpp"
#include "../helpers/utils.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace ctx { struct Ctx; }
namespace lsfgvk::backend {
/// mipmaps shaderchain
class Mipmaps {
public:
/// create a mipmaps shaderchain
/// @param ctx context
/// @param sourceImages pair of source images
Mipmaps(const Ctx& ctx,
const std::pair<vk::Image, vk::Image>& sourceImages);
/// prepare the shaderchain initially
/// @param images vector to fill with image handles
void prepare(std::vector<VkImage>& images) const;
/// render the mipmaps shaderchain
/// @param vk the vulkan instance
/// @param cmd command buffer
/// @param idx frame index
void render(const vk::Vulkan& vk, const vk::CommandBuffer& cmd, size_t idx) const;
/// get the generated mipmap images
/// @return vector of images
[[nodiscard]] const auto& getImages() const { return this->images; }
private:
std::vector<vk::Image> images;
std::vector<ManagedShader> sets;
VkExtent2D dispatchExtent{};
};
}

View file

@ -0,0 +1,53 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "logger.hpp"
#include <iostream>
#include <sstream>
#include <string>
#include <string_view>
using namespace lsfgvk;
using namespace lsfgvk::logger;
namespace {
/// Get the current minimum log level
Level& currentLevel() {
static Level level{Level::Debug};
return level;
}
/// Format a log level as a string
constexpr std::string_view formatLevel(Level level) {
switch (level) {
case Level::Debug:
return "DEBUG";
case Level::Info:
return "INFO ";
case Level::Warning:
return "WARN ";
case Level::Error:
return "ERROR";
}
}
}
void logger::setLevel(Level level) {
#ifdef NDEBUG
if (level == Level::Debug) {
LOG_WARNING("Release builds do not support debug log level, defaulting to info");
level = Level::Info;
}
#endif
currentLevel() = level;
}
void logger::log(Level level, const std::string& message) {
if (level < currentLevel())
return;
std::ostringstream log;
log << "(lsfg-vk) [" << formatLevel(level) << "] " << message << '\n';
std::cerr << log.str();
}

View file

@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include <cstdint>
#include <sstream> // IWYU pragma: keep
#include <string>
#include <string_view> // IWYU pragma: keep
namespace lsfgvk::logger {
/// Various levels for log messages
enum class Level : uint8_t {
/// Detailed debugging information
Debug,
/// General informational messages
Info,
/// Potentially problematic situations
Warning,
/// Irrecoverable errors
Error
};
///
/// Set the minimum log level
///
/// @param level Inclusive minimum log level
///
void setLevel(Level level);
///
/// Log a message
///
/// @param level Log level
/// @param message Log message
///
void log(Level level, const std::string& message);
// NOLINTBEGIN (macro parentheses)
#define LOG(level, msg) { \
std::ostringstream _oss; \
_oss << msg; \
lsfgvk::logger::log(level, _oss.str()); \
}
#define LOG_INFO(msg) LOG(lsfgvk::logger::Level::Info, msg)
#define LOG_WARNING(msg) LOG(lsfgvk::logger::Level::Warning, msg)
#define LOG_ERROR(msg) LOG(lsfgvk::logger::Level::Error, msg)
#ifdef NDEBUG
#define LOG_DEBUG(msg)
#else
#define LOG_DEBUG(msg) LOG(lsfgvk::logger::Level::Debug, msg)
#endif
// NOLINTEND
}