aboutsummaryrefslogtreecommitdiff
path: root/src/engine.cpp
diff options
context:
space:
mode:
authorvimene <vincent.menegaux@gmail.com>2025-12-28 17:34:59 +0100
committervimene <vincent.menegaux@gmail.com>2025-12-28 17:34:59 +0100
commitdd187445972989dc44428e8cf185964da9e5c0c4 (patch)
tree17e23c6bdce4dd686bac2d373243bce99849eb29 /src/engine.cpp
parent8874f4f4c6f95c2db7654c769e7747b3e3cf863a (diff)
downloadengine-dd187445972989dc44428e8cf185964da9e5c0c4.tar.gz
added depth buffer
Diffstat (limited to 'src/engine.cpp')
-rw-r--r--src/engine.cpp232
1 files changed, 213 insertions, 19 deletions
diff --git a/src/engine.cpp b/src/engine.cpp
index 8c517c0..0cf3266 100644
--- a/src/engine.cpp
+++ b/src/engine.cpp
@@ -329,6 +329,7 @@ struct PhysicalDeviceEntry {
uint32_t graphics_queue_family_index, present_queue_family_index;
VkPhysicalDeviceProperties2 props;
VkPhysicalDeviceFeatures2 features;
+ VkFormat depth_format;
};
static bool check_validation_layer_support() {
@@ -411,6 +412,41 @@ static std::tuple<std::optional<uint32_t>, std::optional<uint32_t>> find_queue_f
return { graphics_queue_family_index, present_queue_family_index };
}
+static std::optional<VkFormat> find_format(VkPhysicalDevice physical_device, const std::vector<VkFormat>& formats,
+ VkImageTiling tiling, VkFormatFeatureFlags flags) {
+ for (const auto& format : formats) {
+ VkFormatProperties2 format_props {};
+ format_props.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2;
+ format_props.pNext = nullptr;
+ vkGetPhysicalDeviceFormatProperties2(physical_device, format, &format_props);
+ switch (tiling) {
+ case VK_IMAGE_TILING_OPTIMAL:
+ if ((format_props.formatProperties.optimalTilingFeatures & flags) == flags)
+ return format;
+ break;
+ case VK_IMAGE_TILING_LINEAR:
+ if ((format_props.formatProperties.linearTilingFeatures & flags) == flags)
+ return format;
+ break;
+ default:
+ std::cerr << "tiling not supported: " << string_VkImageTiling(tiling) << std::endl;
+ std::exit(EXIT_FAILURE);
+ }
+ }
+ return {};
+}
+
+static bool has_stencil(VkFormat format) {
+ switch (format) {
+ case VK_FORMAT_D16_UNORM_S8_UINT:
+ case VK_FORMAT_D24_UNORM_S8_UINT:
+ case VK_FORMAT_D32_SFLOAT_S8_UINT:
+ return true;
+ default:
+ return false;
+ }
+}
+
static uint32_t find_mem_type(VkPhysicalDevice physical_device, uint32_t type_filter, VkMemoryPropertyFlags flags) {
VkPhysicalDeviceMemoryProperties2 props {};
props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2;
@@ -614,6 +650,43 @@ create_img(VkPhysicalDevice physical_device, VkDevice device, uint32_t width, ui
return std::tuple { texture_img, texture_img_device_mem };
}
+static std::tuple<VkImage, VkDeviceMemory, VkImageView>
+create_depth_resources(VkPhysicalDevice physical_device, VkDevice device, VkExtent2D swapchain_extent, VkFormat depth_format) {
+ auto [depth_img, depth_img_device_mem] = create_img(physical_device, device, swapchain_extent.width, swapchain_extent.height, depth_format,
+ VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+ auto depth_img_view = [&]() {
+ VkImageViewCreateInfo img_view_ci {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = {},
+ .flags = {},
+ .image = depth_img,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = depth_format,
+ .components = {},
+ .subresourceRange = { // VkImageSubresourceRange
+ .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ };
+ VkImageView depth_img_view;
+ if (VkResult res = vkCreateImageView(device, &img_view_ci, nullptr, &depth_img_view); res != VK_SUCCESS) {
+ std::cerr << "failed to create depth image view, error code: " << string_VkResult(res) << std::endl;
+ std::exit(EXIT_FAILURE);
+ }
+ return depth_img_view;
+ }();
+ return std::tuple { depth_img, depth_img_device_mem, depth_img_view };
+}
+
+static void destroy_depth_resources(VkDevice device, VkImage depth_img, VkDeviceMemory depth_img_device_mem, VkImageView depth_img_view) {
+ vkDestroyImageView(device, depth_img_view, nullptr);
+ vkFreeMemory(device, depth_img_device_mem, nullptr);
+ vkDestroyImage(device, depth_img, nullptr);
+}
+
static void transition_image_layout(VkCommandBuffer cmd_buf, VkImage img,
VkImageLayout old_layout, VkImageLayout new_layout,
VkAccessFlags2 src_access_mask, VkAccessFlags2 dst_access_mask,
@@ -672,13 +745,21 @@ static int main_graphical() {
// TODO: verify alignment requirements
// TODO: maybe move these definitions?
const std::array vertices {
- engine::vk::Vertex { { -.5f, -.5f }, { 1.f, 0.f, 0.f }, { 1.f, 0.f } },
- engine::vk::Vertex { { .5f, -.5f }, { 0.f, 1.f, 0.f }, { 0.f, 0.f } },
- engine::vk::Vertex { { .5f, .5f }, { 0.f, 0.f, 1.f }, { 0.f, 1.f } },
- engine::vk::Vertex { { -.5f, .5f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f } },
+ engine::vk::Vertex { { -.5f, -.5f, 0.f }, { 1.f, 0.f, 0.f }, { 1.f, 0.f } },
+ engine::vk::Vertex { { .5f, -.5f, 0.f }, { 0.f, 1.f, 0.f }, { 0.f, 0.f } },
+ engine::vk::Vertex { { .5f, .5f, 0.f }, { 0.f, 0.f, 1.f }, { 0.f, 1.f } },
+ engine::vk::Vertex { { -.5f, .5f, 0.f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f } },
+
+ engine::vk::Vertex { { -.5f, -.5f, -.5f }, { 1.f, 0.f, 0.f }, { 1.f, 0.f } },
+ engine::vk::Vertex { { .5f, -.5f, -.5f }, { 0.f, 1.f, 0.f }, { 0.f, 0.f } },
+ engine::vk::Vertex { { .5f, .5f, -.5f }, { 0.f, 0.f, 1.f }, { 0.f, 1.f } },
+ engine::vk::Vertex { { -.5f, .5f, -.5f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f } },
};
- const std::array<uint16_t, 6> indices { 0, 1, 2, 2, 3, 0 };
+ const std::array<uint16_t, 12> indices {
+ 0, 1, 2, 2, 3, 0,
+ 4, 5, 6, 6, 7, 4,
+ };
// init Vulkan
std::cout << "Vulkan loader version: " << engine::vk::api { VK_HEADER_VERSION_COMPLETE } << std::endl;
@@ -785,7 +866,7 @@ static int main_graphical() {
}();
// select physical device and queues
- auto [physical_device, graphics_queue_family_index, present_queue_family_index, physical_device_props, physical_device_features] = [&]() {
+ auto [physical_device, graphics_queue_family_index, present_queue_family_index, physical_device_props, physical_device_features, depth_format] = [&]() {
std::multimap<unsigned, PhysicalDeviceEntry> physical_devices;
auto avail_physical_devices = [&]() {
@@ -877,6 +958,16 @@ static int main_graphical() {
std::cout << "none";
std::cout << std::endl;
+ auto depth_format = find_format(avail_physical_device,
+ { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT },
+ VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
+ std::cout << " depth format: ";
+ if (depth_format)
+ std::cout << string_VkFormat(*depth_format);
+ else
+ std::cout << "none";
+ std::cout << std::endl;
+
auto score = [&]() {
if (VK_API_VERSION_VARIANT(physical_device_props.properties.apiVersion) != 0
|| physical_device_props.properties.apiVersion < VK_API_VERSION_1_4)
@@ -891,6 +982,8 @@ static int main_graphical() {
return std::optional<unsigned> {};
if (!physical_device_features.features.samplerAnisotropy)
return std::optional<unsigned> {};
+ if (!depth_format)
+ return std::optional<unsigned> {};
// TODO: lots of checks are probably missing, like checking if the swapchain is
// supported (I'm not sure if it's needed)
@@ -931,6 +1024,7 @@ static int main_graphical() {
.present_queue_family_index = *present_queue_family_index,
.props = physical_device_props,
.features = physical_device_features,
+ .depth_format = *depth_format,
}
});
}
@@ -947,7 +1041,7 @@ static int main_graphical() {
std::cout << "picking: " << (best->second.idx + 1) << ". " << best->second.props.properties.deviceName << " (score: " << best->first << ")" << std::endl;
return std::tuple { best->second.physical_device, best->second.graphics_queue_family_index,
- best->second.present_queue_family_index, best->second.props, best->second.features };
+ best->second.present_queue_family_index, best->second.props, best->second.features, best->second.depth_format };
}();
auto device = [&]() {
@@ -1052,11 +1146,18 @@ static int main_graphical() {
// problem (but I'm not sure)
// TODO: pass the old swapchain to VkSwapchainCreateInfoKHR so the new swapchain can be created
// while the old one is in-flight
+ // TODO: with the same intention of recreating the swapchain in-flight, we should also do
+ // something about the depth image. If we recreate the swapchain in-flight without worrying
+ // about the depth image, we might destroy it while it's being used. But I'm not sure, we have
+ // to test it
VkExtent2D swapchain_extent;
VkSurfaceFormatKHR surface_format;
VkSwapchainKHR swapchain = nullptr;
std::vector<VkImage> swapchain_imgs;
std::vector<VkImageView> swapchain_img_views;
+ VkImage depth_img;
+ VkDeviceMemory depth_img_device_mem;
+ VkImageView depth_img_view;
const auto destroy_swapchain = [&]() {
for (auto it_img_view = swapchain_img_views.rbegin(); it_img_view != swapchain_img_views.rend(); ++it_img_view)
@@ -1065,11 +1166,13 @@ static int main_graphical() {
};
const auto recreate_swapchain = [&]() {
- if (swapchain != nullptr) {
+ bool is_first_swapchain = (swapchain == nullptr);
+ if (!is_first_swapchain) {
if (VkResult res = vkDeviceWaitIdle(device); res != VK_SUCCESS) {
std::cerr << "failed to wait idle for device, error code: " << string_VkResult(res) << std::endl;
std::exit(EXIT_FAILURE);
}
+ destroy_depth_resources(device, depth_img, depth_img_device_mem, depth_img_view);
destroy_swapchain();
}
@@ -1234,6 +1337,9 @@ static int main_graphical() {
}
}
}();
+
+ if (!is_first_swapchain)
+ std::tie(depth_img, depth_img_device_mem, depth_img_view) = create_depth_resources(physical_device, device, swapchain_extent, depth_format);
};
recreate_swapchain();
@@ -1397,6 +1503,21 @@ static int main_graphical() {
.alphaToOneEnable = VK_FALSE,
};
+ VkPipelineDepthStencilStateCreateInfo pl_depth_stencil_state_ci {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = {},
+ .depthTestEnable = VK_TRUE,
+ .depthWriteEnable = VK_TRUE,
+ .depthCompareOp = VK_COMPARE_OP_LESS,
+ .depthBoundsTestEnable = VK_FALSE,
+ .stencilTestEnable = VK_FALSE,
+ .front = {},
+ .back = {},
+ .minDepthBounds = {},
+ .maxDepthBounds = {},
+ };
+
VkPipelineColorBlendAttachmentState pl_col_blend_attachment_state {
.blendEnable = VK_FALSE,
.srcColorBlendFactor = {},
@@ -1448,7 +1569,7 @@ static int main_graphical() {
.viewMask = {},
.colorAttachmentCount = 1,
.pColorAttachmentFormats = &surface_format.format,
- .depthAttachmentFormat = {},
+ .depthAttachmentFormat = depth_format,
.stencilAttachmentFormat = {},
};
@@ -1464,7 +1585,7 @@ static int main_graphical() {
.pViewportState = &pl_viewport_state_ci,
.pRasterizationState = &pl_raster_state_ci,
.pMultisampleState = &pl_ms_state_ci,
- .pDepthStencilState = {},
+ .pDepthStencilState = &pl_depth_stencil_state_ci,
.pColorBlendState = &pl_col_blend_state_ci,
.pDynamicState = &pl_dyn_state_ci,
.layout = pl_layout,
@@ -1487,6 +1608,7 @@ static int main_graphical() {
return std::tuple { pl_layout, graphics_pl };
}();
+ // create command pool
auto cmd_pool = [&]() {
VkCommandPoolCreateInfo cmd_pool_ci {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
@@ -1502,6 +1624,11 @@ static int main_graphical() {
return cmd_pool;
}();
+ // create depth resources
+ // TODO: check why we only need one image. From the looks of it, we need max_frames_in_flight
+ // depth images, because there is at most max_frames_in_flight rendering at the same time
+ std::tie(depth_img, depth_img_device_mem, depth_img_view) = create_depth_resources(physical_device, device, swapchain_extent, depth_format);
+
// create texture image
auto [texture_img, texture_img_device_mem] = [&]() {
int w, h, channels;
@@ -1984,14 +2111,68 @@ static int main_graphical() {
}
}
- transition_image_layout(cmd_bufs[frame_idx], swapchain_imgs[img_idx],
- VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
- {}, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
- VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT);
+ // transition layouts
+ [&]() {
+ std::array img_mem_barriers {
+ VkImageMemoryBarrier2 {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
+ .pNext = nullptr,
+ .srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
+ .srcAccessMask = {},
+ .dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
+ .dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = swapchain_imgs[img_idx],
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ },
+ VkImageMemoryBarrier2 {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
+ .pNext = nullptr,
+ .srcStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT,
+ .srcAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
+ .dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT,
+ .dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = (has_stencil(depth_format) ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL),
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = depth_img,
+ .subresourceRange = {
+ .aspectMask = static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_DEPTH_BIT | (has_stencil(depth_format) ? VK_IMAGE_ASPECT_STENCIL_BIT : 0)),
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ },
+ };
+ VkDependencyInfo dependency_info {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .pNext = nullptr,
+ .dependencyFlags = {},
+ .memoryBarrierCount = 0,
+ .pMemoryBarriers = {},
+ .bufferMemoryBarrierCount = 0,
+ .pBufferMemoryBarriers = {},
+ .imageMemoryBarrierCount = img_mem_barriers.size(),
+ .pImageMemoryBarriers = img_mem_barriers.data(),
+ };
+ vkCmdPipelineBarrier2(cmd_bufs[frame_idx], &dependency_info);
+ }();
{
- VkClearValue clear_val { .color = { .float32 = { 0.f, 0.f, 0.f, 1.f } }, };
- VkRenderingAttachmentInfo attachment_info {
+ VkClearValue clear_color_val { .color { .float32 = { 0.f, 0.f, 0.f, 1.f } } };
+ VkClearValue clear_depth_val { .depthStencil { .depth = 1.f, .stencil = 0 } };
+ VkRenderingAttachmentInfo rendering_color_info {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.pNext = nullptr,
.imageView = swapchain_img_views[img_idx],
@@ -2001,7 +2182,19 @@ static int main_graphical() {
.resolveImageLayout = {},
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
- .clearValue = clear_val,
+ .clearValue = clear_color_val,
+ };
+ VkRenderingAttachmentInfo rendering_depth_info {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
+ .pNext = nullptr,
+ .imageView = depth_img_view,
+ .imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
+ .resolveMode = {},
+ .resolveImageView = {},
+ .resolveImageLayout = {},
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ .clearValue = clear_depth_val,
};
VkRenderingInfo render_info {
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
@@ -2011,8 +2204,8 @@ static int main_graphical() {
.layerCount = 1,
.viewMask = {},
.colorAttachmentCount = 1,
- .pColorAttachments = &attachment_info,
- .pDepthAttachment = {},
+ .pColorAttachments = &rendering_color_info,
+ .pDepthAttachment = &rendering_depth_info,
.pStencilAttachment = {},
};
vkCmdBeginRendering(cmd_bufs[frame_idx], &render_info);
@@ -2136,6 +2329,7 @@ static int main_graphical() {
vkDestroyImageView(device, texture_img_view, nullptr);
vkFreeMemory(device, texture_img_device_mem, nullptr);
vkDestroyImage(device, texture_img, nullptr);
+ destroy_depth_resources(device, depth_img, depth_img_device_mem, depth_img_view);
// no need to free cmd_buf because destroying the command pool will
vkDestroyCommandPool(device, cmd_pool, nullptr);
// no need to free descriptor_sets because destroying the descriptor pool will