/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2016 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/ui/vulkan/vulkan_swap_chain.h" #include #include #include #include "xenia/base/assert.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/ui/vulkan/vulkan.h" #include "xenia/ui/vulkan/vulkan_device.h" #include "xenia/ui/vulkan/vulkan_instance.h" #include "xenia/ui/vulkan/vulkan_util.h" DEFINE_bool(vulkan_random_clear_color, false, "Randomizes framebuffer clear color."); namespace xe { namespace ui { namespace vulkan { VulkanSwapChain::VulkanSwapChain(VulkanInstance* instance, VulkanDevice* device) : instance_(instance), device_(device) {} VulkanSwapChain::~VulkanSwapChain() { Shutdown(); } bool VulkanSwapChain::Initialize(VkSurfaceKHR surface) { surface_ = surface; VkBool32 surface_supported = false; auto err = vkGetPhysicalDeviceSurfaceSupportKHR( *device_, device_->queue_family_index(), surface, &surface_supported); assert_true(surface_supported); CheckResult(err, "vkGetPhysicalDeviceSurfaceSupportKHR"); // Query supported target formats. uint32_t count = 0; err = vkGetPhysicalDeviceSurfaceFormatsKHR(*device_, surface_, &count, nullptr); CheckResult(err, "vkGetPhysicalDeviceSurfaceFormatsKHR"); std::vector surface_formats; surface_formats.resize(count); err = vkGetPhysicalDeviceSurfaceFormatsKHR(*device_, surface_, &count, surface_formats.data()); CheckResult(err, "vkGetPhysicalDeviceSurfaceFormatsKHR"); // If the format list includes just one entry of VK_FORMAT_UNDEFINED the // surface has no preferred format. // Otherwise, at least one supported format will be returned. assert_true(surface_formats.size() >= 1); if (surface_formats.size() == 1 && surface_formats[0].format == VK_FORMAT_UNDEFINED) { // Fallback to common RGBA. surface_format_ = VK_FORMAT_R8G8B8A8_UNORM; } else { // Use first defined format. surface_format_ = surface_formats[0].format; } // Query surface min/max/caps. VkSurfaceCapabilitiesKHR surface_caps; err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(*device_, surface_, &surface_caps); CheckResult(err, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); // Query surface properties so we can configure ourselves within bounds. std::vector present_modes; err = vkGetPhysicalDeviceSurfacePresentModesKHR(*device_, surface_, &count, nullptr); CheckResult(err, "vkGetPhysicalDeviceSurfacePresentModesKHR"); present_modes.resize(count); err = vkGetPhysicalDeviceSurfacePresentModesKHR(*device_, surface_, &count, present_modes.data()); CheckResult(err, "vkGetPhysicalDeviceSurfacePresentModesKHR"); // Calculate swapchain target dimensions. VkExtent2D extent = surface_caps.currentExtent; if (surface_caps.currentExtent.width == -1) { assert_true(surface_caps.currentExtent.height == -1); // Undefined extents, so we need to pick something. XELOGI("Swap chain target surface extents undefined; guessing value"); extent.width = 1280; extent.height = 720; } surface_width_ = extent.width; surface_height_ = extent.height; // Always prefer mailbox mode (non-tearing, low-latency). // If it's not available we'll use immediate (tearing, low-latency). // If not even that we fall back to FIFO, which sucks. VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR; for (size_t i = 0; i < present_modes.size(); ++i) { if (present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) { // This is the best, so early-out. present_mode = VK_PRESENT_MODE_MAILBOX_KHR; break; } else if (present_modes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR) { present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; } } // Determine the number of images (1 + number queued). uint32_t image_count = surface_caps.minImageCount + 1; if (surface_caps.maxImageCount > 0 && image_count > surface_caps.maxImageCount) { // Too many requested - use whatever we can. XELOGI("Requested number of swapchain images (%d) exceeds maximum (%d)", image_count, surface_caps.maxImageCount); image_count = surface_caps.maxImageCount; } // Always pass through whatever transform the surface started with (so long // as it's supported). VkSurfaceTransformFlagBitsKHR pre_transform = surface_caps.currentTransform; VkSwapchainCreateInfoKHR create_info; create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; create_info.pNext = nullptr; create_info.flags = 0; create_info.surface = surface_; create_info.minImageCount = image_count; create_info.imageFormat = surface_format_; create_info.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; create_info.imageExtent.width = extent.width; create_info.imageExtent.height = extent.height; create_info.imageArrayLayers = 1; create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; create_info.queueFamilyIndexCount = 0; create_info.pQueueFamilyIndices = nullptr; create_info.preTransform = pre_transform; create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; create_info.presentMode = present_mode; create_info.clipped = VK_TRUE; create_info.oldSwapchain = nullptr; XELOGVK("Creating swap chain:"); XELOGVK(" minImageCount = %u", create_info.minImageCount); XELOGVK(" imageFormat = %s", to_string(create_info.imageFormat)); XELOGVK(" imageExtent = %d x %d", create_info.imageExtent.width, create_info.imageExtent.height); auto pre_transform_str = to_flags_string(create_info.preTransform); XELOGVK(" preTransform = %s", pre_transform_str.c_str()); XELOGVK(" imageArrayLayers = %u", create_info.imageArrayLayers); XELOGVK(" presentMode = %s", to_string(create_info.presentMode)); XELOGVK(" clipped = %s", create_info.clipped ? "true" : "false"); XELOGVK(" imageColorSpace = %s", to_string(create_info.imageColorSpace)); auto image_usage_flags_str = to_flags_string(create_info.imageUsage); XELOGVK(" imageUsageFlags = %s", image_usage_flags_str.c_str()); XELOGVK(" imageSharingMode = %s", to_string(create_info.imageSharingMode)); XELOGVK(" queueFamilyCount = %u", create_info.queueFamilyIndexCount); err = vkCreateSwapchainKHR(*device_, &create_info, nullptr, &handle); if (err) { XELOGE("Failed to create swapchain: %s", to_string(err)); return false; } // Create the pool used for transient buffers, so we can reset them all at // once. VkCommandPoolCreateInfo cmd_pool_info; cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; cmd_pool_info.pNext = nullptr; cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; cmd_pool_info.queueFamilyIndex = device_->queue_family_index(); err = vkCreateCommandPool(*device_, &cmd_pool_info, nullptr, &cmd_pool_); CheckResult(err, "vkCreateCommandPool"); // Primary command buffer VkCommandBufferAllocateInfo cmd_buffer_info; cmd_buffer_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmd_buffer_info.pNext = nullptr; cmd_buffer_info.commandPool = cmd_pool_; cmd_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmd_buffer_info.commandBufferCount = 2; err = vkAllocateCommandBuffers(*device_, &cmd_buffer_info, &cmd_buffer_); CheckResult(err, "vkCreateCommandBuffer"); // Make two command buffers we'll do all our primary rendering from. VkCommandBuffer command_buffers[2]; cmd_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY; cmd_buffer_info.commandBufferCount = 2; err = vkAllocateCommandBuffers(*device_, &cmd_buffer_info, command_buffers); CheckResult(err, "vkCreateCommandBuffer"); render_cmd_buffer_ = command_buffers[0]; copy_cmd_buffer_ = command_buffers[1]; // Create the render pass used to draw to the swap chain. // The actual framebuffer attached will depend on which image we are drawing // into. VkAttachmentDescription color_attachment; color_attachment.flags = 0; color_attachment.format = surface_format_; color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; // CLEAR; color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; color_attachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; color_attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentReference color_reference; color_reference.attachment = 0; color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentReference depth_reference; depth_reference.attachment = VK_ATTACHMENT_UNUSED; depth_reference.layout = VK_IMAGE_LAYOUT_UNDEFINED; VkSubpassDescription render_subpass; render_subpass.flags = 0; render_subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; render_subpass.inputAttachmentCount = 0; render_subpass.pInputAttachments = nullptr; render_subpass.colorAttachmentCount = 1; render_subpass.pColorAttachments = &color_reference; render_subpass.pResolveAttachments = nullptr; render_subpass.pDepthStencilAttachment = &depth_reference; render_subpass.preserveAttachmentCount = 0, render_subpass.pPreserveAttachments = nullptr; VkRenderPassCreateInfo render_pass_info; render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; render_pass_info.pNext = nullptr; render_pass_info.flags = 0; render_pass_info.attachmentCount = 1; render_pass_info.pAttachments = &color_attachment; render_pass_info.subpassCount = 1; render_pass_info.pSubpasses = &render_subpass; render_pass_info.dependencyCount = 0; render_pass_info.pDependencies = nullptr; err = vkCreateRenderPass(*device_, &render_pass_info, nullptr, &render_pass_); CheckResult(err, "vkCreateRenderPass"); // Create a semaphore we'll use to synchronize with the swapchain. VkSemaphoreCreateInfo semaphore_info; semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; semaphore_info.pNext = nullptr; semaphore_info.flags = 0; err = vkCreateSemaphore(*device_, &semaphore_info, nullptr, &image_available_semaphore_); CheckResult(err, "vkCreateSemaphore"); // Create another semaphore used to synchronize writes to the swap image. err = vkCreateSemaphore(*device_, &semaphore_info, nullptr, &image_usage_semaphore_); CheckResult(err, "vkCreateSemaphore"); // Get images we will be presenting to. // Note that this may differ from our requested amount. uint32_t actual_image_count = 0; std::vector images; err = vkGetSwapchainImagesKHR(*device_, handle, &actual_image_count, nullptr); CheckResult(err, "vkGetSwapchainImagesKHR"); images.resize(actual_image_count); err = vkGetSwapchainImagesKHR(*device_, handle, &actual_image_count, images.data()); CheckResult(err, "vkGetSwapchainImagesKHR"); // Create all buffers. buffers_.resize(images.size()); for (size_t i = 0; i < buffers_.size(); ++i) { if (!InitializeBuffer(&buffers_[i], images[i])) { XELOGE("Failed to initialize a swapchain buffer"); return false; } buffers_[i].image_layout = VK_IMAGE_LAYOUT_UNDEFINED; } XELOGVK("Swap chain initialized successfully!"); return true; } bool VulkanSwapChain::InitializeBuffer(Buffer* buffer, VkImage target_image) { DestroyBuffer(buffer); buffer->image = target_image; // Create an image view for the presentation image. // This will be used as a framebuffer attachment. VkImageViewCreateInfo image_view_info; image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; image_view_info.pNext = nullptr; image_view_info.flags = 0; image_view_info.image = buffer->image; image_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; image_view_info.format = surface_format_; image_view_info.components.r = VK_COMPONENT_SWIZZLE_R; image_view_info.components.g = VK_COMPONENT_SWIZZLE_G; image_view_info.components.b = VK_COMPONENT_SWIZZLE_B; image_view_info.components.a = VK_COMPONENT_SWIZZLE_A; image_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; image_view_info.subresourceRange.baseMipLevel = 0; image_view_info.subresourceRange.levelCount = 1; image_view_info.subresourceRange.baseArrayLayer = 0; image_view_info.subresourceRange.layerCount = 1; auto err = vkCreateImageView(*device_, &image_view_info, nullptr, &buffer->image_view); CheckResult(err, "vkCreateImageView"); // Create the framebuffer used to render into this image. VkImageView attachments[] = {buffer->image_view}; VkFramebufferCreateInfo framebuffer_info; framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebuffer_info.pNext = nullptr; framebuffer_info.flags = 0; framebuffer_info.renderPass = render_pass_; framebuffer_info.attachmentCount = static_cast(xe::countof(attachments)); framebuffer_info.pAttachments = attachments; framebuffer_info.width = surface_width_; framebuffer_info.height = surface_height_; framebuffer_info.layers = 1; err = vkCreateFramebuffer(*device_, &framebuffer_info, nullptr, &buffer->framebuffer); CheckResult(err, "vkCreateFramebuffer"); return true; } void VulkanSwapChain::DestroyBuffer(Buffer* buffer) { if (buffer->framebuffer) { vkDestroyFramebuffer(*device_, buffer->framebuffer, nullptr); buffer->framebuffer = nullptr; } if (buffer->image_view) { vkDestroyImageView(*device_, buffer->image_view, nullptr); buffer->image_view = nullptr; } // Image is taken care of by the presentation engine. buffer->image = nullptr; } bool VulkanSwapChain::Reinitialize() { // Hacky, but stash the surface so we can reuse it. auto surface = surface_; surface_ = nullptr; Shutdown(); return Initialize(surface); } void VulkanSwapChain::WaitAndSignalSemaphore(VkSemaphore sem) { wait_and_signal_semaphores_.push_back(sem); } void VulkanSwapChain::Shutdown() { // TODO(benvanik): properly wait for a clean state. for (auto& buffer : buffers_) { DestroyBuffer(&buffer); } buffers_.clear(); if (image_available_semaphore_) { vkDestroySemaphore(*device_, image_available_semaphore_, nullptr); image_available_semaphore_ = nullptr; } if (render_pass_) { vkDestroyRenderPass(*device_, render_pass_, nullptr); render_pass_ = nullptr; } if (render_cmd_buffer_) { vkFreeCommandBuffers(*device_, cmd_pool_, 1, &render_cmd_buffer_); render_cmd_buffer_ = nullptr; } if (cmd_pool_) { vkDestroyCommandPool(*device_, cmd_pool_, nullptr); cmd_pool_ = nullptr; } // images_ doesn't need to be cleaned up as the swapchain does it implicitly. if (handle) { vkDestroySwapchainKHR(*device_, handle, nullptr); handle = nullptr; } if (surface_) { vkDestroySurfaceKHR(*instance_, surface_, nullptr); surface_ = nullptr; } } bool VulkanSwapChain::Begin() { wait_and_signal_semaphores_.clear(); // Get the index of the next available swapchain image. auto err = vkAcquireNextImageKHR(*device_, handle, 0, image_available_semaphore_, nullptr, ¤t_buffer_index_); CheckResult(err, "vkAcquireNextImageKHR"); // Wait for the acquire semaphore to be signaled so that the following // operations know they can start modifying the image. VkSubmitInfo wait_submit_info; wait_submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; wait_submit_info.pNext = nullptr; VkPipelineStageFlags wait_dst_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; wait_submit_info.waitSemaphoreCount = 1; wait_submit_info.pWaitSemaphores = &image_available_semaphore_; wait_submit_info.pWaitDstStageMask = &wait_dst_stage; wait_submit_info.commandBufferCount = 0; wait_submit_info.pCommandBuffers = nullptr; wait_submit_info.signalSemaphoreCount = 1; wait_submit_info.pSignalSemaphores = &image_usage_semaphore_; { std::lock_guard queue_lock(device_->primary_queue_mutex()); err = vkQueueSubmit(device_->primary_queue(), 1, &wait_submit_info, nullptr); } CheckResult(err, "vkQueueSubmit"); // Reset all command buffers. vkResetCommandBuffer(render_cmd_buffer_, 0); vkResetCommandBuffer(copy_cmd_buffer_, 0); auto& current_buffer = buffers_[current_buffer_index_]; // Build the command buffer that will execute all queued rendering buffers. VkCommandBufferInheritanceInfo inherit_info; inherit_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO; inherit_info.pNext = nullptr; inherit_info.renderPass = render_pass_; inherit_info.subpass = 0; inherit_info.framebuffer = current_buffer.framebuffer; inherit_info.occlusionQueryEnable = VK_FALSE; inherit_info.queryFlags = 0; inherit_info.pipelineStatistics = 0; VkCommandBufferBeginInfo begin_info; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.pNext = nullptr; begin_info.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT | VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; begin_info.pInheritanceInfo = &inherit_info; err = vkBeginCommandBuffer(render_cmd_buffer_, &begin_info); CheckResult(err, "vkBeginCommandBuffer"); // Start recording the copy command buffer as well. begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; err = vkBeginCommandBuffer(copy_cmd_buffer_, &begin_info); CheckResult(err, "vkBeginCommandBuffer"); // First: Issue a command to clear the render target. VkImageSubresourceRange clear_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; VkClearColorValue clear_color; clear_color.float32[0] = 238 / 255.0f; clear_color.float32[1] = 238 / 255.0f; clear_color.float32[2] = 238 / 255.0f; clear_color.float32[3] = 1.0f; if (FLAGS_vulkan_random_clear_color) { clear_color.float32[0] = rand() / static_cast(RAND_MAX); // NOLINT(runtime/threadsafe_fn) clear_color.float32[1] = 1.0f; clear_color.float32[2] = 0.0f; } vkCmdClearColorImage(copy_cmd_buffer_, current_buffer.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1, &clear_range); return true; } bool VulkanSwapChain::End() { auto& current_buffer = buffers_[current_buffer_index_]; auto err = vkEndCommandBuffer(render_cmd_buffer_); CheckResult(err, "vkEndCommandBuffer"); err = vkEndCommandBuffer(copy_cmd_buffer_); CheckResult(err, "vkEndCommandBuffer"); // Build primary command buffer. vkResetCommandBuffer(cmd_buffer_, 0); VkCommandBufferBeginInfo begin_info; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.pNext = nullptr; begin_info.flags = 0; begin_info.pInheritanceInfo = nullptr; vkBeginCommandBuffer(cmd_buffer_, &begin_info); // Transition the image to a format we can copy to. VkImageMemoryBarrier pre_image_copy_barrier; pre_image_copy_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; pre_image_copy_barrier.pNext = nullptr; pre_image_copy_barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; pre_image_copy_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; pre_image_copy_barrier.oldLayout = current_buffer.image_layout; pre_image_copy_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; pre_image_copy_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; pre_image_copy_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; pre_image_copy_barrier.image = current_buffer.image; pre_image_copy_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; vkCmdPipelineBarrier(cmd_buffer_, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &pre_image_copy_barrier); current_buffer.image_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; // Execute copy commands vkCmdExecuteCommands(cmd_buffer_, 1, ©_cmd_buffer_); // Transition the image to a color attachment target for drawing. VkImageMemoryBarrier pre_image_memory_barrier; pre_image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; pre_image_memory_barrier.pNext = nullptr; pre_image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; pre_image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; pre_image_memory_barrier.image = current_buffer.image; pre_image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; pre_image_memory_barrier.subresourceRange.baseMipLevel = 0; pre_image_memory_barrier.subresourceRange.levelCount = 1; pre_image_memory_barrier.subresourceRange.baseArrayLayer = 0; pre_image_memory_barrier.subresourceRange.layerCount = 1; pre_image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; pre_image_memory_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; pre_image_memory_barrier.oldLayout = current_buffer.image_layout; pre_image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; vkCmdPipelineBarrier(cmd_buffer_, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, 0, nullptr, 0, nullptr, 1, &pre_image_memory_barrier); current_buffer.image_layout = pre_image_memory_barrier.newLayout; // Begin render pass. VkRenderPassBeginInfo render_pass_begin_info; render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; render_pass_begin_info.pNext = nullptr; render_pass_begin_info.renderPass = render_pass_; render_pass_begin_info.framebuffer = current_buffer.framebuffer; render_pass_begin_info.renderArea.offset.x = 0; render_pass_begin_info.renderArea.offset.y = 0; render_pass_begin_info.renderArea.extent.width = surface_width_; render_pass_begin_info.renderArea.extent.height = surface_height_; render_pass_begin_info.clearValueCount = 0; render_pass_begin_info.pClearValues = nullptr; vkCmdBeginRenderPass(cmd_buffer_, &render_pass_begin_info, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS); // Render commands. vkCmdExecuteCommands(cmd_buffer_, 1, &render_cmd_buffer_); // End render pass. vkCmdEndRenderPass(cmd_buffer_); // Transition the image to a format the presentation engine can source from. // FIXME: Do we need more synchronization here between the copy buffer? VkImageMemoryBarrier post_image_memory_barrier; post_image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; post_image_memory_barrier.pNext = nullptr; post_image_memory_barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; post_image_memory_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; post_image_memory_barrier.oldLayout = current_buffer.image_layout; post_image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; post_image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; post_image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; post_image_memory_barrier.image = current_buffer.image; post_image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; post_image_memory_barrier.subresourceRange.baseMipLevel = 0; post_image_memory_barrier.subresourceRange.levelCount = 1; post_image_memory_barrier.subresourceRange.baseArrayLayer = 0; post_image_memory_barrier.subresourceRange.layerCount = 1; vkCmdPipelineBarrier(cmd_buffer_, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &post_image_memory_barrier); current_buffer.image_layout = post_image_memory_barrier.newLayout; vkEndCommandBuffer(cmd_buffer_); VkPipelineStageFlags wait_dst_stage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; std::vector semaphores; for (size_t i = 0; i < wait_and_signal_semaphores_.size(); i++) { semaphores.push_back(wait_and_signal_semaphores_[i]); } semaphores.push_back(image_usage_semaphore_); // Submit commands. // Wait on the image usage semaphore (signaled when an image is available) VkSubmitInfo render_submit_info; render_submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; render_submit_info.pNext = nullptr; render_submit_info.waitSemaphoreCount = uint32_t(semaphores.size()); render_submit_info.pWaitSemaphores = semaphores.data(); render_submit_info.pWaitDstStageMask = &wait_dst_stage; render_submit_info.commandBufferCount = 1; render_submit_info.pCommandBuffers = &cmd_buffer_; render_submit_info.signalSemaphoreCount = uint32_t(semaphores.size()) - 1; render_submit_info.pSignalSemaphores = semaphores.data(); { std::lock_guard queue_lock(device_->primary_queue_mutex()); err = vkQueueSubmit(device_->primary_queue(), 1, &render_submit_info, nullptr); } CheckResult(err, "vkQueueSubmit"); // Queue the present of our current image. const VkSwapchainKHR swap_chains[] = {handle}; const uint32_t swap_chain_image_indices[] = {current_buffer_index_}; VkPresentInfoKHR present_info; present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; present_info.pNext = nullptr; present_info.waitSemaphoreCount = 0; present_info.pWaitSemaphores = nullptr; present_info.swapchainCount = static_cast(xe::countof(swap_chains)); present_info.pSwapchains = swap_chains; present_info.pImageIndices = swap_chain_image_indices; present_info.pResults = nullptr; { std::lock_guard queue_lock(device_->primary_queue_mutex()); err = vkQueuePresentKHR(device_->primary_queue(), &present_info); } switch (err) { case VK_SUCCESS: break; case VK_SUBOPTIMAL_KHR: // We are not rendering at the right size - but the presentation engine // will scale the output for us. break; case VK_ERROR_OUT_OF_DATE_KHR: // Lost presentation ability; need to recreate the swapchain. // TODO(benvanik): recreate swapchain. assert_always("Swapchain recreation not implemented"); break; default: XELOGE("Failed to queue present: %s", to_string(err)); assert_always("Unexpected queue present failure"); return false; } return true; } } // namespace vulkan } // namespace ui } // namespace xe