blob: c69e9d263f7d096577c2f75422edd6222a198ef9 [file] [log] [blame]
// 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 "flutter/vulkan/vulkan_swapchain.h"
#include "flutter/vulkan/vulkan_backbuffer.h"
#include "flutter/vulkan/vulkan_device.h"
#include "flutter/vulkan/vulkan_image.h"
#include "flutter/vulkan/vulkan_proc_table.h"
#include "flutter/vulkan/vulkan_surface.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/gpu/GrContext.h"
#include "third_party/skia/include/gpu/vk/GrVkTypes.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,
GrContext* 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();
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 = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.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_ = {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_)) {
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(
GrContext* gr_context,
VkImage image,
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;
}
const GrVkImageInfo image_info = {
image, // image
GrVkAlloc(), // alloc
VK_IMAGE_TILING_OPTIMAL, // tiling
VK_IMAGE_LAYOUT_UNDEFINED, // layout
surface_format_.format, // format
1, // level count
};
// TODO(chinmaygarde): Setup the stencil buffer and the sampleCnt.
GrBackendRenderTarget backend_render_target(size.fWidth, size.fHeight, 0,
image_info);
SkSurfaceProps props(SkSurfaceProps::InitType::kLegacyFontHost_InitType);
return SkSurface::MakeFromBackendRenderTarget(
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(GrContext* skia_context,
SkColorType color_type,
sk_sp<SkColorSpace> color_space) {
std::vector<VkImage> images = GetImages();
if (images.size() == 0) {
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.
auto vulkan_image = std::make_unique<VulkanImage>(image);
if (!vulkan_image->IsValid()) {
return false;
}
images_.emplace_back(std::move(vulkan_image));
// Populate the surface.
auto surface = CreateSkiaSurface(skia_context, image, 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 semaphores in 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 = surface->getBackendRenderTarget(
SkSurface::kFlushRead_BackendHandleAccess);
if (!backendRT.isValid()) {
FML_DLOG(INFO) << "Could not get backend render target.";
return error;
}
backendRT.setVkImageLayout(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.
// ---------------------------------------------------------------------------
surface->flush();
// ---------------------------------------------------------------------------
// 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