blob: 8be88832bb4708815a86f490c57a8668abe1c42d [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/khr/khr_swapchain_impl_vk.h"
#include "fml/synchronization/semaphore.h"
#include "impeller/base/validation.h"
#include "impeller/core/formats.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/swapchain/khr/khr_swapchain_image_vk.h"
#include "impeller/renderer/backend/vulkan/swapchain/surface_vk.h"
#include "impeller/renderer/context.h"
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 KHRFrameSynchronizerVK {
vk::UniqueFence acquire;
vk::UniqueSemaphore render_ready;
vk::UniqueSemaphore present_ready;
std::shared_ptr<CommandBuffer> final_cmd_buffer;
bool is_valid = false;
explicit KHRFrameSynchronizerVK(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);
is_valid = true;
}
~KHRFrameSynchronizerVK() = default;
bool WaitForFence(const vk::Device& device) {
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;
}
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;
}
std::shared_ptr<KHRSwapchainImplVK> KHRSwapchainImplVK::Create(
const std::shared_ptr<Context>& context,
vk::UniqueSurfaceKHR surface,
const ISize& size,
bool enable_msaa,
vk::SwapchainKHR old_swapchain) {
return std::shared_ptr<KHRSwapchainImplVK>(new KHRSwapchainImplVK(
context, std::move(surface), size, enable_msaa, old_swapchain));
}
KHRSwapchainImplVK::KHRSwapchainImplVK(const std::shared_ptr<Context>& context,
vk::UniqueSurfaceKHR surface,
const ISize& size,
bool enable_msaa,
vk::SwapchainKHR old_swapchain) {
if (!context) {
VALIDATION_LOG << "Cannot create a swapchain without a context.";
return;
}
auto& vk_context = ContextVK::Cast(*context);
const auto [caps_result, surface_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(surface_caps.supportedCompositeAlpha);
if (!composite.has_value()) {
VALIDATION_LOG << "No composition mode supported.";
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(static_cast<uint32_t>(size.width),
surface_caps.minImageExtent.width,
surface_caps.maxImageExtent.width),
std::clamp(static_cast<uint32_t>(size.height),
surface_caps.minImageExtent.height,
surface_caps.maxImageExtent.height),
};
swapchain_info.minImageCount =
std::clamp(surface_caps.minImageCount + 1u, // preferred image count
surface_caps.minImageCount, // min count cannot be zero
surface_caps.maxImageCount == 0u
? surface_caps.minImageCount + 1u
: surface_caps.maxImageCount // max zero means no limit
);
swapchain_info.imageArrayLayers = 1u;
// Swapchain images are primarily used as color attachments (via resolve),
// blit targets, or input attachments.
swapchain_info.imageUsage = vk::ImageUsageFlagBits::eColorAttachment |
vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eInputAttachment;
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 = 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<KHRSwapchainImageVK>> swapchain_images;
for (const auto& image : images) {
auto swapchain_image = std::make_shared<KHRSwapchainImageVK>(
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<KHRFrameSynchronizerVK>> synchronizers;
for (size_t i = 0u; i < kMaxFramesInFlight; i++) {
auto sync =
std::make_unique<KHRFrameSynchronizerVK>(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);
surface_format_ = swapchain_info.imageFormat;
swapchain_ = std::move(swapchain);
transients_ = std::make_shared<SwapchainTransientsVK>(context, texture_desc,
enable_msaa);
images_ = std::move(swapchain_images);
synchronizers_ = std::move(synchronizers);
current_frame_ = synchronizers_.size() - 1u;
size_ = size;
enable_msaa_ = enable_msaa;
is_valid_ = true;
}
KHRSwapchainImplVK::~KHRSwapchainImplVK() {
DestroySwapchain();
}
const ISize& KHRSwapchainImplVK::GetSize() const {
return size_;
}
bool KHRSwapchainImplVK::IsValid() const {
return is_valid_;
}
void KHRSwapchainImplVK::WaitIdle() const {
if (auto context = context_.lock()) {
[[maybe_unused]] auto result =
ContextVK::Cast(*context).GetDevice().waitIdle();
}
}
std::pair<vk::UniqueSurfaceKHR, vk::UniqueSwapchainKHR>
KHRSwapchainImplVK::DestroySwapchain() {
WaitIdle();
is_valid_ = false;
synchronizers_.clear();
images_.clear();
context_.reset();
return {std::move(surface_), std::move(swapchain_)};
}
vk::Format KHRSwapchainImplVK::GetSurfaceFormat() const {
return surface_format_;
}
std::shared_ptr<Context> KHRSwapchainImplVK::GetContext() const {
return context_.lock();
}
KHRSwapchainImplVK::AcquireResult KHRSwapchainImplVK::AcquireNextDrawable() {
auto context_strong = context_.lock();
if (!context_strong) {
return KHRSwapchainImplVK::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 KHRSwapchainImplVK::AcquireResult{};
}
//----------------------------------------------------------------------------
/// 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 KHRSwapchainImplVK::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(
transients_, // transients
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 KHRSwapchainImplVK::Present(
const std::shared_ptr<KHRSwapchainImageVK>& 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_];
context.GetGPUTracer()->MarkFrameEnd();
//----------------------------------------------------------------------------
/// 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;
}
}
//----------------------------------------------------------------------------
/// 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 = context.GetGraphicsQueue()->Present(present_info);
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:
break;
default:
VALIDATION_LOG << "Could not present queue: " << vk::to_string(result);
break;
}
return true;
}
} // namespace impeller