// 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/ganesh/GrBackendSurface.h" #include "third_party/skia/include/gpu/ganesh/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/ganesh/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 color_space_; }; } // namespace static std::vector 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 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 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{ swapchain, [this](VkSwapchainKHR swapchain) { [[maybe_unused]] auto result = 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 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 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 VulkanSwapchain::CreateSkiaSurface( GrDirectContext* gr_context, VkImage image, VkImageUsageFlags usage_flags, const SkISize& size, SkColorType color_type, sk_sp 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& color_space, VkImageUsageFlags usage_flags) { std::vector 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( vk, device_.GetHandle(), device_.GetCommandPool()); if (!backbuffer->IsValid()) { return false; } backbuffers_.emplace_back(std::move(backbuffer)); // Populate the image. VulkanHandle image_handle = VulkanHandle{ image, [this](VkImage image) { vk.DestroyImage(device_.GetHandle(), image, nullptr); }}; auto vulkan_image = std::make_unique(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::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 wait_semaphores = {backbuffer->GetUsageSemaphore()}; std::vector signal_semaphores = {}; std::vector 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 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 surface = surfaces_[current_image_index_]; const std::unique_ptr& 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 wait_semaphores = {}; std::vector queue_signal_semaphores = { backbuffer->GetRenderSemaphore()}; std::vector 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(current_image_index_); const VkPresentInfoKHR present_info = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .pNext = nullptr, .waitSemaphoreCount = static_cast(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