// 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 context, sk_sp 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_); zx::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(), ZX_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 VulkanSurface::SemaphoreFromEvent( const zx::event& event) const { VkResult result; VkSemaphore semaphore; zx::event semaphore_event; zx_status_t status = event.duplicate(ZX_RIGHT_SAME_RIGHTS, &semaphore_event); if (status != ZX_OK) { FXL_DLOG(ERROR) << "failed to duplicate semaphore event"; return vulkan::VulkanHandle(); } 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(); } VkImportSemaphoreFuchsiaHandleInfoKHR import_info = { .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FUCHSIA_HANDLE_INFO_KHR, .pNext = nullptr, .semaphore = semaphore, .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_FUCHSIA_FENCE_BIT_KHR, .handle = static_cast(semaphore_event.release())}; result = VK_CALL_LOG_ERROR(vk_.ImportSemaphoreFuchsiaHandleKHR( backend_context_->fDevice, &import_info)); if (result != VK_SUCCESS) { return vulkan::VulkanHandle(); } return vulkan::VulkanHandle( semaphore, [this](VkSemaphore semaphore) { vk_.DestroySemaphore(backend_context_->fDevice, semaphore, nullptr); }); } bool VulkanSurface::CreateFences() { if (zx::event::create(0, &acquire_event_) != ZX_OK) { return false; } acquire_semaphore_ = SemaphoreFromEvent(acquire_event_); if (!acquire_semaphore_) { FXL_DLOG(ERROR) << "failed to create acquire semaphore"; return false; } if (zx::event::create(0, &release_event_) != ZX_OK) { return false; } return true; } bool VulkanSurface::AllocateDeviceMemory(sk_sp context, const SkISize& size, zx::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(size.width()), static_cast(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; VkMemoryGetFuchsiaHandleInfoKHR get_handle_info = { VK_STRUCTURE_TYPE_MEMORY_GET_FUCHSIA_HANDLE_INFO_KHR, nullptr, vk_memory_, VK_EXTERNAL_MEMORY_HANDLE_TYPE_FUCHSIA_VMO_BIT_KHR}; if (VK_CALL_LOG_ERROR(vk_.GetMemoryFuchsiaHandleKHR( backend_context_->fDevice, &get_handle_info, &vmo_handle)) != VK_SUCCESS) { return false; } exported_vmo.reset(static_cast(vmo_handle)); } // Assert that the VMO size was sufficient. size_t vmo_size = 0; if (exported_vmo.get_size(&vmo_size) != ZX_OK || vmo_size < memory_reqs.size) { return false; } return SetupSkiaSurface(std::move(context), size, image_create_info, memory_reqs); } bool VulkanSurface::SetupSkiaSurface(sk_sp 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, zx::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( 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 VulkanSurface::GetSkiaSurface() const { ASSERT_IS_GPU_THREAD; return valid_ ? sk_surface_ : nullptr; } size_t VulkanSurface::AdvanceAndGetAge() { age_++; return age_; } bool VulkanSurface::FlushSessionAcquireAndReleaseEvents() { zx::event acquire, release; if (acquire_event_.duplicate(ZX_RIGHT_SAME_RIGHTS, &acquire) != ZX_OK || release_event_.duplicate(ZX_RIGHT_SAME_RIGHTS, &release) != ZX_OK) { return false; } session_->EnqueueAcquireFence(std::move(acquire)); session_->EnqueueReleaseFence(std::move(release)); age_ = 0; return true; } void VulkanSurface::SignalWritesFinished( std::function 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(ZX_EVENT_SIGNALED, 0u) != ZX_OK || release_event_.signal(ZX_EVENT_SIGNALED, 0u) != ZX_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(zx_handle_t handle, zx_signals_t pending, uint64_t count) { ASSERT_IS_GPU_THREAD; FXL_DCHECK(pending & ZX_EVENT_SIGNALED); FXL_DCHECK(handle == release_event_.get()); Reset(); } } // namespace flutter_runner