From 45d46755d58569623f708ad01f6bb780f0d5939d Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Tue, 8 Apr 2025 15:56:08 -0500 Subject: [PATCH 1/3] cube: Only count non-minimized frames --- cube/cube.c | 32 ++++++++++++++++++++++++-------- cube/cube.cpp | 32 ++++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/cube/cube.c b/cube/cube.c index 9e58ea36c..e424836d7 100644 --- a/cube/cube.c +++ b/cube/cube.c @@ -2656,7 +2656,9 @@ static void demo_run(struct demo *demo) { if (!demo->prepared) return; demo_draw(demo); - demo->curFrame++; + if (demo->is_minimized) { + demo->curFrame++; + } if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { PostQuitMessage(validation_error); } @@ -2844,7 +2846,9 @@ static void demo_run_xlib(struct demo *demo) { } demo_draw(demo); - demo->curFrame++; + if (demo->is_minimized) { + demo->curFrame++; + } if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; } } @@ -2910,7 +2914,9 @@ static void demo_run_xcb(struct demo *demo) { } demo_draw(demo); - demo->curFrame++; + if (demo->is_minimized) { + demo->curFrame++; + } if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; } } @@ -2969,7 +2975,9 @@ static void demo_run(struct demo *demo) { wl_display_dispatch_pending(demo->wayland_display); demo_draw(demo); - demo->curFrame++; + if (demo->is_minimized) { + demo->curFrame++; + } if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; } } @@ -3115,7 +3123,9 @@ 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); demo_draw(demo); - demo->curFrame++; + if (demo->is_minimized) { + demo->curFrame++; + } if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; } } @@ -3126,13 +3136,17 @@ static void demo_run(struct demo *demo) { if (!demo->prepared) 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) { demo_draw(demo); - demo->curFrame++; + if (demo->is_minimized) { + demo->curFrame++; + } if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { demo->quit = TRUE; } @@ -3377,7 +3391,9 @@ static void demo_run(struct demo *demo) { if (demo->pause) { } else { demo_draw(demo); - demo->curFrame++; + if (demo->is_minimized) { + demo->curFrame++; + } if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { demo->quit = true; } diff --git a/cube/cube.cpp b/cube/cube.cpp index 1d462b0e4..24f7b3f07 100644 --- a/cube/cube.cpp +++ b/cube/cube.cpp @@ -3081,7 +3081,9 @@ void Demo::run() { } draw(); - curFrame++; + if (!is_minimized) { + curFrame++; + } if (frameCount != UINT32_MAX && curFrame == frameCount) { PostQuitMessage(validation_error); @@ -3231,7 +3233,9 @@ void Demo::run() { } draw(); - curFrame++; + if (!is_minimized) { + curFrame++; + } if (frameCount != UINT32_MAX && curFrame == frameCount) { quit = true; @@ -3302,7 +3306,9 @@ void Demo::run() { } draw(); - curFrame++; + if (!is_minimized) { + curFrame++; + } if (frameCount != UINT32_MAX && curFrame == frameCount) { quit = true; } @@ -3352,7 +3358,9 @@ void Demo::run() { } else { wl_display_dispatch_pending(wayland_display); draw(); - curFrame++; + if (!is_minimized) { + curFrame++; + } if (frameCount != UINT32_MAX && curFrame == frameCount) { quit = true; } @@ -3465,7 +3473,9 @@ void Demo::run() { if (!event_buffer->GetEvent(event_buffer, DFB_EVENT(&event))) handle_directfb_event(&event); draw(); - curFrame++; + if (!is_minimized) { + curFrame++; + } if (frameCount != UINT32_MAX && curFrame == frameCount) { quit = true; } @@ -3515,7 +3525,9 @@ void Demo::create_window() { template <> void Demo::run() { draw(); - curFrame++; + if (!is_minimized) { + curFrame++; + } if (frameCount != UINT32_MAX && curFrame == frameCount) { quit = true; } @@ -3730,7 +3742,9 @@ void Demo::run() { } else { update_data_buffer(); draw(); - curFrame++; + if (!is_minimized) { + curFrame++; + } if (frameCount != UINT32_MAX && curFrame == frameCount) { quit = true; } @@ -3900,7 +3914,9 @@ void Demo::run() { draw(); - curFrame++; + if (!is_minimized) { + curFrame++; + } elapsed_frames++; if (frameCount != UINT32_MAX && curFrame == frameCount) { From 8a9bd756196ad6b6c365cd416566a35020d6a7e9 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Thu, 10 Apr 2025 15:59:30 -0500 Subject: [PATCH 2/3] cubepp: Fix incremental present on swapchain recreate --- cube/cube.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cube/cube.cpp b/cube/cube.cpp index 24f7b3f07..7301482f4 100644 --- a/cube/cube.cpp +++ b/cube/cube.cpp @@ -500,6 +500,7 @@ struct Demo { 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; @@ -880,6 +881,7 @@ void Demo::draw() { auto present_result = present_queue.presentKHR(&presentInfo); frame_index += 1; frame_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: @@ -2076,6 +2078,7 @@ 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 @@ -2167,6 +2170,7 @@ void Demo::prepare() { current_buffer = 0; prepared = true; + first_swapchain_frame = true; } void Demo::prepare_buffers() { From 83930c71467908798b72ce7f9eb0ac768e8ae495 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Tue, 8 Apr 2025 16:10:15 -0500 Subject: [PATCH 3/3] cube: Re-record command buffers each frame Rerecord command buffers every frame, as command buffer recording isn't slow, and outside of trivial samples (like vkcube) isn't a practical architecture for organizing a Vulkan renderer. This makes vkcube and vkcubepp reflect typical Vulkan renderer architecture and serves as a better sample. Create structs to organize objects into distinct synchronization scopes, one for submissions and the other for swapchains. Resources associated with a swapchain image such, as image views, framebuffers, and presentation semaphores, must be duplicated as many times as there are swapchain images. This number is dependent on the runtime, so can't be pre-determined. The submission resources, which are the command buffers, fences, descriptor sets, device memory, and uniform buffers, only needs duplication for the pipelining. Specifically, this allows the GPU to execute a command buffer while the CPU record the next frame's command buffer. The FRAME_LAG constant dictates the pipeline depth, commonly called double buffering, and is set to 2 since that is sufficient for a vast majority of use cases. Swapchain resizing now only re-creates the bare necessity of resources: Swapchain, image views, framebuffers, presentation semaphores, and the depth buffer. Presentation semaphores are recreated to prevent state from an old swapchain from polluting a new swapchain. The depth buffer is re-created since its size is dependent on the window size. All other objects, such as pipelines and renderpasses, do not need recreation as they either are not liable to change during resizing, or have mutable state that can be flexibly updated, such as dynamic pipeline viewport and scissors. The code for dynamic viewports and scissors was already present but wasn't taken advantage of until now. --- cube/cube.c | 519 ++++++++++++++++++++++++++----------------------- cube/cube.cpp | 524 ++++++++++++++++++++++++++++---------------------- 2 files changed, 574 insertions(+), 469 deletions(-) diff --git a/cube/cube.c b/cube/cube.c index e424836d7..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,7 +2715,7 @@ 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); if (demo->is_minimized) { @@ -2844,12 +2906,13 @@ static void demo_run_xlib(struct demo *demo) { XNextEvent(demo->xlib_display, &event); demo_handle_xlib_event(demo, &event); } - - demo_draw(demo); - if (demo->is_minimized) { - demo->curFrame++; + 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; } - if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; } } #endif @@ -2912,12 +2975,13 @@ static void demo_run_xcb(struct demo *demo) { free(event); event = xcb_poll_for_event(demo->connection); } - - demo_draw(demo); - if (demo->is_minimized) { - demo->curFrame++; + 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; } - if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; } } @@ -2973,12 +3037,13 @@ static void demo_run(struct demo *demo) { // Pump events wl_display_dispatch_pending(demo->wayland_display); - - demo_draw(demo); - if (demo->is_minimized) { - demo->curFrame++; + 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; } - if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; } } } @@ -3121,19 +3186,20 @@ 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); - if (demo->is_minimized) { - demo->curFrame++; + 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; } - 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); if (demo->is_minimized) { @@ -3143,6 +3209,8 @@ static void demo_run(struct demo *demo) { #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); if (demo->is_minimized) { demo->curFrame++; @@ -3388,7 +3456,7 @@ static void demo_run(struct demo *demo) { } } - if (demo->pause) { + if (demo->pause || !demo->initialized || !demo->swapchain_ready) { } else { demo_draw(demo); if (demo->is_minimized) { @@ -4607,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 @@ -4840,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; @@ -4965,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; @@ -4985,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); } @@ -5010,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; } @@ -5026,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; @@ -5044,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 7301482f4..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,8 @@ 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; @@ -556,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 @@ -672,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); @@ -688,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); } @@ -714,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); @@ -788,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( @@ -797,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; @@ -812,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: @@ -836,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 @@ -850,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) { @@ -864,23 +932,23 @@ 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 @@ -892,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) { @@ -903,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() @@ -920,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; @@ -977,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); @@ -1007,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); @@ -1185,6 +1258,8 @@ void Demo::init(int argc, char **argv) { exit(1); } + initialized = false; + init_vk(); spin_angle = 4.0f; @@ -1834,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]; } @@ -2081,56 +2152,28 @@ void Demo::init_vk_swapchain() { 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) { @@ -2139,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]; } } @@ -2155,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 @@ -2168,12 +2208,14 @@ void Demo::prepare() { destroy_texture(staging_texture); } - current_buffer = 0; - prepared = true; - first_swapchain_frame = 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 @@ -2323,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() { @@ -2365,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); @@ -2379,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); } } @@ -2470,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); @@ -2502,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, {}); } } @@ -2517,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) @@ -2530,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 @@ -2879,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, @@ -2986,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); @@ -2999,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 */ @@ -3015,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++) { @@ -3080,7 +3139,7 @@ vk::SurfaceFormatKHR Demo::pick_surface_format(const std::vector void Demo::run() { - if (!prepared) { + if (!initialized || !swapchain_ready) { return; } @@ -3235,14 +3294,15 @@ void Demo::run() { XNextEvent(xlib_display, &event); handle_xlib_event(&event); } + if (initialized && swapchain_ready) { + draw(); + if (!is_minimized) { + curFrame++; + } - draw(); - if (!is_minimized) { - curFrame++; - } - - if (frameCount != UINT32_MAX && curFrame == frameCount) { - quit = true; + if (frameCount != UINT32_MAX && curFrame == frameCount) { + quit = true; + } } } } @@ -3308,13 +3368,14 @@ void Demo::run() { free(event); event = xcb_poll_for_event(connection); } - - draw(); - if (!is_minimized) { - 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; + } } } } @@ -3361,12 +3422,14 @@ void Demo::run() { wl_display_dispatch(wayland_display); } else { wl_display_dispatch_pending(wayland_display); - draw(); - if (!is_minimized) { - 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; + } } } } @@ -3475,13 +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(); - if (!is_minimized) { - 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; + } } } } @@ -3528,6 +3592,9 @@ void Demo::create_window() { #if defined(VK_USE_PLATFORM_METAL_EXT) template <> void Demo::run() { + if (!initialized || !swapchain_ready) { + return; + } draw(); if (!is_minimized) { curFrame++; @@ -3742,7 +3809,7 @@ void Demo::run() { } } - if (pause) { + if (pause || !initialized || !swapchain_ready) { } else { update_data_buffer(); draw(); @@ -3850,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(); } }; @@ -3915,16 +3982,17 @@ void Demo::run() { num_frames = static_cast(fps); elapsed_frames = 0; } + if (initialized && swapchain_ready) { + draw(); - draw(); - - if (!is_minimized) { - 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; + } } } }