diff --git a/cube/cube.c b/cube/cube.c index 9e58ea36c..23f282250 100644 --- a/cube/cube.c +++ b/cube/cube.c @@ -75,6 +75,8 @@ // Allow a maximum of two outstanding presentation operations. #define FRAME_LAG 2 +// Need to know how big of a buffer of swapchain images, image views, and semaphores are needed +#define MAX_SWAPCHAIN_IMAGE_COUNT 8 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) @@ -388,15 +390,22 @@ const char *wsi_to_string(WSI_PLATFORM wsi_platform) { }; typedef struct { - VkImage image; + VkFence fence; + VkSemaphore image_acquired_semaphore; VkCommandBuffer cmd; VkCommandBuffer graphics_to_present_cmd; - VkImageView view; VkBuffer uniform_buffer; VkDeviceMemory uniform_memory; void *uniform_memory_ptr; - VkFramebuffer framebuffer; VkDescriptorSet descriptor_set; +} SubmissionResources; + +typedef struct { + VkImage image; + VkImageView view; + VkFramebuffer framebuffer; + VkSemaphore draw_complete_semaphore; + VkSemaphore image_ownership_semaphore; } SwapchainImageResources; struct demo { @@ -455,10 +464,11 @@ struct demo { #endif WSI_PLATFORM wsi_platform; VkSurfaceKHR surface; - bool prepared; + bool initialized; + bool swapchain_ready; + bool is_minimized; bool use_staging_buffer; bool separate_present_queue; - bool is_minimized; bool invalid_gpu_selection; int32_t gpu_number; @@ -481,12 +491,11 @@ struct demo { VkQueue present_queue; uint32_t graphics_queue_family_index; uint32_t present_queue_family_index; - VkSemaphore image_acquired_semaphores[FRAME_LAG]; - VkSemaphore draw_complete_semaphores[FRAME_LAG]; - VkSemaphore image_ownership_semaphores[FRAME_LAG]; VkPhysicalDeviceProperties gpu_props; VkQueueFamilyProperties *queue_props; VkPhysicalDeviceMemoryProperties memory_properties; + SubmissionResources submission_resources[FRAME_LAG]; + uint32_t current_submission_index; uint32_t enabled_extension_count; uint32_t enabled_layer_count; @@ -499,10 +508,8 @@ struct demo { uint32_t swapchainImageCount; VkSwapchainKHR swapchain; - SwapchainImageResources *swapchain_image_resources; + SwapchainImageResources swapchain_resources[MAX_SWAPCHAIN_IMAGE_COUNT]; VkPresentModeKHR presentMode; - VkFence fences[FRAME_LAG]; - int frame_index; bool first_swapchain_frame; VkCommandPool cmd_pool; @@ -550,7 +557,6 @@ struct demo { VkDebugUtilsMessengerEXT dbg_messenger; - uint32_t current_buffer; uint32_t queue_family_count; }; @@ -706,6 +712,7 @@ bool CanPresentEarlier(uint64_t earliest, uint64_t actual, uint64_t margin, uint // Forward declarations: static void demo_resize(struct demo *demo); + static void demo_create_surface(struct demo *demo); #if defined(__GNUC__) || defined(__clang__) @@ -879,7 +886,8 @@ static void demo_set_image_layout(struct demo *demo, VkImage image, VkImageAspec vkCmdPipelineBarrier(demo->cmd, src_stages, dest_stages, 0, 0, NULL, 0, NULL, 1, pmemory_barrier); } -static void demo_draw_build_cmd(struct demo *demo, VkCommandBuffer cmd_buf) { +static void demo_draw_build_cmd(struct demo *demo, SubmissionResources *submission_resource, + SwapchainImageResources *swapchain_resource) { const VkCommandBufferBeginInfo cmd_buf_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .pNext = NULL, @@ -894,7 +902,7 @@ static void demo_draw_build_cmd(struct demo *demo, VkCommandBuffer cmd_buf) { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .pNext = NULL, .renderPass = demo->render_pass, - .framebuffer = demo->swapchain_image_resources[demo->current_buffer].framebuffer, + .framebuffer = swapchain_resource->framebuffer, .renderArea.offset.x = 0, .renderArea.offset.y = 0, .renderArea.extent.width = demo->width, @@ -904,22 +912,23 @@ static void demo_draw_build_cmd(struct demo *demo, VkCommandBuffer cmd_buf) { }; VkResult U_ASSERT_ONLY err; - err = vkBeginCommandBuffer(cmd_buf, &cmd_buf_info); + err = vkResetCommandBuffer(submission_resource->cmd, 0 /* VK_COMMAND_BUFFER_RESET_FLAGS */); + err = vkBeginCommandBuffer(submission_resource->cmd, &cmd_buf_info); - demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)cmd_buf, "CubeDrawCommandBuf"); + demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)submission_resource->cmd, "CubeDrawCommandBuf"); const float begin_color[4] = {0.4f, 0.3f, 0.2f, 0.1f}; - demo_push_cb_label(demo, cmd_buf, begin_color, "DrawBegin"); + demo_push_cb_label(demo, submission_resource->cmd, begin_color, "DrawBegin"); assert(!err); - vkCmdBeginRenderPass(cmd_buf, &rp_begin, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(submission_resource->cmd, &rp_begin, VK_SUBPASS_CONTENTS_INLINE); const float renderpass_color[4] = {8.4f, 7.3f, 6.2f, 7.1f}; - demo_push_cb_label(demo, cmd_buf, renderpass_color, "InsideRenderPass"); + demo_push_cb_label(demo, submission_resource->cmd, renderpass_color, "InsideRenderPass"); - vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline); - vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline_layout, 0, 1, - &demo->swapchain_image_resources[demo->current_buffer].descriptor_set, 0, NULL); + vkCmdBindPipeline(submission_resource->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline); + vkCmdBindDescriptorSets(submission_resource->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline_layout, 0, 1, + &submission_resource->descriptor_set, 0, NULL); VkViewport viewport; memset(&viewport, 0, sizeof(viewport)); float viewport_dimension; @@ -934,7 +943,7 @@ static void demo_draw_build_cmd(struct demo *demo, VkCommandBuffer cmd_buf) { viewport.width = viewport_dimension; viewport.minDepth = (float)0.0f; viewport.maxDepth = (float)1.0f; - vkCmdSetViewport(cmd_buf, 0, 1, &viewport); + vkCmdSetViewport(submission_resource->cmd, 0, 1, &viewport); VkRect2D scissor; memset(&scissor, 0, sizeof(scissor)); @@ -942,17 +951,17 @@ static void demo_draw_build_cmd(struct demo *demo, VkCommandBuffer cmd_buf) { scissor.extent.height = demo->height; scissor.offset.x = 0; scissor.offset.y = 0; - vkCmdSetScissor(cmd_buf, 0, 1, &scissor); + vkCmdSetScissor(submission_resource->cmd, 0, 1, &scissor); const float draw_color[4] = {-0.4f, -0.3f, -0.2f, -0.1f}; - demo_push_cb_label(demo, cmd_buf, draw_color, "ActualDraw"); - vkCmdDraw(cmd_buf, 12 * 3, 1, 0, 0); - demo_pop_cb_label(demo, cmd_buf); + demo_push_cb_label(demo, submission_resource->cmd, draw_color, "ActualDraw"); + vkCmdDraw(submission_resource->cmd, 12 * 3, 1, 0, 0); + demo_pop_cb_label(demo, submission_resource->cmd); // Note that ending the renderpass changes the image's layout from // COLOR_ATTACHMENT_OPTIMAL to PRESENT_SRC_KHR - vkCmdEndRenderPass(cmd_buf); - demo_pop_cb_label(demo, cmd_buf); + vkCmdEndRenderPass(submission_resource->cmd); + demo_pop_cb_label(demo, submission_resource->cmd); if (demo->separate_present_queue) { // We have to transfer ownership from the graphics queue family to the @@ -968,27 +977,31 @@ static void demo_draw_build_cmd(struct demo *demo, VkCommandBuffer cmd_buf) { .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, .srcQueueFamilyIndex = demo->graphics_queue_family_index, .dstQueueFamilyIndex = demo->present_queue_family_index, - .image = demo->swapchain_image_resources[demo->current_buffer].image, + .image = swapchain_resource->image, .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; - vkCmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, - NULL, 1, &image_ownership_barrier); + vkCmdPipelineBarrier(submission_resource->cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, 0, NULL, 0, NULL, 1, &image_ownership_barrier); } - demo_pop_cb_label(demo, cmd_buf); - err = vkEndCommandBuffer(cmd_buf); + demo_pop_cb_label(demo, submission_resource->cmd); + err = vkEndCommandBuffer(submission_resource->cmd); assert(!err); } -void demo_build_image_ownership_cmd(struct demo *demo, int i) { +void demo_build_image_ownership_cmd(struct demo *demo, SubmissionResources *submission_resource, + SwapchainImageResources *swapchain_resource) { VkResult U_ASSERT_ONLY err; + err = vkResetCommandBuffer(submission_resource->graphics_to_present_cmd, 0 /* VK_COMMAND_BUFFER_RESET_FLAGS */); + assert(!err); + const VkCommandBufferBeginInfo cmd_buf_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .pNext = NULL, .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, .pInheritanceInfo = NULL, }; - err = vkBeginCommandBuffer(demo->swapchain_image_resources[i].graphics_to_present_cmd, &cmd_buf_info); + err = vkBeginCommandBuffer(submission_resource->graphics_to_present_cmd, &cmd_buf_info); assert(!err); VkImageMemoryBarrier image_ownership_barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, @@ -999,16 +1012,16 @@ void demo_build_image_ownership_cmd(struct demo *demo, int i) { .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, .srcQueueFamilyIndex = demo->graphics_queue_family_index, .dstQueueFamilyIndex = demo->present_queue_family_index, - .image = demo->swapchain_image_resources[i].image, + .image = swapchain_resource->image, .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; - vkCmdPipelineBarrier(demo->swapchain_image_resources[i].graphics_to_present_cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + vkCmdPipelineBarrier(submission_resource->graphics_to_present_cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &image_ownership_barrier); - err = vkEndCommandBuffer(demo->swapchain_image_resources[i].graphics_to_present_cmd); + err = vkEndCommandBuffer(submission_resource->graphics_to_present_cmd); assert(!err); } -void demo_update_data_buffer(struct demo *demo) { +void demo_update_data_buffer(struct demo *demo, void *uniform_memory_ptr) { mat4x4 MVP, Model, VP; int matrixSize = sizeof(MVP); @@ -1020,7 +1033,7 @@ void demo_update_data_buffer(struct demo *demo) { mat4x4_orthonormalize(demo->model_matrix, demo->model_matrix); mat4x4_mul(MVP, VP, demo->model_matrix); - memcpy(demo->swapchain_image_resources[demo->current_buffer].uniform_memory_ptr, (const void *)&MVP[0][0], matrixSize); + memcpy(uniform_memory_ptr, (const void *)&MVP[0][0], matrixSize); } void DemoUpdateTargetIPD(struct demo *demo) { @@ -1140,16 +1153,23 @@ void DemoUpdateTargetIPD(struct demo *demo) { } static void demo_draw(struct demo *demo) { + // Don't draw if initialization isn't complete, if the swapchain became outdated, or if the window is minimized + if (!demo->initialized || !demo->swapchain_ready || demo->is_minimized) { + return; + } + VkResult U_ASSERT_ONLY err; + SubmissionResources current_submission = demo->submission_resources[demo->current_submission_index]; + // Ensure no more than FRAME_LAG renderings are outstanding - vkWaitForFences(demo->device, 1, &demo->fences[demo->frame_index], VK_TRUE, UINT64_MAX); - vkResetFences(demo->device, 1, &demo->fences[demo->frame_index]); + vkWaitForFences(demo->device, 1, ¤t_submission.fence, VK_TRUE, UINT64_MAX); + uint32_t current_swapchain_image_index; do { // Get the index of the next available swapchain image: - err = vkAcquireNextImageKHR(demo->device, demo->swapchain, UINT64_MAX, demo->image_acquired_semaphores[demo->frame_index], - VK_NULL_HANDLE, &demo->current_buffer); + err = vkAcquireNextImageKHR(demo->device, demo->swapchain, UINT64_MAX, current_submission.image_acquired_semaphore, + VK_NULL_HANDLE, ¤t_swapchain_image_index); if (err == VK_ERROR_OUT_OF_DATE_KHR) { // demo->swapchain is out of date (e.g. the window was resized) and @@ -1166,9 +1186,16 @@ static void demo_draw(struct demo *demo) { } else { assert(!err); } + + // Stop drawing if we resized but didn't successfully create a new swapchain. + if (!demo->swapchain_ready) { + return; + } } while (err != VK_SUCCESS); - demo_update_data_buffer(demo); + SwapchainImageResources current_swapchain_resource = demo->swapchain_resources[current_swapchain_image_index]; + + demo_update_data_buffer(demo, current_submission.uniform_memory_ptr); if (demo->VK_GOOGLE_display_timing_enabled) { // Look at what happened to previous presents, and make appropriate @@ -1182,6 +1209,15 @@ static void demo_draw(struct demo *demo) { // simple that it doesn't do either of those. } + demo_draw_build_cmd(demo, ¤t_submission, ¤t_swapchain_resource); + + if (demo->separate_present_queue) { + demo_build_image_ownership_cmd(demo, ¤t_submission, ¤t_swapchain_resource); + } + + // Only reset right before submitting so we can't deadlock on an un-signalled fence that has nothing submitted to it + vkResetFences(demo->device, 1, ¤t_submission.fence); + // Wait for the image acquired semaphore to be signaled to ensure // that the image won't be rendered to until the presentation // engine has fully released ownership to the application, and it is @@ -1193,12 +1229,12 @@ static void demo_draw(struct demo *demo) { pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; submit_info.pWaitDstStageMask = &pipe_stage_flags; submit_info.waitSemaphoreCount = 1; - submit_info.pWaitSemaphores = &demo->image_acquired_semaphores[demo->frame_index]; + submit_info.pWaitSemaphores = ¤t_submission.image_acquired_semaphore; submit_info.commandBufferCount = 1; - submit_info.pCommandBuffers = &demo->swapchain_image_resources[demo->current_buffer].cmd; + submit_info.pCommandBuffers = ¤t_submission.cmd; submit_info.signalSemaphoreCount = 1; - submit_info.pSignalSemaphores = &demo->draw_complete_semaphores[demo->frame_index]; - err = vkQueueSubmit(demo->graphics_queue, 1, &submit_info, demo->fences[demo->frame_index]); + submit_info.pSignalSemaphores = ¤t_swapchain_resource.draw_complete_semaphore; + err = vkQueueSubmit(demo->graphics_queue, 1, &submit_info, current_submission.fence); assert(!err); if (demo->separate_present_queue) { @@ -1208,11 +1244,11 @@ static void demo_draw(struct demo *demo) { VkFence nullFence = VK_NULL_HANDLE; pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; submit_info.waitSemaphoreCount = 1; - submit_info.pWaitSemaphores = &demo->draw_complete_semaphores[demo->frame_index]; + submit_info.pWaitSemaphores = ¤t_swapchain_resource.draw_complete_semaphore; submit_info.commandBufferCount = 1; - submit_info.pCommandBuffers = &demo->swapchain_image_resources[demo->current_buffer].graphics_to_present_cmd; + submit_info.pCommandBuffers = ¤t_submission.graphics_to_present_cmd; submit_info.signalSemaphoreCount = 1; - submit_info.pSignalSemaphores = &demo->image_ownership_semaphores[demo->frame_index]; + submit_info.pSignalSemaphores = ¤t_swapchain_resource.image_ownership_semaphore; err = vkQueueSubmit(demo->present_queue, 1, &submit_info, nullFence); assert(!err); } @@ -1223,11 +1259,11 @@ static void demo_draw(struct demo *demo) { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .pNext = NULL, .waitSemaphoreCount = 1, - .pWaitSemaphores = (demo->separate_present_queue) ? &demo->image_ownership_semaphores[demo->frame_index] - : &demo->draw_complete_semaphores[demo->frame_index], + .pWaitSemaphores = (demo->separate_present_queue) ? ¤t_swapchain_resource.image_ownership_semaphore + : ¤t_swapchain_resource.draw_complete_semaphore, .swapchainCount = 1, .pSwapchains = &demo->swapchain, - .pImageIndices = &demo->current_buffer, + .pImageIndices = ¤t_swapchain_image_index, }; VkRectLayerKHR rect; @@ -1302,8 +1338,8 @@ static void demo_draw(struct demo *demo) { } err = vkQueuePresentKHR(demo->present_queue, &present); - demo->frame_index += 1; - demo->frame_index %= FRAME_LAG; + demo->current_submission_index += 1; + demo->current_submission_index %= FRAME_LAG; demo->first_swapchain_frame = false; if (err == VK_ERROR_OUT_OF_DATE_KHR) { @@ -1317,6 +1353,8 @@ static void demo_draw(struct demo *demo) { assert(!err); if (surfCapabilities.currentExtent.width != (uint32_t)demo->width || surfCapabilities.currentExtent.height != (uint32_t)demo->height) { + demo->width = surfCapabilities.currentExtent.width; + demo->height = surfCapabilities.currentExtent.height; demo_resize(demo); } } else if (err == VK_ERROR_SURFACE_LOST_KHR) { @@ -1328,7 +1366,13 @@ static void demo_draw(struct demo *demo) { } } -static void demo_prepare_buffers(struct demo *demo) { +// Forward decls for that demo_prepare_swapchain needs +static void demo_prepare_depth(struct demo *demo); +static void demo_prepare_framebuffers(struct demo *demo); + +// Creates the swapchain, swapchain image views, depth buffer, framebuffers, and sempahores. +// This function returns early if it fails to create a swapchain, setting swapchain_ready to false. +static void demo_prepare_swapchain(struct demo *demo) { VkResult U_ASSERT_ONLY err; VkSwapchainKHR oldSwapchain = demo->swapchain; @@ -1501,10 +1545,6 @@ static void demo_prepare_buffers(struct demo *demo) { err = vkGetSwapchainImagesKHR(demo->device, demo->swapchain, &demo->swapchainImageCount, swapchainImages); assert(!err); - demo->swapchain_image_resources = - (SwapchainImageResources *)malloc(sizeof(SwapchainImageResources) * demo->swapchainImageCount); - assert(demo->swapchain_image_resources); - for (i = 0; i < demo->swapchainImageCount; i++) { demo_name_object(demo, VK_OBJECT_TYPE_IMAGE, (uint64_t)swapchainImages[i], "SwapchainImage(%u)", i); } @@ -1526,14 +1566,35 @@ static void demo_prepare_buffers(struct demo *demo) { .flags = 0, }; - demo->swapchain_image_resources[i].image = swapchainImages[i]; + demo->swapchain_resources[i].image = swapchainImages[i]; - color_image_view.image = demo->swapchain_image_resources[i].image; + color_image_view.image = demo->swapchain_resources[i].image; - err = vkCreateImageView(demo->device, &color_image_view, NULL, &demo->swapchain_image_resources[i].view); + err = vkCreateImageView(demo->device, &color_image_view, NULL, &demo->swapchain_resources[i].view); assert(!err); - demo_name_object(demo, VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)demo->swapchain_image_resources[i].view, "SwapchainView(%u)", - i); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)demo->swapchain_resources[i].view, "SwapchainView(%u)", i); + } + + // Create semaphores to synchronize acquiring presentable buffers before + // rendering and waiting for drawing to be complete before presenting + VkSemaphoreCreateInfo semaphoreCreateInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + }; + for (i = 0; i < demo->swapchainImageCount; i++) { + err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->swapchain_resources[i].draw_complete_semaphore); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->swapchain_resources[i].draw_complete_semaphore, + "DrawCompleteSem(%u)", i); + + if (demo->separate_present_queue) { + err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, + &demo->swapchain_resources[i].image_ownership_semaphore); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->swapchain_resources[i].image_ownership_semaphore, + "ImageOwnerSem(%u)", i); + } } if (demo->VK_GOOGLE_display_timing_enabled) { @@ -1557,15 +1618,21 @@ static void demo_prepare_buffers(struct demo *demo) { if (NULL != presentModes) { free(presentModes); } + + demo_prepare_depth(demo); + demo_prepare_framebuffers(demo); + demo->swapchain_ready = true; + demo->first_swapchain_frame = true; } static void demo_prepare_depth(struct demo *demo) { - const VkFormat depth_format = VK_FORMAT_D16_UNORM; + demo->depth.format = VK_FORMAT_D16_UNORM; + const VkImageCreateInfo image = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = NULL, .imageType = VK_IMAGE_TYPE_2D, - .format = depth_format, + .format = demo->depth.format, .extent = {demo->width, demo->height, 1}, .mipLevels = 1, .arrayLayers = 1, @@ -1579,7 +1646,7 @@ static void demo_prepare_depth(struct demo *demo) { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = NULL, .image = VK_NULL_HANDLE, - .format = depth_format, + .format = demo->depth.format, .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}, .flags = 0, @@ -1595,8 +1662,6 @@ static void demo_prepare_depth(struct demo *demo) { VkResult U_ASSERT_ONLY err; bool U_ASSERT_ONLY pass; - demo->depth.format = depth_format; - /* create image */ err = vkCreateImage(demo->device, &image, NULL, &demo->depth.image); assert(!err); @@ -1953,13 +2018,12 @@ void demo_prepare_cube_data_buffers(struct demo *demo) { buf_info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; buf_info.size = sizeof(data); - for (unsigned int i = 0; i < demo->swapchainImageCount; i++) { - err = vkCreateBuffer(demo->device, &buf_info, NULL, &demo->swapchain_image_resources[i].uniform_buffer); + for (unsigned int i = 0; i < FRAME_LAG; i++) { + err = vkCreateBuffer(demo->device, &buf_info, NULL, &demo->submission_resources[i].uniform_buffer); assert(!err); - demo_name_object(demo, VK_OBJECT_TYPE_BUFFER, (uint64_t)demo->swapchain_image_resources[i].uniform_buffer, - "SwapchainUniformBuf(%u)", i); + demo_name_object(demo, VK_OBJECT_TYPE_BUFFER, (uint64_t)demo->submission_resources[i].uniform_buffer, "UniformBuf(%u)", i); - vkGetBufferMemoryRequirements(demo->device, demo->swapchain_image_resources[i].uniform_buffer, &mem_reqs); + vkGetBufferMemoryRequirements(demo->device, demo->submission_resources[i].uniform_buffer, &mem_reqs); mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; mem_alloc.pNext = NULL; @@ -1971,19 +2035,19 @@ void demo_prepare_cube_data_buffers(struct demo *demo) { &mem_alloc.memoryTypeIndex); assert(pass); - err = vkAllocateMemory(demo->device, &mem_alloc, NULL, &demo->swapchain_image_resources[i].uniform_memory); + err = vkAllocateMemory(demo->device, &mem_alloc, NULL, &demo->submission_resources[i].uniform_memory); assert(!err); - demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)demo->swapchain_image_resources[i].uniform_memory, - "SwapchainUniformMem(%u)", i); + demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)demo->submission_resources[i].uniform_memory, + "UniformMem(%u)", i); - err = vkMapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, 0, VK_WHOLE_SIZE, 0, - &demo->swapchain_image_resources[i].uniform_memory_ptr); + err = vkMapMemory(demo->device, demo->submission_resources[i].uniform_memory, 0, VK_WHOLE_SIZE, 0, + &demo->submission_resources[i].uniform_memory_ptr); assert(!err); - memcpy(demo->swapchain_image_resources[i].uniform_memory_ptr, &data, sizeof data); + memcpy(demo->submission_resources[i].uniform_memory_ptr, &data, sizeof data); - err = vkBindBufferMemory(demo->device, demo->swapchain_image_resources[i].uniform_buffer, - demo->swapchain_image_resources[i].uniform_memory, 0); + err = vkBindBufferMemory(demo->device, demo->submission_resources[i].uniform_buffer, + demo->submission_resources[i].uniform_memory, 0); assert(!err); } } @@ -2281,18 +2345,18 @@ static void demo_prepare_descriptor_pool(struct demo *demo) { [0] = { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .descriptorCount = demo->swapchainImageCount, + .descriptorCount = FRAME_LAG, }, [1] = { .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = demo->swapchainImageCount * DEMO_TEXTURE_COUNT, + .descriptorCount = FRAME_LAG * DEMO_TEXTURE_COUNT, }, }; const VkDescriptorPoolCreateInfo descriptor_pool = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .pNext = NULL, - .maxSets = demo->swapchainImageCount, + .maxSets = FRAME_LAG, .poolSizeCount = 2, .pPoolSizes = type_counts, }; @@ -2337,12 +2401,12 @@ static void demo_prepare_descriptor_set(struct demo *demo) { writes[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; writes[1].pImageInfo = tex_descs; - for (unsigned int i = 0; i < demo->swapchainImageCount; i++) { - err = vkAllocateDescriptorSets(demo->device, &alloc_info, &demo->swapchain_image_resources[i].descriptor_set); + for (unsigned int i = 0; i < FRAME_LAG; i++) { + err = vkAllocateDescriptorSets(demo->device, &alloc_info, &demo->submission_resources[i].descriptor_set); assert(!err); - buffer_info.buffer = demo->swapchain_image_resources[i].uniform_buffer; - writes[0].dstSet = demo->swapchain_image_resources[i].descriptor_set; - writes[1].dstSet = demo->swapchain_image_resources[i].descriptor_set; + buffer_info.buffer = demo->submission_resources[i].uniform_buffer; + writes[0].dstSet = demo->submission_resources[i].descriptor_set; + writes[1].dstSet = demo->submission_resources[i].descriptor_set; vkUpdateDescriptorSets(demo->device, 2, writes, 0, NULL); } } @@ -2365,29 +2429,44 @@ static void demo_prepare_framebuffers(struct demo *demo) { uint32_t i; for (i = 0; i < demo->swapchainImageCount; i++) { - attachments[0] = demo->swapchain_image_resources[i].view; - err = vkCreateFramebuffer(demo->device, &fb_info, NULL, &demo->swapchain_image_resources[i].framebuffer); + attachments[0] = demo->swapchain_resources[i].view; + err = vkCreateFramebuffer(demo->device, &fb_info, NULL, &demo->swapchain_resources[i].framebuffer); assert(!err); - demo_name_object(demo, VK_OBJECT_TYPE_FRAMEBUFFER, (uint64_t)demo->swapchain_image_resources[i].framebuffer, - "Framebuffer(%u)", i); + demo_name_object(demo, VK_OBJECT_TYPE_FRAMEBUFFER, (uint64_t)demo->swapchain_resources[i].framebuffer, "Framebuffer(%u)", + i); } } -static void demo_prepare(struct demo *demo) { - demo_prepare_buffers(demo); +static void demo_prepare_submission_sync_objects(struct demo *demo) { + VkSemaphoreCreateInfo semaphoreCreateInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + }; + // Create fences that we can use to throttle if we get too far + // ahead of the image presents + VkFenceCreateInfo fence_ci = { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = NULL, .flags = VK_FENCE_CREATE_SIGNALED_BIT}; + VkResult U_ASSERT_ONLY err; + for (uint32_t i = 0; i < FRAME_LAG; i++) { + err = vkCreateFence(demo->device, &fence_ci, NULL, &demo->submission_resources[i].fence); + assert(!err); - if (demo->is_minimized) { - demo->prepared = false; - return; + err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->submission_resources[i].image_acquired_semaphore); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->submission_resources[i].image_acquired_semaphore, + "AcquireSem(%u)", i); } +} +static void demo_prepare(struct demo *demo) { VkResult U_ASSERT_ONLY err; if (demo->cmd_pool == VK_NULL_HANDLE) { const VkCommandPoolCreateInfo cmd_pool_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .pNext = NULL, .queueFamilyIndex = demo->graphics_queue_family_index, - .flags = 0, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, }; err = vkCreateCommandPool(demo->device, &cmd_pool_info, NULL, &demo->cmd_pool); assert(!err); @@ -2413,17 +2492,21 @@ static void demo_prepare(struct demo *demo) { demo_push_cb_label(demo, demo->cmd, NULL, "Prepare"); assert(!err); - demo_prepare_depth(demo); demo_prepare_textures(demo); demo_prepare_cube_data_buffers(demo); demo_prepare_descriptor_layout(demo); + + // Only need to know the format of the depth buffer before we create the renderpass + demo->depth.format = VK_FORMAT_D16_UNORM; demo_prepare_render_pass(demo); demo_prepare_pipeline(demo); - for (uint32_t i = 0; i < demo->swapchainImageCount; i++) { - err = vkAllocateCommandBuffers(demo->device, &cmd, &demo->swapchain_image_resources[i].cmd); + for (uint32_t i = 0; i < FRAME_LAG; i++) { + err = vkAllocateCommandBuffers(demo->device, &cmd, &demo->submission_resources[i].cmd); assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)demo->submission_resources[i].cmd, "MainCommandBuffer(%u)", + i); } if (demo->separate_present_queue) { @@ -2442,25 +2525,18 @@ static void demo_prepare(struct demo *demo) { .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; - for (uint32_t i = 0; i < demo->swapchainImageCount; i++) { - err = vkAllocateCommandBuffers(demo->device, &present_cmd_info, - &demo->swapchain_image_resources[i].graphics_to_present_cmd); + for (uint32_t i = 0; i < FRAME_LAG; i++) { + err = vkAllocateCommandBuffers(demo->device, &present_cmd_info, &demo->submission_resources[i].graphics_to_present_cmd); assert(!err); - demo_build_image_ownership_cmd(demo, i); - demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, - (uint64_t)demo->swapchain_image_resources[i].graphics_to_present_cmd, "GfxToPresent(%u)", i); + demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)demo->submission_resources[i].graphics_to_present_cmd, + "GfxToPresent(%u)", i); } } demo_prepare_descriptor_pool(demo); demo_prepare_descriptor_set(demo); - demo_prepare_framebuffers(demo); - - for (uint32_t i = 0; i < demo->swapchainImageCount; i++) { - demo->current_buffer = i; - demo_draw_build_cmd(demo, demo->swapchain_image_resources[i].cmd); - } + demo_prepare_submission_sync_objects(demo); /* * Prepare functions above may generate pipeline commands @@ -2471,34 +2547,24 @@ static void demo_prepare(struct demo *demo) { if (demo->staging_texture.buffer) { demo_destroy_texture(demo, &demo->staging_texture); } - - demo->current_buffer = 0; - demo->prepared = true; - demo->first_swapchain_frame = true; + demo->current_submission_index = 0; + demo->initialized = true; + demo_prepare_swapchain(demo); } static void demo_cleanup(struct demo *demo) { uint32_t i; - demo->prepared = false; + demo->initialized = false; vkDeviceWaitIdle(demo->device); // Wait for fences from present operations for (i = 0; i < FRAME_LAG; i++) { - vkWaitForFences(demo->device, 1, &demo->fences[i], VK_TRUE, UINT64_MAX); - vkDestroyFence(demo->device, demo->fences[i], NULL); - vkDestroySemaphore(demo->device, demo->image_acquired_semaphores[i], NULL); - vkDestroySemaphore(demo->device, demo->draw_complete_semaphores[i], NULL); - if (demo->separate_present_queue) { - vkDestroySemaphore(demo->device, demo->image_ownership_semaphores[i], NULL); - } + vkWaitForFences(demo->device, 1, &demo->submission_resources[i].fence, VK_TRUE, UINT64_MAX); } // If the window is currently minimized, demo_resize has already done some cleanup for us. if (!demo->is_minimized) { - for (i = 0; i < demo->swapchainImageCount; i++) { - vkDestroyFramebuffer(demo->device, demo->swapchain_image_resources[i].framebuffer, NULL); - } vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL); vkDestroyPipeline(demo->device, demo->pipeline, NULL); @@ -2520,13 +2586,21 @@ static void demo_cleanup(struct demo *demo) { vkFreeMemory(demo->device, demo->depth.mem, NULL); for (i = 0; i < demo->swapchainImageCount; i++) { - vkDestroyImageView(demo->device, demo->swapchain_image_resources[i].view, NULL); - vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, &demo->swapchain_image_resources[i].cmd); - vkDestroyBuffer(demo->device, demo->swapchain_image_resources[i].uniform_buffer, NULL); - vkUnmapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory); - vkFreeMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, NULL); + vkDestroyImageView(demo->device, demo->swapchain_resources[i].view, NULL); + vkDestroyFramebuffer(demo->device, demo->swapchain_resources[i].framebuffer, NULL); + vkDestroySemaphore(demo->device, demo->swapchain_resources[i].draw_complete_semaphore, NULL); + if (demo->separate_present_queue) { + vkDestroySemaphore(demo->device, demo->swapchain_resources[i].image_ownership_semaphore, NULL); + } + } + + for (i = 0; i < FRAME_LAG; i++) { + vkDestroyFence(demo->device, demo->submission_resources[i].fence, NULL); + vkDestroySemaphore(demo->device, demo->submission_resources[i].image_acquired_semaphore, NULL); + vkDestroyBuffer(demo->device, demo->submission_resources[i].uniform_buffer, NULL); + vkUnmapMemory(demo->device, demo->submission_resources[i].uniform_memory); + vkFreeMemory(demo->device, demo->submission_resources[i].uniform_memory, NULL); } - free(demo->swapchain_image_resources); free(demo->queue_props); vkDestroyCommandPool(demo->device, demo->cmd_pool, NULL); @@ -2594,58 +2668,46 @@ static void demo_resize(struct demo *demo) { uint32_t i; // Don't react to resize until after first initialization. - if (!demo->prepared) { - if (demo->is_minimized) { - demo_prepare(demo); - } + if (!demo->initialized) { return; } - // In order to properly resize the window, we must re-create the swapchain - // AND redo the command buffers, etc. - // - // First, perform part of the demo_cleanup() function: - demo->prepared = false; - vkDeviceWaitIdle(demo->device); - for (i = 0; i < demo->swapchainImageCount; i++) { - vkDestroyFramebuffer(demo->device, demo->swapchain_image_resources[i].framebuffer, NULL); + // Don't do anything if the surface has zero size, as vulkan disallows creating swapchains with zero area + // We use is_minimized to track this because zero size window usually occurs from minimizing + if (demo->width == 0 || demo->height == 0) { + demo->is_minimized = true; + return; + } else { + demo->is_minimized = false; } - vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL); - vkDestroyPipeline(demo->device, demo->pipeline, NULL); - vkDestroyPipelineCache(demo->device, demo->pipelineCache, NULL); - vkDestroyRenderPass(demo->device, demo->render_pass, NULL); - vkDestroyPipelineLayout(demo->device, demo->pipeline_layout, NULL); - vkDestroyDescriptorSetLayout(demo->device, demo->desc_layout, NULL); + // In order to properly resize the window, we must re-create the + // swapchain + // + // First, destroy the old swapchain and its associated resources, setting swapchain_ready to false to prevent draw from running - for (i = 0; i < DEMO_TEXTURE_COUNT; i++) { - vkDestroyImageView(demo->device, demo->textures[i].view, NULL); - vkDestroyImage(demo->device, demo->textures[i].image, NULL); - vkFreeMemory(demo->device, demo->textures[i].mem, NULL); - vkDestroySampler(demo->device, demo->textures[i].sampler, NULL); - } + if (demo->swapchain_ready) { + demo->swapchain_ready = false; + vkDeviceWaitIdle(demo->device); - vkDestroyImageView(demo->device, demo->depth.view, NULL); - vkDestroyImage(demo->device, demo->depth.image, NULL); - vkFreeMemory(demo->device, demo->depth.mem, NULL); + vkDestroyImageView(demo->device, demo->depth.view, NULL); + vkDestroyImage(demo->device, demo->depth.image, NULL); + vkFreeMemory(demo->device, demo->depth.mem, NULL); + memset(&(demo->depth), 0, sizeof(demo->depth)); - for (i = 0; i < demo->swapchainImageCount; i++) { - vkDestroyImageView(demo->device, demo->swapchain_image_resources[i].view, NULL); - vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, &demo->swapchain_image_resources[i].cmd); - vkDestroyBuffer(demo->device, demo->swapchain_image_resources[i].uniform_buffer, NULL); - vkUnmapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory); - vkFreeMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, NULL); - } - vkDestroyCommandPool(demo->device, demo->cmd_pool, NULL); - demo->cmd_pool = VK_NULL_HANDLE; - if (demo->separate_present_queue) { - vkDestroyCommandPool(demo->device, demo->present_cmd_pool, NULL); + for (i = 0; i < demo->swapchainImageCount; i++) { + vkDestroyImageView(demo->device, demo->swapchain_resources[i].view, NULL); + vkDestroyFramebuffer(demo->device, demo->swapchain_resources[i].framebuffer, NULL); + vkDestroySemaphore(demo->device, demo->swapchain_resources[i].draw_complete_semaphore, NULL); + if (demo->separate_present_queue) { + vkDestroySemaphore(demo->device, demo->swapchain_resources[i].image_ownership_semaphore, NULL); + } + } + memset(&(demo->swapchain_resources), 0, sizeof(SwapchainImageResources) * MAX_SWAPCHAIN_IMAGE_COUNT); } - free(demo->swapchain_image_resources); - // Second, re-perform the demo_prepare() function, which will re-create the - // swapchain: - demo_prepare(demo); + // Second, recreate the swapchain, depth buffer, and framebuffers. + demo_prepare_swapchain(demo); } // On MS-Windows, make this a global, so it's available to WndProc() @@ -2653,10 +2715,12 @@ struct demo demo; #if defined(VK_USE_PLATFORM_WIN32_KHR) static void demo_run(struct demo *demo) { - if (!demo->prepared) return; + if (!demo->initialized || !demo->swapchain_ready) return; demo_draw(demo); - demo->curFrame++; + if (demo->is_minimized) { + demo->curFrame++; + } if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { PostQuitMessage(validation_error); } @@ -2842,10 +2906,13 @@ static void demo_run_xlib(struct demo *demo) { XNextEvent(demo->xlib_display, &event); demo_handle_xlib_event(demo, &event); } - - demo_draw(demo); - demo->curFrame++; - if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + if (demo->initialized && demo->swapchain_ready) { + demo_draw(demo); + if (demo->is_minimized) { + demo->curFrame++; + } + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } } } #endif @@ -2908,10 +2975,13 @@ static void demo_run_xcb(struct demo *demo) { free(event); event = xcb_poll_for_event(demo->connection); } - - demo_draw(demo); - demo->curFrame++; - if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + if (demo->initialized && demo->swapchain_ready) { + demo_draw(demo); + if (demo->is_minimized) { + demo->curFrame++; + } + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } } } @@ -2967,10 +3037,13 @@ static void demo_run(struct demo *demo) { // Pump events wl_display_dispatch_pending(demo->wayland_display); - - demo_draw(demo); - demo->curFrame++; - if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + if (demo->initialized && demo->swapchain_ready) { + demo_draw(demo); + if (demo->is_minimized) { + demo->curFrame++; + } + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } } } } @@ -3113,26 +3186,35 @@ static void demo_run_directfb(struct demo *demo) { if (!demo->event_buffer->GetEvent(demo->event_buffer, DFB_EVENT(&event))) demo_handle_directfb_event(demo, &event); } else { if (!demo->event_buffer->GetEvent(demo->event_buffer, DFB_EVENT(&event))) demo_handle_directfb_event(demo, &event); - - demo_draw(demo); - demo->curFrame++; - if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + if (demo->initialized && demo->swapchain_ready) { + demo_draw(demo); + if (demo->is_minimized) { + demo->curFrame++; + } + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } } } } #endif #if defined(VK_USE_PLATFORM_ANDROID_KHR) static void demo_run(struct demo *demo) { - if (!demo->prepared) return; + if (!demo->initialized || !demo->swapchain_ready) return; demo_draw(demo); - demo->curFrame++; + if (demo->is_minimized) { + demo->curFrame++; + } } #endif #if defined(VK_USE_PLATFORM_METAL_EXT) static void demo_run(struct demo *demo) { + if (!demo->initialized || !demo->swapchain_ready) return; + demo_draw(demo); - demo->curFrame++; + if (demo->is_minimized) { + demo->curFrame++; + } if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { demo->quit = TRUE; } @@ -3374,10 +3456,12 @@ static void demo_run(struct demo *demo) { } } - if (demo->pause) { + if (demo->pause || !demo->initialized || !demo->swapchain_ready) { } else { demo_draw(demo); - demo->curFrame++; + if (demo->is_minimized) { + demo->curFrame++; + } if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { demo->quit = true; } @@ -4591,37 +4675,6 @@ static void demo_init_vk_swapchain(struct demo *demo) { demo->quit = false; demo->curFrame = 0; - // Create semaphores to synchronize acquiring presentable buffers before - // rendering and waiting for drawing to be complete before presenting - VkSemaphoreCreateInfo semaphoreCreateInfo = { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, - .pNext = NULL, - .flags = 0, - }; - - // Create fences that we can use to throttle if we get too far - // ahead of the image presents - VkFenceCreateInfo fence_ci = { - .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = NULL, .flags = VK_FENCE_CREATE_SIGNALED_BIT}; - for (uint32_t i = 0; i < FRAME_LAG; i++) { - err = vkCreateFence(demo->device, &fence_ci, NULL, &demo->fences[i]); - assert(!err); - - err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->image_acquired_semaphores[i]); - assert(!err); - demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->image_acquired_semaphores[i], "AcquireSem(%u)", i); - - err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->draw_complete_semaphores[i]); - assert(!err); - demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->draw_complete_semaphores[i], "DrawCompleteSem(%u)", i); - - if (demo->separate_present_queue) { - err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->image_ownership_semaphores[i]); - assert(!err); - demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->image_ownership_semaphores[i], "ImageOwnerSem(%u)", i); - } - } - demo->frame_index = 0; demo->first_swapchain_frame = true; // Get Memory information and properties @@ -4824,6 +4877,8 @@ static void demo_init(struct demo *demo, int argc, char **argv) { #endif } + demo->initialized = false; + demo_init_vk(demo); demo->spin_angle = 4.0f; @@ -4949,7 +5004,6 @@ static void demo_main(struct demo *demo, void *caMetalLayer, int argc, const cha #include #include "android_util.h" -static bool initialized = false; static bool active = false; struct demo demo; @@ -4969,7 +5023,7 @@ static void processCommand(struct android_app *app, int32_t cmd) { // is tied to the swapchain, it's easiest to simply cleanup and // start over (i.e. use a brute-force approach of re-starting // the app) - if (demo.prepared) { + if (demo.initialized) { demo_cleanup(&demo); } @@ -4994,7 +5048,6 @@ static void processCommand(struct android_app *app, int32_t cmd) { demo_select_physical_device(&demo); demo_init_vk_swapchain(&demo); demo_prepare(&demo); - initialized = true; } break; } @@ -5010,7 +5063,7 @@ static void processCommand(struct android_app *app, int32_t cmd) { } void android_main(struct android_app *app) { - demo.prepared = false; + demo.initialized = false; app->onAppCmd = processCommand; app->onInputEvent = processInput; @@ -5028,7 +5081,7 @@ void android_main(struct android_app *app) { return; } } - if (initialized && active) { + if (demo.initialized && demo.swapchain_ready && active) { demo_run(&demo); } } diff --git a/cube/cube.cpp b/cube/cube.cpp index 1d462b0e4..ec2924363 100644 --- a/cube/cube.cpp +++ b/cube/cube.cpp @@ -315,27 +315,34 @@ const char *wsi_to_string(WsiPlatform wsi_platform) { } }; -struct SwapchainImageResources { - vk::Image image; +struct SubmissionResources { + vk::Fence fence; + vk::Semaphore image_acquired_semaphore; vk::CommandBuffer cmd; vk::CommandBuffer graphics_to_present_cmd; - vk::ImageView view; vk::Buffer uniform_buffer; vk::DeviceMemory uniform_memory; void *uniform_memory_ptr = nullptr; - vk::Framebuffer framebuffer; vk::DescriptorSet descriptor_set; }; +struct SwapchainImageResources { + vk::Image image; + vk::ImageView view; + vk::Framebuffer framebuffer; + vk::Semaphore draw_complete_semaphore; + vk::Semaphore image_ownership_semaphore; +}; + struct Demo { - void build_image_ownership_cmd(const SwapchainImageResources &swapchain_image_resource); + void build_image_ownership_cmd(const SubmissionResources &submission_resource, + const SwapchainImageResources &swapchain_image_resource); vk::Bool32 check_layers(const std::vector &check_names, const std::vector &layers); void cleanup(); - void destroy_swapchain_related_resources(); void create_device(); void destroy_texture(texture_object &tex_objs); void draw(); - void draw_build_cmd(const SwapchainImageResources &swapchain_image_resource); + void draw_build_cmd(const SubmissionResources &submission_resource, const SwapchainImageResources &swapchain_image_resource); void prepare_init_cmd(); void flush_init_cmd(); void init(int argc, char **argv); @@ -343,14 +350,13 @@ struct Demo { void init_vk(); void select_physical_device(); void init_vk_swapchain(); + void prepare(); - void prepare_buffers(); void prepare_cube_data_buffers(); - void prepare_depth(); void prepare_descriptor_layout(); void prepare_descriptor_pool(); void prepare_descriptor_set(); - void prepare_framebuffers(); + void prepare_submission_sync_objects(); vk::ShaderModule prepare_shader_module(const uint32_t *code, size_t size); vk::ShaderModule prepare_vs(); vk::ShaderModule prepare_fs(); @@ -363,9 +369,13 @@ struct Demo { void resize(); void create_surface(); + void prepare_swapchain(); + void prepare_framebuffers(); + void prepare_depth(); + void set_image_layout(vk::Image image, vk::ImageAspectFlags aspectMask, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, vk::AccessFlags srcAccessMask, vk::PipelineStageFlags src_stages, vk::PipelineStageFlags dest_stages); - void update_data_buffer(); + void update_data_buffer(void *uniform_memory_ptr); bool loadTexture(const char *filename, uint8_t *rgba_data, vk::SubresourceLayout &layout, uint32_t &width, uint32_t &height); bool memory_type_from_properties(uint32_t typeBits, vk::MemoryPropertyFlags requirements_mask, uint32_t &typeIndex); vk::SurfaceFormatKHR pick_surface_format(const std::vector &surface_formats); @@ -461,7 +471,9 @@ struct Demo { #endif WsiPlatform wsi_platform = WsiPlatform::auto_; vk::SurfaceKHR surface; - bool prepared = false; + bool initialized; + bool swapchain_ready; + bool is_minimized; bool use_staging_buffer = false; bool separate_present_queue = false; bool invalid_gpu_selection = false; @@ -479,12 +491,11 @@ struct Demo { vk::Queue present_queue; uint32_t graphics_queue_family_index = 0; uint32_t present_queue_family_index = 0; - std::array image_acquired_semaphores; - std::array draw_complete_semaphores; - std::array image_ownership_semaphores; vk::PhysicalDeviceProperties gpu_props; std::vector queue_props; vk::PhysicalDeviceMemoryProperties memory_properties; + std::array submission_resources; + uint32_t current_submission_index = 0; std::vector enabled_instance_extensions; std::vector enabled_layers; @@ -496,10 +507,9 @@ struct Demo { vk::ColorSpaceKHR color_space; vk::SwapchainKHR swapchain; - std::vector swapchain_image_resources; + std::vector swapchain_resources; vk::PresentModeKHR presentMode = vk::PresentModeKHR::eFifo; - std::array fences; - uint32_t frame_index = 0; + bool first_swapchain_frame; vk::CommandPool cmd_pool; vk::CommandPool present_cmd_pool; @@ -555,9 +565,6 @@ struct Demo { bool use_break = false; bool suppress_popups = false; bool force_errors = false; - bool is_minimized = false; - - uint32_t current_buffer = 0; }; #ifdef _WIN32 @@ -671,8 +678,12 @@ static void registry_handle_global_remove(void *data, wl_registry *registry, uin static const wl_registry_listener registry_listener = {registry_handle_global, registry_handle_global_remove}; #endif -void Demo::build_image_ownership_cmd(const SwapchainImageResources &swapchain_image_resource) { - auto result = swapchain_image_resource.graphics_to_present_cmd.begin( +void Demo::build_image_ownership_cmd(const SubmissionResources &submission_resource, + const SwapchainImageResources &swapchain_image_resource) { + auto result = submission_resource.graphics_to_present_cmd.reset(); + VERIFY(result == vk::Result::eSuccess); + + result = submission_resource.graphics_to_present_cmd.begin( vk::CommandBufferBeginInfo().setFlags(vk::CommandBufferUsageFlagBits::eSimultaneousUse)); VERIFY(result == vk::Result::eSuccess); @@ -687,11 +698,11 @@ void Demo::build_image_ownership_cmd(const SwapchainImageResources &swapchain_im .setImage(swapchain_image_resource.image) .setSubresourceRange(vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); - swapchain_image_resource.graphics_to_present_cmd.pipelineBarrier(vk::PipelineStageFlagBits::eBottomOfPipe, - vk::PipelineStageFlagBits::eBottomOfPipe, - vk::DependencyFlagBits(), {}, {}, image_ownership_barrier); + submission_resource.graphics_to_present_cmd.pipelineBarrier(vk::PipelineStageFlagBits::eBottomOfPipe, + vk::PipelineStageFlagBits::eBottomOfPipe, vk::DependencyFlagBits(), + {}, {}, image_ownership_barrier); - result = swapchain_image_resource.graphics_to_present_cmd.end(); + result = submission_resource.graphics_to_present_cmd.end(); VERIFY(result == vk::Result::eSuccess); } @@ -713,24 +724,53 @@ vk::Bool32 Demo::check_layers(const std::vector &check_names, cons } void Demo::cleanup() { - prepared = false; + initialized = false; auto result = device.waitIdle(); VERIFY(result == vk::Result::eSuccess); - if (!is_minimized) { - destroy_swapchain_related_resources(); + + device.destroyCommandPool(cmd_pool); + if (separate_present_queue) { + device.destroyCommandPool(present_cmd_pool); + } + + device.destroyDescriptorPool(desc_pool); + + device.destroyPipeline(pipeline); + device.destroyPipelineCache(pipelineCache); + device.destroyRenderPass(render_pass); + device.destroyPipelineLayout(pipeline_layout); + device.destroyDescriptorSetLayout(desc_layout); + + for (const auto &tex : textures) { + device.destroyImageView(tex.view); + device.destroyImage(tex.image); + device.freeMemory(tex.mem); + device.destroySampler(tex.sampler); } - // Wait for fences from present operations - for (uint32_t i = 0; i < FRAME_LAG; i++) { - device.destroyFence(fences[i]); - device.destroySemaphore(image_acquired_semaphores[i]); - device.destroySemaphore(draw_complete_semaphores[i]); + + device.destroyImageView(depth.view); + device.destroyImage(depth.image); + device.freeMemory(depth.mem); + + for (auto &swapchain_resource : swapchain_resources) { + device.destroyFramebuffer(swapchain_resource.framebuffer); + device.destroyImageView(swapchain_resource.view); + device.destroySemaphore(swapchain_resource.draw_complete_semaphore); if (separate_present_queue) { - device.destroySemaphore(image_ownership_semaphores[i]); + device.destroySemaphore(swapchain_resource.image_ownership_semaphore); } } device.destroySwapchainKHR(swapchain); + for (const auto &submission_resource : submission_resources) { + device.destroyFence(submission_resource.fence); + device.destroySemaphore(submission_resource.image_acquired_semaphore); + device.destroyBuffer(submission_resource.uniform_buffer); + device.unmapMemory(submission_resource.uniform_memory); + device.freeMemory(submission_resource.uniform_memory); + } + device.destroy(); inst.destroySurfaceKHR(surface); @@ -787,8 +827,12 @@ void Demo::create_device() { float priorities = 0.0; std::vector queues; - const vk::DeviceQueueCreateFlags queue_create_flags = protected_output ? vk::DeviceQueueCreateFlagBits::eProtected : vk::DeviceQueueCreateFlags{}; - queues.push_back(vk::DeviceQueueCreateInfo().setQueueFamilyIndex(graphics_queue_family_index).setQueuePriorities(priorities).setFlags(queue_create_flags)); + const vk::DeviceQueueCreateFlags queue_create_flags = + protected_output ? vk::DeviceQueueCreateFlagBits::eProtected : vk::DeviceQueueCreateFlags{}; + queues.push_back(vk::DeviceQueueCreateInfo() + .setQueueFamilyIndex(graphics_queue_family_index) + .setQueuePriorities(priorities) + .setFlags(queue_create_flags)); if (separate_present_queue) { queues.push_back( @@ -796,7 +840,10 @@ void Demo::create_device() { } auto const protected_memory_features = vk::PhysicalDeviceProtectedMemoryFeatures().setProtectedMemory(protected_output); - auto deviceInfo = vk::DeviceCreateInfo().setPNext(protected_output ? &protected_memory_features : nullptr).setQueueCreateInfos(queues).setPEnabledExtensionNames(enabled_device_extensions); + auto deviceInfo = vk::DeviceCreateInfo() + .setPNext(protected_output ? &protected_memory_features : nullptr) + .setQueueCreateInfos(queues) + .setPEnabledExtensionNames(enabled_device_extensions); auto device_return = gpu.createDevice(deviceInfo); VERIFY(device_return.result == vk::Result::eSuccess); device = device_return.value; @@ -811,15 +858,22 @@ void Demo::destroy_texture(texture_object &tex_objs) { } void Demo::draw() { + // Don't draw if initialization isn't complete, if the swapchain became outdated, or if the window is minimized + if (!initialized || !swapchain_ready || is_minimized) { + return; + } + + auto ¤t_submission = submission_resources[current_submission_index]; + // Ensure no more than FRAME_LAG renderings are outstanding - const vk::Result wait_result = device.waitForFences(fences[frame_index], VK_TRUE, UINT64_MAX); + const vk::Result wait_result = device.waitForFences(current_submission.fence, VK_TRUE, UINT64_MAX); VERIFY(wait_result == vk::Result::eSuccess || wait_result == vk::Result::eTimeout); - device.resetFences({fences[frame_index]}); vk::Result acquire_result; + uint32_t current_swapchain_image_index = 0; do { - acquire_result = - device.acquireNextImageKHR(swapchain, UINT64_MAX, image_acquired_semaphores[frame_index], vk::Fence(), ¤t_buffer); + acquire_result = device.acquireNextImageKHR(swapchain, UINT64_MAX, current_submission.image_acquired_semaphore, vk::Fence(), + ¤t_swapchain_image_index); if (acquire_result == vk::Result::eErrorOutOfDateKHR) { // demo.swapchain is out of date (e.g. the window was resized) and // must be recreated: @@ -835,9 +889,24 @@ void Demo::draw() { } else { VERIFY(acquire_result == vk::Result::eSuccess); } + // If we minimized then stop trying to draw + if (!swapchain_ready) { + return; + } } while (acquire_result != vk::Result::eSuccess); - update_data_buffer(); + auto ¤t_swapchain_resource = swapchain_resources[current_swapchain_image_index]; + + update_data_buffer(submission_resources[current_submission_index].uniform_memory_ptr); + + draw_build_cmd(current_submission, current_swapchain_resource); + + if (separate_present_queue) { + build_image_ownership_cmd(current_submission, current_swapchain_resource); + } + + // Only reset right before submitting so we can't deadlock on an un-signalled fence that has nothing submitted to it + device.resetFences({current_submission.fence}); // Wait for the image acquired semaphore to be signaled to ensure // that the image won't be rendered to until the presentation @@ -849,10 +918,10 @@ void Demo::draw() { auto submit_result = graphics_queue.submit(vk::SubmitInfo() .setPNext(protected_output ? &protected_submit_info : nullptr) .setWaitDstStageMask(pipe_stage_flags) - .setWaitSemaphores(image_acquired_semaphores[frame_index]) - .setCommandBuffers(swapchain_image_resources[current_buffer].cmd) - .setSignalSemaphores(draw_complete_semaphores[frame_index]), - fences[frame_index]); + .setWaitSemaphores(current_submission.image_acquired_semaphore) + .setCommandBuffers(current_submission.cmd) + .setSignalSemaphores(current_swapchain_resource.draw_complete_semaphore), + current_submission.fence); VERIFY(submit_result == vk::Result::eSuccess); if (separate_present_queue) { @@ -863,23 +932,24 @@ void Demo::draw() { auto change_owner_result = present_queue.submit(vk::SubmitInfo() .setWaitDstStageMask(pipe_stage_flags) - .setWaitSemaphores(draw_complete_semaphores[frame_index]) - .setCommandBuffers(swapchain_image_resources[current_buffer].graphics_to_present_cmd) - .setSignalSemaphores(image_ownership_semaphores[frame_index])); + .setWaitSemaphores(current_swapchain_resource.draw_complete_semaphore) + .setCommandBuffers(current_submission.graphics_to_present_cmd) + .setSignalSemaphores(current_swapchain_resource.image_ownership_semaphore)); VERIFY(change_owner_result == vk::Result::eSuccess); } const auto presentInfo = vk::PresentInfoKHR() - .setWaitSemaphores(separate_present_queue ? image_ownership_semaphores[frame_index] - : draw_complete_semaphores[frame_index]) + .setWaitSemaphores(separate_present_queue ? current_swapchain_resource.image_ownership_semaphore + : current_swapchain_resource.draw_complete_semaphore) .setSwapchains(swapchain) - .setImageIndices(current_buffer); + .setImageIndices(current_swapchain_image_index); // If we are using separate queues we have to wait for image ownership, // otherwise wait for draw complete auto present_result = present_queue.presentKHR(&presentInfo); - frame_index += 1; - frame_index %= FRAME_LAG; + current_submission_index += 1; + current_submission_index %= FRAME_LAG; + first_swapchain_frame = false; if (present_result == vk::Result::eErrorOutOfDateKHR) { // swapchain is out of date (e.g. the window was resized) and // must be recreated: @@ -890,6 +960,8 @@ void Demo::draw() { auto caps_result = gpu.getSurfaceCapabilitiesKHR(surface, &surfCapabilities); VERIFY(caps_result == vk::Result::eSuccess); if (surfCapabilities.currentExtent.width != width || surfCapabilities.currentExtent.height != height) { + width = surfCapabilities.currentExtent.width; + height = surfCapabilities.currentExtent.height; resize(); } } else if (present_result == vk::Result::eErrorSurfaceLostKHR) { @@ -901,12 +973,14 @@ void Demo::draw() { } } -void Demo::draw_build_cmd(const SwapchainImageResources &swapchain_image_resource) { - const auto commandBuffer = swapchain_image_resource.cmd; +void Demo::draw_build_cmd(const SubmissionResources &submission_resource, const SwapchainImageResources &swapchain_image_resource) { + const auto commandBuffer = submission_resource.cmd; vk::ClearValue const clearValues[2] = {vk::ClearColorValue(std::array({{0.2f, 0.2f, 0.2f, 0.2f}})), vk::ClearDepthStencilValue(1.0f, 0u)}; + auto result = commandBuffer.reset(); + VERIFY(result == vk::Result::eSuccess); - auto result = commandBuffer.begin(vk::CommandBufferBeginInfo().setFlags(vk::CommandBufferUsageFlagBits::eSimultaneousUse)); + result = commandBuffer.begin(vk::CommandBufferBeginInfo().setFlags(vk::CommandBufferUsageFlagBits::eSimultaneousUse)); VERIFY(result == vk::Result::eSuccess); commandBuffer.beginRenderPass(vk::RenderPassBeginInfo() @@ -918,8 +992,7 @@ void Demo::draw_build_cmd(const SwapchainImageResources &swapchain_image_resourc vk::SubpassContents::eInline); commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); - commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, swapchain_image_resource.descriptor_set, - {}); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, submission_resource.descriptor_set, {}); float viewport_dimension; float viewport_x = 0.0f; float viewport_y = 0.0f; @@ -975,6 +1048,7 @@ void Demo::draw_build_cmd(const SwapchainImageResources &swapchain_image_resourc void Demo::prepare_init_cmd() { vk::CommandPoolCreateFlags cmd_pool_create_flags = protected_output ? vk::CommandPoolCreateFlagBits::eProtected : vk::CommandPoolCreateFlags{}; + cmd_pool_create_flags |= vk::CommandPoolCreateFlagBits::eResetCommandBuffer; auto cmd_pool_return = device.createCommandPool( vk::CommandPoolCreateInfo().setQueueFamilyIndex(graphics_queue_family_index).setFlags(cmd_pool_create_flags)); VERIFY(cmd_pool_return.result == vk::Result::eSuccess); @@ -1005,7 +1079,8 @@ void Demo::flush_init_cmd() { auto fence = fence_return.value; auto const protected_submit_info = vk::ProtectedSubmitInfo().setProtectedSubmit(protected_output); - result = graphics_queue.submit(vk::SubmitInfo().setPNext(protected_output ? &protected_submit_info : nullptr).setCommandBuffers(cmd), fence); + result = graphics_queue.submit( + vk::SubmitInfo().setPNext(protected_output ? &protected_submit_info : nullptr).setCommandBuffers(cmd), fence); VERIFY(result == vk::Result::eSuccess); result = device.waitForFences(fence, VK_TRUE, UINT64_MAX); @@ -1183,6 +1258,8 @@ void Demo::init(int argc, char **argv) { exit(1); } + initialized = false; + init_vk(); spin_angle = 4.0f; @@ -1832,21 +1909,17 @@ void Demo::select_physical_device() { assert(physicalDeviceProperties.deviceType <= vk::PhysicalDeviceType::eCpu); auto support_result = physical_devices[i].getSurfaceSupportKHR(0, surface); - if (support_result.result != vk::Result::eSuccess || - support_result.value != vk::True) { + if (support_result.result != vk::Result::eSuccess || support_result.value != vk::True) { continue; } std::map device_type_priorities = { - {vk::PhysicalDeviceType::eDiscreteGpu, 5}, - {vk::PhysicalDeviceType::eIntegratedGpu, 4}, - {vk::PhysicalDeviceType::eVirtualGpu, 3}, - {vk::PhysicalDeviceType::eCpu, 2}, + {vk::PhysicalDeviceType::eDiscreteGpu, 5}, {vk::PhysicalDeviceType::eIntegratedGpu, 4}, + {vk::PhysicalDeviceType::eVirtualGpu, 3}, {vk::PhysicalDeviceType::eCpu, 2}, {vk::PhysicalDeviceType::eOther, 1}, }; int priority = -1; - if (device_type_priorities.find(physicalDeviceProperties.deviceType) != - device_type_priorities.end()) { + if (device_type_priorities.find(physicalDeviceProperties.deviceType) != device_type_priorities.end()) { priority = device_type_priorities[physicalDeviceProperties.deviceType]; } @@ -2076,58 +2149,31 @@ void Demo::init_vk_swapchain() { color_space = surfaceFormat.colorSpace; quit = false; + first_swapchain_frame = true; curFrame = 0; - // Create semaphores to synchronize acquiring presentable buffers before - // rendering and waiting for drawing to be complete before presenting - auto const semaphoreCreateInfo = vk::SemaphoreCreateInfo(); - - // Create fences that we can use to throttle if we get too far - // ahead of the image presents - auto const fence_ci = vk::FenceCreateInfo().setFlags(vk::FenceCreateFlagBits::eSignaled); - for (uint32_t i = 0; i < FRAME_LAG; i++) { - vk::Result result = device.createFence(&fence_ci, nullptr, &fences[i]); - VERIFY(result == vk::Result::eSuccess); - - result = device.createSemaphore(&semaphoreCreateInfo, nullptr, &image_acquired_semaphores[i]); - VERIFY(result == vk::Result::eSuccess); - - result = device.createSemaphore(&semaphoreCreateInfo, nullptr, &draw_complete_semaphores[i]); - VERIFY(result == vk::Result::eSuccess); - - if (separate_present_queue) { - result = device.createSemaphore(&semaphoreCreateInfo, nullptr, &image_ownership_semaphores[i]); - VERIFY(result == vk::Result::eSuccess); - } - } - frame_index = 0; - // Get Memory information and properties memory_properties = gpu.getMemoryProperties(); } void Demo::prepare() { - prepare_buffers(); - if (is_minimized) { - prepared = false; - return; - } prepare_init_cmd(); - prepare_depth(); prepare_textures(); prepare_cube_data_buffers(); prepare_descriptor_layout(); + // Only need to know the format of the depth buffer before we create the renderpass + depth.format = vk::Format::eD16Unorm; prepare_render_pass(); prepare_pipeline(); - for (auto &swapchain_image_resource : swapchain_image_resources) { + for (auto &submission_resource : submission_resources) { auto alloc_return = device.allocateCommandBuffers(vk::CommandBufferAllocateInfo() .setCommandPool(cmd_pool) .setLevel(vk::CommandBufferLevel::ePrimary) .setCommandBufferCount(1)); VERIFY(alloc_return.result == vk::Result::eSuccess); - swapchain_image_resource.cmd = alloc_return.value[0]; + submission_resource.cmd = alloc_return.value[0]; } if (separate_present_queue) { @@ -2136,14 +2182,13 @@ void Demo::prepare() { VERIFY(present_cmd_pool_return.result == vk::Result::eSuccess); present_cmd_pool = present_cmd_pool_return.value; - for (auto &swapchain_image_resource : swapchain_image_resources) { + for (auto &submission_resource : submission_resources) { auto alloc_cmd_return = device.allocateCommandBuffers(vk::CommandBufferAllocateInfo() .setCommandPool(present_cmd_pool) .setLevel(vk::CommandBufferLevel::ePrimary) .setCommandBufferCount(1)); VERIFY(alloc_cmd_return.result == vk::Result::eSuccess); - swapchain_image_resource.graphics_to_present_cmd = alloc_cmd_return.value[0]; - build_image_ownership_cmd(swapchain_image_resource); + submission_resource.graphics_to_present_cmd = alloc_cmd_return.value[0]; } } @@ -2152,9 +2197,7 @@ void Demo::prepare() { prepare_framebuffers(); - for (const auto &swapchain_image_resource : swapchain_image_resources) { - draw_build_cmd(swapchain_image_resource); - } + prepare_submission_sync_objects(); /* * Prepare functions above may generate pipeline commands @@ -2165,11 +2208,14 @@ void Demo::prepare() { destroy_texture(staging_texture); } - current_buffer = 0; - prepared = true; + initialized = true; + + prepare_swapchain(); } -void Demo::prepare_buffers() { +// Creates the swapchain, swapchain image views, depth buffer, framebuffers, and sempahores. +// This function returns early if it fails to create a swapchain, setting swapchain_ready to false. +void Demo::prepare_swapchain() { vk::SwapchainKHR oldSwapchain = swapchain; // Check the surface capabilities and formats @@ -2319,22 +2365,41 @@ void Demo::prepare_buffers() { auto swapchain_images_return = device.getSwapchainImagesKHR(swapchain); VERIFY(swapchain_images_return.result == vk::Result::eSuccess); - swapchain_image_resources.resize(swapchain_images_return.value.size()); + swapchain_resources.resize(swapchain_images_return.value.size()); - for (uint32_t i = 0; i < swapchain_image_resources.size(); ++i) { + for (uint32_t i = 0; i < swapchain_resources.size(); ++i) { auto color_image_view = vk::ImageViewCreateInfo() .setViewType(vk::ImageViewType::e2D) .setFormat(format) .setSubresourceRange(vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); - swapchain_image_resources[i].image = swapchain_images_return.value[i]; + swapchain_resources[i].image = swapchain_images_return.value[i]; - color_image_view.image = swapchain_image_resources[i].image; + color_image_view.image = swapchain_resources[i].image; auto image_view_return = device.createImageView(color_image_view); VERIFY(image_view_return.result == vk::Result::eSuccess); - swapchain_image_resources[i].view = image_view_return.value; + swapchain_resources[i].view = image_view_return.value; } + + // Create semaphores to synchronize acquiring presentable buffers before + // rendering and waiting for drawing to be complete before presenting + auto const semaphoreCreateInfo = vk::SemaphoreCreateInfo(); + + for (auto &swapchain_resource : swapchain_resources) { + auto result = device.createSemaphore(&semaphoreCreateInfo, nullptr, &swapchain_resource.draw_complete_semaphore); + VERIFY(result == vk::Result::eSuccess); + + if (separate_present_queue) { + result = device.createSemaphore(&semaphoreCreateInfo, nullptr, &swapchain_resource.image_ownership_semaphore); + VERIFY(result == vk::Result::eSuccess); + } + } + + prepare_depth(); + prepare_framebuffers(); + swapchain_ready = true; + first_swapchain_frame = true; } void Demo::prepare_cube_data_buffers() { @@ -2361,12 +2426,12 @@ void Demo::prepare_cube_data_buffers() { auto const buf_info = vk::BufferCreateInfo().setSize(sizeof(data)).setUsage(vk::BufferUsageFlagBits::eUniformBuffer); - for (auto &swapchain_image_resource : swapchain_image_resources) { - auto result = device.createBuffer(&buf_info, nullptr, &swapchain_image_resource.uniform_buffer); + for (auto &submission_resource : submission_resources) { + auto result = device.createBuffer(&buf_info, nullptr, &submission_resource.uniform_buffer); VERIFY(result == vk::Result::eSuccess); vk::MemoryRequirements mem_reqs; - device.getBufferMemoryRequirements(swapchain_image_resource.uniform_buffer, &mem_reqs); + device.getBufferMemoryRequirements(submission_resource.uniform_buffer, &mem_reqs); auto mem_alloc = vk::MemoryAllocateInfo().setAllocationSize(mem_reqs.size).setMemoryTypeIndex(0); @@ -2375,16 +2440,16 @@ void Demo::prepare_cube_data_buffers() { mem_alloc.memoryTypeIndex); VERIFY(pass); - result = device.allocateMemory(&mem_alloc, nullptr, &swapchain_image_resource.uniform_memory); + result = device.allocateMemory(&mem_alloc, nullptr, &submission_resource.uniform_memory); VERIFY(result == vk::Result::eSuccess); - result = device.mapMemory(swapchain_image_resource.uniform_memory, 0, VK_WHOLE_SIZE, vk::MemoryMapFlags(), - &swapchain_image_resource.uniform_memory_ptr); + result = device.mapMemory(submission_resource.uniform_memory, 0, VK_WHOLE_SIZE, vk::MemoryMapFlags(), + &submission_resource.uniform_memory_ptr); VERIFY(result == vk::Result::eSuccess); - memcpy(swapchain_image_resource.uniform_memory_ptr, &data, sizeof data); + memcpy(submission_resource.uniform_memory_ptr, &data, sizeof data); - result = device.bindBufferMemory(swapchain_image_resource.uniform_buffer, swapchain_image_resource.uniform_memory, 0); + result = device.bindBufferMemory(submission_resource.uniform_buffer, submission_resource.uniform_memory, 0); VERIFY(result == vk::Result::eSuccess); } } @@ -2466,13 +2531,13 @@ void Demo::prepare_descriptor_pool() { std::array const poolSizes = { vk::DescriptorPoolSize() .setType(vk::DescriptorType::eUniformBuffer) - .setDescriptorCount(static_cast(swapchain_image_resources.size())), + .setDescriptorCount(static_cast(submission_resources.size())), vk::DescriptorPoolSize() .setType(vk::DescriptorType::eCombinedImageSampler) - .setDescriptorCount(static_cast(swapchain_image_resources.size()) * texture_count)}; + .setDescriptorCount(static_cast(submission_resources.size()) * texture_count)}; auto const descriptor_pool = - vk::DescriptorPoolCreateInfo().setMaxSets(static_cast(swapchain_image_resources.size())).setPoolSizes(poolSizes); + vk::DescriptorPoolCreateInfo().setMaxSets(static_cast(submission_resources.size())).setPoolSizes(poolSizes); auto result = device.createDescriptorPool(&descriptor_pool, nullptr, &desc_pool); VERIFY(result == vk::Result::eSuccess); @@ -2498,13 +2563,13 @@ void Demo::prepare_descriptor_set() { .setDescriptorType(vk::DescriptorType::eCombinedImageSampler) .setImageInfo(tex_descs); - for (auto &swapchain_image_resource : swapchain_image_resources) { - auto result = device.allocateDescriptorSets(&alloc_info, &swapchain_image_resource.descriptor_set); + for (auto &submission_resource : submission_resources) { + auto result = device.allocateDescriptorSets(&alloc_info, &submission_resource.descriptor_set); VERIFY(result == vk::Result::eSuccess); - buffer_info.setBuffer(swapchain_image_resource.uniform_buffer); - writes[0].setDstSet(swapchain_image_resource.descriptor_set); - writes[1].setDstSet(swapchain_image_resource.descriptor_set); + buffer_info.setBuffer(submission_resource.uniform_buffer); + writes[0].setDstSet(submission_resource.descriptor_set); + writes[1].setDstSet(submission_resource.descriptor_set); device.updateDescriptorSets(writes, {}); } } @@ -2513,7 +2578,7 @@ void Demo::prepare_framebuffers() { std::array attachments; attachments[1] = depth.view; - for (auto &swapchain_image_resource : swapchain_image_resources) { + for (auto &swapchain_image_resource : swapchain_resources) { attachments[0] = swapchain_image_resource.view; auto const framebuffer_return = device.createFramebuffer(vk::FramebufferCreateInfo() .setRenderPass(render_pass) @@ -2526,6 +2591,23 @@ void Demo::prepare_framebuffers() { } } +void Demo::prepare_submission_sync_objects() { + // Create semaphores to synchronize acquiring presentable buffers before + // rendering and waiting for drawing to be complete before presenting + auto const semaphoreCreateInfo = vk::SemaphoreCreateInfo(); + + // Create fences that we can use to throttle if we get too far + // ahead of the image presents + auto const fence_ci = vk::FenceCreateInfo().setFlags(vk::FenceCreateFlagBits::eSignaled); + for (auto &submission_resource : submission_resources) { + vk::Result result = device.createFence(&fence_ci, nullptr, &submission_resource.fence); + VERIFY(result == vk::Result::eSuccess); + + result = device.createSemaphore(&semaphoreCreateInfo, nullptr, &submission_resource.image_acquired_semaphore); + VERIFY(result == vk::Result::eSuccess); + } +} + vk::ShaderModule Demo::prepare_fs() { const uint32_t fragShaderCode[] = { #ifdef CUBE_FRAG_INC @@ -2875,63 +2957,47 @@ vk::ShaderModule Demo::prepare_vs() { return vert_shader_module; } -void Demo::destroy_swapchain_related_resources() { - device.destroyDescriptorPool(desc_pool); - - device.destroyPipeline(pipeline); - device.destroyPipelineCache(pipelineCache); - device.destroyRenderPass(render_pass); - device.destroyPipelineLayout(pipeline_layout); - device.destroyDescriptorSetLayout(desc_layout); - - for (const auto &tex : textures) { - device.destroyImageView(tex.view); - device.destroyImage(tex.image); - device.freeMemory(tex.mem); - device.destroySampler(tex.sampler); - } - - device.destroyImageView(depth.view); - device.destroyImage(depth.image); - device.freeMemory(depth.mem); - - for (const auto &resource : swapchain_image_resources) { - device.destroyFramebuffer(resource.framebuffer); - device.destroyImageView(resource.view); - device.freeCommandBuffers(cmd_pool, {resource.cmd}); - device.destroyBuffer(resource.uniform_buffer); - device.unmapMemory(resource.uniform_memory); - device.freeMemory(resource.uniform_memory); - } - - device.destroyCommandPool(cmd_pool); - if (separate_present_queue) { - device.destroyCommandPool(present_cmd_pool); - } -} - void Demo::resize() { // Don't react to resize until after first initialization. - if (!prepared) { - if (is_minimized) { - prepare(); - } + if (!initialized) { + return; + } + + // Don't do anything if the surface has zero size, as vulkan disallows creating swapchains with zero area + // We use is_minimized to track this because zero size window usually occurs from minimizing + if (width == 0 || height == 0) { + is_minimized = true; return; + } else { + is_minimized = false; } // In order to properly resize the window, we must re-create the // swapchain - // AND redo the command buffers, etc. // - // First, perform part of the cleanup() function: - prepared = false; - auto result = device.waitIdle(); - VERIFY(result == vk::Result::eSuccess); - destroy_swapchain_related_resources(); + // First, destroy the old swapchain and its associated resources, setting swapchain_ready to false to prevent draw from running + if (swapchain_ready) { + swapchain_ready = false; + auto result = device.waitIdle(); + VERIFY(result == vk::Result::eSuccess); - // Second, re-perform the prepare() function, which will re-create the - // swapchain. - prepare(); + device.destroyImageView(depth.view); + device.destroyImage(depth.image); + device.freeMemory(depth.mem); + depth = {}; + + for (auto &swapchain_resource : swapchain_resources) { + device.destroyFramebuffer(swapchain_resource.framebuffer); + device.destroyImageView(swapchain_resource.view); + device.destroySemaphore(swapchain_resource.draw_complete_semaphore); + if (separate_present_queue) { + device.destroySemaphore(swapchain_resource.image_ownership_semaphore); + } + } + swapchain_resources.clear(); + } + // Second, recreate the swapchain, depth buffer, and framebuffers. + prepare_swapchain(); } void Demo::set_image_layout(vk::Image image, vk::ImageAspectFlags aspectMask, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, @@ -2982,7 +3048,7 @@ void Demo::set_image_layout(vk::Image image, vk::ImageAspectFlags aspectMask, vk .setSubresourceRange(vk::ImageSubresourceRange(aspectMask, 0, 1, 0, 1))); } -void Demo::update_data_buffer() { +void Demo::update_data_buffer(void *uniform_memory_ptr) { mat4x4 VP; mat4x4_mul(VP, projection_matrix, view_matrix); @@ -2995,7 +3061,7 @@ void Demo::update_data_buffer() { mat4x4 MVP; mat4x4_mul(MVP, VP, model_matrix); - memcpy(swapchain_image_resources[current_buffer].uniform_memory_ptr, (const void *)&MVP[0][0], sizeof(MVP)); + memcpy(uniform_memory_ptr, (const void *)&MVP[0][0], sizeof(MVP)); } /* Convert ppm image data from header file into RGBA texture image */ @@ -3011,19 +3077,16 @@ bool Demo::loadTexture(const char *filename, uint8_t *rgba_data, vk::Subresource if ((unsigned char *)cPtr >= (lunarg_ppm + lunarg_ppm_len) || strncmp(cPtr, "P6\n", 3)) { return false; } - while (strncmp(cPtr++, "\n", 1)) - ; + while (strncmp(cPtr++, "\n", 1)); sscanf(cPtr, "%u %u", &width, &height); if (rgba_data == nullptr) { return true; } - while (strncmp(cPtr++, "\n", 1)) - ; + while (strncmp(cPtr++, "\n", 1)); if ((unsigned char *)cPtr >= (lunarg_ppm + lunarg_ppm_len) || strncmp(cPtr, "255\n", 4)) { return false; } - while (strncmp(cPtr++, "\n", 1)) - ; + while (strncmp(cPtr++, "\n", 1)); for (uint32_t y = 0; y < height; y++) { uint8_t *rowPtr = rgba_data; for (uint32_t x = 0; x < width; x++) { @@ -3076,12 +3139,14 @@ vk::SurfaceFormatKHR Demo::pick_surface_format(const std::vector void Demo::run() { - if (!prepared) { + if (!initialized || !swapchain_ready) { return; } draw(); - curFrame++; + if (!is_minimized) { + curFrame++; + } if (frameCount != UINT32_MAX && curFrame == frameCount) { PostQuitMessage(validation_error); @@ -3229,12 +3294,15 @@ void Demo::run() { XNextEvent(xlib_display, &event); handle_xlib_event(&event); } + if (initialized && swapchain_ready) { + draw(); + if (!is_minimized) { + curFrame++; + } - draw(); - curFrame++; - - if (frameCount != UINT32_MAX && curFrame == frameCount) { - quit = true; + if (frameCount != UINT32_MAX && curFrame == frameCount) { + quit = true; + } } } } @@ -3300,11 +3368,14 @@ void Demo::run() { free(event); event = xcb_poll_for_event(connection); } - - draw(); - curFrame++; - if (frameCount != UINT32_MAX && curFrame == frameCount) { - quit = true; + if (initialized && swapchain_ready) { + draw(); + if (!is_minimized) { + curFrame++; + } + if (frameCount != UINT32_MAX && curFrame == frameCount) { + quit = true; + } } } } @@ -3351,10 +3422,14 @@ void Demo::run() { wl_display_dispatch(wayland_display); } else { wl_display_dispatch_pending(wayland_display); - draw(); - curFrame++; - if (frameCount != UINT32_MAX && curFrame == frameCount) { - quit = true; + if (initialized && swapchain_ready) { + draw(); + if (!is_minimized) { + curFrame++; + } + if (frameCount != UINT32_MAX && curFrame == frameCount) { + quit = true; + } } } } @@ -3463,11 +3538,14 @@ void Demo::run() { if (!event_buffer->GetEvent(event_buffer, DFB_EVENT(&event))) handle_directfb_event(&event); } else { if (!event_buffer->GetEvent(event_buffer, DFB_EVENT(&event))) handle_directfb_event(&event); - - draw(); - curFrame++; - if (frameCount != UINT32_MAX && curFrame == frameCount) { - quit = true; + if (initialized && swapchain_ready) { + draw(); + if (!is_minimized) { + curFrame++; + } + if (frameCount != UINT32_MAX && curFrame == frameCount) { + quit = true; + } } } } @@ -3514,8 +3592,13 @@ void Demo::create_window() { #if defined(VK_USE_PLATFORM_METAL_EXT) template <> void Demo::run() { + if (!initialized || !swapchain_ready) { + return; + } draw(); - curFrame++; + if (!is_minimized) { + curFrame++; + } if (frameCount != UINT32_MAX && curFrame == frameCount) { quit = true; } @@ -3726,11 +3809,13 @@ void Demo::run() { } } - if (pause) { + if (pause || !initialized || !swapchain_ready) { } else { update_data_buffer(); draw(); - curFrame++; + if (!is_minimized) { + curFrame++; + } if (frameCount != UINT32_MAX && curFrame == frameCount) { quit = true; } @@ -3832,7 +3917,7 @@ void Demo::create_window() { auto resize_callback = [this](uint32_t width, uint32_t height) { this->width = width; this->height = height; - if (prepared) { + if (initialized) { resize(); } }; @@ -3897,14 +3982,17 @@ void Demo::run() { num_frames = static_cast(fps); elapsed_frames = 0; } + if (initialized && swapchain_ready) { + draw(); - draw(); - - curFrame++; - elapsed_frames++; + if (!is_minimized) { + curFrame++; + } + elapsed_frames++; - if (frameCount != UINT32_MAX && curFrame == frameCount) { - quit = true; + if (frameCount != UINT32_MAX && curFrame == frameCount) { + quit = true; + } } } }