refactor(cleanup): support device loader function in backend

This commit is contained in:
PancakeTAS 2025-12-15 14:55:28 +01:00
parent 5b13d239d2
commit b0cc8e2954
No known key found for this signature in database
12 changed files with 40 additions and 863 deletions

View file

@ -1,91 +0,0 @@
#pragma once
#include "mini/commandpool.hpp"
#include <vulkan/vulkan_core.h>
#include <vector>
#include <memory>
namespace Mini {
/// State of the command buffer.
enum class CommandBufferState {
/// Command buffer is not initialized or has been destroyed.
Invalid,
/// Command buffer has been created.
Empty,
/// Command buffer recording has started.
Recording,
/// Command buffer recording has ended.
Full,
/// Command buffer has been submitted to a queue.
Submitted
};
///
/// C++ wrapper class for a Vulkan command buffer.
///
/// This class manages the lifetime of a Vulkan command buffer.
///
class CommandBuffer {
public:
CommandBuffer() noexcept = default;
///
/// Create the command buffer.
///
/// @param device Vulkan device
/// @param pool Vulkan command pool
///
/// @throws LSFG::vulkan_error if object creation fails.
///
CommandBuffer(VkDevice device, const CommandPool& pool);
///
/// Begin recording commands in the command buffer.
///
/// @throws std::logic_error if the command buffer is in Empty state
/// @throws LSFG::vulkan_error if beginning the command buffer fails.
///
void begin();
///
/// End recording commands in the command buffer.
///
/// @throws std::logic_error if the command buffer is not in Recording state
/// @throws LSFG::vulkan_error if ending the command buffer fails.
///
void end();
///
/// Submit the command buffer to a queue.
///
/// @param queue Vulkan queue to submit to
/// @param waitSemaphores Semaphores to wait on before executing the command buffer
/// @param signalSemaphores Semaphores to signal after executing the command buffer
///
/// @throws std::logic_error if the command buffer is not in Full state.
/// @throws LSFG::vulkan_error if submission fails.
///
void submit(VkQueue queue,
const std::vector<VkSemaphore>& waitSemaphores = {},
const std::vector<VkSemaphore>& signalSemaphores = {});
/// Get the state of the command buffer.
[[nodiscard]] CommandBufferState getState() const { return *this->state; }
/// Get the Vulkan handle.
[[nodiscard]] auto handle() const { return *this->commandBuffer; }
/// Trivially copyable, moveable and destructible
CommandBuffer(const CommandBuffer&) noexcept = default;
CommandBuffer& operator=(const CommandBuffer&) noexcept = default;
CommandBuffer(CommandBuffer&&) noexcept = default;
CommandBuffer& operator=(CommandBuffer&&) noexcept = default;
~CommandBuffer() = default;
private:
std::shared_ptr<CommandBufferState> state;
std::shared_ptr<VkCommandBuffer> commandBuffer;
};
}

View file

@ -1,60 +0,0 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <memory>
namespace Mini {
///
/// C++ wrapper class for a Vulkan image.
///
/// This class manages the lifetime of a Vulkan image.
///
class Image {
public:
Image() noexcept = default;
///
/// Create the image and export the backing fd
///
/// @param device Vulkan device
/// @param physicalDevice Vulkan physical device
/// @param extent Extent of the image in pixels.
/// @param format Vulkan format of the image
/// @param usage Usage flags for the image
/// @param aspectFlags Aspect flags for the image view
/// @param fd Pointer to an integer where the file descriptor will be stored.
///
/// @throws LSFG::vulkan_error if object creation fails.
///
Image(VkDevice device, VkPhysicalDevice physicalDevice, VkExtent2D extent, VkFormat format,
VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int* fd);
/// Get the Vulkan handle.
[[nodiscard]] auto handle() const { return *this->image; }
/// Get the Vulkan device memory handle.
[[nodiscard]] auto getMemory() const { return *this->memory; }
/// Get the extent of the image.
[[nodiscard]] VkExtent2D getExtent() const { return this->extent; }
/// Get the format of the image.
[[nodiscard]] VkFormat getFormat() const { return this->format; }
/// Get the aspect flags of the image.
[[nodiscard]] VkImageAspectFlags getAspectFlags() const { return this->aspectFlags; }
/// Trivially copyable, moveable and destructible
Image(const Image&) noexcept = default;
Image& operator=(const Image&) noexcept = default;
Image(Image&&) noexcept = default;
Image& operator=(Image&&) noexcept = default;
~Image() = default;
private:
std::shared_ptr<VkImage> image;
std::shared_ptr<VkDeviceMemory> memory;
VkExtent2D extent{};
VkFormat format{};
VkImageAspectFlags aspectFlags{};
};
}

View file

@ -1,50 +0,0 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <memory>
namespace Mini {
///
/// C++ wrapper class for a Vulkan semaphore.
///
/// This class manages the lifetime of a Vulkan semaphore.
///
class Semaphore {
public:
Semaphore() noexcept = default;
///
/// Create the semaphore.
///
/// @param device Vulkan device
///
/// @throws LSFG::vulkan_error if object creation fails.
///
Semaphore(VkDevice device);
///
/// Import a semaphore.
///
/// @param device Vulkan device
/// @param fd File descriptor to import the semaphore from.
///
/// @throws LSFG::vulkan_error if object creation fails.
///
Semaphore(VkDevice device, int* fd);
/// Get the Vulkan handle.
[[nodiscard]] auto handle() const { return *this->semaphore; }
// Trivially copyable, moveable and destructible
Semaphore(const Semaphore&) noexcept = default;
Semaphore& operator=(const Semaphore&) noexcept = default;
Semaphore(Semaphore&&) noexcept = default;
Semaphore& operator=(Semaphore&&) noexcept = default;
~Semaphore() = default;
private:
std::shared_ptr<VkSemaphore> semaphore;
};
}

View file

@ -1,102 +0,0 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <cstddef>
#include <utility>
#include <string>
#include <vector>
namespace Utils {
///
/// Find a queue in the physical device that supports the given queue flags.
///
/// @param device The Vulkan device to use for queue retrieval.
/// @param physicalDevice The physical device to search in.
/// @param desc The device creation info, used to determine enabled queue families.
/// @param flags The queue flags to search for (e.g., VK_QUEUE_GRAPHICS_BIT).
/// @return Pair of queue family index and queue handle.
///
/// @throws LSFG::vulkan_error if no suitable queue is found.
///
std::pair<uint32_t, VkQueue> findQueue(VkDevice device, VkPhysicalDevice physicalDevice,
VkDeviceCreateInfo* desc, VkQueueFlags flags);
///
/// Get the UUID of the physical device.
///
/// @param physicalDevice The physical device to get the UUID from.
/// @return The UUID of the physical device.
///
uint64_t getDeviceUUID(VkPhysicalDevice physicalDevice);
///
/// Get the max image count for a swapchain.
///
/// @param physicalDevice The physical device to query.
/// @param surface The surface to query the capabilities for.
/// @return The maximum image count for the swapchain.
///
uint32_t getMaxImageCount(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface);
///
/// Ensure a list of extensions is present in the given array.
///
/// @param extensions The array of extensions to check.
/// @param requiredExtensions The list of required extensions to ensure are present.
///
std::vector<const char*> addExtensions(const char* const* extensions, size_t count,
const std::vector<const char*>& requiredExtensions);
///
/// Copy an image from source to destination in a command buffer.
///
/// @param buf The command buffer to record the copy operation into.
/// @param src The source image to copy from.
/// @param dst The destination image to copy to.
/// @param width The width of the image to copy.
/// @param height The height of the image to copy.
/// @param pre The pipeline stage to wait on.
/// @param post The pipeline stage to provide after the copy.
/// @param makeSrcPresentable If true, the source image will be made presentable after the copy.
/// @param makeDstPresentable If true, the destination image will be made presentable after the copy.
///
void copyImage(VkCommandBuffer buf,
VkImage src, VkImage dst,
uint32_t width, uint32_t height,
VkPipelineStageFlags pre, VkPipelineStageFlags post,
bool makeSrcPresentable, bool makeDstPresentable);
///
/// Log a message at most n times.
///
/// @param id The identifier for the log message.
/// @param n The maximum number of times to log the message.
/// @param message The message to log.
///
void logLimitN(const std::string& id, size_t n, const std::string& message);
///
/// Reset the log limit for a given identifier.
///
/// @param id The identifier for the log message.
///
void resetLimitN(const std::string& id) noexcept;
///
/// Get the process name of the current executable.
///
/// @return The name of the process.
///
std::pair<std::string, std::string> getProcessName();
///
/// Get the configuration file path.
///
/// @return The path to the configuration file.
///
std::string getConfigFile();
}

View file

@ -10,6 +10,7 @@
#include <vector>
#include <vulkan/vulkan_core.h>
#include <vulkan/vk_layer.h>
namespace vk {
@ -91,7 +92,6 @@ namespace vk {
PFN_vkCreateComputePipelines CreateComputePipelines;
PFN_vkDestroyPipeline DestroyPipeline;
// extension functions
PFN_vkSignalSemaphoreKHR SignalSemaphoreKHR;
PFN_vkWaitSemaphoresKHR WaitSemaphoresKHR;
@ -136,7 +136,8 @@ namespace vk {
Vulkan(const std::string& appName, version appVersion,
const std::string& engineName, version engineVersion,
PhysicalDeviceSelector selectPhysicalDevice,
bool isGraphical = false);
bool isGraphical = false,
std::optional<PFN_vkSetDeviceLoaderData> setLoaderData = std::nullopt);
/// create based on an existing externally managed vulkan instance.
/// @param instance vulkan instance handle
@ -150,7 +151,8 @@ namespace vk {
VkPhysicalDevice physdev,
VulkanInstanceFuncs instanceFuncs,
VulkanDeviceFuncs deviceFuncs,
bool isGraphical = true);
bool isGraphical = true,
std::optional<PFN_vkSetDeviceLoaderData> setLoaderData = std::nullopt);
/// find a memory type index
/// @param validTypes bitset of valid memory types
@ -182,6 +184,9 @@ namespace vk {
/// get device-level function pointers
/// @return the device function pointers
[[nodiscard]] const auto& df() const { return this->device_funcs; }
/// get optional setLoaderData function
/// @return the setLoaderData function
[[nodiscard]] const auto& loaderdatafunc() const { return this->setLoaderData; }
private:
ls::owned_ptr<VkInstance> instance;
VulkanInstanceFuncs instance_funcs;
@ -191,6 +196,7 @@ namespace vk {
bool fp16;
ls::owned_ptr<VkDevice> device;
std::optional<PFN_vkSetDeviceLoaderData> setLoaderData;
VulkanDeviceFuncs device_funcs;
VkQueue computeQueue;

View file

@ -32,6 +32,13 @@ namespace {
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkAllocateCommandBuffers() failed");
auto setLoaderData = vk.loaderdatafunc();
if (setLoaderData) {
res = (*setLoaderData)(vk.dev(), handle);
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkSetDeviceLoaderData() failed");
}
return ls::owned_ptr<VkCommandBuffer>(
new VkCommandBuffer(handle),
[dev = vk.dev(), pool = vk.cmdpool(), defunc = vk.df().FreeCommandBuffers](

View file

@ -10,6 +10,7 @@
#include <vector>
#include <dlfcn.h>
#include <vulkan/vk_layer.h>
#include <vulkan/vulkan_core.h>
using namespace vk;
@ -199,11 +200,18 @@ namespace {
}
/// get a queue from the logical device
VkQueue getQueue(const VulkanDeviceFuncs& fd,
VkDevice device, uint32_t cfi) {
VkQueue getQueue(const VulkanDeviceFuncs& fd, VkDevice device,
std::optional<PFN_vkSetDeviceLoaderData> setLoaderData,
uint32_t cfi) {
VkQueue queue{};
fd.GetDeviceQueue(device, cfi, 0, &queue);
if (setLoaderData) { // optionally set loader data
auto res = (*setLoaderData)(device, queue);
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkSetDeviceLoaderData() failed");
}
return queue;
}
@ -353,7 +361,8 @@ VulkanDeviceFuncs vk::initVulkanDeviceFuncs(const VulkanInstanceFuncs& f, VkDevi
Vulkan::Vulkan(const std::string& appName, version appVersion,
const std::string& engineName, version engineVersion,
PhysicalDeviceSelector selectPhysicalDevice,
bool isGraphical) :
bool isGraphical,
std::optional<PFN_vkSetDeviceLoaderData> setLoaderData) :
instance(createInstance(
appName, appVersion,
engineName, engineVersion
@ -371,11 +380,14 @@ Vulkan::Vulkan(const std::string& appName, version appVersion,
this->queueFamilyIdx,
this->fp16
)),
setLoaderData(setLoaderData),
device_funcs(initVulkanDeviceFuncs(
this->instance_funcs,
*this->device
)),
computeQueue(getQueue(this->device_funcs, *this->device, this->queueFamilyIdx)),
computeQueue(getQueue(this->device_funcs, *this->device,
this->setLoaderData,
this->queueFamilyIdx)),
cmdPool(createCommandPool(this->device_funcs,
*this->device,
this->queueFamilyIdx
@ -389,7 +401,8 @@ Vulkan::Vulkan(VkInstance instance, VkDevice device,
VkPhysicalDevice physdev,
VulkanInstanceFuncs instanceFuncs,
VulkanDeviceFuncs deviceFuncs,
bool isGraphical) :
bool isGraphical,
std::optional<PFN_vkSetDeviceLoaderData> setLoaderData) :
instance(new VkInstance(instance)),
instance_funcs(instanceFuncs),
physdev(physdev),
@ -397,8 +410,11 @@ Vulkan::Vulkan(VkInstance instance, VkDevice device,
isGraphical ? VK_QUEUE_GRAPHICS_BIT : VK_QUEUE_COMPUTE_BIT)),
fp16(false),
device(new VkDevice(device)),
setLoaderData(setLoaderData),
device_funcs(deviceFuncs),
computeQueue(getQueue(this->device_funcs, *this->device, this->queueFamilyIdx)),
computeQueue(getQueue(this->device_funcs, *this->device,
this->setLoaderData,
this->queueFamilyIdx)),
cmdPool(createCommandPool(this->device_funcs,
*this->device,
this->queueFamilyIdx

View file

@ -212,7 +212,8 @@ namespace {
global->instance, *device,
physdev,
global->fi,
vk::initVulkanDeviceFuncs(global->fi, *device)
vk::initVulkanDeviceFuncs(global->fi, *device),
setLoaderData
)
)
);

View file

@ -1,91 +0,0 @@
#include "mini/commandbuffer.hpp"
#include "mini/commandpool.hpp"
#include "common/exception.hpp"
#include "layer.hpp"
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <memory>
#include <stdexcept>
#include <vector>
using namespace Mini;
CommandBuffer::CommandBuffer(VkDevice device, const CommandPool& pool) {
// create command buffer
const VkCommandBufferAllocateInfo desc{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = pool.handle(),
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1
};
VkCommandBuffer commandBufferHandle{};
auto res = Layer::ovkAllocateCommandBuffers(device, &desc, &commandBufferHandle);
if (res != VK_SUCCESS || commandBufferHandle == VK_NULL_HANDLE)
throw LSFG::vulkan_error(res, "Unable to allocate command buffer");
res = Layer::ovkSetDeviceLoaderData(device, commandBufferHandle);
if (res != VK_SUCCESS)
throw LSFG::vulkan_error(res, "Unable to set device loader data for command buffer");
// store command buffer in shared ptr
this->state = std::make_shared<CommandBufferState>(CommandBufferState::Empty);
this->commandBuffer = std::shared_ptr<VkCommandBuffer>(
new VkCommandBuffer(commandBufferHandle),
[dev = device, pool = pool.handle()](VkCommandBuffer* cmdBuffer) {
Layer::ovkFreeCommandBuffers(dev, pool, 1, cmdBuffer);
}
);
}
void CommandBuffer::begin() {
if (*this->state != CommandBufferState::Empty)
throw std::logic_error("Command buffer is not in Empty state");
const VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
};
auto res = Layer::ovkBeginCommandBuffer(*this->commandBuffer, &beginInfo);
if (res != VK_SUCCESS)
throw LSFG::vulkan_error(res, "Unable to begin command buffer");
*this->state = CommandBufferState::Recording;
}
void CommandBuffer::end() {
if (*this->state != CommandBufferState::Recording)
throw std::logic_error("Command buffer is not in Recording state");
auto res = Layer::ovkEndCommandBuffer(*this->commandBuffer);
if (res != VK_SUCCESS)
throw LSFG::vulkan_error(res, "Unable to end command buffer");
*this->state = CommandBufferState::Full;
}
void CommandBuffer::submit(VkQueue queue,
const std::vector<VkSemaphore>& waitSemaphores,
const std::vector<VkSemaphore>& signalSemaphores) {
if (*this->state != CommandBufferState::Full)
throw std::logic_error("Command buffer is not in Full state");
const std::vector<VkPipelineStageFlags> waitStages(waitSemaphores.size(),
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
const VkSubmitInfo submitInfo{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = static_cast<uint32_t>(waitSemaphores.size()),
.pWaitSemaphores = waitSemaphores.data(),
.pWaitDstStageMask = waitStages.data(),
.commandBufferCount = 1,
.pCommandBuffers = &(*this->commandBuffer),
.signalSemaphoreCount = static_cast<uint32_t>(signalSemaphores.size()),
.pSignalSemaphores = signalSemaphores.data()
};
auto res = Layer::ovkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);
if (res != VK_SUCCESS)
throw LSFG::vulkan_error(res, "Unable to submit command buffer");
*this->state = CommandBufferState::Submitted;
}

View file

@ -1,112 +0,0 @@
#include "mini/image.hpp"
#include "common/exception.hpp"
#include "layer.hpp"
#include <vulkan/vulkan_core.h>
#include <memory>
#include <cstdint>
#include <optional>
using namespace Mini;
Image::Image(VkDevice device, VkPhysicalDevice physicalDevice,
VkExtent2D extent, VkFormat format,
VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int* fd)
: extent(extent), format(format), aspectFlags(aspectFlags) {
// create image
const VkExternalMemoryImageCreateInfo externalInfo{
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR
};
const VkImageCreateInfo desc{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = &externalInfo,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = {
.width = extent.width,
.height = extent.height,
.depth = 1
},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.usage = usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE
};
VkImage imageHandle{};
auto res = Layer::ovkCreateImage(device, &desc, nullptr, &imageHandle);
if (res != VK_SUCCESS || imageHandle == VK_NULL_HANDLE)
throw LSFG::vulkan_error(res, "Failed to create Vulkan image");
// find memory type
VkPhysicalDeviceMemoryProperties memProps;
Layer::ovkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps);
VkMemoryRequirements memReqs;
Layer::ovkGetImageMemoryRequirements(device, imageHandle, &memReqs);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
std::optional<uint32_t> memType{};
for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN
(memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
memType.emplace(i);
break;
} // NOLINTEND
}
if (!memType.has_value())
throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for image");
#pragma clang diagnostic pop
// allocate and bind memory
const VkMemoryDedicatedAllocateInfoKHR dedicatedInfo{
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR,
.image = imageHandle,
};
const VkExportMemoryAllocateInfo exportInfo{
.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO,
.pNext = &dedicatedInfo,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR
};
const VkMemoryAllocateInfo allocInfo{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &exportInfo,
.allocationSize = memReqs.size,
.memoryTypeIndex = memType.value()
};
VkDeviceMemory memoryHandle{};
res = Layer::ovkAllocateMemory(device, &allocInfo, nullptr, &memoryHandle);
if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE)
throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan image");
res = Layer::ovkBindImageMemory(device, imageHandle, memoryHandle, 0);
if (res != VK_SUCCESS)
throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan image");
// obtain the sharing fd
const VkMemoryGetFdInfoKHR fdInfo{
.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
.memory = memoryHandle,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR,
};
res = Layer::ovkGetMemoryFdKHR(device, &fdInfo, fd);
if (res != VK_SUCCESS || *fd < 0)
throw LSFG::vulkan_error(res, "Failed to obtain sharing fd for Vulkan image");
// store objects in shared ptr
this->image = std::shared_ptr<VkImage>(
new VkImage(imageHandle),
[dev = device](VkImage* img) {
Layer::ovkDestroyImage(dev, *img, nullptr);
}
);
this->memory = std::shared_ptr<VkDeviceMemory>(
new VkDeviceMemory(memoryHandle),
[dev = device](VkDeviceMemory* mem) {
Layer::ovkFreeMemory(dev, *mem, nullptr);
}
);
}

View file

@ -1,62 +0,0 @@
#include "mini/semaphore.hpp"
#include "common/exception.hpp"
#include "layer.hpp"
#include <vulkan/vulkan_core.h>
#include <memory>
using namespace Mini;
Semaphore::Semaphore(VkDevice device) {
// create semaphore
const VkSemaphoreCreateInfo desc{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
};
VkSemaphore semaphoreHandle{};
auto res = Layer::ovkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle);
if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE)
throw LSFG::vulkan_error(res, "Unable to create semaphore");
// store semaphore in shared ptr
this->semaphore = std::shared_ptr<VkSemaphore>(
new VkSemaphore(semaphoreHandle),
[dev = device](VkSemaphore* semaphoreHandle) {
Layer::ovkDestroySemaphore(dev, *semaphoreHandle, nullptr);
}
);
}
Semaphore::Semaphore(VkDevice device, int* fd) {
// create semaphore
const VkExportSemaphoreCreateInfo exportInfo{
.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO,
.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT
};
const VkSemaphoreCreateInfo desc{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
.pNext = &exportInfo
};
VkSemaphore semaphoreHandle{};
auto res = Layer::ovkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle);
if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE)
throw LSFG::vulkan_error(res, "Unable to create semaphore");
// export semaphore to fd
const VkSemaphoreGetFdInfoKHR fdInfo{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR,
.semaphore = semaphoreHandle,
.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT
};
res = Layer::ovkGetSemaphoreFdKHR(device, &fdInfo, fd);
if (res != VK_SUCCESS || *fd < 0)
throw LSFG::vulkan_error(res, "Unable to export semaphore to fd");
// store semaphore in shared ptr
this->semaphore = std::shared_ptr<VkSemaphore>(
new VkSemaphore(semaphoreHandle),
[dev = device](VkSemaphore* semaphoreHandle) {
Layer::ovkDestroySemaphore(dev, *semaphoreHandle, nullptr);
}
);
}

View file

@ -1,285 +0,0 @@
#include "utils/utils.hpp"
#include "common/exception.hpp"
#include "layer.hpp"
#include <vulkan/vulkan_core.h>
#include <sys/types.h>
#include <string.h> // NOLINT
#include <unistd.h>
#include <unordered_map>
#include <algorithm>
#include <optional>
#include <iostream>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <utility>
#include <fstream>
#include <string>
#include <vector>
#include <array>
using namespace Utils;
std::pair<uint32_t, VkQueue> Utils::findQueue(VkDevice device, VkPhysicalDevice physicalDevice,
VkDeviceCreateInfo* desc, VkQueueFlags flags) {
std::vector<VkDeviceQueueCreateInfo> enabledQueues(desc->queueCreateInfoCount);
std::copy_n(desc->pQueueCreateInfos, enabledQueues.size(), enabledQueues.data());
uint32_t familyCount{};
Layer::ovkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, nullptr);
std::vector<VkQueueFamilyProperties> families(familyCount);
Layer::ovkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount,
families.data());
std::optional<uint32_t> idx;
for (const auto& queueInfo : enabledQueues) {
if ((queueInfo.queueFamilyIndex < families.size()) &&
(families[queueInfo.queueFamilyIndex].queueFlags & flags)) {
idx = queueInfo.queueFamilyIndex;
break;
}
}
if (!idx.has_value())
throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No suitable queue found");
VkQueue queue{};
Layer::ovkGetDeviceQueue(device, *idx, 0, &queue);
auto res = Layer::ovkSetDeviceLoaderData(device, queue);
if (res != VK_SUCCESS)
throw LSFG::vulkan_error(res, "Unable to set device loader data for queue");
return { *idx, queue };
}
uint64_t Utils::getDeviceUUID(VkPhysicalDevice physicalDevice) {
VkPhysicalDeviceProperties properties{};
Layer::ovkGetPhysicalDeviceProperties(physicalDevice, &properties);
return static_cast<uint64_t>(properties.vendorID) << 32 | properties.deviceID;
}
uint32_t Utils::getMaxImageCount(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface) {
VkSurfaceCapabilitiesKHR capabilities{};
auto res = Layer::ovkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice,
surface, &capabilities);
if (res != VK_SUCCESS)
throw LSFG::vulkan_error(res, "Failed to get surface capabilities");
if (capabilities.maxImageCount == 0)
return 999; // :3
return capabilities.maxImageCount;
}
std::vector<const char*> Utils::addExtensions(const char* const* extensions, size_t count,
const std::vector<const char*>& requiredExtensions) {
std::vector<const char*> ext(count);
std::copy_n(extensions, count, ext.data());
for (const auto& e : requiredExtensions) {
auto it = std::ranges::find_if(ext,
[e](const char* extName) {
return std::string(extName) == std::string(e);
});
if (it == ext.end())
ext.push_back(e);
}
return ext;
}
void Utils::copyImage(VkCommandBuffer buf,
VkImage src, VkImage dst,
uint32_t width, uint32_t height,
VkPipelineStageFlags pre, VkPipelineStageFlags post,
bool makeSrcPresentable, bool makeDstPresentable) {
const VkImageMemoryBarrier srcBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.image = src,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = 1,
.layerCount = 1
}
};
const VkImageMemoryBarrier dstBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.image = dst,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = 1,
.layerCount = 1
}
};
const std::vector<VkImageMemoryBarrier> barriers = { srcBarrier, dstBarrier };
Layer::ovkCmdPipelineBarrier(buf,
pre, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
0, nullptr, 0, nullptr,
static_cast<uint32_t>(barriers.size()), barriers.data());
const VkImageBlit imageBlit{
.srcSubresource = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.layerCount = 1
},
.srcOffsets = {
{ 0, 0, 0 },
{ static_cast<int32_t>(width), static_cast<int32_t>(height), 1 }
},
.dstSubresource = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.layerCount = 1
},
.dstOffsets = {
{ 0, 0, 0 },
{ static_cast<int32_t>(width), static_cast<int32_t>(height), 1 }
}
};
Layer::ovkCmdBlitImage(
buf,
src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &imageBlit,
VK_FILTER_NEAREST
);
if (makeSrcPresentable) {
const VkImageMemoryBarrier presentBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.image = src,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = 1,
.layerCount = 1
}
};
Layer::ovkCmdPipelineBarrier(buf,
VK_PIPELINE_STAGE_TRANSFER_BIT, post, 0,
0, nullptr, 0, nullptr,
1, &presentBarrier);
}
if (makeDstPresentable) {
const VkImageMemoryBarrier presentBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.image = dst,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = 1,
.layerCount = 1
}
};
Layer::ovkCmdPipelineBarrier(buf,
VK_PIPELINE_STAGE_TRANSFER_BIT, post, 0,
0, nullptr, 0, nullptr,
1, &presentBarrier);
}
}
namespace {
auto& logCounts() {
static std::unordered_map<std::string, size_t> map;
return map;
}
}
void Utils::logLimitN(const std::string& id, size_t n, const std::string& message) {
auto& count = logCounts()[id];
if (count <= n)
std::cerr << "lsfg-vk: " << message << '\n';
if (count == n)
std::cerr << "(above message has been repeated " << n << " times, suppressing further)\n";
count++;
}
void Utils::resetLimitN(const std::string& id) noexcept {
logCounts().erase(id);
}
std::pair<std::string, std::string> Utils::getProcessName() {
// check benchmark flag
const char* benchmark_flag = std::getenv("LSFG_BENCHMARK");
if (benchmark_flag)
return { "benchmark", "benchmark" };
std::array<char, 4096> exe{};
// then check override
const char* process_name = std::getenv("LSFG_PROCESS");
if (process_name && *process_name != '\0')
return { process_name, process_name };
// find executed binary
const ssize_t exe_len = readlink("/proc/self/exe", exe.data(), exe.size() - 1);
if (exe_len <= 0)
return { "Unknown Process", "unknown" };
exe.at(static_cast<size_t>(exe_len)) = '\0';
std::string exe_str(exe.data());
// find command name as well
std::ifstream comm_file("/proc/self/comm");
if (!comm_file.is_open())
return { std::string(exe.data()), "unknown" };
std::array<char, 257> comm{};
comm_file.read(comm.data(), 256);
comm.at(static_cast<size_t>(comm_file.gcount())) = '\0';
std::string comm_str(comm.data());
if (comm_str.back() == '\n')
comm_str.pop_back();
// replace binary with exe for wine apps
if (exe_str.find("wine") != std::string::npos
|| exe_str.find("proton") != std::string::npos) {
std::ifstream proc_maps("/proc/self/maps");
if (!proc_maps.is_open())
return{ exe_str, comm_str };
std::string line;
while (std::getline(proc_maps, line)) {
if (!line.ends_with(".exe"))
continue;
size_t pos = line.find_first_of('/');
if (pos == std::string::npos) {
pos = line.find_last_of(' ');
if (pos == std::string::npos)
continue;
pos += 1; // skip space
}
const std::string exe_name = line.substr(pos);
if (exe_name.empty())
continue;
exe_str = exe_name;
break;
}
}
return{ exe_str, comm_str };
}
std::string Utils::getConfigFile() {
const char* configFile = std::getenv("LSFG_CONFIG");
if (configFile && *configFile != '\0')
return{configFile};
const char* xdgPath = std::getenv("XDG_CONFIG_HOME");
if (xdgPath && *xdgPath != '\0')
return std::string(xdgPath) + "/lsfg-vk/conf.toml";
const char* homePath = std::getenv("HOME");
if (homePath && *homePath != '\0')
return std::string(homePath) + "/.config/lsfg-vk/conf.toml";
return "/etc/lsfg-vk/conf.toml";
}