SDEngine
Game Engine
Loading...
Searching...
No Matches
utils.hpp
Go to the documentation of this file.
1// TODO(docs): Add file-level Doxygen header
2// - @file Utils.hpp
3// - @brief Vulkan utility functions and macros
4// - Note: This is a "kitchen sink" file - consider splitting
5// - Categories: Error checking, buffer/image creation, texture loading
6#pragma once
7#include <expected>
8#include <filesystem>
9#include <limits>
10#include <stb_image.h>
11
12#include <vulkan/vulkan.hpp>
13
14#include "SD/core/base.hpp"
16
21namespace sd {
22
23
24// TODO(docs): Document VK_CHECK macro
25// - Purpose: Vulkan error checking with abort on failure
26// - When to use vs CheckVulkanRes
27// - Example usage
28#define VK_CHECK(x) \
29 do { \
30 VkResult res = (x); \
31 if (res != VK_SUCCESS) \
32 engine_abort("Vulkan error: " #x); \
33 } while (0)
34
35// TODO(docs): Document SingleTimeCommand function
36// - Purpose: Execute Vulkan commands with automatic synchronization
37// - Error handling (returns std::expected)
38// - Performance considerations
39// - Example usage
40inline std::expected<void, std::string>
41single_time_command(const vk::Device& device, const vk::Queue& queue,
42 const vk::CommandPool& command_pool,
43 const std::function<void(const vk::CommandBuffer&)>& action) {
44 vk::CommandBufferAllocateInfo alloc_info(command_pool, vk::CommandBufferLevel::ePrimary, 1);
45
46 auto alloc_res = device.allocateCommandBuffers(alloc_info);
47 if (alloc_res.result != vk::Result::eSuccess) {
48 return std::unexpected("Failed to allocate command buffers: " +
49 vk::to_string(alloc_res.result));
50 }
51 vk::CommandBuffer command_buffers = alloc_res.value.front();
52
53 vk::CommandBufferBeginInfo begin_info(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
54 if (auto res = command_buffers.begin(begin_info); res != vk::Result::eSuccess) {
55 device.freeCommandBuffers(command_pool, command_buffers);
56 return std::unexpected("Failed to begin cmdBuffer: " + vk::to_string(res));
57 }
58
60
61 if (auto res = command_buffers.end(); res != vk::Result::eSuccess) {
62 device.freeCommandBuffers(command_pool, command_buffers);
63 return std::unexpected("Failed to end cmdbuffer: " + vk::to_string(res));
64 }
65
66 auto fence_res = device.createFenceUnique({});
67 if (fence_res.result != vk::Result::eSuccess) {
68 device.freeCommandBuffers(command_pool, command_buffers);
69 return std::unexpected("Failed to create fence: " + vk::to_string(fence_res.result));
70 }
71 vk::UniqueFence fence = std::move(fence_res.value);
72
73 vk::SubmitInfo submit_info;
74 submit_info.setCommandBuffers(command_buffers);
75 if (auto res = queue.submit(submit_info, *fence); res != vk::Result::eSuccess) {
76 device.freeCommandBuffers(command_pool, command_buffers);
77 return std::unexpected("Failed to submit to queue: " + vk::to_string(res));
78 }
79
80 if (auto res = device.waitForFences(*fence, VK_TRUE, std::numeric_limits<uint64_t>::max());
81 res != vk::Result::eSuccess) {
82 device.freeCommandBuffers(command_pool, command_buffers);
83 return std::unexpected("Failed to wait for fence: " + vk::to_string(res));
84 }
85
86 device.freeCommandBuffers(command_pool, command_buffers);
87 return {};
88}
89
90
91// TODO(docs): Document CreateBuffer function
92// - Purpose: Create a Vulkan buffer with memory allocation
93// - Memory property flags explanation
94// - Note about VMA TODO - this will be deprecated
95// - Example usage
96inline std::pair<vk::UniqueBuffer, vk::UniqueDeviceMemory>
97create_buffer(const vk::Device& device, const vk::PhysicalDevice& physical_device,
98 vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties) {
99 // TODO: Use VMA (Vulkan Memory Allocator) instead of manual memory allocation
100 // TODO: Create a Buffer abstraction class to handle creation, mapping, and destruction
101 vk::BufferCreateInfo buffer_info({}, size, usage, vk::SharingMode::eExclusive);
102 vk::UniqueBuffer buffer = check_vulkan_res_val(device.createBufferUnique(buffer_info),
103 "Failed to create unique buffer: ");
104
105 vk::MemoryRequirements mem_requirements = device.getBufferMemoryRequirements(*buffer);
106
107 vk::MemoryAllocateInfo allocate_info(
108 mem_requirements.size,
110 vk::UniqueDeviceMemory buffer_memory = check_vulkan_res_val(
111 device.allocateMemoryUnique(allocate_info), "Failed to allocate unique memory for buffer: ");
112
113 check_vulkan_res(device.bindBufferMemory(*buffer, *buffer_memory, 0),
114 "Failed to bind buffer memory");
115
116 return {std::move(buffer), std::move(buffer_memory)};
117}
118
119// TODO(docs): Document CreateImage function
120// - Purpose: Create a Vulkan image with memory allocation
121// - Format, tiling, usage parameter guidance
122// - Note about VMA TODO
123inline std::pair<vk::UniqueImage, vk::UniqueDeviceMemory>
124create_image(const vk::Device& device, const vk::PhysicalDevice& physical_device, uint32_t width,
125 uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage,
126 vk::MemoryPropertyFlags properties) {
127 // TODO: Use VMA (Vulkan Memory Allocator) instead of manual memory allocation
128 // TODO: Create an Image abstraction class to handle creation, views, and memory
129 vk::ImageCreateInfo image_info({}, vk::ImageType::e2D, format, vk::Extent3D(width, height, 1), 1,
130 1, vk::SampleCountFlagBits::e1, tiling, usage,
131 vk::SharingMode::eExclusive);
132
133 vk::UniqueImage image =
134 check_vulkan_res_val(device.createImageUnique(image_info), "Failed to create unique image: ");
135
136 vk::MemoryRequirements mem_requirements = device.getImageMemoryRequirements(*image);
137 vk::MemoryAllocateInfo allocate_info(
138 mem_requirements.size,
140 vk::UniqueDeviceMemory image_memory = check_vulkan_res_val(
141 device.allocateMemoryUnique(allocate_info), "Failed to allocate unique memory:");
142
143 check_vulkan_res(device.bindImageMemory(*image, *image_memory, 0),
144 "Failed to bind image memory: ");
145
146 return {std::move(image), std::move(image_memory)};
147}
148
149// TODO(docs): Document CopyBufferToImage function
150// - Purpose: Copy buffer data to an image via command buffer
151// - Requires image to be in eTransferDstOptimal layout
152inline void copy_buffer_to_image(const vk::CommandBuffer& cmd_buffer, const vk::Buffer& buffer,
153 const vk::Image& image, uint32_t width, uint32_t height) {
154 vk::BufferImageCopy region(0, 0, 0,
155 vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1),
156 vk::Offset3D(0, 0, 0), vk::Extent3D(width, height, 1));
157 cmd_buffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, region);
158}
159
160// TODO(docs): Document TransitionImageLayout function
161// - Purpose: Transition image layout with pipeline barrier
162// - Supported transitions (Undefined->TransferDst, TransferDst->ShaderReadOnly)
163// - Pipeline stage and access mask logic
164// - Note about ImageMemoryBarrier2 TODO
165inline void transition_image_layout(const vk::CommandBuffer& cmd_buffer, const vk::Image& image,
166 [[maybe_unused]] vk::Format format, vk::ImageLayout old_layout,
167 vk::ImageLayout new_layout) {
168 // TODO: Use vk::ImageMemoryBarrier2 for better synchronization (requires Vulkan 1.3 or extension)
169 vk::ImageMemoryBarrier barrier;
170 barrier.oldLayout = old_layout;
171 barrier.newLayout = new_layout;
172 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
173 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
174 barrier.image = image;
175 barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
176 barrier.subresourceRange.baseMipLevel = 0;
177 barrier.subresourceRange.levelCount = 1;
178 barrier.subresourceRange.baseArrayLayer = 0;
179 barrier.subresourceRange.layerCount = 1;
180
181 vk::PipelineStageFlags source_stage;
182 vk::PipelineStageFlags destination_stage;
183
184 if (old_layout == vk::ImageLayout::eUndefined &&
185 new_layout == vk::ImageLayout::eTransferDstOptimal) {
186 barrier.srcAccessMask = {};
187 barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
188
189 source_stage = vk::PipelineStageFlagBits::eTopOfPipe;
190 destination_stage = vk::PipelineStageFlagBits::eTransfer;
191 } else if (old_layout == vk::ImageLayout::eTransferDstOptimal &&
192 new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) {
193 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
194 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
195
196 source_stage = vk::PipelineStageFlagBits::eTransfer;
197 destination_stage = vk::PipelineStageFlagBits::eFragmentShader;
198 } else {
199 engine_abort("unsupported layout transition!");
200 }
201
202 cmd_buffer.pipelineBarrier(source_stage, destination_stage, {}, nullptr, nullptr, barrier);
203}
204
205
206// TODO(docs): Document Texture struct
207// - Purpose: Simple texture container (image + memory + view)
208// - Note about Texture class TODO - this will be replaced
209// - Ownership semantics
210struct Texture {
211 vk::UniqueImage image;
212 vk::UniqueDeviceMemory image_memory;
213 vk::UniqueImageView image_view;
214};
215
216// TODO(docs): Document CreateTexture function
217// - Purpose: Load a texture from file (STB image + Vulkan upload)
218// - Format (R8G8B8A8Srgb) and why
219// - Error handling (file not found, Vulkan errors)
220// - Performance notes (staging buffer)
221// - Example usage
222inline std::expected<Texture, std::string> create_texture(const vk::Device& device,
223 const vk::PhysicalDevice& physical_device,
224 const vk::Queue& graphics_queue,
225 const vk::CommandPool& command_pool,
226 const std::filesystem::path& file_path) {
228 stbi_uc* pixels =
230 const vk::DeviceSize image_size = tex_width * tex_height * 4;
231
232 if (!pixels) {
233 return std::unexpected("Failed to load texture image: " + file_path.string());
234 }
235
237 device, physical_device, image_size, vk::BufferUsageFlagBits::eTransferSrc,
238 vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
239
240 void* data = check_vulkan_res_val(device.mapMemory(*staging_buffer_memory, 0, image_size),
241 "Failed to map texture image: ");
242
243 memcpy(data, pixels, image_size);
244 device.unmapMemory(*staging_buffer_memory);
245
247
248 auto [image, image_memory] =
249 create_image(device, physical_device, tex_width, tex_height, vk::Format::eR8G8B8A8Srgb,
250 vk::ImageTiling::eOptimal,
251 vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
252 vk::MemoryPropertyFlagBits::eDeviceLocal);
253
255 device, graphics_queue, command_pool, [&](const vk::CommandBuffer& cmdBuffer) {
256 transition_image_layout(cmdBuffer, *image, vk::Format::eR8G8B8A8Srgb,
257 vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal);
259 static_cast<uint32_t>(tex_height));
260 transition_image_layout(cmdBuffer, *image, vk::Format::eR8G8B8A8Srgb,
261 vk::ImageLayout::eTransferDstOptimal,
262 vk::ImageLayout::eShaderReadOnlyOptimal);
263 });
264
265 if (!cmd_res) {
266 return std::unexpected(cmd_res.error());
267 }
268
269 vk::ImageViewCreateInfo view_info({}, *image, vk::ImageViewType::e2D, vk::Format::eR8G8B8A8Srgb,
270 {}, {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
271 vk::UniqueImageView image_view = check_vulkan_res_val(device.createImageViewUnique(view_info),
272 "Failed to create unique image view: ");
273
274 return Texture{std::move(image), std::move(image_memory), std::move(image_view)};
275}
276
283inline vk::UniqueShaderModule create_shader_module(const vk::Device& device,
284 const std::vector<char>& code) {
285 ASSERT_ALWAYS(code.size() % 4 == 0 && "SPIR-V code size must be a multiple of 4");
286 vk::ShaderModuleCreateInfo create_info({}, code.size(),
287 reinterpret_cast<const uint32_t*>(code.data()));
288 return check_vulkan_res_val(device.createShaderModuleUnique(create_info),
289 "Failed to create unique shaderModule: ");
290}
291} // namespace sd
#define ASSERT_ALWAYS(x)
Definition base.hpp:14
Definition Application.hpp:28
void transition_image_layout(const vk::CommandBuffer &cmd_buffer, const vk::Image &image, vk::Format format, vk::ImageLayout old_layout, vk::ImageLayout new_layout)
Definition utils.hpp:165
auto check_vulkan_res_val(T &&result, std::string_view message, std::source_location loc=std::source_location::current())
Definition vulkan_utils.hpp:20
void engine_abort()
Definition base.hpp:46
vk::UniqueShaderModule create_shader_module(const vk::Device &device, const std::vector< char > &code)
Definition utils.hpp:283
u32 find_memory_type(const vk::PhysicalDevice &physical_device, u32 type_filter, vk::MemoryPropertyFlags properties)
Definition vulkan_utils.hpp:37
std::pair< vk::UniqueImage, vk::UniqueDeviceMemory > create_image(const vk::Device &device, const vk::PhysicalDevice &physical_device, uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties)
Definition utils.hpp:124
void check_vulkan_res(vk::Result result, std::string_view message, std::source_location loc=std::source_location::current())
Definition vulkan_utils.hpp:10
void copy_buffer_to_image(const vk::CommandBuffer &cmd_buffer, const vk::Buffer &buffer, const vk::Image &image, uint32_t width, uint32_t height)
Definition utils.hpp:152
std::pair< vk::UniqueBuffer, vk::UniqueDeviceMemory > create_buffer(const vk::Device &device, const vk::PhysicalDevice &physical_device, vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties)
Definition utils.hpp:97
std::expected< void, std::string > single_time_command(const vk::Device &device, const vk::Queue &queue, const vk::CommandPool &command_pool, const std::function< void(const vk::CommandBuffer &)> &action)
Definition utils.hpp:41
std::expected< Texture, std::string > create_texture(const vk::Device &device, const vk::PhysicalDevice &physical_device, const vk::Queue &graphics_queue, const vk::CommandPool &command_pool, const std::filesystem::path &file_path)
Definition utils.hpp:222
Definition utils.hpp:210
vk::UniqueDeviceMemory image_memory
Definition utils.hpp:212
vk::UniqueImageView image_view
Definition utils.hpp:213
vk::UniqueImage image
Definition utils.hpp:211
constexpr T g_type_max
Definition types.hpp:21