diff --git a/cpp/examples/Shakti/Vulkan/hello_vulkan.cpp b/cpp/examples/Shakti/Vulkan/hello_vulkan.cpp index 95d2c2ca1..0f7070c0f 100644 --- a/cpp/examples/Shakti/Vulkan/hello_vulkan.cpp +++ b/cpp/examples/Shakti/Vulkan/hello_vulkan.cpp @@ -158,7 +158,7 @@ class VulkanTriangleRenderer : public kvk::GraphicsBackend svk::record_copy_buffer(vbo_staging, _vbo, copy_cmd_buf); SARA_DEBUG << "Submitting data transfer command...\n"; - _graphics_queue.submit_copy_commands(copy_cmd_bufs); + _graphics_queue.submit_commands(copy_cmd_bufs); _graphics_queue.wait(); } @@ -198,7 +198,7 @@ class VulkanTriangleRenderer : public kvk::GraphicsBackend svk::record_copy_buffer(ebo_staging, _ebo, copy_cmd_buf); SARA_DEBUG << "Submitting data transfer command...\n"; - _graphics_queue.submit_copy_commands(copy_cmd_bufs); + _graphics_queue.submit_commands(copy_cmd_bufs); _graphics_queue.wait(); } @@ -379,8 +379,8 @@ class VulkanTriangleRenderer : public kvk::GraphicsBackend // Reset the command buffer associated to the current frame. SARA_DEBUG << "[VK] Resetting for the command buffer...\n"; - vkResetCommandBuffer(_graphics_cmd_bufs[_current_frame], - /*VkCommandBufferResetFlagBits*/ 0); + _graphics_cmd_bufs.reset(_current_frame, + /*VkCommandBufferResetFlagBits*/ 0); // Record the draw command to be performed on this swapchain image. SARA_CHECK(_framebuffers.fbs.size()); diff --git a/cpp/src/DO/Shakti/Vulkan/Buffer.hpp b/cpp/src/DO/Shakti/Vulkan/Buffer.hpp index 96e949896..0162640a3 100644 --- a/cpp/src/DO/Shakti/Vulkan/Buffer.hpp +++ b/cpp/src/DO/Shakti/Vulkan/Buffer.hpp @@ -91,7 +91,7 @@ namespace DO::Shakti::Vulkan { std::swap(_size, other._size); } - auto get_memory_requirements() const -> VkMemoryRequirements + auto memory_requirements() const -> VkMemoryRequirements { auto mem_requirements = VkMemoryRequirements{}; vkGetBufferMemoryRequirements(_device, _handle, &mem_requirements); diff --git a/cpp/src/DO/Shakti/Vulkan/CommandBuffer.hpp b/cpp/src/DO/Shakti/Vulkan/CommandBuffer.hpp index 8c8c262c6..058624a0e 100644 --- a/cpp/src/DO/Shakti/Vulkan/CommandBuffer.hpp +++ b/cpp/src/DO/Shakti/Vulkan/CommandBuffer.hpp @@ -78,7 +78,8 @@ namespace DO::Shakti::Vulkan { auto clear() -> void { - if (_device == nullptr || _command_pool == nullptr) + if (_device == nullptr || _command_pool == nullptr || + _command_buffers.empty()) return; SARA_DEBUG << fmt::format( @@ -107,7 +108,11 @@ namespace DO::Shakti::Vulkan { auto reset(int i, VkCommandBufferResetFlags flags = 0) const -> void { - vkResetCommandBuffer(_command_buffers[i], flags); + const auto status = vkResetCommandBuffer(_command_buffers[i], flags); + if (status != VK_SUCCESS) + throw std::runtime_error{fmt::format( + "[VK] Failed to reset to reset command buffer {}! Error code: {}", + i, static_cast(status))}; } auto data() const -> const VkCommandBuffer* diff --git a/cpp/src/DO/Shakti/Vulkan/DescriptorSet.hpp b/cpp/src/DO/Shakti/Vulkan/DescriptorSet.hpp index c9e6cbc87..5cfd8959d 100644 --- a/cpp/src/DO/Shakti/Vulkan/DescriptorSet.hpp +++ b/cpp/src/DO/Shakti/Vulkan/DescriptorSet.hpp @@ -23,6 +23,9 @@ namespace DO::Shakti::Vulkan { class DescriptorSetLayout { public: + class Builder; + friend class Builder; + DescriptorSetLayout() = default; DescriptorSetLayout(const DescriptorSetLayout&) = delete; @@ -63,43 +66,85 @@ namespace DO::Shakti::Vulkan { std::swap(_handle, other._handle); } - static auto create_for_single_ubo(VkDevice device) -> DescriptorSetLayout + private: + VkDevice _device = nullptr; + VkDescriptorSetLayout _handle = nullptr; + }; + + class DescriptorSetLayout::Builder + { + public: + explicit Builder(VkDevice device) + : _device{device} + { + } + + auto push_uniform_buffer_layout_binding() -> DescriptorSetLayout::Builder& { // UBO object: matrix-view-projection matrix stack auto ubo_layout_binding = VkDescriptorSetLayoutBinding{}; // In the vertex shader code, we have something like: // layout(binding = 0) uniform UBO { ... } ubo; - ubo_layout_binding.binding = 0; - ubo_layout_binding.descriptorCount = 1; + ubo_layout_binding.binding = static_cast(_bindings.size()); + ubo_layout_binding.descriptorCount = 1; // TODO: see if this ever + // needs to change. ubo_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; ubo_layout_binding.pImmutableSamplers = nullptr; // Accessible from the vertex shader only. ubo_layout_binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - // We only need 1 set of descriptors for the MVP UBO. + _bindings.emplace_back(ubo_layout_binding); + + return *this; + } + + auto push_sampler_layout_binding() -> DescriptorSetLayout::Builder& + { + auto sampler_layout_binding = VkDescriptorSetLayoutBinding{}; + + sampler_layout_binding.binding = + static_cast(_bindings.size()); + sampler_layout_binding.descriptorCount = 1; // TODO: see if this ever + // needs to change. + sampler_layout_binding.descriptorType = + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + sampler_layout_binding.pImmutableSamplers = nullptr; + + // Accessible from the fragment shader only. + sampler_layout_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + _bindings.emplace_back(sampler_layout_binding); + + return *this; + } + + auto create() const -> DescriptorSetLayout + { auto create_info = VkDescriptorSetLayoutCreateInfo{}; create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - create_info.bindingCount = 1; - create_info.pBindings = &ubo_layout_binding; - - // Finally really create the descriptor set layout. - auto ubo_set_layout = DescriptorSetLayout{}; - ubo_set_layout._device = device; - const auto status = vkCreateDescriptorSetLayout( - device, &create_info, nullptr, &ubo_set_layout._handle); + create_info.bindingCount = static_cast(_bindings.size()); + create_info.pBindings = _bindings.data(); + + auto desc_set_layout = DescriptorSetLayout{}; + desc_set_layout._device = _device; + const auto status = vkCreateDescriptorSetLayout( // + _device, &create_info, nullptr, // + &desc_set_layout._handle // + ); if (status != VK_SUCCESS) - throw std::runtime_error{fmt::format( - "[VK] Error: failed to create UBO set layout! Error code: {}", - static_cast(status))}; + throw std::runtime_error{ + fmt::format("[VK] Error: failed to create descriptor set layout! " + "Error code: {}", + static_cast(status))}; - return ubo_set_layout; + return desc_set_layout; } private: - VkDevice _device = nullptr; - VkDescriptorSetLayout _handle = nullptr; + VkDevice _device = VK_NULL_HANDLE; + std::vector _bindings; }; diff --git a/cpp/src/DO/Shakti/Vulkan/DeviceMemory.hpp b/cpp/src/DO/Shakti/Vulkan/DeviceMemory.hpp index 6082bc711..f5aa55f86 100644 --- a/cpp/src/DO/Shakti/Vulkan/DeviceMemory.hpp +++ b/cpp/src/DO/Shakti/Vulkan/DeviceMemory.hpp @@ -12,6 +12,7 @@ #pragma once #include +#include #include #include @@ -135,7 +136,7 @@ namespace DO::Shakti::Vulkan { VK_MEMORY_PROPERTY_HOST_COHERENT_BIT // }; - const auto mem_reqs = buffer.get_memory_requirements(); + const auto mem_reqs = buffer.memory_requirements(); const auto mem_type = _physical_device.find_memory_type(mem_reqs.memoryTypeBits, mem_props); @@ -149,7 +150,7 @@ namespace DO::Shakti::Vulkan { VK_MEMORY_PROPERTY_HOST_COHERENT_BIT // }; - const auto mem_reqs = buffer.get_memory_requirements(); + const auto mem_reqs = buffer.memory_requirements(); const auto mem_type = _physical_device.find_memory_type(mem_reqs.memoryTypeBits, mem_props); @@ -161,7 +162,19 @@ namespace DO::Shakti::Vulkan { static constexpr auto mem_props = VkMemoryPropertyFlags{VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT}; - const auto mem_reqs = buffer.get_memory_requirements(); + const auto mem_reqs = buffer.memory_requirements(); + const auto mem_type = + _physical_device.find_memory_type(mem_reqs.memoryTypeBits, mem_props); + + return {_device, mem_reqs.size, mem_type}; + } + + auto allocate_for_device_image(const Image& image) const -> DeviceMemory + { + static constexpr auto mem_props = + VkMemoryPropertyFlags{VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT}; + + const auto mem_reqs = image.memory_requirements(); const auto mem_type = _physical_device.find_memory_type(mem_reqs.memoryTypeBits, mem_props); diff --git a/cpp/src/DO/Shakti/Vulkan/GraphicsPipeline.hpp b/cpp/src/DO/Shakti/Vulkan/GraphicsPipeline.hpp index 4701a6369..e3458f9d8 100644 --- a/cpp/src/DO/Shakti/Vulkan/GraphicsPipeline.hpp +++ b/cpp/src/DO/Shakti/Vulkan/GraphicsPipeline.hpp @@ -75,7 +75,8 @@ namespace DO::Kalpana::Vulkan { return _device; } - auto model_view_projection_layout() const -> const Shakti::Vulkan::DescriptorSetLayout& + auto model_view_projection_layout() const + -> const Shakti::Vulkan::DescriptorSetLayout& { return _mvp_layout; } @@ -210,7 +211,9 @@ namespace DO::Kalpana::Vulkan { graphics_pipeline._device = device; graphics_pipeline._mvp_layout = - Shakti::Vulkan::DescriptorSetLayout::create_for_single_ubo(device); + Shakti::Vulkan::DescriptorSetLayout::Builder{device} + .push_uniform_buffer_layout_binding() + .create(); // Initialize the graphics pipeline layout. SARA_DEBUG << "Initializing the graphics pipeline layout...\n"; diff --git a/cpp/src/DO/Shakti/Vulkan/Image.hpp b/cpp/src/DO/Shakti/Vulkan/Image.hpp new file mode 100644 index 000000000..6e326a173 --- /dev/null +++ b/cpp/src/DO/Shakti/Vulkan/Image.hpp @@ -0,0 +1,288 @@ +// ========================================================================== // +// This file is part of Sara, a basic set of libraries in C++ for computer +// vision. +// +// Copyright (C) 2023 David Ok +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. +// ========================================================================== // + +#pragma once + +#include + +#include + +#include + + +namespace DO::Shakti::Vulkan { + + class Image + { + public: + class Builder; + friend class Builder; + + Image() = default; + + Image(const Image&) = delete; + + Image(Image&&) = default; + + ~Image() + { + if (_device == nullptr || _handle == nullptr) + return; + vkDestroyImage(_device, _handle, nullptr); + } + + auto operator=(const Image&) -> Image& = delete; + + auto operator=(Image&& other) -> Image& + { + _device = std::move(other._device); + _handle = std::move(other._handle); + _sizes = std::move(other._sizes); + return *this; + } + + operator VkImage&() + { + return _handle; + } + + operator VkImage() const + { + return _handle; + } + + auto memory_requirements() const -> VkMemoryRequirements + { + auto mem_reqs = VkMemoryRequirements{}; + vkGetImageMemoryRequirements(_device, _handle, &mem_reqs); + return mem_reqs; + } + + auto bind(VkDeviceMemory device_memory, const std::uint32_t offset) const + -> void + { + const auto status = + vkBindImageMemory(_device, _handle, device_memory, offset); + if (status != VK_SUCCESS) + throw std::runtime_error{ + fmt::format("[VK] Failed to bind image to device memory region! " + "Error code: {}", + static_cast(status))}; + } + + auto sizes() const -> const VkExtent3D& + { + return _sizes; + } + + private: + VkDevice _device = VK_NULL_HANDLE; + VkImage _handle = VK_NULL_HANDLE; + VkExtent3D _sizes = {.width = 0, .height = 0, .depth = 0}; + }; + + + class Image::Builder + { + public: + Builder() = default; + + Builder(VkDevice device) + : _device{device} + { + } + + auto image_type(const VkImageType value) -> Builder& + { + _image_type = value; + return *this; + } + + auto sizes(const VkExtent2D& value) -> Builder& + { + _sizes.width = value.width; + _sizes.height = value.height; + _sizes.depth = 1; + return *this; + } + + auto sizes(const VkExtent3D& value) -> Builder& + { + _sizes = value; + return *this; + } + + auto format(const VkFormat value) -> Builder& + { + _format = value; + return *this; + } + + auto tiling(const VkImageTiling value) -> Builder& + { + _tiling = value; + return *this; + } + + auto mip_levels(const std::uint32_t value) -> Builder& + { + _mip_levels = value; + return *this; + } + + auto initial_layout(const VkImageLayout value) -> Builder& + { + _initial_layout = value; + return *this; + } + + auto usage(const VkImageUsageFlags value) -> Builder& + { + _usage = value; + return *this; + } + + auto create() const -> Image + { + auto create_info = VkImageCreateInfo{}; + create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + create_info.imageType = _image_type; + create_info.extent = _sizes; + create_info.mipLevels = _mip_levels; + create_info.arrayLayers = _array_layers; + create_info.format = _format; + create_info.tiling = _tiling; + create_info.initialLayout = _initial_layout; + create_info.usage = _usage; + create_info.samples = _samples; + create_info.sharingMode = _sharing_mode; + + auto image = Image{}; + image._device = _device; + image._sizes = _sizes; + const auto status = vkCreateImage(_device, &create_info, nullptr, // + &image._handle); + if (status != VK_SUCCESS) + throw std::runtime_error{ + fmt::format("[VK] Failed to create image! Error code: {}", + static_cast(status))}; + + return image; + } + + private: + VkDevice _device = VK_NULL_HANDLE; + VkExtent3D _sizes = {.width = 0, .height = 0, .depth = 1}; + VkImageType _image_type = VK_IMAGE_TYPE_2D; + VkFormat _format; // VK_FORMAT_R8G8B8A8_SRGB + VkImageTiling _tiling; // VK_IMAGE_TILING_OPTIMAL + std::uint32_t _mip_levels = 1; + std::uint32_t _array_layers = 1; + VkImageLayout _initial_layout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImageUsageFlags _usage; // VK_IMAGE_USAGE_TRANSFER_DST_BIT | + // VK_IMAGE_USAGE_SAMPLED_BIT + VkSampleCountFlagBits _samples = VK_SAMPLE_COUNT_1_BIT; + VkSharingMode _sharing_mode = VK_SHARING_MODE_EXCLUSIVE; + }; + + + inline auto record_copy_buffer_to_image(const Buffer& src, const Image& dst, + const VkCommandBuffer cmd_buffer) + -> void + { + // Specify the copy operation for this command buffer. + auto cmd_buf_begin_info = VkCommandBufferBeginInfo{}; + cmd_buf_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + cmd_buf_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(cmd_buffer, &cmd_buf_begin_info); + { + auto region = VkBufferImageCopy{}; + // Source region. + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + // Destination region. + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = dst.sizes(); + + vkCmdCopyBufferToImage(cmd_buffer, src, dst, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + } + vkEndCommandBuffer(cmd_buffer); + } + + inline auto record_image_layout_transition(const VkImage image, + const VkImageLayout old_layout, + const VkImageLayout new_layout, + const VkCommandBuffer cmd_buffer) + -> void + { + // Specify the copy operation for this command buffer. + auto cmd_buf_begin_info = VkCommandBufferBeginInfo{}; + cmd_buf_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + cmd_buf_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(cmd_buffer, &cmd_buf_begin_info); + { + auto barrier = VkImageMemoryBarrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + // + barrier.oldLayout = old_layout; + barrier.newLayout = new_layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + auto src_stage = VkPipelineStageFlags{}; + auto dst_stage = VkPipelineStageFlags{}; + + if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED && + new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) + { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + src_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + dst_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } + else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + src_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; + dst_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } + else + throw std::invalid_argument{ + "[VK] Error: unimplemented/unsupported layout transition!" // + }; + + vkCmdPipelineBarrier(cmd_buffer, src_stage, dst_stage, 0, 0, nullptr, 0, + nullptr, 1, &barrier); + } + vkEndCommandBuffer(cmd_buffer); + } + + +} // namespace DO::Shakti::Vulkan diff --git a/cpp/src/DO/Shakti/Vulkan/ImageView.hpp b/cpp/src/DO/Shakti/Vulkan/ImageView.hpp new file mode 100644 index 000000000..1f23f6e52 --- /dev/null +++ b/cpp/src/DO/Shakti/Vulkan/ImageView.hpp @@ -0,0 +1,143 @@ +// ========================================================================== // +// This file is part of Sara, a basic set of libraries in C++ for computer +// vision. +// +// Copyright (C) 2023 David Ok +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. +// ========================================================================== // + +#pragma once + +#include + +#include + +#include + + +namespace DO::Shakti::Vulkan { + + class ImageView + { + public: + class Builder; + friend class Builder; + + ImageView() = default; + + ImageView(const ImageView&) = delete; + + ImageView(ImageView&& other) + { + swap(other); + } + + ~ImageView() + { + if (_device == nullptr || _handle == nullptr) + return; + vkDestroyImageView(_device, _handle, nullptr); + } + + auto operator=(const ImageView&) -> ImageView& = delete; + + auto operator=(ImageView&& other) -> ImageView& + { + swap(other); + return *this; + } + + operator VkImageView&() + { + return _handle; + } + + operator VkImageView() const + { + return _handle; + } + + auto swap(ImageView& other) -> void + { + std::swap(_device, other._device); + std::swap(_handle, other._handle); + } + + private: + VkDevice _device = VK_NULL_HANDLE; + VkImageView _handle = VK_NULL_HANDLE; + }; + + + class ImageView::Builder + { + public: + explicit Builder(VkDevice device) + : _device{device} + { + } + + auto image(const VkImage value) -> Builder& + { + _image = value; + return *this; + } + + auto view_type(const VkImageViewType value) -> Builder& + { + _view_type = value; + return *this; + } + + auto format(const VkFormat value) -> Builder& + { + _format = value; + return *this; + } + + auto aspect_mask(const VkImageAspectFlags value) -> Builder& + { + _aspect_mask = value; + return *this; + } + + auto create() const -> ImageView + { + auto create_info = VkImageViewCreateInfo{}; + create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + create_info.image = _image; + create_info.viewType = _view_type; + create_info.format = _format; + create_info.subresourceRange.aspectMask = _aspect_mask; + + // TODO: Worry about this later. + create_info.subresourceRange.baseMipLevel = 0; + create_info.subresourceRange.levelCount = 1; + create_info.subresourceRange.baseArrayLayer = 0; + create_info.subresourceRange.layerCount = 1; + + auto image_view = ImageView{}; + image_view._device = _device; + + const auto status = vkCreateImageView(_device, &create_info, nullptr, + &image_view._handle); + if (status != VK_SUCCESS) + throw std::runtime_error{ + fmt::format("[VK] Failed to create image view! Error code: {}", + static_cast(status))}; + + return image_view; + } + + private: + VkDevice _device = VK_NULL_HANDLE; + VkImage _image = VK_NULL_HANDLE; + VkImageViewType _view_type = VK_IMAGE_VIEW_TYPE_2D; + VkFormat _format; + VkImageAspectFlags _aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT; + }; + +} // namespace DO::Shakti::Vulkan diff --git a/cpp/src/DO/Shakti/Vulkan/PhysicalDevice.hpp b/cpp/src/DO/Shakti/Vulkan/PhysicalDevice.hpp index 2d3c563dc..3b63d1d02 100644 --- a/cpp/src/DO/Shakti/Vulkan/PhysicalDevice.hpp +++ b/cpp/src/DO/Shakti/Vulkan/PhysicalDevice.hpp @@ -152,6 +152,12 @@ namespace DO::Shakti::Vulkan { "[VK] Error: failed to find suitable memory type!"}; } + auto properties() const -> VkPhysicalDeviceProperties + { + auto properties = VkPhysicalDeviceProperties{}; + vkGetPhysicalDeviceProperties(handle, &properties); + return properties; + } operator VkPhysicalDevice&() { diff --git a/cpp/src/DO/Shakti/Vulkan/Queue.hpp b/cpp/src/DO/Shakti/Vulkan/Queue.hpp index 5ff6dc962..1e4eb9438 100644 --- a/cpp/src/DO/Shakti/Vulkan/Queue.hpp +++ b/cpp/src/DO/Shakti/Vulkan/Queue.hpp @@ -40,14 +40,13 @@ namespace DO::Shakti::Vulkan { static_cast(status))}; }; - auto submit_copy_commands(const CommandBufferSequence& copy_cmd_bufs) const - -> void + auto submit_commands(const CommandBufferSequence& cmd_bufs) const -> void { auto submit_info = VkSubmitInfo{}; submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.commandBufferCount = - static_cast(copy_cmd_bufs.size()); - submit_info.pCommandBuffers = copy_cmd_bufs.data(); + static_cast(cmd_bufs.size()); + submit_info.pCommandBuffers = cmd_bufs.data(); submit(submit_info, VK_NULL_HANDLE); } diff --git a/cpp/src/DO/Shakti/Vulkan/Sampler.hpp b/cpp/src/DO/Shakti/Vulkan/Sampler.hpp new file mode 100644 index 000000000..32644a242 --- /dev/null +++ b/cpp/src/DO/Shakti/Vulkan/Sampler.hpp @@ -0,0 +1,116 @@ +// ========================================================================== // +// This file is part of Sara, a basic set of libraries in C++ for computer +// vision. +// +// Copyright (C) 2023 David Ok +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. +// ========================================================================== // + +#pragma once + +#include + +#include + +#include + + +namespace DO::Shakti::Vulkan { + + class Sampler + { + public: + class Builder; + friend class Builder; + + Sampler() = default; + + Sampler(const Sampler&) = delete; + + Sampler(Sampler&& other) = default; + + ~Sampler() + { + if (_device == VK_NULL_HANDLE || _handle == VK_NULL_HANDLE) + return; + vkDestroySampler(_device, _handle, nullptr); + } + + auto operator=(const Sampler&) -> Sampler& = delete; + + auto operator=(Sampler&& other) -> Sampler& + { + _device = std::move(other._device); + _handle = std::move(other._handle); + return *this; + } + + operator VkSampler&() + { + return _handle; + } + + operator VkSampler() const + { + return _handle; + } + + private: + VkDevice _device = VK_NULL_HANDLE; + VkSampler _handle = VK_NULL_HANDLE; + }; + + + class Sampler::Builder + { + public: + Builder(const PhysicalDevice& physical_device, const VkDevice device) + : _physical_device{physical_device} + , _device{device} + { + } + + auto create() -> Sampler + { + auto properties = _physical_device.properties(); + + auto create_info = VkSamplerCreateInfo{}; + create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + // Bilinear interpolation. + create_info.magFilter = VK_FILTER_LINEAR; + create_info.minFilter = VK_FILTER_LINEAR; + // For out-of-bound sampling. + create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + create_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + create_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + // Anisotropy. + create_info.anisotropyEnable = VK_TRUE; + create_info.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + // + create_info.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + create_info.unnormalizedCoordinates = VK_FALSE; + create_info.compareEnable = VK_FALSE; + create_info.compareOp = VK_COMPARE_OP_ALWAYS; + create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + + auto sampler = Sampler{}; + sampler._device = _device; + const auto status = vkCreateSampler(_device, &create_info, nullptr, // + &sampler._handle); + if (status != VK_SUCCESS) + throw std::runtime_error{ + fmt::format("[VK] Failed to create sampler! Error code: {}", + static_cast(status))}; + + return sampler; + } + + private: + const PhysicalDevice& _physical_device; + VkDevice _device = VK_NULL_HANDLE; + }; + +} // namespace DO::Shakti::Vulkan diff --git a/cpp/test/Shakti/Vulkan/test_vulkan_buffer.cpp b/cpp/test/Shakti/Vulkan/test_vulkan_buffer.cpp index 9b34509c7..2323a5223 100644 --- a/cpp/test/Shakti/Vulkan/test_vulkan_buffer.cpp +++ b/cpp/test/Shakti/Vulkan/test_vulkan_buffer.cpp @@ -109,7 +109,7 @@ BOOST_AUTO_TEST_CASE(test_staging_buffer) // Our hardware requirements is substantified by the memory type needed to // created such a buffer. - const auto mem_reqs = staging_buffer.get_memory_requirements(); + const auto mem_reqs = staging_buffer.memory_requirements(); SARA_CHECK(mem_reqs.size); SARA_CHECK(mem_reqs.alignment); SARA_CHECK(mem_reqs.memoryTypeBits); @@ -228,7 +228,7 @@ BOOST_AUTO_TEST_CASE(test_device_buffer) // Our hardware requirements is substantified by the memory type needed to // created such a buffer. - const auto mem_reqs = device_buffer.get_memory_requirements(); + const auto mem_reqs = device_buffer.memory_requirements(); SARA_CHECK(mem_reqs.size); SARA_CHECK(mem_reqs.alignment); SARA_CHECK(mem_reqs.memoryTypeBits); diff --git a/cpp/test/Shakti/Vulkan/test_vulkan_descriptor_set.cpp b/cpp/test/Shakti/Vulkan/test_vulkan_descriptor_set.cpp index 3631ce01c..04ef2369b 100644 --- a/cpp/test/Shakti/Vulkan/test_vulkan_descriptor_set.cpp +++ b/cpp/test/Shakti/Vulkan/test_vulkan_descriptor_set.cpp @@ -145,8 +145,9 @@ BOOST_AUTO_TEST_CASE(test_device) // Each descriptor set must have a layout associated to it. // // 3.a) Create one descriptor set layout for each each descriptor. - const auto ubo_layout_binding = - svk::DescriptorSetLayout::create_for_single_ubo(device); + const auto ubo_layout_binding = svk::DescriptorSetLayout::Builder{device} + .push_uniform_buffer_layout_binding() + .create(); const auto desc_set_layouts = std::array{ ubo_layout_binding, // diff --git a/cpp/test/Shakti/Vulkan/test_vulkan_image.cpp b/cpp/test/Shakti/Vulkan/test_vulkan_image.cpp new file mode 100644 index 000000000..e3040ff46 --- /dev/null +++ b/cpp/test/Shakti/Vulkan/test_vulkan_image.cpp @@ -0,0 +1,268 @@ +// ========================================================================== // +// This file is part of Sara, a basic set of libraries in C++ for computer +// vision. +// +// Copyright (C) 2023 David Ok +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. +// ========================================================================== // + +#define BOOST_TEST_MODULE "Vulkan/Image" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +static constexpr auto debug_vulkan_instance = true; +#if defined(__APPLE__) +static constexpr auto compile_for_apple = true; +#else +static constexpr auto compile_for_apple = false; +#endif + + +BOOST_AUTO_TEST_CASE(test_image) +{ + namespace sara = DO::Sara; + namespace svk = DO::Shakti::Vulkan; + + // Vulkan instance. + auto instance_extensions = std::vector{}; + if constexpr (debug_vulkan_instance) + instance_extensions.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + if constexpr (compile_for_apple) + { + instance_extensions.emplace_back( + VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + instance_extensions.emplace_back( + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + } + + const auto validation_layers_required = + debug_vulkan_instance ? std::vector{"VK_LAYER_KHRONOS_validation"} + : std::vector{}; + + const auto instance = + svk::Instance::Builder{} + .application_name("Vulkan Application") + .engine_name("No Engine") + .enable_instance_extensions(instance_extensions) + .enable_validation_layers(validation_layers_required) + .create(); + + // List all Vulkan physical devices. + const auto physical_devices = + svk::PhysicalDevice::list_physical_devices(instance); + BOOST_CHECK(!physical_devices.empty()); + const auto& physical_device = physical_devices.front(); + + // The physical device should at least support a compute queue family. + auto compute_queue_family_index = std::uint32_t{}; + for (auto i = 0u; i != physical_device.queue_families.size(); ++i) + { + if (physical_device.supports_queue_family_type(i, VK_QUEUE_COMPUTE_BIT)) + { + compute_queue_family_index = i; + break; + } + } + BOOST_CHECK(compute_queue_family_index != + physical_device.queue_families.size()); + + // Create a logical device. + auto device_extensions = std::vector{}; + if constexpr (compile_for_apple) + device_extensions.emplace_back("VK_KHR_portability_subset"); + // Allow anisotropic sampling. + auto physical_device_features = VkPhysicalDeviceFeatures{}; + physical_device_features.samplerAnisotropy = VK_TRUE; + const auto device = svk::Device::Builder{physical_device} + .enable_device_extensions(device_extensions) + .enable_queue_families({compute_queue_family_index}) + .enable_device_features(physical_device_features) + .enable_validation_layers(validation_layers_required) + .create(); + BOOST_CHECK(static_cast(device) != VK_NULL_HANDLE); + + + // ======================================================================== // + // TODO: SPECIFY THE GRAPHICS PIPELINE LAYOUT. + // + // 1. Create the description set layout that the shaders need. + const auto desc_set_layout = svk::DescriptorSetLayout::Builder{device} + .push_sampler_layout_binding() + .create(); + // 2. Hook this to the graphics pipeline layout data structure. + + + // ======================================================================== // + // ALLOCATE IMAGE RESOURCE ON VULKAN SIDE. + // + // Make some constants. + static constexpr auto width = 800; + static constexpr auto height = 600; + static const auto white = sara::Rgba8{255, 255, 255, 255}; + static const auto black = sara::Rgba8{0, 0, 0, 0}; + + // Create a white image on the host side. + auto host_image = sara::Image{width, height}; + host_image.flat_array().fill(white); + + // Create a staging buffer on the Vulkan device side. + const auto staging_image_buffer = + svk::BufferFactory{device} // + .make_staging_buffer(host_image.size()); + BOOST_CHECK(static_cast(staging_image_buffer) != VK_NULL_HANDLE); + const auto staging_image_dmem = + svk::DeviceMemoryFactory{physical_device, device} + .allocate_for_staging_buffer(staging_image_buffer); + BOOST_CHECK(static_cast(staging_image_dmem) != + VK_NULL_HANDLE); + staging_image_buffer.bind(staging_image_dmem, 0); + + // Copy the image data from the host side to the device side. + staging_image_dmem.copy_from(host_image.data(), host_image.size()); + + // Double-check the content of the staging buffer. + { + const auto staging_image_ptr_2 = + staging_image_dmem.map_memory(host_image.size()); + auto staging_image_dmem_copy_on_host = + sara::Image{width, height}; + staging_image_dmem_copy_on_host.flat_array().fill(black); + std::memcpy(staging_image_dmem_copy_on_host.data(), staging_image_ptr_2, + staging_image_dmem.size()); + staging_image_dmem.unmap_memory(); + BOOST_CHECK(host_image == staging_image_dmem_copy_on_host); + } + + // Create an image on the device side. + const auto image = + svk::Image::Builder{device} + .sizes(VkExtent2D{width, height}) + .format(VK_FORMAT_R8G8B8A8_SRGB) + .tiling(VK_IMAGE_TILING_OPTIMAL) + // .initial_layout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) + .usage(VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT) + .create(); + BOOST_CHECK(static_cast(image) != VK_NULL_HANDLE); + + // We must allocate a device memory for the image before creating an image + // view. + const auto image_dmem = svk::DeviceMemoryFactory{physical_device, device} + .allocate_for_device_image(image); + BOOST_CHECK(static_cast(image_dmem) != VK_NULL_HANDLE); + image.bind(image_dmem, 0); + + // Copy the data from the staging buffer object to the image object. + // + // So first create a queue, a command pool, and a command buffer to submit + // operations on Vulkan side. + const auto compute_queue = svk::Queue{device, compute_queue_family_index}; + const auto cmd_pool = svk::CommandPool{device, compute_queue_family_index}; + auto cmd_bufs = svk::CommandBufferSequence{1, device, cmd_pool}; + const auto& cmd_buf = cmd_bufs[0]; + + // Allow the image to be used as a copy destination resource. + svk::record_image_layout_transition(image, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + cmd_buf); + compute_queue.submit_commands(cmd_bufs); + compute_queue.wait(); + cmd_bufs.reset(0); + + // Copy the data from the staging buffer to the device image. + svk::record_copy_buffer_to_image(staging_image_buffer, image, cmd_buf); + compute_queue.submit_commands(cmd_bufs); + compute_queue.wait(); + + // Finally tell Vulkan that the image can only be used a read-only resource + // from a shader now on. + svk::record_image_layout_transition( + image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, cmd_buf); + compute_queue.submit_commands(cmd_bufs); + compute_queue.wait(); + cmd_bufs.reset(0); + // Optional since the destructor calls it: but this helped to fix a bug in the + // implementation of the `clear` method. + cmd_bufs.clear(); + + // To use the image resource from a shader: + // 1. Create an image view + // 2. Create an image sampler + // 3. Add a DescriptorSetLayout for the image sampler. + const auto image_view = svk::ImageView::Builder{device} + .image(image) + .format(VK_FORMAT_R8G8B8A8_SRGB) + .aspect_mask(VK_IMAGE_ASPECT_COLOR_BIT) + .create(); + BOOST_CHECK(static_cast(image_view) != VK_NULL_HANDLE); + + const auto image_sampler = + svk::Sampler::Builder{physical_device, device}.create(); + BOOST_CHECK(static_cast(image_sampler) != VK_NULL_HANDLE); + + + // ======================================================================== // + // ALLOCATE RENDER RESOURCES ON VULKAN (descriptor pool, sets and so on.) + // + // 2. A set of descriptors allocated by a descriptor pool. + // + // We only need 1 pool of image sampler descriptors. + static constexpr auto num_pools = 1; + static constexpr auto num_frames_in_flight = 2; + auto desc_pool_builder = svk::DescriptorPool::Builder{device} + .pool_count(num_pools) // + .pool_max_sets(num_frames_in_flight); + desc_pool_builder.descriptor_count(0) = num_frames_in_flight; + desc_pool_builder.pool_type(0) = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + + auto desc_pool = desc_pool_builder.create(); + BOOST_CHECK(static_cast(desc_pool) != VK_NULL_HANDLE); + + const auto desc_set_layouts = std::vector( + num_frames_in_flight, + static_cast(desc_set_layout)); + + // We create num_frames_in_flight sets of descriptors. + auto desc_sets = svk::DescriptorSets{ + desc_set_layouts.data(), // + static_cast(desc_set_layouts.size()), // + desc_pool // + }; + + + // We describe each sets of descriptors. + auto image_info = VkDescriptorImageInfo{}; + image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + image_info.imageView = image_view; + image_info.sampler = image_sampler; + + auto desc_write = VkWriteDescriptorSet{}; + desc_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + desc_write.dstSet = desc_sets[0]; + desc_write.dstBinding = 0; + desc_write.dstArrayElement = 0; + desc_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + desc_write.descriptorCount = 1; + desc_write.pImageInfo = &image_info; + vkUpdateDescriptorSets(device, 1, &desc_write, 0, nullptr); +}