From d0bd00d412b6b345879c520c47cac1879c02a9f5 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 19:04:45 +0200 Subject: [PATCH] core sampler and buffer objects --- include/core/buffer.hpp | 59 +++++++++++++++++++++++++++ include/core/sampler.hpp | 50 +++++++++++++++++++++++ src/core/buffer.cpp | 86 ++++++++++++++++++++++++++++++++++++++++ src/core/sampler.cpp | 33 +++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 include/core/buffer.hpp create mode 100644 include/core/sampler.hpp create mode 100644 src/core/buffer.cpp create mode 100644 src/core/sampler.cpp diff --git a/include/core/buffer.hpp b/include/core/buffer.hpp new file mode 100644 index 0000000..fecf3e1 --- /dev/null +++ b/include/core/buffer.hpp @@ -0,0 +1,59 @@ +#ifndef BUFFER_HPP +#define BUFFER_HPP + +#include "device.hpp" + +#include + +#include +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan buffer. + /// + /// This class manages the lifetime of a Vulkan buffer. + /// + class Buffer { + public: + /// + /// Create the buffer. + /// + /// @param device Vulkan device + /// @param size Size of the buffer in bytes. + /// @param data Initial data for the buffer. + /// @param usage Usage flags for the buffer + /// + /// @throws std::invalid_argument if the device or buffer size is invalid + /// @throws ls::vulkan_error if object creation fails. + /// + Buffer(const Device& device, uint32_t size, std::vector data, + VkBufferUsageFlags usage); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->buffer; } + /// Get the size of the buffer. + [[nodiscard]] uint32_t getSize() const { return this->size; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->buffer); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// Trivially copyable, moveable and destructible + Buffer(const Buffer&) noexcept = default; + Buffer& operator=(const Buffer&) noexcept = default; + Buffer(Buffer&&) noexcept = default; + Buffer& operator=(Buffer&&) noexcept = default; + ~Buffer() = default; + private: + std::shared_ptr buffer; + std::shared_ptr memory; + + uint32_t size; + }; + +} + +#endif // BUFFER_HPP diff --git a/include/core/sampler.hpp b/include/core/sampler.hpp new file mode 100644 index 0000000..84ce43a --- /dev/null +++ b/include/core/sampler.hpp @@ -0,0 +1,50 @@ +#ifndef SAMPLER_HPP +#define SAMPLER_HPP + +#include "device.hpp" + +#include + +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan sampler. + /// + /// This class manages the lifetime of a Vulkan sampler. + /// + class Sampler { + public: + /// + /// Create the sampler. + /// + /// @param device Vulkan device + /// @param mode Address mode for the sampler. + /// + /// @throws std::invalid_argument if the device is invalid. + /// @throws ls::vulkan_error if object creation fails. + /// + Sampler(const Device& device, VkSamplerAddressMode mode); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->sampler; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->sampler); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// Trivially copyable, moveable and destructible + Sampler(const Sampler&) noexcept = default; + Sampler& operator=(const Sampler&) noexcept = default; + Sampler(Sampler&&) noexcept = default; + Sampler& operator=(Sampler&&) noexcept = default; + ~Sampler() = default; + private: + std::shared_ptr sampler; + }; + +} + +#endif // SAMPLER_HPP diff --git a/src/core/buffer.cpp b/src/core/buffer.cpp new file mode 100644 index 0000000..32e0e60 --- /dev/null +++ b/src/core/buffer.cpp @@ -0,0 +1,86 @@ +#include "core/buffer.hpp" +#include "utils/exceptions.hpp" + +#include +#include + +using namespace Vulkan::Core; + +Buffer::Buffer(const Device& device, uint32_t size, std::vector data, + VkBufferUsageFlags usage) : size(size) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + if (size < data.size()) + throw std::invalid_argument("Invalid buffer size"); + + // create buffer + const VkBufferCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = size, + .usage = usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE + }; + VkBuffer bufferHandle{}; + auto res = vkCreateBuffer(device.handle(), &desc, nullptr, &bufferHandle); + if (res != VK_SUCCESS || bufferHandle == VK_NULL_HANDLE) + throw ls::vulkan_error(res, "Failed to create Vulkan buffer"); + + // find memory type + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties(device.getPhysicalDevice(), &memProps); + + VkMemoryRequirements memReqs; + vkGetBufferMemoryRequirements(device.handle(), bufferHandle, &memReqs); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + std::optional memType{}; + for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { + if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN + (memProps.memoryTypes[i].propertyFlags & + (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))) { + memType.emplace(i); + break; + } // NOLINTEND + } + if (!memType.has_value()) + throw ls::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for buffer"); +#pragma clang diagnostic pop + + // allocate and bind memory + const VkMemoryAllocateInfo allocInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memReqs.size, + .memoryTypeIndex = memType.value() + }; + VkDeviceMemory memoryHandle{}; + res = vkAllocateMemory(device.handle(), &allocInfo, nullptr, &memoryHandle); + if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE) + throw ls::vulkan_error(res, "Failed to allocate memory for Vulkan buffer"); + + res = vkBindBufferMemory(device.handle(), bufferHandle, memoryHandle, 0); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Failed to bind memory to Vulkan buffer"); + + // store buffer and memory in shared ptr + this->buffer = std::shared_ptr( + new VkBuffer(bufferHandle), + [dev = device.handle()](VkBuffer* img) { + vkDestroyBuffer(dev, *img, nullptr); + } + ); + this->memory = std::shared_ptr( + new VkDeviceMemory(memoryHandle), + [dev = device.handle()](VkDeviceMemory* mem) { + vkFreeMemory(dev, *mem, nullptr); + } + ); + + // upload data to buffer + uint8_t* buf{}; + res = vkMapMemory(device.handle(), memoryHandle, 0, size, 0, reinterpret_cast(&buf)); + if (res != VK_SUCCESS || buf == nullptr) + throw ls::vulkan_error(res, "Failed to map memory for Vulkan buffer"); + std::copy_n(data.data(), size, buf); + vkUnmapMemory(device.handle(), memoryHandle); +} diff --git a/src/core/sampler.cpp b/src/core/sampler.cpp new file mode 100644 index 0000000..e817e54 --- /dev/null +++ b/src/core/sampler.cpp @@ -0,0 +1,33 @@ +#include "core/sampler.hpp" +#include "utils/exceptions.hpp" + +using namespace Vulkan::Core; + +Sampler::Sampler(const Device& device, VkSamplerAddressMode mode) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + // create sampler + const VkSamplerCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, + .addressModeU = mode, + .addressModeV = mode, + .addressModeW = mode, + .maxLod = VK_LOD_CLAMP_NONE + }; + VkSampler samplerHandle{}; + auto res = vkCreateSampler(device.handle(), &desc, nullptr, &samplerHandle); + if (res != VK_SUCCESS || samplerHandle == VK_NULL_HANDLE) + throw ls::vulkan_error(res, "Unable to create sampler"); + + // store the sampler in a shared pointer + this->sampler = std::shared_ptr( + new VkSampler(samplerHandle), + [dev = device.handle()](VkSampler* samplerHandle) { + vkDestroySampler(dev, *samplerHandle, nullptr); + } + ); +}