refactor(cleanup): main backend context & instance logic

This commit is contained in:
PancakeTAS 2025-11-30 13:09:37 +01:00
parent 28ee6dbce0
commit 0d95d550aa
No known key found for this signature in database
27 changed files with 719 additions and 7575 deletions

View file

@ -1,37 +0,0 @@
Checks:
# enable basic checks
- "clang-analyzer-*"
# configure performance checks
- "performance-*"
- "-performance-enum-size"
# configure readability and bugprone checks
- "readability-*"
- "bugprone-*"
- "misc-*"
- "-readability-braces-around-statements"
- "-readability-function-cognitive-complexity"
- "-readability-identifier-length"
- "-readability-implicit-bool-conversion"
- "-readability-magic-numbers"
- "-readability-math-missing-parentheses"
- "-readability-named-parameter"
- "-bugprone-easily-swappable-parameters"
# configure modernization
- "modernize-*"
- "-modernize-use-trailing-return-type"
# configure cppcoreguidelines
- "cppcoreguidelines-*"
- "-cppcoreguidelines-avoid-magic-numbers"
- "-cppcoreguidelines-pro-type-reinterpret-cast" # allows reinterpret_cast
- "-cppcoreguidelines-avoid-non-const-global-variables"
- "-cppcoreguidelines-pro-type-union-access"
# disable slow and pointless checks
- "-modernize-use-std-numbers"
- "-modernize-type-traits"
- "-cppcoreguidelines-owning-memory"
- "-cppcoreguidelines-macro-to-enum"
- "-readability-container-contains"
- "-bugprone-reserved-identifier"
- "-bugprone-stringview-nullptr"
- "-bugprone-standalone-empty"
- "-misc-unused-using-decls"

View file

@ -1,3 +0,0 @@
*.cpp diff=cpp eol=lf
*.hpp diff=cpp eol=lf
*.md diff=markdown eol=lf

9
framegen/.gitignore vendored
View file

@ -1,9 +0,0 @@
# cmake files
/build
# ide/lsp files
/.zed
/.vscode
/.clangd
/.cache
/.ccls

View file

@ -1,76 +0,0 @@
cmake_minimum_required(VERSION 3.10)
if(NOT LSFGVK_EXCESS_DEBUG)
set(CMAKE_C_VISIBILITY_PRESET "hidden")
set(CMAKE_CXX_VISIBILITY_PRESET "hidden")
endif()
if(LSFGVK_EXCESS_DEBUG)
add_compile_definitions(LSFGVK_EXCESS_DEBUG)
endif()
project(lsfg-vk-framegen
DESCRIPTION "Lossless Scaling Frame Generation Backend"
LANGUAGES C CXX)
file(GLOB SOURCES
"src/common/*.cpp"
"src/config/*.cpp"
"src/core/*.cpp"
"src/pool/*.cpp"
"src/*.cpp"
"v3.1_src/core/*.cpp"
"v3.1_src/pool/*.cpp"
"v3.1_src/shaders/*.cpp"
"v3.1_src/utils/*.cpp"
"v3.1_src/*.cpp"
"v3.1p_src/core/*.cpp"
"v3.1p_src/pool/*.cpp"
"v3.1p_src/shaders/*.cpp"
"v3.1p_src/utils/*.cpp"
"v3.1p_src/*.cpp"
"src/thirdparty/*.c"
)
add_library(lsfg-vk-framegen STATIC ${SOURCES})
# target
set_target_properties(lsfg-vk-framegen PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON)
target_include_directories(lsfg-vk-framegen SYSTEM
PUBLIC include/thirdparty)
target_include_directories(lsfg-vk-framegen
PUBLIC include
PUBLIC public
PRIVATE v3.1_include
PRIVATE v3.1p_include)
# diagnostics
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set_target_properties(lsfg-vk-framegen PROPERTIES
EXPORT_COMPILE_COMMANDS ON)
endif()
if(LSFGVK_EXCESS_DEBUG)
target_compile_options(lsfg-vk-framegen PRIVATE
-Weverything
# disable compat c++ flags
-Wno-pre-c++20-compat-pedantic
-Wno-pre-c++17-compat
-Wno-c++98-compat-pedantic
-Wno-c++98-compat
# disable other flags
-Wno-missing-designated-field-initializers
-Wno-shadow # allow shadowing
-Wno-switch-enum # ignore missing cases
-Wno-switch-default # ignore missing default
-Wno-padded # ignore automatic padding
-Wno-exit-time-destructors # allow globals
-Wno-global-constructors # allow globals
-Wno-cast-function-type-strict # for vulkan
)
set_target_properties(lsfg-vk-framegen PROPERTIES
CXX_CLANG_TIDY clang-tidy)
endif()

View file

@ -1,21 +0,0 @@
## MIT License
Copyright (c) 2025 lsfg-vk
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,14 +0,0 @@
## lsfg-vk-framegen
Lossless Scaling Frame Generation
This is a subproject of lsfg-vk and contains the dedicated Vulkan logic for generating frames.
The project is intentionally structured as a fully external project, such that it can be integrated into other applications.
### Interface
Interfacing with lsfg-vk-framegen is done via `lsfg_x_x.hpp` header. The internal Vulkan instance is created using `LSFG_X_X::initialize()` and requires a specific deviceUUID, as well as parts of the lsfg-vk configuration, including a function loading SPIR-V shaders by name. Cleanup is done via `LSFG_X_X::finalize()` after which `LSFG_X_X::initialize()` may be called again. Please note that the initialization process is expensive and may take a while. It is recommended to call this function once during the applications lifetime.
Once the format and extent of the requested images is determined, `LSFG_X_X::createContext()` should be called to initialize a frame generation context. The Vulkan images are created from backing memory, which is passed through the file descriptor arguments. A context can be destroyed using `LSFG_X_X::deleteContext()`.
Presenting the context can be done via `LSFG_X_X::presentContext()`. Before calling the function a second time, make sure the outgoing semaphores have been signaled.

View file

@ -1,108 +0,0 @@
#pragma once
#include "core/commandbuffer.hpp"
#include "core/commandpool.hpp"
#include "core/descriptorpool.hpp"
#include "core/image.hpp"
#include "core/device.hpp"
#include "pool/resourcepool.hpp"
#include "pool/shaderpool.hpp"
#include <vulkan/vulkan_core.h>
#include <optional>
#include <cstddef>
#include <cstdint>
#include <string>
#include <array>
#include <vector>
namespace LSFG::Utils {
///
/// Insert memory barriers for images in a command buffer.
///
/// @throws std::logic_error if the command buffer is not in Recording state
///
class BarrierBuilder {
public:
/// Create a barrier builder.
BarrierBuilder(const Core::CommandBuffer& buffer)
: commandBuffer(&buffer) {
this->barriers.reserve(16); // this is performance critical
}
// Add a resource to the barrier builder.
BarrierBuilder& addR2W(Core::Image& image);
BarrierBuilder& addW2R(Core::Image& image);
// Add an optional resource to the barrier builder.
BarrierBuilder& addR2W(std::optional<Core::Image>& image) {
if (image.has_value()) this->addR2W(*image); return *this; }
BarrierBuilder& addW2R(std::optional<Core::Image>& image) {
if (image.has_value()) this->addW2R(*image); return *this; }
/// Add a list of resources to the barrier builder.
BarrierBuilder& addR2W(std::vector<Core::Image>& images) {
for (auto& image : images) this->addR2W(image); return *this; }
BarrierBuilder& addW2R(std::vector<Core::Image>& images) {
for (auto& image : images) this->addW2R(image); return *this; }
/// Add an array of resources to the barrier builder.
template<std::size_t N>
BarrierBuilder& addR2W(std::array<Core::Image, N>& images) {
for (auto& image : images) this->addR2W(image); return *this; }
template<std::size_t N>
BarrierBuilder& addW2R(std::array<Core::Image, N>& images) {
for (auto& image : images) this->addW2R(image); return *this; }
/// Finish building the barrier
void build() const;
private:
const Core::CommandBuffer* commandBuffer;
std::vector<VkImageMemoryBarrier2> barriers;
};
///
/// Upload a DDS file to a Vulkan image.
///
/// @param device The Vulkan device
/// @param commandPool The command pool
/// @param image The Vulkan image to upload to
/// @param path The path to the DDS file.
///
/// @throws std::system_error If the file cannot be opened or read.
/// @throws ls:vulkan_error If the Vulkan image cannot be created or updated.
///
void uploadImage(const Core::Device& device,
const Core::CommandPool& commandPool,
Core::Image& image, const std::string& path);
///
/// Clear a texture to white during setup.
///
/// @param device The Vulkan device.
/// @param image The image to clear.
/// @param white If true, the image will be cleared to white, otherwise to black.
///
/// @throws LSFG::vulkan_error If the Vulkan image cannot be cleared.
///
void clearImage(const Core::Device& device, Core::Image& image, bool white = false);
}
namespace LSFG {
struct Vulkan {
Core::Device device;
Core::CommandPool commandPool;
Core::DescriptorPool descriptorPool;
uint64_t generationCount;
float flowScale;
bool isHdr;
Pool::ShaderPool shaders;
Pool::ResourcePool resources;
};
}

View file

@ -1,69 +0,0 @@
#pragma once
#include "core/device.hpp"
#include "core/buffer.hpp"
#include "core/sampler.hpp"
#include "vulkan/vulkan_core.h"
#include <cstdint>
#include <unordered_map>
namespace LSFG::Pool {
///
/// Resource pool for each Vulkan device.
///
class ResourcePool {
public:
ResourcePool() noexcept = default;
///
/// Create the resource pool.
///
/// @param isHdr HDR support stored in buffers.
/// @param flowScale Scale factor stored in buffers.
///
/// @throws std::runtime_error if the resource pool cannot be created.
///
ResourcePool(bool isHdr, float flowScale)
: isHdr(isHdr), flowScale(flowScale) {}
///
/// Retrieve a buffer with given parameters or create it.
///
/// @param timestamp Timestamp stored in buffer
/// @param firstIter First iteration stored in buffer
/// @param firstIterS First special iteration stored in buffer
/// @return Created or cached buffer
///
/// @throws LSFG::vulkan_error if the buffer cannot be created.
///
Core::Buffer getBuffer(
const Core::Device& device,
float timestamp = 0.0F, bool firstIter = false, bool firstIterS = false);
///
/// Retrieve a sampler by type or create it.
///
/// @param type Type of the sampler
/// @param compare Compare operation for the sampler
/// @param isWhite Whether the sampler is white
/// @return Created or cached sampler
///
/// @throws LSFG::vulkan_error if the sampler cannot be created.
///
Core::Sampler getSampler(
const Core::Device& device,
VkSamplerAddressMode type = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
VkCompareOp compare = VK_COMPARE_OP_NEVER,
bool isWhite = false);
private:
std::unordered_map<uint64_t, Core::Buffer> buffers;
std::unordered_map<uint64_t, Core::Sampler> samplers;
bool isHdr{};
float flowScale{};
};
}

File diff suppressed because it is too large Load diff

View file

@ -1,83 +0,0 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <functional>
#include <cstdint>
#include <string>
#include <vector>
namespace LSFG_3_1 {
///
/// Initialize the LSFG library.
///
/// @param deviceUUID The UUID of the Vulkan device to use.
/// @param isHdr Whether the images are in HDR format.
/// @param flowScale Internal flow scale factor.
/// @param generationCount Number of frames to generate.
/// @param forceDisableFp16 Whether to force-disable FP16 optimizations.
/// @param loader Function to load shader source code by name.
///
/// @throws LSFG::vulkan_error if Vulkan objects fail to initialize.
///
[[gnu::visibility("default")]]
void initialize(uint64_t deviceUUID,
bool isHdr, float flowScale, uint64_t generationCount,
bool forceDisableFp16,
const std::function<std::vector<uint8_t>(const std::string&, bool)>& loader);
#ifdef LSFGVK_EXCESS_DEBUG
///
/// Initialize the renderdoc API.
///
/// @throws LSFG::vulkan_error if the renderdoc API cannot be initialized.
///
[[gnu::visibility("default")]]
void initializeRenderDoc();
#endif // LSFGVK_EXCESS_DEBUG
///
/// Create a new LSFG context on a swapchain.
///
/// @param in0 File descriptor for the first input image.
/// @param in1 File descriptor for the second input image.
/// @param outN File descriptor for each output image. This defines the LSFG level.
/// @param extent The size of the images
/// @param format The format of the images.
/// @return A unique identifier for the created context.
///
/// @throws LSFG::vulkan_error if the context cannot be created.
///
[[gnu::visibility("default")]]
int32_t createContext(
int in0, int in1, const std::vector<int>& outN,
VkExtent2D extent, VkFormat format);
///
/// Present a context.
///
/// @param id Unique identifier of the context to present.
/// @param inSem Semaphore to wait on before starting the generation.
/// @param outSem Semaphores to signal once each output image is ready.
///
/// @throws LSFG::vulkan_error if the context cannot be presented.
///
[[gnu::visibility("default")]]
void presentContext(int32_t id, int inSem, const std::vector<int>& outSem);
///
/// Delete an LSFG context.
///
/// @param id Unique identifier of the context to delete.
///
[[gnu::visibility("default")]]
void deleteContext(int32_t id);
///
/// Deinitialize the LSFG library.
///
[[gnu::visibility("default")]]
void finalize();
}

View file

@ -1,83 +0,0 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <functional>
#include <cstdint>
#include <string>
#include <vector>
namespace LSFG_3_1P {
///
/// Initialize the LSFG library.
///
/// @param deviceUUID The UUID of the Vulkan device to use.
/// @param isHdr Whether the images are in HDR format.
/// @param flowScale Internal flow scale factor.
/// @param generationCount Number of frames to generate.
/// @param forceDisableFp16 Whether to force-disable FP16 optimizations.
/// @param loader Function to load shader source code by name.
///
/// @throws LSFG::vulkan_error if Vulkan objects fail to initialize.
///
[[gnu::visibility("default")]]
void initialize(uint64_t deviceUUID,
bool isHdr, float flowScale, uint64_t generationCount,
bool forceDisableFp16,
const std::function<std::vector<uint8_t>(const std::string&, bool)>& loader);
#ifdef LSFGVK_EXCESS_DEBUG
///
/// Initialize the renderdoc API.
///
/// @throws LSFG::vulkan_error if the renderdoc API cannot be initialized.
///
[[gnu::visibility("default")]]
void initializeRenderDoc();
#endif // LSFGVK_EXCESS_DEBUG
///
/// Create a new LSFG context on a swapchain.
///
/// @param in0 File descriptor for the first input image.
/// @param in1 File descriptor for the second input image.
/// @param outN File descriptor for each output image. This defines the LSFG level.
/// @param extent The size of the images
/// @param format The format of the images.
/// @return A unique identifier for the created context.
///
/// @throws LSFG::vulkan_error if the context cannot be created.
///
[[gnu::visibility("default")]]
int32_t createContext(
int in0, int in1, const std::vector<int>& outN,
VkExtent2D extent, VkFormat format);
///
/// Present a context.
///
/// @param id Unique identifier of the context to present.
/// @param inSem Semaphore to wait on before starting the generation.
/// @param outSem Semaphores to signal once each output image is ready.
///
/// @throws LSFG::vulkan_error if the context cannot be presented.
///
[[gnu::visibility("default")]]
void presentContext(int32_t id, int inSem, const std::vector<int>& outSem);
///
/// Delete an LSFG context.
///
/// @param id Unique identifier of the context to delete.
///
[[gnu::visibility("default")]]
void deleteContext(int32_t id);
///
/// Deinitialize the LSFG library.
///
[[gnu::visibility("default")]]
void finalize();
}

View file

@ -1,191 +0,0 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "common/utils.hpp"
#include "core/buffer.hpp"
#include "core/image.hpp"
#include "core/device.hpp"
#include "core/commandpool.hpp"
#include "core/fence.hpp"
#include "common/exception.hpp"
#include <cstdint>
#include <cerrno>
#include <cstdlib>
#include <fstream>
#include <string>
#include <ios>
#include <system_error>
#include <vector>
using namespace LSFG;
using namespace LSFG::Utils;
BarrierBuilder& BarrierBuilder::addR2W(Core::Image& image) {
this->barriers.emplace_back(VkImageMemoryBarrier2 {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.srcAccessMask = VK_ACCESS_2_SHADER_READ_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.dstAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT,
.oldLayout = image.getLayout(),
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.image = image.handle(),
.subresourceRange = {
.aspectMask = image.getAspectFlags(),
.levelCount = 1,
.layerCount = 1
}
});
image.setLayout(VK_IMAGE_LAYOUT_GENERAL);
return *this;
}
BarrierBuilder& BarrierBuilder::addW2R(Core::Image& image) {
this->barriers.emplace_back(VkImageMemoryBarrier2 {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT,
.oldLayout = image.getLayout(),
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.image = image.handle(),
.subresourceRange = {
.aspectMask = image.getAspectFlags(),
.levelCount = 1,
.layerCount = 1
}
});
image.setLayout(VK_IMAGE_LAYOUT_GENERAL);
return *this;
}
void BarrierBuilder::build() const {
const VkDependencyInfo dependencyInfo = {
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.imageMemoryBarrierCount = static_cast<uint32_t>(this->barriers.size()),
.pImageMemoryBarriers = this->barriers.data()
};
vkCmdPipelineBarrier2(this->commandBuffer->handle(), &dependencyInfo);
}
void Utils::uploadImage(const Core::Device& device, const Core::CommandPool& commandPool,
Core::Image& image, const std::string& path) {
// read image bytecode
std::ifstream file(path.data(), std::ios::binary | std::ios::ate);
if (!file.is_open())
throw std::system_error(errno, std::generic_category(), "Failed to open image: " + path);
std::streamsize size = file.tellg();
size -= 124 + 4; // dds header and magic bytes
std::vector<char> code(static_cast<size_t>(size));
file.seekg(124 + 4, std::ios::beg);
if (!file.read(code.data(), size))
throw std::system_error(errno, std::generic_category(), "Failed to read image: " + path);
file.close();
// copy data to buffer
const Core::Buffer stagingBuffer(
device, code.data(), static_cast<uint32_t>(code.size()),
VK_BUFFER_USAGE_TRANSFER_SRC_BIT
);
// perform the upload
Core::CommandBuffer commandBuffer(device, commandPool);
commandBuffer.begin();
const VkImageMemoryBarrier barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_NONE,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = image.getLayout(),
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.image = image.handle(),
.subresourceRange = {
.aspectMask = image.getAspectFlags(),
.levelCount = 1,
.layerCount = 1
}
};
image.setLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
vkCmdPipelineBarrier(
commandBuffer.handle(),
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, nullptr, 0, nullptr, 1, &barrier
);
auto extent = image.getExtent();
const VkBufferImageCopy region{
.bufferImageHeight = 0,
.imageSubresource = {
.aspectMask = image.getAspectFlags(),
.layerCount = 1
},
.imageExtent = { extent.width, extent.height, 1 }
};
vkCmdCopyBufferToImage(
commandBuffer.handle(),
stagingBuffer.handle(), image.handle(),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region
);
commandBuffer.end();
Core::Fence fence(device);
commandBuffer.submit(device.getComputeQueue(), fence);
// wait for the upload to complete
if (!fence.wait(device))
throw LSFG::vulkan_error(VK_TIMEOUT, "Upload operation timed out");
}
void Utils::clearImage(const Core::Device& device, Core::Image& image, bool white) {
Core::Fence fence(device);
const Core::CommandPool cmdPool(device);
Core::CommandBuffer cmdBuf(device, cmdPool);
cmdBuf.begin();
const VkImageMemoryBarrier2 barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
.oldLayout = image.getLayout(),
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.image = image.handle(),
.subresourceRange = {
.aspectMask = image.getAspectFlags(),
.levelCount = 1,
.layerCount = 1
}
};
const VkDependencyInfo dependencyInfo = {
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.imageMemoryBarrierCount = 1,
.pImageMemoryBarriers = &barrier
};
image.setLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
vkCmdPipelineBarrier2(cmdBuf.handle(), &dependencyInfo);
const float clearValue = white ? 1.0F : 0.0F;
const VkClearColorValue clearColor = {{ clearValue, clearValue, clearValue, clearValue }};
const VkImageSubresourceRange subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = 1,
.layerCount = 1
};
vkCmdClearColorImage(cmdBuf.handle(),
image.handle(), image.getLayout(),
&clearColor,
1, &subresourceRange);
cmdBuf.end();
cmdBuf.submit(device.getComputeQueue(), fence);
if (!fence.wait(device))
throw LSFG::vulkan_error(VK_TIMEOUT, "Failed to wait for clearing fence.");
}

View file

@ -1,72 +0,0 @@
#include "pool/resourcepool.hpp"
#include "core/buffer.hpp"
#include "core/device.hpp"
#include "core/sampler.hpp"
#include <vulkan/vulkan_core.h>
#include <array>
#include <cstdint>
using namespace LSFG;
using namespace LSFG::Pool;
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;
};
Core::Buffer ResourcePool::getBuffer(
const Core::Device& device,
float timestamp, bool firstIter, bool firstIterS) {
uint64_t hash = 0;
const union { float f; uint32_t i; } u{
.f = timestamp };
hash |= u.i;
hash |= static_cast<uint64_t>(firstIter) << 32;
hash |= static_cast<uint64_t>(firstIterS) << 33;
auto it = buffers.find(hash);
if (it != buffers.end())
return it->second;
// create the buffer
const ConstantBuffer data{
.inputOffset = { 0, 0 },
.advancedColorKind = this->isHdr ? 2U : 0U,
.hdrSupport = this->isHdr,
.resolutionInvScale = this->flowScale,
.timestamp = timestamp,
.uiThreshold = 0.5F,
};
Core::Buffer buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT);
buffers[hash] = buffer;
return buffer;
}
Core::Sampler ResourcePool::getSampler(
const Core::Device& device,
VkSamplerAddressMode type,
VkCompareOp compare,
bool isWhite) {
uint64_t hash = 0;
hash |= static_cast<uint64_t>(type) << 0;
hash |= static_cast<uint64_t>(compare) << 8;
hash |= static_cast<uint64_t>(isWhite) << 16;
auto it = samplers.find(hash);
if (it != samplers.end())
return it->second;
// create the sampler
Core::Sampler sampler(device, type, compare, isWhite);
samplers[hash] = sampler;
return sampler;
}

File diff suppressed because it is too large Load diff

View file

@ -1,85 +0,0 @@
#pragma once
#include "core/image.hpp"
#include "core/semaphore.hpp"
#include "core/fence.hpp"
#include "core/commandbuffer.hpp"
#include "shaders/alpha.hpp"
#include "shaders/beta.hpp"
#include "shaders/delta.hpp"
#include "shaders/gamma.hpp"
#include "shaders/generate.hpp"
#include "shaders/mipmaps.hpp"
#include "common/utils.hpp"
#include <vulkan/vulkan_core.h>
#include <vector>
#include <cstdint>
#include <array>
namespace LSFG_3_1 {
using namespace LSFG;
class Context {
public:
///
/// Create a context
///
/// @param vk The Vulkan instance to use.
/// @param in0 File descriptor for the first input image.
/// @param in1 File descriptor for the second input image.
/// @param outN File descriptors for the output images.
/// @param extent The size of the images.
/// @param format The format of the images.
///
/// @throws LSFG::vulkan_error if the context fails to initialize.
///
Context(Vulkan& vk,
int in0, int in1, const std::vector<int>& outN,
VkExtent2D extent, VkFormat format);
///
/// Present on the context.
///
/// @param inSem Semaphore to wait on before starting the generation.
/// @param outSem Semaphores to signal after each generation is done.
///
/// @throws LSFG::vulkan_error if the context fails to present.
///
void present(Vulkan& vk,
int inSem, const std::vector<int>& outSem);
// Trivially copyable, moveable and destructible
Context(const Context&) = default;
Context& operator=(const Context&) = default;
Context(Context&&) = default;
Context& operator=(Context&&) = default;
~Context() = default;
private:
Core::Image inImg_0, inImg_1; // inImg_0 is next when fc % 2 == 0
uint64_t frameIdx{0};
struct RenderData {
Core::Semaphore inSemaphore; // signaled when input is ready
std::vector<Core::Semaphore> internalSemaphores; // signaled when first step is done
std::vector<Core::Semaphore> outSemaphores; // signaled when each pass is done
std::vector<Core::Fence> completionFences; // fence for completion of each pass
Core::CommandBuffer cmdBuffer1;
std::vector<Core::CommandBuffer> cmdBuffers2; // command buffers for second step
bool shouldWait{false};
};
std::array<RenderData, 8> data;
Shaders::Mipmaps mipmaps;
std::array<Shaders::Alpha, 7> alpha;
Shaders::Beta beta;
std::array<Shaders::Gamma, 7> gamma;
std::array<Shaders::Delta, 3> delta;
Shaders::Generate generate;
};
}

View file

@ -1,122 +0,0 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1/context.hpp"
#include "common/utils.hpp"
#include "common/exception.hpp"
#include <vector>
#include <cstddef>
#include <algorithm>
#include <optional>
#include <cstdint>
using namespace LSFG_3_1;
Context::Context(Vulkan& vk,
int in0, int in1, const std::vector<int>& outN,
VkExtent2D extent, VkFormat format) {
// import input images
this->inImg_0 = Core::Image(vk.device, extent, format,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_ASPECT_COLOR_BIT, in0);
this->inImg_1 = Core::Image(vk.device, extent, format,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_ASPECT_COLOR_BIT, in1);
// prepare render data
for (size_t i = 0; i < 8; i++) {
auto& data = this->data.at(i);
data.internalSemaphores.resize(vk.generationCount);
data.outSemaphores.resize(vk.generationCount);
data.completionFences.resize(vk.generationCount);
data.cmdBuffers2.resize(vk.generationCount);
}
// create shader chains
this->mipmaps = Shaders::Mipmaps(vk, this->inImg_0, this->inImg_1);
for (size_t i = 0; i < 7; i++)
this->alpha.at(i) = Shaders::Alpha(vk, this->mipmaps.getOutImages().at(i));
this->beta = Shaders::Beta(vk, this->alpha.at(0).getOutImages());
for (size_t i = 0; i < 7; i++) {
this->gamma.at(i) = Shaders::Gamma(vk,
this->alpha.at(6 - i).getOutImages(),
this->beta.getOutImages().at(std::min<size_t>(6 - i, 5)),
(i == 0) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage()));
if (i < 4) continue;
this->delta.at(i - 4) = Shaders::Delta(vk,
this->alpha.at(6 - i).getOutImages(),
this->beta.getOutImages().at(6 - i),
(i == 4) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage()),
(i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage1()),
(i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage2()));
}
this->generate = Shaders::Generate(vk,
this->inImg_0, this->inImg_1,
this->gamma.at(6).getOutImage(),
this->delta.at(2).getOutImage1(),
this->delta.at(2).getOutImage2(),
outN, format);
}
void Context::present(Vulkan& vk,
int inSem, const std::vector<int>& outSem) {
auto& data = this->data.at(this->frameIdx % 8);
// 3. wait for completion of previous frame in this slot
if (data.shouldWait)
for (auto& fence : data.completionFences)
if (!fence.wait(vk.device, UINT64_MAX))
throw LSFG::vulkan_error(VK_TIMEOUT, "Fence wait timed out");
data.shouldWait = true;
// 1. create mipmaps and process input image
if (inSem >= 0) data.inSemaphore = Core::Semaphore(vk.device, inSem);
for (size_t i = 0; i < vk.generationCount; i++)
data.internalSemaphores.at(i) = Core::Semaphore(vk.device);
data.cmdBuffer1 = Core::CommandBuffer(vk.device, vk.commandPool);
data.cmdBuffer1.begin();
this->mipmaps.Dispatch(data.cmdBuffer1, this->frameIdx);
for (size_t i = 0; i < 7; i++)
this->alpha.at(6 - i).Dispatch(data.cmdBuffer1, this->frameIdx);
this->beta.Dispatch(data.cmdBuffer1, this->frameIdx);
data.cmdBuffer1.end();
std::vector<Core::Semaphore> waits = { data.inSemaphore };
if (inSem < 0) waits.clear();
data.cmdBuffer1.submit(vk.device.getComputeQueue(), std::nullopt,
waits, std::nullopt,
data.internalSemaphores, std::nullopt);
// 2. generate intermediary frames
for (size_t pass = 0; pass < vk.generationCount; pass++) {
auto& internalSemaphore = data.internalSemaphores.at(pass);
auto& outSemaphore = data.outSemaphores.at(pass);
if (inSem >= 0) outSemaphore = Core::Semaphore(vk.device, outSem.empty() ? -1 : outSem.at(pass));
auto& completionFence = data.completionFences.at(pass);
completionFence = Core::Fence(vk.device);
auto& buf2 = data.cmdBuffers2.at(pass);
buf2 = Core::CommandBuffer(vk.device, vk.commandPool);
buf2.begin();
for (size_t i = 0; i < 7; i++) {
this->gamma.at(i).Dispatch(buf2, this->frameIdx, pass);
if (i >= 4)
this->delta.at(i - 4).Dispatch(buf2, this->frameIdx, pass);
}
this->generate.Dispatch(buf2, this->frameIdx, pass);
buf2.end();
std::vector<Core::Semaphore> signals = { outSemaphore };
if (inSem < 0) signals.clear();
buf2.submit(vk.device.getComputeQueue(), completionFence,
{ internalSemaphore }, std::nullopt,
signals, std::nullopt);
}
this->frameIdx++;
}

View file

@ -1,138 +0,0 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "lsfg_3_1.hpp"
#include "v3_1/context.hpp"
#include "core/commandpool.hpp"
#include "core/descriptorpool.hpp"
#include "core/instance.hpp"
#include "pool/shaderpool.hpp"
#include "common/exception.hpp"
#include "common/utils.hpp"
#ifdef LSFGVK_EXCESS_DEBUG
#include <renderdoc_app.h>
#include <dlfcn.h>
#endif // LSFGVK_EXCESS_DEBUG
#include <cstdint>
#include <optional>
#include <cstdlib>
#include <ctime>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
using namespace LSFG;
using namespace LSFG_3_1;
namespace {
std::optional<Core::Instance> instance;
std::optional<Vulkan> device;
std::unordered_map<int32_t, Context> contexts;
#ifdef LSFGVK_EXCESS_DEBUG
std::optional<RENDERDOC_API_1_6_0*> renderdoc;
#endif // LSFGVK_EXCESS_DEBUG
}
void LSFG_3_1::initialize(uint64_t deviceUUID,
bool isHdr, float flowScale, uint64_t generationCount,
bool forceDisableFp16,
const std::function<std::vector<uint8_t>(const std::string&, bool)>& loader) {
if (instance.has_value() || device.has_value())
return;
instance.emplace();
device.emplace(Vulkan {
.device{*instance, deviceUUID, forceDisableFp16},
.generationCount = generationCount,
.flowScale = flowScale,
.isHdr = isHdr
});
contexts = std::unordered_map<int32_t, Context>();
device->commandPool = Core::CommandPool(device->device);
device->descriptorPool = Core::DescriptorPool(device->device);
device->resources = Pool::ResourcePool(device->isHdr, device->flowScale);
device->shaders = Pool::ShaderPool(loader, device->device.getFP16Support());
std::srand(static_cast<uint32_t>(std::time(nullptr)));
}
#ifdef LSFGVK_EXCESS_DEBUG
void LSFG_3_1::initializeRenderDoc() {
if (renderdoc.has_value())
return;
if (void* mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD)) {
auto rdocGetAPI = reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
RENDERDOC_API_1_6_0* rdoc{};
rdocGetAPI(eRENDERDOC_API_Version_1_6_0, reinterpret_cast<void**>(&rdoc));
renderdoc.emplace(rdoc);
}
if (!renderdoc.has_value()) {
throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "RenderDoc API not found");
}
}
#endif // LSFGVK_EXCESS_DEBUG
int32_t LSFG_3_1::createContext(
int in0, int in1, const std::vector<int>& outN,
VkExtent2D extent, VkFormat format) {
if (!instance.has_value() || !device.has_value())
throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized");
const int32_t id = std::rand();
contexts.emplace(id, Context(*device, in0, in1, outN, extent, format));
return id;
}
void LSFG_3_1::presentContext(int32_t id, int inSem, const std::vector<int>& outSem) {
if (!instance.has_value() || !device.has_value())
throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized");
auto it = contexts.find(id);
if (it == contexts.end())
throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Context not found");
#ifdef LSFGVK_EXCESS_DEBUG
if (renderdoc.has_value())
(*renderdoc)->StartFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance->handle()), nullptr);
#endif // LSFGVK_EXCESS_DEBUG
it->second.present(*device, inSem, outSem);
#ifdef LSFGVK_EXCESS_DEBUG
if (renderdoc.has_value()) {
vkDeviceWaitIdle(device->device.handle());
(*renderdoc)->EndFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance->handle()), nullptr);
}
#endif // LSFGVK_EXCESS_DEBUG
}
void LSFG_3_1::deleteContext(int32_t id) {
if (!instance.has_value() || !device.has_value())
throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized");
auto it = contexts.find(id);
if (it == contexts.end())
throw LSFG::vulkan_error(VK_ERROR_DEVICE_LOST, "No such context");
vkDeviceWaitIdle(device->device.handle());
contexts.erase(it);
}
void LSFG_3_1::finalize() {
if (!instance.has_value() || !device.has_value())
return;
vkDeviceWaitIdle(device->device.handle());
contexts.clear();
device.reset();
instance.reset();
}

View file

@ -1,85 +0,0 @@
#pragma once
#include "core/image.hpp"
#include "core/semaphore.hpp"
#include "core/fence.hpp"
#include "core/commandbuffer.hpp"
#include "shaders/alpha.hpp"
#include "shaders/beta.hpp"
#include "shaders/delta.hpp"
#include "shaders/gamma.hpp"
#include "shaders/generate.hpp"
#include "shaders/mipmaps.hpp"
#include "common/utils.hpp"
#include <vulkan/vulkan_core.h>
#include <vector>
#include <cstdint>
#include <array>
namespace LSFG_3_1P {
using namespace LSFG;
class Context {
public:
///
/// Create a context
///
/// @param vk The Vulkan instance to use.
/// @param in0 File descriptor for the first input image.
/// @param in1 File descriptor for the second input image.
/// @param outN File descriptors for the output images.
/// @param extent The size of the images.
/// @param format The format of the images.
///
/// @throws LSFG::vulkan_error if the context fails to initialize.
///
Context(Vulkan& vk,
int in0, int in1, const std::vector<int>& outN,
VkExtent2D extent, VkFormat format);
///
/// Present on the context.
///
/// @param inSem Semaphore to wait on before starting the generation.
/// @param outSem Semaphores to signal after each generation is done.
///
/// @throws LSFG::vulkan_error if the context fails to present.
///
void present(Vulkan& vk,
int inSem, const std::vector<int>& outSem);
// Trivially copyable, moveable and destructible
Context(const Context&) = default;
Context& operator=(const Context&) = default;
Context(Context&&) = default;
Context& operator=(Context&&) = default;
~Context() = default;
private:
Core::Image inImg_0, inImg_1; // inImg_0 is next when fc % 2 == 0
uint64_t frameIdx{0};
struct RenderData {
Core::Semaphore inSemaphore; // signaled when input is ready
std::vector<Core::Semaphore> internalSemaphores; // signaled when first step is done
std::vector<Core::Semaphore> outSemaphores; // signaled when each pass is done
std::vector<Core::Fence> completionFences; // fence for completion of each pass
Core::CommandBuffer cmdBuffer1;
std::vector<Core::CommandBuffer> cmdBuffers2; // command buffers for second step
bool shouldWait{false};
};
std::array<RenderData, 8> data;
Shaders::Mipmaps mipmaps;
std::array<Shaders::Alpha, 7> alpha;
Shaders::Beta beta;
std::array<Shaders::Gamma, 7> gamma;
std::array<Shaders::Delta, 3> delta;
Shaders::Generate generate;
};
}

View file

@ -1,123 +0,0 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1p/context.hpp"
#include "common/utils.hpp"
#include "common/exception.hpp"
#include <vector>
#include <cstddef>
#include <algorithm>
#include <optional>
#include <cstdint>
using namespace LSFG;
using namespace LSFG_3_1P;
Context::Context(Vulkan& vk,
int in0, int in1, const std::vector<int>& outN,
VkExtent2D extent, VkFormat format) {
// import input images
this->inImg_0 = Core::Image(vk.device, extent, format,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_ASPECT_COLOR_BIT, in0);
this->inImg_1 = Core::Image(vk.device, extent, format,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_ASPECT_COLOR_BIT, in1);
// prepare render data
for (size_t i = 0; i < 8; i++) {
auto& data = this->data.at(i);
data.internalSemaphores.resize(vk.generationCount);
data.outSemaphores.resize(vk.generationCount);
data.completionFences.resize(vk.generationCount);
data.cmdBuffers2.resize(vk.generationCount);
}
// create shader chains
this->mipmaps = Shaders::Mipmaps(vk, this->inImg_0, this->inImg_1);
for (size_t i = 0; i < 7; i++)
this->alpha.at(i) = Shaders::Alpha(vk, this->mipmaps.getOutImages().at(i));
this->beta = Shaders::Beta(vk, this->alpha.at(0).getOutImages());
for (size_t i = 0; i < 7; i++) {
this->gamma.at(i) = Shaders::Gamma(vk,
this->alpha.at(6 - i).getOutImages(),
this->beta.getOutImages().at(std::min<size_t>(6 - i, 5)),
(i == 0) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage()));
if (i < 4) continue;
this->delta.at(i - 4) = Shaders::Delta(vk,
this->alpha.at(6 - i).getOutImages(),
this->beta.getOutImages().at(6 - i),
(i == 4) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage()),
(i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage1()),
(i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage2()));
}
this->generate = Shaders::Generate(vk,
this->inImg_0, this->inImg_1,
this->gamma.at(6).getOutImage(),
this->delta.at(2).getOutImage1(),
this->delta.at(2).getOutImage2(),
outN, format);
}
void Context::present(Vulkan& vk,
int inSem, const std::vector<int>& outSem) {
auto& data = this->data.at(this->frameIdx % 8);
// 3. wait for completion of previous frame in this slot
if (data.shouldWait)
for (auto& fence : data.completionFences)
if (!fence.wait(vk.device, UINT64_MAX))
throw LSFG::vulkan_error(VK_TIMEOUT, "Fence wait timed out");
data.shouldWait = true;
// 1. create mipmaps and process input image
if (inSem >= 0) data.inSemaphore = Core::Semaphore(vk.device, inSem);
for (size_t i = 0; i < vk.generationCount; i++)
data.internalSemaphores.at(i) = Core::Semaphore(vk.device);
data.cmdBuffer1 = Core::CommandBuffer(vk.device, vk.commandPool);
data.cmdBuffer1.begin();
this->mipmaps.Dispatch(data.cmdBuffer1, this->frameIdx);
for (size_t i = 0; i < 7; i++)
this->alpha.at(6 - i).Dispatch(data.cmdBuffer1, this->frameIdx);
this->beta.Dispatch(data.cmdBuffer1, this->frameIdx);
data.cmdBuffer1.end();
std::vector<Core::Semaphore> waits = { data.inSemaphore };
if (inSem < 0) waits.clear();
data.cmdBuffer1.submit(vk.device.getComputeQueue(), std::nullopt,
waits, std::nullopt,
data.internalSemaphores, std::nullopt);
// 2. generate intermediary frames
for (size_t pass = 0; pass < vk.generationCount; pass++) {
auto& internalSemaphore = data.internalSemaphores.at(pass);
auto& outSemaphore = data.outSemaphores.at(pass);
if (inSem >= 0) outSemaphore = Core::Semaphore(vk.device, outSem.empty() ? -1 : outSem.at(pass));
auto& completionFence = data.completionFences.at(pass);
completionFence = Core::Fence(vk.device);
auto& buf2 = data.cmdBuffers2.at(pass);
buf2 = Core::CommandBuffer(vk.device, vk.commandPool);
buf2.begin();
for (size_t i = 0; i < 7; i++) {
this->gamma.at(i).Dispatch(buf2, this->frameIdx, pass);
if (i >= 4)
this->delta.at(i - 4).Dispatch(buf2, this->frameIdx, pass);
}
this->generate.Dispatch(buf2, this->frameIdx, pass);
buf2.end();
std::vector<Core::Semaphore> signals = { outSemaphore };
if (inSem < 0) signals.clear();
buf2.submit(vk.device.getComputeQueue(), completionFence,
{ internalSemaphore }, std::nullopt,
signals, std::nullopt);
}
this->frameIdx++;
}

View file

@ -1,138 +0,0 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "lsfg_3_1p.hpp"
#include "v3_1p/context.hpp"
#include "core/commandpool.hpp"
#include "core/descriptorpool.hpp"
#include "core/instance.hpp"
#include "pool/shaderpool.hpp"
#include "common/exception.hpp"
#include "common/utils.hpp"
#ifdef LSFGVK_EXCESS_DEBUG
#include <renderdoc_app.h>
#include <dlfcn.h>
#endif // LSFGVK_EXCESS_DEBUG
#include <cstdint>
#include <optional>
#include <cstdlib>
#include <ctime>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
using namespace LSFG;
using namespace LSFG_3_1P;
namespace {
std::optional<Core::Instance> instance;
std::optional<Vulkan> device;
std::unordered_map<int32_t, Context> contexts;
#ifdef LSFGVK_EXCESS_DEBUG
std::optional<RENDERDOC_API_1_6_0*> renderdoc;
#endif // LSFGVK_EXCESS_DEBUG
}
void LSFG_3_1P::initialize(uint64_t deviceUUID,
bool isHdr, float flowScale, uint64_t generationCount,
bool forceDisableFp16,
const std::function<std::vector<uint8_t>(const std::string&, bool)>& loader) {
if (instance.has_value() || device.has_value())
return;
instance.emplace();
device.emplace(Vulkan {
.device{*instance, deviceUUID, forceDisableFp16},
.generationCount = generationCount,
.flowScale = flowScale,
.isHdr = isHdr
});
contexts = std::unordered_map<int32_t, Context>();
device->commandPool = Core::CommandPool(device->device);
device->descriptorPool = Core::DescriptorPool(device->device);
device->resources = Pool::ResourcePool(device->isHdr, device->flowScale);
device->shaders = Pool::ShaderPool(loader, device->device.getFP16Support());
std::srand(static_cast<uint32_t>(std::time(nullptr)));
}
#ifdef LSFGVK_EXCESS_DEBUG
void LSFG_3_1P::initializeRenderDoc() {
if (renderdoc.has_value())
return;
if (void* mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD)) {
auto rdocGetAPI = reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
RENDERDOC_API_1_6_0* rdoc{};
rdocGetAPI(eRENDERDOC_API_Version_1_6_0, reinterpret_cast<void**>(&rdoc));
renderdoc.emplace(rdoc);
}
if (!renderdoc.has_value()) {
throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "RenderDoc API not found");
}
}
#endif // LSFGVK_EXCESS_DEBUG
int32_t LSFG_3_1P::createContext(
int in0, int in1, const std::vector<int>& outN,
VkExtent2D extent, VkFormat format) {
if (!instance.has_value() || !device.has_value())
throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized");
const int32_t id = std::rand();
contexts.emplace(id, Context(*device, in0, in1, outN, extent, format));
return id;
}
void LSFG_3_1P::presentContext(int32_t id, int inSem, const std::vector<int>& outSem) {
if (!instance.has_value() || !device.has_value())
throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized");
auto it = contexts.find(id);
if (it == contexts.end())
throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Context not found");
#ifdef LSFGVK_EXCESS_DEBUG
if (renderdoc.has_value())
(*renderdoc)->StartFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance->handle()), nullptr);
#endif // LSFGVK_EXCESS_DEBUG
it->second.present(*device, inSem, outSem);
#ifdef LSFGVK_EXCESS_DEBUG
if (renderdoc.has_value()) {
vkDeviceWaitIdle(device->device.handle());
(*renderdoc)->EndFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance->handle()), nullptr);
}
#endif // LSFGVK_EXCESS_DEBUG
}
void LSFG_3_1P::deleteContext(int32_t id) {
if (!instance.has_value() || !device.has_value())
throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized");
auto it = contexts.find(id);
if (it == contexts.end())
throw LSFG::vulkan_error(VK_ERROR_DEVICE_LOST, "No such context");
vkDeviceWaitIdle(device->device.handle());
contexts.erase(it);
}
void LSFG_3_1P::finalize() {
if (!instance.has_value() || !device.has_value())
return;
vkDeviceWaitIdle(device->device.handle());
contexts.clear();
device.reset();
instance.reset();
}

View file

@ -1,7 +1,9 @@
set(BACKEND_SOURCES set(BACKEND_SOURCES
"src/extraction/dll_reader.cpp" "src/extraction/dll_reader.cpp"
"src/extraction/shader_registry.cpp" "src/extraction/shader_registry.cpp"
"src/helpers/managed_shader.cpp") "src/helpers/managed_shader.cpp"
"src/helpers/utils.cpp"
"src/lsfgvk.cpp")
add_library(lsfg-vk-backend STATIC ${BACKEND_SOURCES}) add_library(lsfg-vk-backend STATIC ${BACKEND_SOURCES})

View file

@ -0,0 +1,122 @@
#pragma once
#include <exception>
#include <filesystem>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
namespace lsfgvk {
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 { // NOLINT
public:
///
/// Construct an error
///
/// @param msg Error message.
/// @param inner Inner exception.
///
explicit error(const std::string& msg, const std::exception& inner);
/// Get the exception message
[[nodiscard]] const char* what() const noexcept override {
return msg.c_str();
}
~error() override;
private:
std::string msg;
};
///
/// 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 its name.
/// @param shaderDllPath Path to the Lossless.dll file to load shaders from.
/// @param allowLowPrecision Whether to load low-precision (FP16) shaders if supported by the device.
///
/// @throws lsfgvk::error on failure
///
Instance(
const std::function<bool(const std::string&)>& 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 lsfgvk::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 lsfgvk::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;
};
}

View file

@ -0,0 +1,37 @@
#include "utils.hpp"
#include <cstddef>
#include <cstdint>
#include <vulkan/vulkan_core.h>
using namespace ls;
ConstantBuffer ls::getDefaultConstantBuffer(
size_t index, size_t total,
bool hdr, float invFlow,
bool isFirst, bool isFirst2) {
return ConstantBuffer {
.firstIter = isFirst ? 1U : 0U,
.firstIterS = isFirst2 ? 1U : 0U,
.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 ls::shift_extent(VkExtent2D extent, uint32_t i) {
return VkExtent2D{
.width = extent.width >> i,
.height = extent.height >> i
};
}
VkExtent2D ls::add_shift_extent(VkExtent2D extent, uint32_t a, uint32_t i) {
return VkExtent2D{
.width = (extent.width + a) >> i,
.height = (extent.height + a) >> i
};
}

View file

@ -0,0 +1,74 @@
#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/sampler.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
#include <vulkan/vulkan_core.h>
namespace ls {
/// exposed context data
struct Ctx {
ls::R<const vk::Vulkan> vk; // safe back reference
ls::R<const extr::ShaderRegistry> shaders; // safe back reference
vk::Buffer constantBuffer;
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
/// @param isFirst whether this is the first iteration
/// @param isFirst2 whether this is the first iteration (second flag)
/// @return prefilled constant buffer
ConstantBuffer getDefaultConstantBuffer(
size_t index, size_t total,
bool hdr, float invFlow,
bool isFirst, bool isFirst2 // TODO: figure out if necessary
);
/// 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);
}

View file

@ -0,0 +1,378 @@
#include "lsfg-vk-backend/lsfgvk.hpp"
#include "extraction/dll_reader.hpp"
#include "extraction/shader_registry.hpp"
#include "helpers/utils.hpp"
#include "lsfg-vk-common/helpers/errors.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/timeline_semaphore.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <filesystem>
#include <functional>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
#ifdef LSFGVK__RENDERDOC_INTEGRATION
#include <renderdoc_app.h>
#include <dlfcn.h>
#endif
using namespace lsfgvk;
namespace lsfgvk {
/// 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__RENDERDOC_INTEGRATION
/// get the RenderDoc API
/// @return the RenderDoc API
[[nodiscard]] const auto& getRenderDocAPI() const { return this->renderdoc; }
#endif
private:
vk::Vulkan vk;
extr::ShaderRegistry shaders;
#ifdef LSFGVK__RENDERDOC_INTEGRATION
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::TimelineSemaphore syncSemaphore; // imported
vk::TimelineSemaphore prepassSemaphore;
size_t idx{1};
std::vector<vk::CommandBuffer> cmdbufs; // TODO: ponder reuse
size_t cmdbuf_idx{0};
ls::Ctx ctx;
};
}
Instance::Instance(
const std::function<bool(const std::string&)>& devicePicker,
const std::filesystem::path& shaderDllPath,
bool allowLowPrecision) {
const auto selectFunc = [&devicePicker](const std::vector<VkPhysicalDevice>& devices) {
for (const auto& device : devices) {
VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(device, &props);
std::array<char, 256> devname = std::to_array(props.deviceName);
devname[255] = '\0'; // ensure null-termination
const std::string& deviceName{devname.data()};
if (devicePicker(deviceName))
return device;
}
throw ls::vulkan_error("no suitable physical device found");
};
this->m_impl = std::make_unique<InstanceImpl>(
selectFunc, shaderDllPath, allowLowPrecision
);
}
namespace {
/// create a Vulkan instance
vk::Vulkan createVulkanInstance(vk::PhysicalDeviceSelector selectPhysicalDevice) {
try {
return{
"lsfg-vk", vk::version{1, 1, 0},
"lsfg-vk-engine", vk::version{1, 1, 0},
selectPhysicalDevice
};
} catch (const std::exception& e) {
throw lsfgvk::error("Unable to initialize Vulkan", e);
}
}
/// build a shader registry
extr::ShaderRegistry createShaderRegistry(vk::Vulkan& vk,
const std::filesystem::path& shaderDllPath,
bool allowLowPrecision) {
std::unordered_map<uint32_t, std::vector<uint8_t>> resources{};
try {
resources = extr::extractResourcesFromDLL(shaderDllPath);
} catch (const std::exception& e) {
throw lsfgvk::error("Unable to parse Lossless Scaling DLL", e);
}
try {
return extr::buildShaderRegistry(
vk, allowLowPrecision && vk.supportsFP16(),
resources
);
} catch (const std::exception& e) {
throw lsfgvk::error("Unable to build shader registry", e);
}
}
#ifdef LSFGVK__RENDERDOC_INTEGRATION
/// 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__RENDERDOC_INTEGRATION
this->renderdoc = loadRenderDocIntegration();
#endif
}
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 lsfgvk::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 lsfgvk::error("Unable to import destination images", e);
}
}
/// import timeline semaphore
vk::TimelineSemaphore importTimelineSemaphore(const vk::Vulkan& vk, int syncFd) {
try {
return{vk, 0, syncFd};
} catch (const std::exception& e) {
throw lsfgvk::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 lsfgvk::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 lsfgvk::error("Unable to create command buffers", e);
}
}
/// create context data
ls::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 {
return {
.vk = std::ref(vk),
.shaders = std::ref(shaders),
.constantBuffer{vk,
ls::getDefaultConstantBuffer(0, 1,
hdr, flow, false, false)},
.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 lsfgvk::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)),
syncSemaphore(importTimelineSemaphore(instance.getVulkan(), syncFd)),
prepassSemaphore(createPrepassSemaphore(instance.getVulkan())),
cmdbufs(createCommandBuffers(instance.getVulkan(), 16)),
ctx(createCtx(instance, extent, hdr, flow, perf, destFds.size())) {
// initialize all images
const vk::CommandBuffer cmdbuf{ctx.vk};
// (...)
cmdbuf.submit(ctx.vk); // wait for completion
}
void Instance::scheduleFrames(Context& context) {
#ifdef LSFGVK__RENDERDOC_INTEGRATION
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 lsfgvk::error("Unable to schedule frames", e);
}
#ifdef LSFGVK__RENDERDOC_INTEGRATION
if (impl->getRenderDocAPI()) {
vkDeviceWaitIdle(impl->getVulkan().dev());
impl->getRenderDocAPI()->EndFrameCapture(
RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(impl->getVulkan().inst()),
nullptr);
}
#endif
}
void Context::scheduleFrames() {
// schedule pre-pass
vk::CommandBuffer& cmdbuf = this->cmdbufs.at(this->cmdbuf_idx++ % this->cmdbufs.size());
cmdbuf = vk::CommandBuffer(this->ctx.vk);
// (...)
cmdbuf.submit(this->ctx.vk,
this->syncSemaphore, this->idx,
this->prepassSemaphore, this->idx
);
this->idx++;
// schedule main passes
for (size_t i = 0; i < this->destImages.size(); ++i) {
vk::CommandBuffer& cmdbuf = this->cmdbufs.at(this->cmdbuf_idx++ % this->cmdbufs.size());
cmdbuf = vk::CommandBuffer(this->ctx.vk);
// (...)
cmdbuf.submit(this->ctx.vk,
this->prepassSemaphore, this->idx - 1,
this->syncSemaphore, this->idx + i
);
}
this->idx += this->destImages.size();
}
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 lsfgvk::error("attempted to close unknown context",
std::runtime_error("no such context"));
vkDeviceWaitIdle(this->m_impl->getVulkan().dev());
this->m_contexts.erase(it);
}
Instance::~Instance() = default;
error::error(const std::string& msg, const std::exception& inner)
: std::runtime_error(msg + "\n- " + inner.what()) {}
error::~error() = default;

View file

@ -2,10 +2,13 @@
#include "../helpers/pointers.hpp" #include "../helpers/pointers.hpp"
#include "descriptor_set.hpp" #include "descriptor_set.hpp"
#include "image.hpp"
#include "shader.hpp" #include "shader.hpp"
#include "timeline_semaphore.hpp"
#include "vulkan.hpp" #include "vulkan.hpp"
#include <cstdint> #include <cstdint>
#include <optional>
#include <vector> #include <vector>
#include <vulkan/vulkan_core.h> #include <vulkan/vulkan_core.h>
@ -22,6 +25,12 @@ namespace vk {
/// @throws ls::vulkan_error on failure /// @throws ls::vulkan_error on failure
CommandBuffer(const vk::Vulkan& vk); CommandBuffer(const vk::Vulkan& vk);
/// initialize an image
/// @param image the image to initialize
/// @param clearColor optional clear color
void prepareImage(const vk::Image& image,
const std::optional<VkClearColorValue>& clearColor = std::nullopt) const;
/// dispatch a compute shader /// dispatch a compute shader
/// @param shader the compute shader /// @param shader the compute shader
/// @param set the descriptor set /// @param set the descriptor set
@ -34,8 +43,20 @@ namespace vk {
uint32_t x, uint32_t y, uint32_t z) const; uint32_t x, uint32_t y, uint32_t z) const;
/// submit the command buffer /// submit the command buffer
/// @param vk the vulkan instance
/// @param waitSemaphore the semaphore to wait on
/// @param waitValue the value to wait for
/// @param signalSemaphore the semaphore to signal
/// @param signalValue the value to signal
/// @throws ls::vulkan_error on failure /// @throws ls::vulkan_error on failure
void submit(); // FIXME: method needs to actually submit, depending on needs void submit(const vk::Vulkan& vk,
const vk::TimelineSemaphore& waitSemaphore, uint64_t waitValue,
const vk::TimelineSemaphore& signalSemaphore, uint64_t signalValue) const;
/// submit the command buffer instantly
/// @param vk the vulkan instance
/// @throws ls::vulkan_error on failure
void submit(const vk::Vulkan& vk) const;
private: private:
ls::owned_ptr<VkCommandBuffer> commandBuffer; ls::owned_ptr<VkCommandBuffer> commandBuffer;
}; };

View file

@ -2,10 +2,14 @@
#include "lsfg-vk-common/helpers/errors.hpp" #include "lsfg-vk-common/helpers/errors.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp" #include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/descriptor_set.hpp" #include "lsfg-vk-common/vulkan/descriptor_set.hpp"
#include "lsfg-vk-common/vulkan/fence.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/shader.hpp" #include "lsfg-vk-common/vulkan/shader.hpp"
#include "lsfg-vk-common/vulkan/timeline_semaphore.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp" #include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstdint> #include <cstdint>
#include <optional>
#include <vector> #include <vector>
#include <vulkan/vulkan_core.h> #include <vulkan/vulkan_core.h>
@ -48,6 +52,39 @@ CommandBuffer::CommandBuffer(const vk::Vulkan& vk)
throw ls::vulkan_error(res, "vkBeginCommandBuffer() failed"); throw ls::vulkan_error(res, "vkBeginCommandBuffer() failed");
} }
void CommandBuffer::prepareImage(const vk::Image& image,
const std::optional<VkClearColorValue>& clearColor) const {
const VkImageMemoryBarrier barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_NONE,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.image = image.handle(),
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = 1,
.layerCount = 1
}
};
vkCmdPipelineBarrier(*this->commandBuffer,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0, nullptr,
0, nullptr,
1, &barrier
);
if (clearColor.has_value()) {
vkCmdClearColorImage(*this->commandBuffer,
image.handle(),
VK_IMAGE_LAYOUT_GENERAL,
&clearColor.value(),
1, &barrier.subresourceRange
);
}
}
void CommandBuffer::dispatch(const vk::Shader& shader, void CommandBuffer::dispatch(const vk::Shader& shader,
const vk::DescriptorSet& set, const vk::DescriptorSet& set,
const std::vector<vk::Barrier>& barriers, const std::vector<vk::Barrier>& barriers,
@ -73,8 +110,53 @@ void CommandBuffer::dispatch(const vk::Shader& shader,
vkCmdDispatch(*this->commandBuffer, x, y, z); vkCmdDispatch(*this->commandBuffer, x, y, z);
} }
void CommandBuffer::submit() { void CommandBuffer::submit(const vk::Vulkan& vk,
const vk::TimelineSemaphore& waitSemaphore, uint64_t waitValue,
const vk::TimelineSemaphore& signalSemaphore, uint64_t signalValue) const {
auto res = vkEndCommandBuffer(*this->commandBuffer); auto res = vkEndCommandBuffer(*this->commandBuffer);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkEndCommandBuffer() failed"); throw ls::vulkan_error(res, "vkEndCommandBuffer() failed");
const VkTimelineSemaphoreSubmitInfo timelineInfo{
.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
.waitSemaphoreValueCount = 1,
.pWaitSemaphoreValues = &waitValue,
.signalSemaphoreValueCount = 1,
.pSignalSemaphoreValues = &signalValue
};
const VkPipelineStageFlags stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
const VkSubmitInfo submitInfo{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pNext = &timelineInfo,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &waitSemaphore.handle(),
.pWaitDstStageMask = &stage,
.commandBufferCount = 1,
.pCommandBuffers = &*this->commandBuffer,
.signalSemaphoreCount = 1,
.pSignalSemaphores = &signalSemaphore.handle()
};
res = vkQueueSubmit(vk.queue(), 1, &submitInfo, VK_NULL_HANDLE);
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkQueueSubmit() failed");
}
void CommandBuffer::submit(const vk::Vulkan& vk) const {
auto res = vkEndCommandBuffer(*this->commandBuffer);
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkEndCommandBuffer() failed");
const VkSubmitInfo submitInfo{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &*this->commandBuffer
};
const vk::Fence fence{vk};
res = vkQueueSubmit(vk.queue(), 1, &submitInfo, fence.handle());
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkQueueSubmit() failed");
if (!fence.wait(vk))
throw ls::vulkan_error(VK_TIMEOUT, "Fence::wait() timed out");
} }