blob: 6ef98cd4fc5295d68ca6fc879bf51e7d668356b3 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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/content_handler/vulkan_surface.h"
#include "flutter/common/threads.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/gpu/GrBackendSemaphore.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/gpu/GrContext.h"
#include "third_party/skia/src/gpu/vk/GrVkImage.h"
namespace flutter_runner {
VulkanSurface::VulkanSurface(vulkan::VulkanProcTable& p_vk,
sk_sp<GrContext> context,
sk_sp<GrVkBackendContext> backend_context,
scenic_lib::Session* session,
const SkISize& size)
: vk_(p_vk),
backend_context_(std::move(backend_context)),
session_(session) {
ASSERT_IS_GPU_THREAD;
FXL_DCHECK(session_);
mx::vmo exported_vmo;
if (!AllocateDeviceMemory(std::move(context), size, exported_vmo)) {
FXL_DLOG(INFO) << "Could not allocate device memory.";
return;
}
if (!CreateFences()) {
FXL_DLOG(INFO) << "Could not create signal fences.";
return;
}
if (!PushSessionImageSetupOps(session, std::move(exported_vmo))) {
FXL_DLOG(INFO) << "Could not push session image setup ops.";
return;
}
event_handler_key_ = fsl::MessageLoop::GetCurrent()->AddHandler(
this, release_event_.get(), MX_EVENT_SIGNALED);
// Probably not necessary as the events should be in the unsignalled state
// already.
Reset();
valid_ = true;
}
VulkanSurface::~VulkanSurface() {
ASSERT_IS_GPU_THREAD;
if (event_handler_key_ != 0) {
fsl::MessageLoop::GetCurrent()->RemoveHandler(event_handler_key_);
event_handler_key_ = 0;
}
}
bool VulkanSurface::IsValid() const {
return valid_;
}
SkISize VulkanSurface::GetSize() const {
if (!valid_) {
return SkISize::Make(0, 0);
}
return SkISize::Make(sk_surface_->width(), sk_surface_->height());
}
GrBackendSemaphore VulkanSurface::GetAcquireSemaphore() const {
GrBackendSemaphore gr_semaphore;
gr_semaphore.initVulkan(acquire_semaphore_);
return gr_semaphore;
}
vulkan::VulkanHandle<VkSemaphore> VulkanSurface::SemaphoreFromEvent(
const mx::event& event) const {
VkResult result;
VkSemaphore semaphore;
mx::event semaphore_event;
mx_status_t status = event.duplicate(MX_RIGHT_SAME_RIGHTS, &semaphore_event);
if (status != MX_OK) {
FXL_DLOG(ERROR) << "failed to duplicate semaphore event";
return vulkan::VulkanHandle<VkSemaphore>();
}
VkSemaphoreCreateInfo create_info = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
};
result = VK_CALL_LOG_ERROR(vk_.CreateSemaphore(
backend_context_->fDevice, &create_info, nullptr, &semaphore));
if (result != VK_SUCCESS) {
return vulkan::VulkanHandle<VkSemaphore>();
}
VkImportSemaphoreFdInfoKHR import_info = {
.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR,
.pNext = nullptr,
.semaphore = semaphore,
.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT_KHR,
.fd = semaphore_event.release()};
result = VK_CALL_LOG_ERROR(
vk_.ImportSemaphoreFdKHR(backend_context_->fDevice, &import_info));
if (result != VK_SUCCESS) {
return vulkan::VulkanHandle<VkSemaphore>();
}
return vulkan::VulkanHandle<VkSemaphore>(
semaphore, [this](VkSemaphore semaphore) {
vk_.DestroySemaphore(backend_context_->fDevice, semaphore, nullptr);
});
}
bool VulkanSurface::CreateFences() {
if (mx::event::create(0, &acquire_event_) != MX_OK) {
return false;
}
acquire_semaphore_ = SemaphoreFromEvent(acquire_event_);
if (!acquire_semaphore_) {
FXL_DLOG(ERROR) << "failed to create acquire semaphore";
return false;
}
if (mx::event::create(0, &release_event_) != MX_OK) {
return false;
}
return true;
}
bool VulkanSurface::AllocateDeviceMemory(sk_sp<GrContext> context,
const SkISize& size,
mx::vmo& exported_vmo) {
if (size.isEmpty()) {
return false;
}
if (backend_context_ == nullptr) {
return false;
}
// Create the image.
const VkImageCreateInfo image_create_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
.imageType = VK_IMAGE_TYPE_2D,
.format = VK_FORMAT_B8G8R8A8_UNORM,
.extent = VkExtent3D{static_cast<uint32_t>(size.width()),
static_cast<uint32_t>(size.height()), 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
{
VkImage vk_image = VK_NULL_HANDLE;
if (VK_CALL_LOG_ERROR(vk_.CreateImage(backend_context_->fDevice,
&image_create_info, nullptr,
&vk_image)) != VK_SUCCESS) {
return false;
}
vk_image_ = {vk_image, [this](VkImage image) {
vk_.DestroyImage(backend_context_->fDevice, image, NULL);
}};
}
// Create the memory.
VkMemoryRequirements memory_reqs;
vk_.GetImageMemoryRequirements(backend_context_->fDevice, //
vk_image_, //
&memory_reqs //
);
uint32_t memory_type = 0;
for (; memory_type < 32; memory_type++) {
if ((memory_reqs.memoryTypeBits & (1 << memory_type))) {
break;
}
}
const VkMemoryAllocateInfo alloc_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = nullptr,
.allocationSize = memory_reqs.size,
.memoryTypeIndex = memory_type,
};
{
VkDeviceMemory vk_memory = VK_NULL_HANDLE;
if (VK_CALL_LOG_ERROR(vk_.AllocateMemory(backend_context_->fDevice,
&alloc_info, NULL, &vk_memory)) !=
VK_SUCCESS) {
return false;
}
vk_memory_ = {vk_memory, [this](VkDeviceMemory memory) {
vk_.FreeMemory(backend_context_->fDevice, memory, NULL);
}};
}
// Bind image memory.
if (VK_CALL_LOG_ERROR(vk_.BindImageMemory(
backend_context_->fDevice, vk_image_, vk_memory_, 0)) != VK_SUCCESS) {
return false;
}
{
// Acquire the VMO for the device memory.
uint32_t vmo_handle = 0;
if (VK_CALL_LOG_ERROR(vk_.ExportDeviceMemoryMAGMA(
backend_context_->fDevice, vk_memory_, &vmo_handle)) !=
VK_SUCCESS) {
return false;
}
exported_vmo.reset(static_cast<mx_handle_t>(vmo_handle));
}
// Assert that the VMO size was sufficient.
size_t vmo_size = 0;
if (exported_vmo.get_size(&vmo_size) != MX_OK ||
vmo_size < memory_reqs.size) {
return false;
}
return SetupSkiaSurface(std::move(context), size, image_create_info,
memory_reqs);
}
bool VulkanSurface::SetupSkiaSurface(sk_sp<GrContext> context,
const SkISize& size,
const VkImageCreateInfo& image_create_info,
const VkMemoryRequirements& memory_reqs) {
if (context == nullptr) {
return false;
}
const GrVkImageInfo image_info = {
.fImage = vk_image_,
.fAlloc = {vk_memory_, 0, memory_reqs.size, 0},
.fImageTiling = image_create_info.tiling,
.fImageLayout = image_create_info.initialLayout,
.fFormat = image_create_info.format,
.fLevelCount = image_create_info.mipLevels,
};
GrBackendRenderTarget sk_render_target(size.width(), size.height(), 0, 0,
image_info);
SkSurfaceProps sk_surface_props(
SkSurfaceProps::InitType::kLegacyFontHost_InitType);
auto sk_surface =
SkSurface::MakeFromBackendRenderTarget(context.get(), //
sk_render_target, //
kTopLeft_GrSurfaceOrigin, //
nullptr, //
&sk_surface_props //
);
if (!sk_surface || sk_surface->getCanvas() == nullptr) {
return false;
}
sk_surface_ = std::move(sk_surface);
return true;
}
bool VulkanSurface::PushSessionImageSetupOps(scenic_lib::Session* session,
mx::vmo exported_vmo) {
if (sk_surface_ == nullptr) {
return false;
}
scenic_lib::Memory memory(session, std::move(exported_vmo),
scenic::MemoryType::VK_DEVICE_MEMORY);
auto image_info = scenic::ImageInfo::New();
image_info->width = sk_surface_->width();
image_info->height = sk_surface_->height();
image_info->stride = 4 * sk_surface_->width();
image_info->pixel_format = scenic::ImageInfo::PixelFormat::BGRA_8;
image_info->color_space = scenic::ImageInfo::ColorSpace::SRGB;
image_info->tiling = scenic::ImageInfo::Tiling::LINEAR;
session_image_ = std::make_unique<scenic_lib::Image>(
memory, 0 /* memory offset */, std::move(image_info));
return session_image_ != nullptr;
}
scenic_lib::Image* VulkanSurface::GetImage() {
ASSERT_IS_GPU_THREAD;
if (!valid_) {
return 0;
}
return session_image_.get();
}
sk_sp<SkSurface> VulkanSurface::GetSkiaSurface() const {
ASSERT_IS_GPU_THREAD;
return valid_ ? sk_surface_ : nullptr;
}
size_t VulkanSurface::AdvanceAndGetAge() {
age_++;
return age_;
}
bool VulkanSurface::FlushSessionAcquireAndReleaseEvents() {
mx::event acquire, release;
if (acquire_event_.duplicate(MX_RIGHT_SAME_RIGHTS, &acquire) != MX_OK ||
release_event_.duplicate(MX_RIGHT_SAME_RIGHTS, &release) != MX_OK) {
return false;
}
session_->EnqueueAcquireFence(std::move(acquire));
session_->EnqueueReleaseFence(std::move(release));
age_ = 0;
return true;
}
void VulkanSurface::SignalWritesFinished(
std::function<void(void)> on_writes_committed) {
ASSERT_IS_GPU_THREAD;
FXL_DCHECK(on_writes_committed);
if (!valid_) {
on_writes_committed();
return;
}
FXL_CHECK(pending_on_writes_committed_ == nullptr)
<< "Attempted to signal a write on the surface when the previous write "
"has not yet been acknowledged by the compositor.";
pending_on_writes_committed_ = on_writes_committed;
}
void VulkanSurface::Reset() {
ASSERT_IS_GPU_THREAD;
if (acquire_event_.signal(MX_EVENT_SIGNALED, 0u) != MX_OK ||
release_event_.signal(MX_EVENT_SIGNALED, 0u) != MX_OK) {
valid_ = false;
FXL_DLOG(ERROR)
<< "Could not reset fences. The surface is no longer valid.";
}
// Need to make a new acquire semaphore every frame or else validation layers
// get confused about why no one is waiting on it in this VkInstance
acquire_semaphore_.Reset();
acquire_semaphore_ = SemaphoreFromEvent(acquire_event_);
if (!acquire_semaphore_) {
FXL_DLOG(ERROR) << "failed to create acquire semaphore";
}
// It is safe for the caller to collect the surface in the callback.
auto callback = pending_on_writes_committed_;
pending_on_writes_committed_ = nullptr;
if (callback) {
callback();
}
}
void VulkanSurface::OnHandleReady(mx_handle_t handle,
mx_signals_t pending,
uint64_t count) {
ASSERT_IS_GPU_THREAD;
FXL_DCHECK(pending & MX_EVENT_SIGNALED);
FXL_DCHECK(handle == release_event_.get());
Reset();
}
} // namespace flutter_runner