diff --git a/include/core/commandbuffer.hpp b/include/core/commandbuffer.hpp new file mode 100644 index 0000000..b60c437 --- /dev/null +++ b/include/core/commandbuffer.hpp @@ -0,0 +1,105 @@ +#ifndef COMMANDBUFFER_HPP +#define COMMANDBUFFER_HPP + +#include "core/commandpool.hpp" +#include "core/semaphore.hpp" +#include "device.hpp" +#include + +#include +#include +#include + +namespace Vulkan::Core { + + /// 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: + /// + /// Create the command buffer. + /// + /// @param device Vulkan device + /// @param pool Vulkan command pool + /// + /// @throws std::invalid_argument if the device or pool are invalid. + /// @throws ls::vulkan_error if object creation fails. + /// + CommandBuffer(const Device& device, const CommandPool& pool); + + /// + /// Begin recording commands in the command buffer. + /// + /// @throws std::logic_error if the command buffer is in Empty state + /// @throws ls::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 ls::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 waitSemaphoreValues Values for the semaphores to wait on + /// @param signalSemaphores Semaphores to signal after executing the command buffer + /// @param signalSemaphoreValues Values for the semaphores to signal + /// + /// @throws std::invalid_argument if the queue is null. + /// @throws std::logic_error if the command buffer is not in Full state. + /// @throws ls::vulkan_error if submission fails. + /// + void submit(VkQueue queue, // TODO: fence + const std::vector& waitSemaphores = {}, + std::optional> waitSemaphoreValues = std::nullopt, + const std::vector& signalSemaphores = {}, + std::optional> signalSemaphoreValues = std::nullopt); + + /// Get the state of the command buffer. + [[nodiscard]] CommandBufferState getState() const { return *this->state; } + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { *this->commandBuffer; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->commandBuffer); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// 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 state; + std::shared_ptr commandBuffer; + }; + +} + +#endif // COMMANDBUFFER_HPP diff --git a/include/core/commandpool.hpp b/include/core/commandpool.hpp new file mode 100644 index 0000000..b2d0909 --- /dev/null +++ b/include/core/commandpool.hpp @@ -0,0 +1,56 @@ +#ifndef COMMANDPOOL_HPP +#define COMMANDPOOL_HPP + +#include "device.hpp" + +#include + +#include + +namespace Vulkan::Core { + + /// Enumeration for different types of command pools. + enum class CommandPoolType { + /// Used for compute-type command buffers. + Compute + }; + + /// + /// C++ wrapper class for a Vulkan command pool. + /// + /// This class manages the lifetime of a Vulkan command pool. + /// + class CommandPool { + public: + /// + /// Create the command pool. + /// + /// @param device Vulkan device + /// @param type Type of command pool to create. + /// + /// @throws std::invalid_argument if the device is invalid. + /// @throws ls::vulkan_error if object creation fails. + /// + CommandPool(const Device& device, CommandPoolType type); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->commandPool; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->commandPool); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// Trivially copyable, moveable and destructible + CommandPool(const CommandPool&) noexcept = default; + CommandPool& operator=(const CommandPool&) noexcept = default; + CommandPool(CommandPool&&) noexcept = default; + CommandPool& operator=(CommandPool&&) noexcept = default; + ~CommandPool() = default; + private: + std::shared_ptr commandPool; + }; + +} + +#endif // COMMANDPOOL_HPP diff --git a/include/core/fence.hpp b/include/core/fence.hpp new file mode 100644 index 0000000..6b9135d --- /dev/null +++ b/include/core/fence.hpp @@ -0,0 +1,69 @@ +#ifndef FENCE_HPP +#define FENCE_HPP + +#include "device.hpp" + +#include + +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan fence. + /// + /// This class manages the lifetime of a Vulkan fence. + /// + class Fence { + public: + /// + /// Create the fence. + /// + /// @param device Vulkan device + /// + /// @throws std::invalid_argument if the device is null. + /// @throws ls::vulkan_error if object creation fails. + /// + Fence(const Device& device); + + /// + /// Reset the fence to an unsignaled state. + /// + /// @throws std::logic_error if the fence is not valid. + /// @throws ls::vulkan_error if resetting fails. + /// + void reset() const; + + /// + /// Wait for the fence + /// + /// @param timeout The timeout in nanoseconds, or UINT64_MAX for no timeout. + /// @returns true if the fence signaled, false if it timed out. + /// + /// @throws std::logic_error if the fence is not valid. + /// @throws ls::vulkan_error if waiting fails. + /// + bool wait(uint64_t timeout = UINT64_MAX); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->fence; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->fence); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + // Trivially copyable, moveable and destructible + Fence(const Fence&) noexcept = default; + Fence& operator=(const Fence&) noexcept = default; + Fence(Fence&&) noexcept = default; + Fence& operator=(Fence&&) noexcept = default; + ~Fence() = default; + private: + std::shared_ptr fence; + VkDevice device{}; + }; + +} + +#endif // FENCE_HPP diff --git a/include/core/semaphore.hpp b/include/core/semaphore.hpp new file mode 100644 index 0000000..e20c8c6 --- /dev/null +++ b/include/core/semaphore.hpp @@ -0,0 +1,75 @@ +#ifndef SEMAPHORE_HPP +#define SEMAPHORE_HPP + +#include "device.hpp" + +#include + +#include +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan semaphore. + /// + /// This class manages the lifetime of a Vulkan semaphore. + /// + class Semaphore { + public: + /// + /// Create the semaphore. + /// + /// @param device Vulkan device + /// @param initial Optional initial value for creating a timeline semaphore. + /// + /// @throws std::invalid_argument if the device is null. + /// @throws ls::vulkan_error if object creation fails. + /// + Semaphore(const Device& device, std::optional initial = std::nullopt); + + /// + /// Signal the semaphore to a specific value. + /// + /// @param value The value to signal the semaphore to. + /// + /// @throws std::logic_error if the semaphore is not a timeline semaphore. + /// @throws ls::vulkan_error if signaling fails. + /// + void signal(uint64_t value) const; + + /// + /// Wait for the semaphore to reach a specific value. + /// + /// @param value The value to wait for. + /// @param timeout The timeout in nanoseconds, or UINT64_MAX for no timeout. + /// @returns true if the semaphore reached the value, false if it timed out. + /// + /// @throws std::logic_error if the semaphore is not a timeline semaphore. + /// @throws ls::vulkan_error if waiting fails. + /// + bool wait(uint64_t value, uint64_t timeout = UINT64_MAX); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->semaphore; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->semaphore); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + // 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 semaphore; + VkDevice device{}; + bool isTimeline{}; + }; + +} + +#endif // SEMAPHORE_HPP diff --git a/include/core/shadermodule.hpp b/include/core/shadermodule.hpp new file mode 100644 index 0000000..18b40dc --- /dev/null +++ b/include/core/shadermodule.hpp @@ -0,0 +1,52 @@ +#ifndef SHADERMODULE_HPP +#define SHADERMODULE_HPP + +#include "device.hpp" + +#include +#include + +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan shader module. + /// + /// This class manages the lifetime of a Vulkan shader module. + /// + class ShaderModule { + public: + /// + /// Create the shader module. + /// + /// @param device Vulkan device + /// @param path Path to the shader file. + /// + /// @throws std::invalid_argument if the device is invalid. + /// @throws std::system_error if the shader file cannot be opened or read. + /// @throws ls::vulkan_error if object creation fails. + /// + ShaderModule(const Device& device, const std::string& path); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->shaderModule; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->shaderModule); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// Trivially copyable, moveable and destructible + ShaderModule(const ShaderModule&) noexcept = default; + ShaderModule& operator=(const ShaderModule&) noexcept = default; + ShaderModule(ShaderModule&&) noexcept = default; + ShaderModule& operator=(ShaderModule&&) noexcept = default; + ~ShaderModule() = default; + private: + std::shared_ptr shaderModule; + }; + +} + +#endif // SHADERMODULE_HPP diff --git a/include/utils/exceptions.hpp b/include/utils/exceptions.hpp new file mode 100644 index 0000000..9b47718 --- /dev/null +++ b/include/utils/exceptions.hpp @@ -0,0 +1,37 @@ +#ifndef EXCEPTIONS_HPP +#define EXCEPTIONS_HPP + +#include + +#include +#include + +namespace ls { + + /// Simple exception class for Vulkan errors. + class vulkan_error : public std::runtime_error { + public: + /// + /// Construct a vulkan_error with a message and a Vulkan result code. + /// + /// @param result The Vulkan result code associated with the error. + /// @param message The error message. + /// + explicit vulkan_error(VkResult result, const std::string& message); + + /// Get the Vulkan result code associated with this error. + [[nodiscard]] VkResult error() const { return this->result; } + + // Trivially copyable, moveable and destructible + vulkan_error(const vulkan_error&) = default; + vulkan_error(vulkan_error&&) = default; + vulkan_error& operator=(const vulkan_error&) = default; + vulkan_error& operator=(vulkan_error&&) = default; + ~vulkan_error() noexcept override; + private: + VkResult result; + }; + +} + +#endif // EXCEPTIONS_HPP diff --git a/src/core/commandbuffer.cpp b/src/core/commandbuffer.cpp new file mode 100644 index 0000000..15b4b8d --- /dev/null +++ b/src/core/commandbuffer.cpp @@ -0,0 +1,115 @@ +#include "core/commandbuffer.hpp" +#include "core/semaphore.hpp" +#include "utils/exceptions.hpp" + +using namespace Vulkan::Core; + +CommandBuffer::CommandBuffer(const Device& device, const CommandPool& pool) { + if (!device || !pool) + throw std::invalid_argument("Invalid Vulkan device or command 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 = vkAllocateCommandBuffers(device.handle(), &desc, &commandBufferHandle); + if (res != VK_SUCCESS || commandBuffer == VK_NULL_HANDLE) + throw ls::vulkan_error(res, "Unable to allocate command buffer"); + + // store command buffer in shared ptr + this->state = std::make_shared(CommandBufferState::Empty); + this->commandBuffer = std::shared_ptr( + new VkCommandBuffer(commandBufferHandle), + [dev = device.handle(), pool = pool.handle()](VkCommandBuffer* cmdBuffer) { + vkFreeCommandBuffers(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 = vkBeginCommandBuffer(*this->commandBuffer, &beginInfo); + if (res != VK_SUCCESS) + throw ls::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 = vkEndCommandBuffer(*this->commandBuffer); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Unable to end command buffer"); + + *this->state = CommandBufferState::Full; +} + +void CommandBuffer::submit(VkQueue queue, + const std::vector& waitSemaphores, + std::optional> waitSemaphoreValues, + const std::vector& signalSemaphores, + std::optional> signalSemaphoreValues) { + if (!queue) + throw std::invalid_argument("Invalid Vulkan queue"); + if (*this->state != CommandBufferState::Full) + throw std::logic_error("Command buffer is not in Full state"); + + const std::vector waitStages(waitSemaphores.size(), + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); + VkTimelineSemaphoreSubmitInfo timelineInfo = { + .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, + }; + if (waitSemaphoreValues.has_value()) { + timelineInfo.waitSemaphoreValueCount = + static_cast(waitSemaphoreValues->size()); + timelineInfo.pWaitSemaphoreValues = waitSemaphoreValues->data(); + } + if (signalSemaphoreValues.has_value()) { + timelineInfo.signalSemaphoreValueCount = + static_cast(signalSemaphoreValues->size()); + timelineInfo.pSignalSemaphoreValues = signalSemaphoreValues->data(); + } + + std::vector waitSemaphoresHandles; + for (const auto& semaphore : waitSemaphores) { + if (!semaphore) + throw std::invalid_argument("Invalid Vulkan semaphore in waitSemaphores"); + waitSemaphoresHandles.push_back(semaphore.handle()); + } + std::vector signalSemaphoresHandles; + for (const auto& semaphore : signalSemaphores) { + if (!semaphore) + throw std::invalid_argument("Invalid Vulkan semaphore in signalSemaphores"); + signalSemaphoresHandles.push_back(semaphore.handle()); + } + + const VkSubmitInfo submitInfo = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = (waitSemaphoreValues.has_value() || signalSemaphoreValues.has_value()) + ? &timelineInfo : nullptr, + .waitSemaphoreCount = static_cast(waitSemaphores.size()), + .pWaitSemaphores = waitSemaphoresHandles.data(), + .pWaitDstStageMask = waitStages.data(), + .commandBufferCount = 1, + .pCommandBuffers = &(*this->commandBuffer), + .signalSemaphoreCount = static_cast(signalSemaphores.size()), + .pSignalSemaphores = signalSemaphoresHandles.data() + }; + auto res = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Unable to submit command buffer"); + + *this->state = CommandBufferState::Submitted; +} diff --git a/src/core/commandpool.cpp b/src/core/commandpool.cpp new file mode 100644 index 0000000..65b7ddf --- /dev/null +++ b/src/core/commandpool.cpp @@ -0,0 +1,34 @@ +#include "core/commandpool.hpp" +#include "utils/exceptions.hpp" + +using namespace Vulkan::Core; + +CommandPool::CommandPool(const Device& device, CommandPoolType type) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + uint32_t familyIdx{}; + switch (type) { + case CommandPoolType::Compute: + familyIdx = device.getComputeFamilyIdx(); + break; + } + + // create command pool + const VkCommandPoolCreateInfo desc = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .queueFamilyIndex = familyIdx + }; + VkCommandPool commandPoolHandle{}; + auto res = vkCreateCommandPool(device.handle(), &desc, nullptr, &commandPoolHandle); + if (res != VK_SUCCESS || commandPoolHandle == VK_NULL_HANDLE) + throw ls::vulkan_error(res, "Unable to create command pool"); + + // store the command pool in a shared pointer + this->commandPool = std::shared_ptr( + new VkCommandPool(commandPoolHandle), + [dev = device.handle()](VkCommandPool* commandPoolHandle) { + vkDestroyCommandPool(dev, *commandPoolHandle, nullptr); + } + ); +} diff --git a/src/core/fence.cpp b/src/core/fence.cpp new file mode 100644 index 0000000..b4d6610 --- /dev/null +++ b/src/core/fence.cpp @@ -0,0 +1,50 @@ +#include "core/fence.hpp" +#include "utils/exceptions.hpp" +#include + +using namespace Vulkan::Core; + +Fence::Fence(const Device& device) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + // create fence + const VkFenceCreateInfo desc = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO + }; + VkFence fenceHandle{}; + auto res = vkCreateFence(device.handle(), &desc, nullptr, &fenceHandle); + if (res != VK_SUCCESS || fenceHandle == VK_NULL_HANDLE) + throw ls::vulkan_error(res, "Unable to create fence"); + + // store fence in shared ptr + this->device = device.handle(); + this->fence = std::shared_ptr( + new VkFence(fenceHandle), + [dev = device.handle()](VkFence* fenceHandle) { + vkDestroyFence(dev, *fenceHandle, nullptr); + } + ); +} + +void Fence::reset() const { + if (!this->isValid()) + throw std::runtime_error("Invalid fence"); + + VkFence fenceHandle = this->handle(); + auto res = vkResetFences(this->device, 1, &fenceHandle); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Unable to reset fence"); +} + +bool Fence::wait(uint64_t timeout) { + if (!this->isValid()) + throw std::runtime_error("Invalid fence"); + + VkFence fenceHandle = this->handle(); + auto res = vkWaitForFences(this->device, 1, &fenceHandle, VK_TRUE, timeout); + if (res != VK_SUCCESS && res != VK_TIMEOUT) + throw ls::vulkan_error(res, "Unable to wait for fence"); + + return res == VK_SUCCESS; +} diff --git a/src/core/semaphore.cpp b/src/core/semaphore.cpp new file mode 100644 index 0000000..cc484a9 --- /dev/null +++ b/src/core/semaphore.cpp @@ -0,0 +1,66 @@ +#include "core/semaphore.hpp" +#include "utils/exceptions.hpp" + +using namespace Vulkan::Core; + +Semaphore::Semaphore(const Device& device, std::optional initial) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + // create semaphore + const VkSemaphoreTypeCreateInfo typeInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, + .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE, + .initialValue = initial.value_or(0) + }; + const VkSemaphoreCreateInfo desc = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = initial.has_value() ? &typeInfo : nullptr, + }; + VkSemaphore semaphoreHandle{}; + auto res = vkCreateSemaphore(device.handle(), &desc, nullptr, &semaphoreHandle); + if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE) + throw ls::vulkan_error(res, "Unable to create semaphore"); + + // store semaphore in shared ptr + this->isTimeline = initial.has_value(); + this->device = device.handle(); + this->semaphore = std::shared_ptr( + new VkSemaphore(semaphoreHandle), + [dev = device.handle()](VkSemaphore* semaphoreHandle) { + vkDestroySemaphore(dev, *semaphoreHandle, nullptr); + } + ); +} + +void Semaphore::signal(uint64_t value) const { + if (!this->isValid() || !this->isTimeline) + throw std::runtime_error("Invalid timeline semaphore"); + + const VkSemaphoreSignalInfo signalInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO, + .semaphore = this->handle(), + .value = value + }; + auto res = vkSignalSemaphore(this->device, &signalInfo); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Unable to signal semaphore"); +} + +bool Semaphore::wait(uint64_t value, uint64_t timeout) { + if (!this->isValid() || !this->isTimeline) + throw std::runtime_error("Invalid timeline semaphore"); + + VkSemaphore semaphore = this->handle(); + const VkSemaphoreWaitInfo waitInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO, + .semaphoreCount = 1, + .pSemaphores = &semaphore, + .pValues = &value + }; + auto res = vkWaitSemaphores(this->device, &waitInfo, timeout); + if (res != VK_SUCCESS && res != VK_TIMEOUT) + throw ls::vulkan_error(res, "Unable to wait for semaphore"); + + return res == VK_SUCCESS; +} diff --git a/src/core/shadermodule.cpp b/src/core/shadermodule.cpp new file mode 100644 index 0000000..f3edcd8 --- /dev/null +++ b/src/core/shadermodule.cpp @@ -0,0 +1,46 @@ +#include "core/shadermodule.hpp" +#include "utils/exceptions.hpp" + +#include +#include + +using namespace Vulkan::Core; + +ShaderModule::ShaderModule(const Device& device, const std::string& path) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + // read shader bytecode + std::ifstream file(path, std::ios::ate | std::ios::binary); + if (!file) + throw std::system_error(errno, std::generic_category(), "Failed to open shader file: " + path); + + const std::streamsize size = file.tellg(); + std::vector code(static_cast(size)); + + file.seekg(0, std::ios::beg); + if (!file.read(reinterpret_cast(code.data()), size)) + throw std::system_error(errno, std::generic_category(), "Failed to read shader file: " + path); + + file.close(); + + // create shader module + const uint8_t* data_ptr = code.data(); + const VkShaderModuleCreateInfo createInfo{ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = code.size() * sizeof(uint32_t), + .pCode = reinterpret_cast(data_ptr) + }; + VkShaderModule shaderModuleHandle{}; + auto res = vkCreateShaderModule(device.handle(), &createInfo, nullptr, &shaderModuleHandle); + if (res != VK_SUCCESS || !shaderModuleHandle) + throw ls::vulkan_error(res, "Failed to create shader module"); + + // store shader module in shared ptr + this->shaderModule = std::shared_ptr( + new VkShaderModule(shaderModuleHandle), + [dev = device.handle()](VkShaderModule* shaderModuleHandle) { + vkDestroyShaderModule(dev, *shaderModuleHandle, nullptr); + } + ); +} diff --git a/src/utils/exceptions.cpp b/src/utils/exceptions.cpp new file mode 100644 index 0000000..3c41429 --- /dev/null +++ b/src/utils/exceptions.cpp @@ -0,0 +1,10 @@ +#include "utils/exceptions.hpp" + +#include + +using namespace ls; + +vulkan_error::vulkan_error(VkResult result, const std::string& message) + : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), result(result) {} + +vulkan_error::~vulkan_error() noexcept = default;