blob: ac31b6eea5be8cb6ae4ef361c35c06032155af57 [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 "impeller/renderer/backend/vulkan/swapchain_impl_vk.h"
#include "fml/synchronization/count_down_latch.h"
#include "impeller/base/validation.h"
#include "impeller/renderer/backend/vulkan/command_buffer_vk.h"
#include "impeller/renderer/backend/vulkan/command_encoder_vk.h"
#include "impeller/renderer/backend/vulkan/context_vk.h"
#include "impeller/renderer/backend/vulkan/formats_vk.h"
#include "impeller/renderer/backend/vulkan/gpu_tracer_vk.h"
#include "impeller/renderer/backend/vulkan/surface_vk.h"
#include "impeller/renderer/backend/vulkan/swapchain_image_vk.h"
#include "impeller/renderer/context.h"
#include "vulkan/vulkan_structs.hpp"
namespace impeller {
static constexpr size_t kMaxFramesInFlight = 3u;
// Number of frames to poll for orientation changes. For example `1u` means
// that the orientation will be polled every frame, while `2u` means that the
// orientation will be polled every other frame.
static constexpr size_t kPollFramesForOrientation = 1u;
struct FrameSynchronizer {
vk::UniqueFence acquire;
vk::UniqueSemaphore render_ready;
vk::UniqueSemaphore present_ready;
std::shared_ptr<CommandBuffer> final_cmd_buffer;
/// @brief A latch that is signaled _after_ a given swapchain image is
/// presented.
std::shared_ptr<fml::CountDownLatch> present_latch;
bool is_valid = false;
explicit FrameSynchronizer(const vk::Device& device) {
auto acquire_res = device.createFenceUnique(
vk::FenceCreateInfo{vk::FenceCreateFlagBits::eSignaled});
auto render_res = device.createSemaphoreUnique({});
auto present_res = device.createSemaphoreUnique({});
if (acquire_res.result != vk::Result::eSuccess ||
render_res.result != vk::Result::eSuccess ||
present_res.result != vk::Result::eSuccess) {
VALIDATION_LOG << "Could not create synchronizer.";
return;
}
acquire = std::move(acquire_res.value);
render_ready = std::move(render_res.value);
present_ready = std::move(present_res.value);
present_latch = std::make_shared<fml::CountDownLatch>(0u);
is_valid = true;
}
~FrameSynchronizer() = default;
bool WaitForFence(const vk::Device& device) {
present_latch->Wait();
if (auto result = device.waitForFences(
*acquire, // fence
true, // wait all
std::numeric_limits<uint64_t>::max() // timeout (ns)
);
result != vk::Result::eSuccess) {
VALIDATION_LOG << "Fence wait failed: " << vk::to_string(result);
return false;
}
if (auto result = device.resetFences(*acquire);
result != vk::Result::eSuccess) {
VALIDATION_LOG << "Could not reset fence: " << vk::to_string(result);
return false;
}
present_latch = std::make_shared<fml::CountDownLatch>(1u);
return true;
}
};
static bool ContainsFormat(const std::vector<vk::SurfaceFormatKHR>& formats,
vk::SurfaceFormatKHR format) {
return std::find(formats.begin(), formats.end(), format) != formats.end();
}
static std::optional<vk::SurfaceFormatKHR> ChooseSurfaceFormat(
const std::vector<vk::SurfaceFormatKHR>& formats,
PixelFormat preference) {
const auto colorspace = vk::ColorSpaceKHR::eSrgbNonlinear;
const auto vk_preference =
vk::SurfaceFormatKHR{ToVKImageFormat(preference), colorspace};
if (ContainsFormat(formats, vk_preference)) {
return vk_preference;
}
std::vector<vk::SurfaceFormatKHR> options = {
{vk::Format::eB8G8R8A8Unorm, colorspace},
{vk::Format::eR8G8B8A8Unorm, colorspace}};
for (const auto& format : options) {
if (ContainsFormat(formats, format)) {
return format;
}
}
return std::nullopt;
}
static std::optional<vk::CompositeAlphaFlagBitsKHR> ChooseAlphaCompositionMode(
vk::CompositeAlphaFlagsKHR flags) {
if (flags & vk::CompositeAlphaFlagBitsKHR::eInherit) {
return vk::CompositeAlphaFlagBitsKHR::eInherit;
}
if (flags & vk::CompositeAlphaFlagBitsKHR::ePreMultiplied) {
return vk::CompositeAlphaFlagBitsKHR::ePreMultiplied;
}
if (flags & vk::CompositeAlphaFlagBitsKHR::ePostMultiplied) {
return vk::CompositeAlphaFlagBitsKHR::ePostMultiplied;
}
if (flags & vk::CompositeAlphaFlagBitsKHR::eOpaque) {
return vk::CompositeAlphaFlagBitsKHR::eOpaque;
}
return std::nullopt;
}
static std::optional<vk::Queue> ChoosePresentQueue(
const vk::PhysicalDevice& physical_device,
const vk::Device& device,
const vk::SurfaceKHR& surface) {
const auto families = physical_device.getQueueFamilyProperties();
for (size_t family_index = 0u; family_index < families.size();
family_index++) {
auto [result, supported] =
physical_device.getSurfaceSupportKHR(family_index, surface);
if (result == vk::Result::eSuccess && supported) {
return device.getQueue(family_index, 0u);
}
}
return std::nullopt;
}
std::shared_ptr<SwapchainImplVK> SwapchainImplVK::Create(
const std::shared_ptr<Context>& context,
vk::UniqueSurfaceKHR surface,
vk::SwapchainKHR old_swapchain,
vk::SurfaceTransformFlagBitsKHR last_transform) {
return std::shared_ptr<SwapchainImplVK>(new SwapchainImplVK(
context, std::move(surface), old_swapchain, last_transform));
}
SwapchainImplVK::SwapchainImplVK(
const std::shared_ptr<Context>& context,
vk::UniqueSurfaceKHR surface,
vk::SwapchainKHR old_swapchain,
vk::SurfaceTransformFlagBitsKHR last_transform) {
if (!context) {
VALIDATION_LOG << "Cannot create a swapchain without a context.";
return;
}
auto& vk_context = ContextVK::Cast(*context);
auto [caps_result, caps] =
vk_context.GetPhysicalDevice().getSurfaceCapabilitiesKHR(*surface);
if (caps_result != vk::Result::eSuccess) {
VALIDATION_LOG << "Could not get surface capabilities: "
<< vk::to_string(caps_result);
return;
}
auto [formats_result, formats] =
vk_context.GetPhysicalDevice().getSurfaceFormatsKHR(*surface);
if (formats_result != vk::Result::eSuccess) {
VALIDATION_LOG << "Could not get surface formats: "
<< vk::to_string(formats_result);
return;
}
const auto format = ChooseSurfaceFormat(
formats, vk_context.GetCapabilities()->GetDefaultColorFormat());
if (!format.has_value()) {
VALIDATION_LOG << "Swapchain has no supported formats.";
return;
}
vk_context.SetOffscreenFormat(ToPixelFormat(format.value().format));
const auto composite =
ChooseAlphaCompositionMode(caps.supportedCompositeAlpha);
if (!composite.has_value()) {
VALIDATION_LOG << "No composition mode supported.";
return;
}
auto present_queue = ChoosePresentQueue(vk_context.GetPhysicalDevice(), //
vk_context.GetDevice(), //
*surface //
);
if (!present_queue.has_value()) {
VALIDATION_LOG << "Could not pick present queue.";
return;
}
vk::SwapchainCreateInfoKHR swapchain_info;
swapchain_info.surface = *surface;
swapchain_info.imageFormat = format.value().format;
swapchain_info.imageColorSpace = format.value().colorSpace;
swapchain_info.presentMode = vk::PresentModeKHR::eFifo;
swapchain_info.imageExtent = vk::Extent2D{
std::clamp(caps.currentExtent.width, caps.minImageExtent.width,
caps.maxImageExtent.width),
std::clamp(caps.currentExtent.height, caps.minImageExtent.height,
caps.maxImageExtent.height),
};
swapchain_info.minImageCount = std::clamp(
caps.minImageCount + 1u, // preferred image count
caps.minImageCount, // min count cannot be zero
caps.maxImageCount == 0u ? caps.minImageCount + 1u
: caps.maxImageCount // max zero means no limit
);
swapchain_info.imageArrayLayers = 1u;
// Swapchain images are primarily used as color attachments (via resolve) or
// blit targets.
swapchain_info.imageUsage = vk::ImageUsageFlagBits::eColorAttachment |
vk::ImageUsageFlagBits::eTransferDst;
swapchain_info.preTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity;
swapchain_info.compositeAlpha = composite.value();
// If we set the clipped value to true, Vulkan expects we will never read back
// from the buffer. This is analogous to [CAMetalLayer framebufferOnly] in
// Metal.
swapchain_info.clipped = true;
// Setting queue family indices is irrelevant since the present mode is
// exclusive.
swapchain_info.imageSharingMode = vk::SharingMode::eExclusive;
swapchain_info.oldSwapchain = old_swapchain;
auto [swapchain_result, swapchain] =
vk_context.GetDevice().createSwapchainKHRUnique(swapchain_info);
if (swapchain_result != vk::Result::eSuccess) {
VALIDATION_LOG << "Could not create swapchain: "
<< vk::to_string(swapchain_result);
return;
}
auto [images_result, images] =
vk_context.GetDevice().getSwapchainImagesKHR(*swapchain);
if (images_result != vk::Result::eSuccess) {
VALIDATION_LOG << "Could not get swapchain images.";
return;
}
TextureDescriptor texture_desc;
texture_desc.usage =
static_cast<decltype(texture_desc.usage)>(TextureUsage::kRenderTarget);
texture_desc.storage_mode = StorageMode::kDevicePrivate;
texture_desc.format = ToPixelFormat(swapchain_info.imageFormat);
texture_desc.size = ISize::MakeWH(swapchain_info.imageExtent.width,
swapchain_info.imageExtent.height);
std::vector<std::shared_ptr<SwapchainImageVK>> swapchain_images;
for (const auto& image : images) {
auto swapchain_image =
std::make_shared<SwapchainImageVK>(texture_desc, // texture descriptor
vk_context.GetDevice(), // device
image // image
);
if (!swapchain_image->IsValid()) {
VALIDATION_LOG << "Could not create swapchain image.";
return;
}
ContextVK::SetDebugName(
vk_context.GetDevice(), swapchain_image->GetImage(),
"SwapchainImage" + std::to_string(swapchain_images.size()));
ContextVK::SetDebugName(
vk_context.GetDevice(), swapchain_image->GetImageView(),
"SwapchainImageView" + std::to_string(swapchain_images.size()));
swapchain_images.emplace_back(swapchain_image);
}
std::vector<std::unique_ptr<FrameSynchronizer>> synchronizers;
for (size_t i = 0u; i < kMaxFramesInFlight; i++) {
auto sync = std::make_unique<FrameSynchronizer>(vk_context.GetDevice());
if (!sync->is_valid) {
VALIDATION_LOG << "Could not create frame synchronizers.";
return;
}
synchronizers.emplace_back(std::move(sync));
}
FML_DCHECK(!synchronizers.empty());
context_ = context;
surface_ = std::move(surface);
present_queue_ = present_queue.value();
surface_format_ = swapchain_info.imageFormat;
swapchain_ = std::move(swapchain);
images_ = std::move(swapchain_images);
synchronizers_ = std::move(synchronizers);
current_frame_ = synchronizers_.size() - 1u;
is_valid_ = true;
transform_if_changed_discard_swapchain_ = last_transform;
}
SwapchainImplVK::~SwapchainImplVK() {
DestroySwapchain();
}
bool SwapchainImplVK::IsValid() const {
return is_valid_;
}
void SwapchainImplVK::WaitIdle() const {
if (auto context = context_.lock()) {
[[maybe_unused]] auto result =
ContextVK::Cast(*context).GetDevice().waitIdle();
}
}
std::pair<vk::UniqueSurfaceKHR, vk::UniqueSwapchainKHR>
SwapchainImplVK::DestroySwapchain() {
WaitIdle();
is_valid_ = false;
synchronizers_.clear();
images_.clear();
context_.reset();
return {std::move(surface_), std::move(swapchain_)};
}
vk::Format SwapchainImplVK::GetSurfaceFormat() const {
return surface_format_;
}
vk::SurfaceTransformFlagBitsKHR SwapchainImplVK::GetLastTransform() const {
return transform_if_changed_discard_swapchain_;
}
std::shared_ptr<Context> SwapchainImplVK::GetContext() const {
return context_.lock();
}
SwapchainImplVK::AcquireResult SwapchainImplVK::AcquireNextDrawable() {
auto context_strong = context_.lock();
if (!context_strong) {
return SwapchainImplVK::AcquireResult{};
}
const auto& context = ContextVK::Cast(*context_strong);
current_frame_ = (current_frame_ + 1u) % synchronizers_.size();
const auto& sync = synchronizers_[current_frame_];
//----------------------------------------------------------------------------
/// Wait on the host for the synchronizer fence.
///
if (!sync->WaitForFence(context.GetDevice())) {
VALIDATION_LOG << "Could not wait for fence.";
return SwapchainImplVK::AcquireResult{};
}
//----------------------------------------------------------------------------
/// Poll to see if the orientation has changed.
///
/// https://developer.android.com/games/optimize/vulkan-prerotation#using_polling
current_transform_poll_count_++;
if (current_transform_poll_count_ >= kPollFramesForOrientation) {
current_transform_poll_count_ = 0u;
auto [caps_result, caps] =
context.GetPhysicalDevice().getSurfaceCapabilitiesKHR(*surface_);
if (caps_result != vk::Result::eSuccess) {
VALIDATION_LOG << "Could not get surface capabilities: "
<< vk::to_string(caps_result);
return SwapchainImplVK::AcquireResult{};
}
if (caps.currentTransform != transform_if_changed_discard_swapchain_) {
transform_if_changed_discard_swapchain_ = caps.currentTransform;
return AcquireResult{true /* out of date */};
}
}
//----------------------------------------------------------------------------
/// Get the next image index.
///
auto [acq_result, index] = context.GetDevice().acquireNextImageKHR(
*swapchain_, // swapchain
1'000'000'000, // timeout (ns) 1000ms
*sync->render_ready, // signal semaphore
nullptr // fence
);
switch (acq_result) {
case vk::Result::eSuccess:
// Keep going.
break;
case vk::Result::eSuboptimalKHR:
case vk::Result::eErrorOutOfDateKHR:
// A recoverable error. Just say we are out of date.
return AcquireResult{true /* out of date */};
break;
default:
// An unrecoverable error.
VALIDATION_LOG << "Could not acquire next swapchain image: "
<< vk::to_string(acq_result);
return AcquireResult{false /* out of date */};
}
if (index >= images_.size()) {
VALIDATION_LOG << "Swapchain returned an invalid image index.";
return SwapchainImplVK::AcquireResult{};
}
/// Record all subsequent cmd buffers as part of the current frame.
context.GetGPUTracer()->MarkFrameStart();
auto image = images_[index % images_.size()];
uint32_t image_index = index;
return AcquireResult{SurfaceVK::WrapSwapchainImage(
context_strong, // context
image, // swapchain image
[weak_swapchain = weak_from_this(), image, image_index]() -> bool {
auto swapchain = weak_swapchain.lock();
if (!swapchain) {
return false;
}
return swapchain->Present(image, image_index);
} // swap callback
)};
}
bool SwapchainImplVK::Present(const std::shared_ptr<SwapchainImageVK>& image,
uint32_t index) {
auto context_strong = context_.lock();
if (!context_strong) {
return false;
}
const auto& context = ContextVK::Cast(*context_strong);
const auto& sync = synchronizers_[current_frame_];
//----------------------------------------------------------------------------
/// Transition the image to color-attachment-optimal.
///
sync->final_cmd_buffer = context.CreateCommandBuffer();
if (!sync->final_cmd_buffer) {
return false;
}
auto vk_final_cmd_buffer = CommandBufferVK::Cast(*sync->final_cmd_buffer)
.GetEncoder()
->GetCommandBuffer();
{
BarrierVK barrier;
barrier.new_layout = vk::ImageLayout::ePresentSrcKHR;
barrier.cmd_buffer = vk_final_cmd_buffer;
barrier.src_access = vk::AccessFlagBits::eColorAttachmentWrite;
barrier.src_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
barrier.dst_access = {};
barrier.dst_stage = vk::PipelineStageFlagBits::eBottomOfPipe;
if (!image->SetLayout(barrier).ok()) {
return false;
}
if (vk_final_cmd_buffer.end() != vk::Result::eSuccess) {
return false;
}
}
//----------------------------------------------------------------------------
/// Signal that the presentation semaphore is ready.
///
{
vk::SubmitInfo submit_info;
vk::PipelineStageFlags wait_stage =
vk::PipelineStageFlagBits::eColorAttachmentOutput;
submit_info.setWaitDstStageMask(wait_stage);
submit_info.setWaitSemaphores(*sync->render_ready);
submit_info.setSignalSemaphores(*sync->present_ready);
submit_info.setCommandBuffers(vk_final_cmd_buffer);
auto result =
context.GetGraphicsQueue()->Submit(submit_info, *sync->acquire);
if (result != vk::Result::eSuccess) {
VALIDATION_LOG << "Could not wait on render semaphore: "
<< vk::to_string(result);
return false;
}
}
context.GetGPUTracer()->MarkFrameEnd();
auto task = [&, index, current_frame = current_frame_] {
auto context_strong = context_.lock();
if (!context_strong) {
return;
}
const auto& sync = synchronizers_[current_frame];
//----------------------------------------------------------------------------
/// Present the image.
///
uint32_t indices[] = {static_cast<uint32_t>(index)};
vk::PresentInfoKHR present_info;
present_info.setSwapchains(*swapchain_);
present_info.setImageIndices(indices);
present_info.setWaitSemaphores(*sync->present_ready);
auto result = present_queue_.presentKHR(present_info);
sync->present_latch->CountDown();
switch (result) {
case vk::Result::eErrorOutOfDateKHR:
// Caller will recreate the impl on acquisition, not submission.
[[fallthrough]];
case vk::Result::eErrorSurfaceLostKHR:
// Vulkan guarantees that the set of queue operations will still
// complete successfully.
[[fallthrough]];
case vk::Result::eSuboptimalKHR:
// Even though we're handling rotation changes via polling, we
// still need to handle the case where the swapchain signals that
// it's suboptimal (i.e. every frame when we are rotated given we
// aren't doing Vulkan pre-rotation).
[[fallthrough]];
case vk::Result::eSuccess:
return;
default:
VALIDATION_LOG << "Could not present queue: " << vk::to_string(result);
return;
}
FML_UNREACHABLE();
};
if (context.GetSyncPresentation()) {
task();
} else {
context.GetQueueSubmitRunner()->PostTask(task);
}
return true;
}
} // namespace impeller