| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "vulkan_swapchain.h" | |
| #include "flutter/vulkan/procs/vulkan_proc_table.h" | |
| #include "third_party/skia/include/core/SkColorSpace.h" | |
| #include "third_party/skia/include/core/SkSurface.h" | |
| #include "third_party/skia/include/gpu/GrBackendSurface.h" | |
| #include "third_party/skia/include/gpu/GrDirectContext.h" | |
| #include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" | |
| #include "third_party/skia/include/gpu/ganesh/vk/GrVkBackendSurface.h" | |
| #include "third_party/skia/include/gpu/vk/GrVkTypes.h" | |
| #include "vulkan_backbuffer.h" | |
| #include "vulkan_device.h" | |
| #include "vulkan_image.h" | |
| #include "vulkan_surface.h" | |
| namespace vulkan { | |
| namespace { | |
| struct FormatInfo { | |
| VkFormat format_; | |
| SkColorType color_type_; | |
| sk_sp<SkColorSpace> color_space_; | |
| }; | |
| } // namespace | |
| static std::vector<FormatInfo> DesiredFormatInfos() { | |
| return {{VK_FORMAT_R8G8B8A8_SRGB, kRGBA_8888_SkColorType, | |
| SkColorSpace::MakeSRGB()}, | |
| {VK_FORMAT_B8G8R8A8_SRGB, kRGBA_8888_SkColorType, | |
| SkColorSpace::MakeSRGB()}, | |
| {VK_FORMAT_R16G16B16A16_SFLOAT, kRGBA_F16_SkColorType, | |
| SkColorSpace::MakeSRGBLinear()}, | |
| {VK_FORMAT_R8G8B8A8_UNORM, kRGBA_8888_SkColorType, | |
| SkColorSpace::MakeSRGB()}, | |
| {VK_FORMAT_B8G8R8A8_UNORM, kRGBA_8888_SkColorType, | |
| SkColorSpace::MakeSRGB()}}; | |
| } | |
| VulkanSwapchain::VulkanSwapchain(const VulkanProcTable& p_vk, | |
| const VulkanDevice& device, | |
| const VulkanSurface& surface, | |
| GrDirectContext* skia_context, | |
| std::unique_ptr<VulkanSwapchain> old_swapchain, | |
| uint32_t queue_family_index) | |
| : vk(p_vk), | |
| device_(device), | |
| capabilities_(), | |
| surface_format_(), | |
| current_pipeline_stage_(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT), | |
| current_backbuffer_index_(0), | |
| current_image_index_(0), | |
| valid_(false) { | |
| if (!device_.IsValid() || !surface.IsValid() || skia_context == nullptr) { | |
| FML_DLOG(INFO) << "Device or surface is invalid."; | |
| return; | |
| } | |
| if (!device_.GetSurfaceCapabilities(surface, &capabilities_)) { | |
| FML_DLOG(INFO) << "Could not find surface capabilities."; | |
| return; | |
| } | |
| const auto format_infos = DesiredFormatInfos(); | |
| std::vector<VkFormat> desired_formats(format_infos.size()); | |
| for (size_t i = 0; i < format_infos.size(); ++i) { | |
| if (skia_context->colorTypeSupportedAsSurface( | |
| format_infos[i].color_type_)) { | |
| desired_formats[i] = format_infos[i].format_; | |
| } else { | |
| desired_formats[i] = VK_FORMAT_UNDEFINED; | |
| } | |
| } | |
| int format_index = | |
| device_.ChooseSurfaceFormat(surface, desired_formats, &surface_format_); | |
| if (format_index < 0) { | |
| FML_DLOG(INFO) << "Could not choose surface format."; | |
| return; | |
| } | |
| VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR; | |
| if (!device_.ChoosePresentMode(surface, &present_mode)) { | |
| FML_DLOG(INFO) << "Could not choose present mode."; | |
| return; | |
| } | |
| // Check if the surface can present. | |
| VkBool32 supported = VK_FALSE; | |
| if (VK_CALL_LOG_ERROR(vk.GetPhysicalDeviceSurfaceSupportKHR( | |
| device_.GetPhysicalDeviceHandle(), // physical device | |
| queue_family_index, // queue family | |
| surface.Handle(), // surface to test | |
| &supported)) != VK_SUCCESS) { | |
| FML_DLOG(INFO) << "Could not get physical device surface support."; | |
| return; | |
| } | |
| if (supported != VK_TRUE) { | |
| FML_DLOG(INFO) << "Surface was not supported by the physical device."; | |
| return; | |
| } | |
| // Construct the Swapchain | |
| VkSwapchainKHR old_swapchain_handle = VK_NULL_HANDLE; | |
| if (old_swapchain != nullptr && old_swapchain->IsValid()) { | |
| old_swapchain_handle = old_swapchain->swapchain_; | |
| // The unique pointer to the swapchain will go out of scope here | |
| // and its handle collected after the appropriate device wait. | |
| } | |
| VkSurfaceKHR surface_handle = surface.Handle(); | |
| VkImageUsageFlags usage_flags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | | |
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT | | |
| VK_IMAGE_USAGE_TRANSFER_DST_BIT; | |
| const VkSwapchainCreateInfoKHR create_info = { | |
| .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, | |
| .pNext = nullptr, | |
| .flags = 0, | |
| .surface = surface_handle, | |
| .minImageCount = capabilities_.minImageCount, | |
| .imageFormat = surface_format_.format, | |
| .imageColorSpace = surface_format_.colorSpace, | |
| .imageExtent = capabilities_.currentExtent, | |
| .imageArrayLayers = 1, | |
| .imageUsage = usage_flags, | |
| .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, | |
| .queueFamilyIndexCount = 0, // Because of the exclusive sharing mode. | |
| .pQueueFamilyIndices = nullptr, | |
| .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, | |
| .compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, | |
| .presentMode = present_mode, | |
| .clipped = VK_FALSE, | |
| .oldSwapchain = old_swapchain_handle, | |
| }; | |
| VkSwapchainKHR swapchain = VK_NULL_HANDLE; | |
| if (VK_CALL_LOG_ERROR(vk.CreateSwapchainKHR(device_.GetHandle(), &create_info, | |
| nullptr, &swapchain)) != | |
| VK_SUCCESS) { | |
| FML_DLOG(INFO) << "Could not create the swapchain."; | |
| return; | |
| } | |
| swapchain_ = VulkanHandle<VkSwapchainKHR>{ | |
| swapchain, [this](VkSwapchainKHR swapchain) { | |
| FML_ALLOW_UNUSED_LOCAL(device_.WaitIdle()); | |
| vk.DestroySwapchainKHR(device_.GetHandle(), swapchain, nullptr); | |
| }}; | |
| if (!CreateSwapchainImages( | |
| skia_context, format_infos[format_index].color_type_, | |
| format_infos[format_index].color_space_, usage_flags)) { | |
| FML_DLOG(INFO) << "Could not create swapchain images."; | |
| return; | |
| } | |
| valid_ = true; | |
| } | |
| VulkanSwapchain::~VulkanSwapchain() = default; | |
| bool VulkanSwapchain::IsValid() const { | |
| return valid_; | |
| } | |
| std::vector<VkImage> VulkanSwapchain::GetImages() const { | |
| uint32_t count = 0; | |
| if (VK_CALL_LOG_ERROR(vk.GetSwapchainImagesKHR( | |
| device_.GetHandle(), swapchain_, &count, nullptr)) != VK_SUCCESS) { | |
| return {}; | |
| } | |
| if (count == 0) { | |
| return {}; | |
| } | |
| std::vector<VkImage> images; | |
| images.resize(count); | |
| if (VK_CALL_LOG_ERROR(vk.GetSwapchainImagesKHR( | |
| device_.GetHandle(), swapchain_, &count, images.data())) != | |
| VK_SUCCESS) { | |
| return {}; | |
| } | |
| return images; | |
| } | |
| SkISize VulkanSwapchain::GetSize() const { | |
| VkExtent2D extents = capabilities_.currentExtent; | |
| if (extents.width < capabilities_.minImageExtent.width) { | |
| extents.width = capabilities_.minImageExtent.width; | |
| } else if (extents.width > capabilities_.maxImageExtent.width) { | |
| extents.width = capabilities_.maxImageExtent.width; | |
| } | |
| if (extents.height < capabilities_.minImageExtent.height) { | |
| extents.height = capabilities_.minImageExtent.height; | |
| } else if (extents.height > capabilities_.maxImageExtent.height) { | |
| extents.height = capabilities_.maxImageExtent.height; | |
| } | |
| return SkISize::Make(extents.width, extents.height); | |
| } | |
| sk_sp<SkSurface> VulkanSwapchain::CreateSkiaSurface( | |
| GrDirectContext* gr_context, | |
| VkImage image, | |
| VkImageUsageFlags usage_flags, | |
| const SkISize& size, | |
| SkColorType color_type, | |
| sk_sp<SkColorSpace> color_space) const { | |
| if (gr_context == nullptr) { | |
| return nullptr; | |
| } | |
| if (color_type == kUnknown_SkColorType) { | |
| // Unexpected Vulkan format. | |
| return nullptr; | |
| } | |
| GrVkImageInfo image_info; | |
| image_info.fImage = image; | |
| image_info.fImageTiling = VK_IMAGE_TILING_OPTIMAL; | |
| image_info.fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
| image_info.fFormat = surface_format_.format; | |
| image_info.fImageUsageFlags = usage_flags; | |
| image_info.fSampleCount = 1; | |
| image_info.fLevelCount = 1; | |
| // TODO(chinmaygarde): Setup the stencil buffer and the sampleCnt. | |
| auto backend_render_target = | |
| GrBackendRenderTargets::MakeVk(size.fWidth, size.fHeight, image_info); | |
| SkSurfaceProps props(0, kUnknown_SkPixelGeometry); | |
| return SkSurfaces::WrapBackendRenderTarget( | |
| gr_context, // context | |
| backend_render_target, // backend render target | |
| kTopLeft_GrSurfaceOrigin, // origin | |
| color_type, // color type | |
| std::move(color_space), // color space | |
| &props // surface properties | |
| ); | |
| } | |
| bool VulkanSwapchain::CreateSwapchainImages( | |
| GrDirectContext* skia_context, | |
| SkColorType color_type, | |
| const sk_sp<SkColorSpace>& color_space, | |
| VkImageUsageFlags usage_flags) { | |
| std::vector<VkImage> images = GetImages(); | |
| if (images.empty()) { | |
| return false; | |
| } | |
| const SkISize surface_size = GetSize(); | |
| for (const VkImage& image : images) { | |
| // Populate the backbuffer. | |
| auto backbuffer = std::make_unique<VulkanBackbuffer>( | |
| vk, device_.GetHandle(), device_.GetCommandPool()); | |
| if (!backbuffer->IsValid()) { | |
| return false; | |
| } | |
| backbuffers_.emplace_back(std::move(backbuffer)); | |
| // Populate the image. | |
| VulkanHandle<VkImage> image_handle = VulkanHandle<VkImage>{ | |
| image, [this](VkImage image) { | |
| vk.DestroyImage(device_.GetHandle(), image, nullptr); | |
| }}; | |
| auto vulkan_image = std::make_unique<VulkanImage>(std::move(image_handle)); | |
| if (!vulkan_image->IsValid()) { | |
| return false; | |
| } | |
| images_.emplace_back(std::move(vulkan_image)); | |
| // Populate the surface. | |
| auto surface = CreateSkiaSurface(skia_context, image, usage_flags, | |
| surface_size, color_type, color_space); | |
| if (surface == nullptr) { | |
| return false; | |
| } | |
| surfaces_.emplace_back(std::move(surface)); | |
| } | |
| FML_DCHECK(backbuffers_.size() == images_.size()); | |
| FML_DCHECK(images_.size() == surfaces_.size()); | |
| return true; | |
| } | |
| VulkanBackbuffer* VulkanSwapchain::GetNextBackbuffer() { | |
| auto available_backbuffers = backbuffers_.size(); | |
| if (available_backbuffers == 0) { | |
| return nullptr; | |
| } | |
| auto next_backbuffer_index = | |
| (current_backbuffer_index_ + 1) % backbuffers_.size(); | |
| auto& backbuffer = backbuffers_[next_backbuffer_index]; | |
| if (!backbuffer->IsValid()) { | |
| return nullptr; | |
| } | |
| current_backbuffer_index_ = next_backbuffer_index; | |
| return backbuffer.get(); | |
| } | |
| VulkanSwapchain::AcquireResult VulkanSwapchain::AcquireSurface() { | |
| AcquireResult error = {AcquireStatus::ErrorSurfaceLost, nullptr}; | |
| if (!IsValid()) { | |
| FML_DLOG(INFO) << "Swapchain was invalid."; | |
| return error; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Step 0: | |
| // Acquire the next available backbuffer. | |
| // --------------------------------------------------------------------------- | |
| auto backbuffer = GetNextBackbuffer(); | |
| if (backbuffer == nullptr) { | |
| FML_DLOG(INFO) << "Could not get the next backbuffer."; | |
| return error; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Step 1: | |
| // Wait for use readiness. | |
| // --------------------------------------------------------------------------- | |
| if (!backbuffer->WaitFences()) { | |
| FML_DLOG(INFO) << "Failed waiting on fences."; | |
| return error; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Step 2: | |
| // Put fences in an unsignaled state. | |
| // --------------------------------------------------------------------------- | |
| if (!backbuffer->ResetFences()) { | |
| FML_DLOG(INFO) << "Could not reset fences."; | |
| return error; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Step 3: | |
| // Acquire the next image index. | |
| // --------------------------------------------------------------------------- | |
| uint32_t next_image_index = 0; | |
| VkResult acquire_result = VK_CALL_LOG_ERROR( | |
| vk.AcquireNextImageKHR(device_.GetHandle(), // | |
| swapchain_, // | |
| std::numeric_limits<uint64_t>::max(), // | |
| backbuffer->GetUsageSemaphore(), // | |
| VK_NULL_HANDLE, // | |
| &next_image_index)); | |
| switch (acquire_result) { | |
| case VK_SUCCESS: | |
| break; | |
| case VK_ERROR_OUT_OF_DATE_KHR: | |
| return {AcquireStatus::ErrorSurfaceOutOfDate, nullptr}; | |
| case VK_ERROR_SURFACE_LOST_KHR: | |
| return {AcquireStatus::ErrorSurfaceLost, nullptr}; | |
| default: | |
| FML_LOG(INFO) << "Unexpected result from AcquireNextImageKHR: " | |
| << acquire_result; | |
| return {AcquireStatus::ErrorSurfaceLost, nullptr}; | |
| } | |
| // Simple sanity checking of image index. | |
| if (next_image_index >= images_.size()) { | |
| FML_DLOG(INFO) << "Image index returned was out-of-bounds."; | |
| return error; | |
| } | |
| auto& image = images_[next_image_index]; | |
| if (!image->IsValid()) { | |
| FML_DLOG(INFO) << "Image at index was invalid."; | |
| return error; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Step 4: | |
| // Start recording to the command buffer. | |
| // --------------------------------------------------------------------------- | |
| if (!backbuffer->GetUsageCommandBuffer().Begin()) { | |
| FML_DLOG(INFO) << "Could not begin recording to the command buffer."; | |
| return error; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Step 5: | |
| // Set image layout to color attachment mode. | |
| // --------------------------------------------------------------------------- | |
| VkPipelineStageFlagBits destination_pipeline_stage = | |
| VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; | |
| VkImageLayout destination_image_layout = | |
| VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; | |
| if (!image->InsertImageMemoryBarrier( | |
| backbuffer->GetUsageCommandBuffer(), // command buffer | |
| current_pipeline_stage_, // src_pipeline_bits | |
| destination_pipeline_stage, // dest_pipeline_bits | |
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, // dest_access_flags | |
| destination_image_layout // dest_layout | |
| )) { | |
| FML_DLOG(INFO) << "Could not insert image memory barrier."; | |
| return error; | |
| } else { | |
| current_pipeline_stage_ = destination_pipeline_stage; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Step 6: | |
| // End recording to the command buffer. | |
| // --------------------------------------------------------------------------- | |
| if (!backbuffer->GetUsageCommandBuffer().End()) { | |
| FML_DLOG(INFO) << "Could not end recording to the command buffer."; | |
| return error; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Step 7: | |
| // Submit the command buffer to the device queue. | |
| // --------------------------------------------------------------------------- | |
| std::vector<VkSemaphore> wait_semaphores = {backbuffer->GetUsageSemaphore()}; | |
| std::vector<VkSemaphore> signal_semaphores = {}; | |
| std::vector<VkCommandBuffer> command_buffers = { | |
| backbuffer->GetUsageCommandBuffer().Handle()}; | |
| if (!device_.QueueSubmit( | |
| {destination_pipeline_stage}, // wait_dest_pipeline_stages | |
| wait_semaphores, // wait_semaphores | |
| signal_semaphores, // signal_semaphores | |
| command_buffers, // command_buffers | |
| backbuffer->GetUsageFence() // fence | |
| )) { | |
| FML_DLOG(INFO) << "Could not submit to the device queue."; | |
| return error; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Step 8: | |
| // Tell Skia about the updated image layout. | |
| // --------------------------------------------------------------------------- | |
| sk_sp<SkSurface> surface = surfaces_[next_image_index]; | |
| if (surface == nullptr) { | |
| FML_DLOG(INFO) << "Could not access surface at the image index."; | |
| return error; | |
| } | |
| GrBackendRenderTarget backendRT = SkSurfaces::GetBackendRenderTarget( | |
| surface.get(), SkSurfaces::BackendHandleAccess::kFlushRead); | |
| if (!backendRT.isValid()) { | |
| FML_DLOG(INFO) << "Could not get backend render target."; | |
| return error; | |
| } | |
| GrBackendRenderTargets::SetVkImageLayout(&backendRT, | |
| destination_image_layout); | |
| current_image_index_ = next_image_index; | |
| return {AcquireStatus::Success, surface}; | |
| } | |
| bool VulkanSwapchain::Submit() { | |
| if (!IsValid()) { | |
| FML_DLOG(INFO) << "Swapchain was invalid."; | |
| return false; | |
| } | |
| sk_sp<SkSurface> surface = surfaces_[current_image_index_]; | |
| const std::unique_ptr<VulkanImage>& image = images_[current_image_index_]; | |
| auto backbuffer = backbuffers_[current_backbuffer_index_].get(); | |
| // --------------------------------------------------------------------------- | |
| // Step 0: | |
| // Make sure Skia has flushed all work for the surface to the gpu. | |
| // --------------------------------------------------------------------------- | |
| skgpu::ganesh::FlushAndSubmit(surface); | |
| // --------------------------------------------------------------------------- | |
| // Step 1: | |
| // Start recording to the command buffer. | |
| // --------------------------------------------------------------------------- | |
| if (!backbuffer->GetRenderCommandBuffer().Begin()) { | |
| FML_DLOG(INFO) << "Could not start recording to the command buffer."; | |
| return false; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Step 2: | |
| // Set image layout to present mode. | |
| // --------------------------------------------------------------------------- | |
| VkPipelineStageFlagBits destination_pipeline_stage = | |
| VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; | |
| VkImageLayout destination_image_layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; | |
| if (!image->InsertImageMemoryBarrier( | |
| backbuffer->GetRenderCommandBuffer(), // command buffer | |
| current_pipeline_stage_, // src_pipeline_bits | |
| destination_pipeline_stage, // dest_pipeline_bits | |
| VK_ACCESS_MEMORY_READ_BIT, // dest_access_flags | |
| destination_image_layout // dest_layout | |
| )) { | |
| FML_DLOG(INFO) << "Could not insert memory barrier."; | |
| return false; | |
| } else { | |
| current_pipeline_stage_ = destination_pipeline_stage; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Step 3: | |
| // End recording to the command buffer. | |
| // --------------------------------------------------------------------------- | |
| if (!backbuffer->GetRenderCommandBuffer().End()) { | |
| FML_DLOG(INFO) << "Could not end recording to the command buffer."; | |
| return false; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Step 4: | |
| // Submit the command buffer to the device queue. Tell it to signal the render | |
| // semaphore. | |
| // --------------------------------------------------------------------------- | |
| std::vector<VkSemaphore> wait_semaphores = {}; | |
| std::vector<VkSemaphore> queue_signal_semaphores = { | |
| backbuffer->GetRenderSemaphore()}; | |
| std::vector<VkCommandBuffer> command_buffers = { | |
| backbuffer->GetRenderCommandBuffer().Handle()}; | |
| if (!device_.QueueSubmit( | |
| {/* Empty. No wait semaphores. */}, // wait_dest_pipeline_stages | |
| wait_semaphores, // wait_semaphores | |
| queue_signal_semaphores, // signal_semaphores | |
| command_buffers, // command_buffers | |
| backbuffer->GetRenderFence() // fence | |
| )) { | |
| FML_DLOG(INFO) << "Could not submit to the device queue."; | |
| return false; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Step 5: | |
| // Submit the present operation and wait on the render semaphore. | |
| // --------------------------------------------------------------------------- | |
| VkSwapchainKHR swapchain = swapchain_; | |
| uint32_t present_image_index = static_cast<uint32_t>(current_image_index_); | |
| const VkPresentInfoKHR present_info = { | |
| .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, | |
| .pNext = nullptr, | |
| .waitSemaphoreCount = | |
| static_cast<uint32_t>(queue_signal_semaphores.size()), | |
| .pWaitSemaphores = queue_signal_semaphores.data(), | |
| .swapchainCount = 1, | |
| .pSwapchains = &swapchain, | |
| .pImageIndices = &present_image_index, | |
| .pResults = nullptr, | |
| }; | |
| if (VK_CALL_LOG_ERROR(vk.QueuePresentKHR(device_.GetQueueHandle(), | |
| &present_info)) != VK_SUCCESS) { | |
| FML_DLOG(INFO) << "Could not submit the present operation."; | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // namespace vulkan |