| // 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 "vulkan_surface_producer.h" | |
| #include <lib/async/cpp/task.h> | |
| #include <lib/async/default.h> | |
| #include <memory> | |
| #include <string> | |
| #include <vector> | |
| #include "flutter/fml/trace_event.h" | |
| #include "flutter/vulkan/vulkan_skia_proc_table.h" | |
| #include "flutter_vma/flutter_skia_vma.h" | |
| #include "third_party/skia/include/core/SkSurface.h" | |
| #include "third_party/skia/include/gpu/GrBackendSemaphore.h" | |
| #include "third_party/skia/include/gpu/GrBackendSurface.h" | |
| #include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" | |
| #include "third_party/skia/include/gpu/ganesh/vk/GrVkBackendSurface.h" | |
| #include "third_party/skia/include/gpu/ganesh/vk/GrVkDirectContext.h" | |
| #include "third_party/skia/include/gpu/vk/GrVkBackendContext.h" | |
| #include "third_party/skia/include/gpu/vk/GrVkExtensions.h" | |
| #include "third_party/skia/include/gpu/vk/GrVkTypes.h" | |
| namespace flutter_runner { | |
| namespace { | |
| // Tuning advice: | |
| // If you see the following 3 things happening simultaneously in a trace: | |
| // * Over budget ("flutter", "GPURasterizer::Draw") durations | |
| // * Many ("skia", "GrGpu::createTexture") events within the | |
| // "GPURasterizer::Draw"s | |
| // * The Skia GPU resource cache is full, as indicated by the | |
| // "SkiaCacheBytes" field in the ("flutter", "SurfacePool") trace counter | |
| // (compare it to the bytes value here) | |
| // then you should consider increasing the size of the GPU resource cache. | |
| constexpr size_t kGrCacheMaxByteSize = 1024 * 600 * 12 * 4; | |
| } // namespace | |
| VulkanSurfaceProducer::VulkanSurfaceProducer() { | |
| valid_ = Initialize(); | |
| if (!valid_) { | |
| FML_LOG(FATAL) << "VulkanSurfaceProducer: Initialization failed"; | |
| } | |
| } | |
| VulkanSurfaceProducer::~VulkanSurfaceProducer() { | |
| // Make sure queue is idle before we start destroying surfaces | |
| if (valid_) { | |
| VkResult wait_result = VK_CALL_LOG_ERROR( | |
| vk_->QueueWaitIdle(logical_device_->GetQueueHandle())); | |
| FML_DCHECK(wait_result == VK_SUCCESS); | |
| } | |
| }; | |
| bool VulkanSurfaceProducer::Initialize() { | |
| vk_ = fml::MakeRefCounted<vulkan::VulkanProcTable>(); | |
| std::vector<std::string> extensions = { | |
| VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, | |
| }; | |
| // On Fuchsia, the validation layers need to be packaged as part of the | |
| // flutter_runner in order to work. As a result, we can use the presence | |
| // of the layers to mean that we want the layers enabled. | |
| application_ = std::make_unique<vulkan::VulkanApplication>( | |
| *vk_, "FlutterRunner", std::move(extensions), VK_MAKE_VERSION(1, 0, 0), | |
| VK_MAKE_VERSION(1, 1, 0), true /* enable_validation_layers */); | |
| if (!application_->IsValid() || !vk_->AreInstanceProcsSetup()) { | |
| // Make certain the application instance was created and it set up the | |
| // instance proc table entries. | |
| FML_LOG(ERROR) << "VulkanSurfaceProducer: Instance proc addresses have not " | |
| "been set up."; | |
| return false; | |
| } | |
| // Create the device. | |
| logical_device_ = application_->AcquireFirstCompatibleLogicalDevice(); | |
| if (logical_device_ == nullptr || !logical_device_->IsValid() || | |
| !vk_->AreDeviceProcsSetup()) { | |
| // Make certain the device was created and it set up the device proc table | |
| // entries. | |
| FML_LOG(ERROR) | |
| << "VulkanSurfaceProducer: Device proc addresses have not been set up."; | |
| return false; | |
| } | |
| if (!vk_->HasAcquiredMandatoryProcAddresses()) { | |
| FML_LOG(ERROR) | |
| << "VulkanSurfaceProducer: Failed to acquire mandatory proc addresses."; | |
| return false; | |
| } | |
| if (!vk_->IsValid()) { | |
| FML_LOG(ERROR) << "VulkanSurfaceProducer: VulkanProcTable invalid"; | |
| return false; | |
| } | |
| auto getProc = CreateSkiaGetProc(vk_); | |
| if (getProc == nullptr) { | |
| FML_LOG(ERROR) << "VulkanSurfaceProducer: Failed to create skia getProc."; | |
| return false; | |
| } | |
| uint32_t skia_features = 0; | |
| if (!logical_device_->GetPhysicalDeviceFeaturesSkia(&skia_features)) { | |
| FML_LOG(ERROR) | |
| << "VulkanSurfaceProducer: Failed to get physical device features."; | |
| return false; | |
| } | |
| memory_allocator_ = flutter::FlutterSkiaVulkanMemoryAllocator::Make( | |
| application_->GetAPIVersion(), application_->GetInstance(), | |
| logical_device_->GetPhysicalDeviceHandle(), logical_device_->GetHandle(), | |
| vk_, true); | |
| GrVkBackendContext backend_context; | |
| backend_context.fInstance = application_->GetInstance(); | |
| backend_context.fPhysicalDevice = logical_device_->GetPhysicalDeviceHandle(); | |
| backend_context.fDevice = logical_device_->GetHandle(); | |
| backend_context.fQueue = logical_device_->GetQueueHandle(); | |
| backend_context.fGraphicsQueueIndex = | |
| logical_device_->GetGraphicsQueueIndex(); | |
| backend_context.fMinAPIVersion = application_->GetAPIVersion(); | |
| backend_context.fMaxAPIVersion = application_->GetAPIVersion(); | |
| backend_context.fFeatures = skia_features; | |
| backend_context.fGetProc = std::move(getProc); | |
| backend_context.fOwnsInstanceAndDevice = false; | |
| backend_context.fMemoryAllocator = memory_allocator_; | |
| // The memory_requirements_2 extension is required on Fuchsia as the AMD | |
| // memory allocator used by Skia benefit from it. | |
| const char* device_extensions[] = { | |
| VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, | |
| }; | |
| const int device_extensions_count = | |
| sizeof(device_extensions) / sizeof(device_extensions[0]); | |
| GrVkExtensions vk_extensions; | |
| vk_extensions.init(backend_context.fGetProc, backend_context.fInstance, | |
| backend_context.fPhysicalDevice, 0, nullptr, | |
| device_extensions_count, device_extensions); | |
| backend_context.fVkExtensions = &vk_extensions; | |
| GrContextOptions options; | |
| options.fReduceOpsTaskSplitting = GrContextOptions::Enable::kNo; | |
| context_ = GrDirectContexts::MakeVulkan(backend_context, options); | |
| if (context_ == nullptr) { | |
| FML_LOG(ERROR) | |
| << "VulkanSurfaceProducer: Failed to create GrDirectContext."; | |
| return false; | |
| } | |
| // Use local limits specified in this file above instead of flutter defaults. | |
| context_->setResourceCacheLimit(kGrCacheMaxByteSize); | |
| surface_pool_ = std::make_unique<VulkanSurfacePool>(*this, context_); | |
| return true; | |
| } | |
| void VulkanSurfaceProducer::SubmitSurfaces( | |
| std::vector<std::unique_ptr<SurfaceProducerSurface>> surfaces) { | |
| TRACE_EVENT0("flutter", "VulkanSurfaceProducer::SubmitSurfaces"); | |
| // Do a single flush for all canvases derived from the context. | |
| { | |
| TRACE_EVENT0("flutter", "GrDirectContext::flushAndSignalSemaphores"); | |
| context_->flushAndSubmit(); | |
| } | |
| if (!TransitionSurfacesToExternal(surfaces)) | |
| FML_LOG(ERROR) << "TransitionSurfacesToExternal failed"; | |
| // Submit surface | |
| for (auto& surface : surfaces) { | |
| SubmitSurface(std::move(surface)); | |
| } | |
| // Buffer management. | |
| surface_pool_->AgeAndCollectOldBuffers(); | |
| // If no further surface production has taken place for 10 frames (TODO: | |
| // Don't hardcode refresh rate here), then shrink our surface pool to fit. | |
| constexpr auto kShouldShrinkThreshold = zx::msec(10 * 16.67); | |
| async::PostDelayedTask( | |
| async_get_default_dispatcher(), | |
| [self = weak_factory_.GetWeakPtr(), kShouldShrinkThreshold] { | |
| if (!self) { | |
| return; | |
| } | |
| auto time_since_last_produce = | |
| async::Now(async_get_default_dispatcher()) - | |
| self->last_produce_time_; | |
| if (time_since_last_produce >= kShouldShrinkThreshold) { | |
| self->surface_pool_->ShrinkToFit(); | |
| } | |
| }, | |
| kShouldShrinkThreshold); | |
| } | |
| bool VulkanSurfaceProducer::TransitionSurfacesToExternal( | |
| const std::vector<std::unique_ptr<SurfaceProducerSurface>>& surfaces) { | |
| for (auto& surface : surfaces) { | |
| auto vk_surface = static_cast<VulkanSurface*>(surface.get()); | |
| if (!vk_surface) { | |
| continue; | |
| } | |
| vulkan::VulkanCommandBuffer* command_buffer = | |
| vk_surface->GetCommandBuffer(logical_device_->GetCommandPool()); | |
| if (!command_buffer->Begin()) | |
| return false; | |
| GrBackendRenderTarget backendRT = SkSurfaces::GetBackendRenderTarget( | |
| vk_surface->GetSkiaSurface().get(), | |
| SkSurfaces::BackendHandleAccess::kFlushRead); | |
| if (!backendRT.isValid()) { | |
| return false; | |
| } | |
| GrVkImageInfo imageInfo; | |
| if (!GrBackendRenderTargets::GetVkImageInfo(backendRT, &imageInfo)) { | |
| return false; | |
| } | |
| VkImageMemoryBarrier image_barrier = { | |
| .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, | |
| .pNext = nullptr, | |
| .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, | |
| .dstAccessMask = 0, | |
| .oldLayout = imageInfo.fImageLayout, | |
| // Understand why this is causing issues on Intel. TODO(fxb/53449) | |
| #if defined(__aarch64__) | |
| .newLayout = imageInfo.fImageLayout, | |
| #else | |
| .newLayout = VK_IMAGE_LAYOUT_GENERAL, | |
| #endif | |
| .srcQueueFamilyIndex = 0, | |
| .dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR, | |
| .image = vk_surface->GetVkImage(), | |
| .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; | |
| if (!command_buffer->InsertPipelineBarrier( | |
| VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, | |
| VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, | |
| 0, // dependencyFlags | |
| 0, nullptr, // memory barriers | |
| 0, nullptr, // buffer barriers | |
| 1, &image_barrier)) | |
| return false; | |
| GrBackendRenderTargets::SetVkImageLayout(&backendRT, | |
| image_barrier.newLayout); | |
| if (!command_buffer->End()) | |
| return false; | |
| if (!logical_device_->QueueSubmit( | |
| {}, {}, {vk_surface->GetAcquireVkSemaphore()}, | |
| {command_buffer->Handle()}, vk_surface->GetCommandBufferFence())) | |
| return false; | |
| } | |
| return true; | |
| } | |
| std::unique_ptr<SurfaceProducerSurface> VulkanSurfaceProducer::ProduceSurface( | |
| const SkISize& size) { | |
| FML_CHECK(valid_); | |
| last_produce_time_ = async::Now(async_get_default_dispatcher()); | |
| return surface_pool_->AcquireSurface(size); | |
| } | |
| void VulkanSurfaceProducer::SubmitSurface( | |
| std::unique_ptr<SurfaceProducerSurface> surface) { | |
| FML_CHECK(valid_); | |
| surface_pool_->SubmitSurface(std::move(surface)); | |
| } | |
| std::unique_ptr<SurfaceProducerSurface> | |
| VulkanSurfaceProducer::ProduceOffscreenSurface(const SkISize& size) { | |
| return surface_pool_->CreateSurface(size); | |
| } | |
| } // namespace flutter_runner |