feat(bindless): Implement backend coordinating class

This commit is contained in:
PancakeTAS 2026-04-25 20:23:12 +02:00
parent f8097eddb9
commit 9da7c8fdf9
No known key found for this signature in database
4 changed files with 368 additions and 1 deletions

View file

@ -1,7 +1,231 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "lsfg-vk/lsfgvk.hpp"
#include "lsfgvk.hpp"
#include "modules/library.hpp"
#include "modules/pipeline.hpp"
#include "utility/pipelines.hpp"
#include "utility/vkhelper.hpp"
#include "vulkan/vulkan.hpp"
#include <cstdint>
#include <filesystem>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
using namespace lsfgvk;
/* TODO */
Instance::Instance(
const std::string& deviceId,
const std::filesystem::path& lsfgvkDllPath,
bool allowFP16
) {
// Create Vulkan context
vk::detail::DispatchLoaderDynamic dld{};
auto instance{vkhelper::createInstance(dld)};
auto physdev{vkhelper::findPhysicalDevice(dld, *instance, deviceId)};
const uint32_t qfi{vkhelper::findComputeQueueFamilyIndex(dld, physdev)};
const bool fp16{allowFP16 && vkhelper::checkHalfPrecisionSupport(dld, physdev)};
auto [device, queue] = vkhelper::createDevice(dld, physdev, qfi, fp16);
// Construct instance
library::ShaderLibrary library{
dld,
*device,
fp16,
lsfgvkDllPath
};
this->m_priv = std::make_unique<priv::Instance>(priv::Instance {
.vk = {
.dld = dld,
.instance = std::move(instance),
.physdev = physdev,
.device = std::move(device),
.queue = queue,
.qfi = qfi,
.fp16 = fp16
},
.shaderLibrary = std::move(library)
});
}
Context::Context(
const Instance& instance,
uint32_t width,
uint32_t height,
float flowScale,
bool performanceMode
) {
const auto& vk{instance.m_priv->vk};
pipeline::Pipeline pipeline{
vk.dld,
*vk.device,
vk.physdev,
vk.queue,
vk.qfi,
instance.m_priv->shaderLibrary,
lsfgvk::getPipelineSignature(performanceMode),
{ width, height },
flowScale,
performanceMode,
false /* TODO: HDR */
};
this->m_priv = std::make_unique<priv::Context>(priv::Context {
.instance = std::ref(*instance.m_priv),
.pipeline = std::move(pipeline),
.syncSemaphore = { vkhelper::createTimelineSemaphore(vk.dld, *vk.device, true), 0 },
.internalSemaphores = { vkhelper::createTimelineSemaphore(vk.dld, *vk.device), 0 },
.fence = vkhelper::createFence(vk.dld, *vk.device),
});
}
FileDescriptors Context::exportFds() const {
const auto& vk{this->m_priv->instance.get().vk};
const auto& pipeline{this->m_priv->pipeline};
return{
.sourceFd = vkhelper::exportMemoryFd(
vk.dld, *vk.device,
pipeline.getExternalInputs().front().memory
),
.destinationFd = vkhelper::exportMemoryFd(
vk.dld, *vk.device,
pipeline.getExternalOutputs().front().memory
),
.syncFd = vkhelper::exportSemaphoreFd(
vk.dld, *vk.device,
*this->m_priv->syncSemaphore.first
)
};
}
void Context::dispatch(uint32_t total) {
auto& ctx{*this->m_priv};
const auto& vk{ctx.instance.get().vk};
// Increment iteration counter after previous frame is completed
auto* mapped{ctx.pipeline.getMappedBuffer()};
if (ctx.firstIteration) {
ctx.firstIteration = false;
mapped->iteration = 0;
} else {
if (vk.device->waitForFences(*ctx.fence, true, UINT64_MAX, vk.dld) != vk::Result::eSuccess)
throw std::runtime_error("Unable to wait for completion of previous iteration");
vk.device->resetFences(*ctx.fence, vk.dld);
mapped->iteration++;
}
const auto& cmdbufs{ctx.pipeline.getCmdbufs()};
// Dispatch pre-pass
auto& sync{ctx.syncSemaphore};
sync.second++;
auto& internal{ctx.internalSemaphores};
internal.second++;
vk::TimelineSemaphoreSubmitInfo timelineInfo{
.waitSemaphoreValueCount = 1,
.pWaitSemaphoreValues = &sync.second,
.signalSemaphoreValueCount = 1,
.pSignalSemaphoreValues = &internal.second
};
const vk::PipelineStageFlags waitStage{vk::PipelineStageFlagBits::eTopOfPipe};
vk.queue.submit(
{{
.pNext = &timelineInfo,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &*sync.first,
.pWaitDstStageMask = &waitStage,
.commandBufferCount = 1U,
.pCommandBuffers = &*cmdbufs.at(0),
.signalSemaphoreCount = 1,
.pSignalSemaphores = &*internal.first
}},
nullptr,
vk.dld
);
// Dispatch main passes
uint64_t prevInternal{};
for (uint32_t i = 0; i < total; i++) {
const auto& transCmdbuf{ctx.pipeline.getCmdbufs().at(0)}; // FIXME: replace with actual buf
// Transition command buffer to next timestamp
if (i == 0) {
prevInternal = internal.second;
timelineInfo.pWaitSemaphoreValues = &prevInternal;
} else {
sync.second++;
timelineInfo.pWaitSemaphoreValues = &sync.second;
}
internal.second++;
timelineInfo.pSignalSemaphoreValues = &internal.second;
vk.queue.submit(
{{
.pNext = &timelineInfo,
.waitSemaphoreCount = 1,
.pWaitSemaphores = i == 0 ? &*internal.first : &*sync.first,
.pWaitDstStageMask = &waitStage,
.commandBufferCount = 1,
.pCommandBuffers = &*transCmdbuf,
.signalSemaphoreCount = 1,
.pSignalSemaphores = &*internal.first
}},
nullptr,
vk.dld
);
// Dispatch main pass
timelineInfo.pWaitSemaphoreValues = &internal.second;
sync.second++;
timelineInfo.pSignalSemaphoreValues = &sync.second;
vk.queue.submit(
{{
.pNext = &timelineInfo,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &*internal.first,
.pWaitDstStageMask = &waitStage,
.commandBufferCount = 1,
.pCommandBuffers = &*cmdbufs.at(1),
.signalSemaphoreCount = 1,
.pSignalSemaphores = &*sync.first
}},
i == (total - 1) ? *ctx.fence : nullptr,
vk.dld
);
}
}
void Context::idle() const {
const auto& ctx{*this->m_priv};
const auto& vk{ctx.instance.get().vk};
vk.device->waitIdle(vk.dld);
}
Context::~Context() {
try {
// NOTE: This will freeze if the user didn't signal the sync semaphore high enough to
// allow the pipeline to complete.
this->idle();
} catch (...) { // NOLINT (empty catch)
// Not much we can do here..
}
}
Instance::~Instance() = default;

View file

@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "modules/pipeline.hpp"
#include "modules/library.hpp"
#include "utility/vkhelper.hpp"
#include <cstdint>
#include <functional>
#include <utility>
namespace lsfgvk::priv {
/// Internal state of lsfg-vk
struct Instance {
/// Vulkan context
struct Vulkan {
/// Vulkan dispatch loader
vk::detail::DispatchLoaderDynamic dld;
/// Vulkan instance (1.2)
vk::UniqueInstance instance;
/// Vulkan physical device
vk::PhysicalDevice physdev;
/// Vulkan device with synchronization2 (extension), external memory & semaphore
/// fd (extension) and timeline semaphores (core) enabled
vk::UniqueDevice device;
/// Compute queue
vk::Queue queue;
/// Compute queue family index
uint32_t qfi;
/// Whether fp16 is enabled and supported (shaderFloat16 is enabled)
bool fp16;
} vk;
/// Shader library
library::ShaderLibrary shaderLibrary;
};
/// Internal context for frame generation
struct Context {
/// Parent instance
std::reference_wrapper<Instance> instance;
/// Pipeline instance
pipeline::Pipeline pipeline;
/// Shared synchronization semaphores
std::pair<vk::UniqueSemaphore, uint64_t> syncSemaphore;
/// Internal synchronization semaphores
std::pair<vk::UniqueSemaphore, uint64_t> internalSemaphores;
/// Frames-in-flight fence
vk::UniqueFence fence;
/// Is first iteration
bool firstIteration{true};
};
}

View file

@ -470,6 +470,24 @@ vk::UniqueCommandBuffer vkhelper::createCommandBuffer(
return { std::move(device.allocateCommandBuffersUnique(cmdbufInfo, dld).front()) };
}
vk::UniqueSemaphore vkhelper::createTimelineSemaphore(
const vk::detail::DispatchLoaderDynamic& dld,
const vk::Device& device,
bool exportable
) {
const vk::ExportSemaphoreCreateInfo exportInfo{
.handleTypes = vk::ExternalSemaphoreHandleTypeFlagBits::eOpaqueFd
};
const vk::SemaphoreTypeCreateInfo typeInfo{
.pNext = exportable ? &exportInfo : nullptr,
.semaphoreType = vk::SemaphoreType::eTimeline,
};
const vk::SemaphoreCreateInfo createInfo{
.pNext = &typeInfo,
};
return device.createSemaphoreUnique(createInfo, nullptr, dld);
}
vk::UniqueFence vkhelper::createFence(
const vk::detail::DispatchLoaderDynamic& dld,
const vk::Device& device
@ -549,3 +567,27 @@ std::pair<vk::UniqueImage, vk::UniqueDeviceMemory> vkhelper::createExternalImage
std::move(memory)
};
}
int vkhelper::exportMemoryFd(
const vk::detail::DispatchLoaderDynamic& dld,
const vk::Device& device,
const vk::DeviceMemory& memory
) {
const vk::MemoryGetFdInfoKHR fdInfo{
.memory = memory,
.handleType = vk::ExternalMemoryHandleTypeFlagBits::eOpaqueFd
};
return device.getMemoryFdKHR(fdInfo, dld);
}
int vkhelper::exportSemaphoreFd(
const vk::detail::DispatchLoaderDynamic& dld,
const vk::Device& device,
const vk::Semaphore& semaphore
) {
const vk::SemaphoreGetFdInfoKHR fdInfo{
.semaphore = semaphore,
.handleType = vk::ExternalSemaphoreHandleTypeFlagBits::eOpaqueFd
};
return device.getSemaphoreFdKHR(fdInfo, dld);
}

View file

@ -338,6 +338,21 @@ namespace vkhelper {
const vk::CommandPool& cmdpool
);
///
/// Create a timeline semaphore
///
/// @param dld Dynamic dispatch loader
/// @param device Vulkan device
/// @param exportable Whether the semaphore should be exportable as a fd
/// @return RAII-wrapped Vulkan semaphore
/// @throws std::runtime_error on failure
///
vk::UniqueSemaphore createTimelineSemaphore(
const vk::detail::DispatchLoaderDynamic& dld,
const vk::Device& device,
bool exportable = false
);
///
/// Create a fence
///
@ -376,4 +391,34 @@ namespace vkhelper {
vk::ImageUsageFlags usage
);
///
/// Export a Vulkan memory allocation as a fd
///
/// @param dld Dynamic dispatch loader
/// @param device Vulkan device
/// @param memory Vulkan device memory
/// @return File descriptor to the allocation
/// @throws std::runtime_error on failure
///
int exportMemoryFd(
const vk::detail::DispatchLoaderDynamic& dld,
const vk::Device& device,
const vk::DeviceMemory& memory
);
///
/// Export a Vulkan semaphore as a fd
///
/// @param dld Dynamic dispatch loader
/// @param device Vulkan device
/// @param semaphore Vulkan semaphore
/// @return File descriptor to the semaphore
/// @throws std::runtime_error on failure
///
int exportSemaphoreFd(
const vk::detail::DispatchLoaderDynamic& dld,
const vk::Device& device,
const vk::Semaphore& semaphore
);
}