// 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. // FLUTTER_NOLINT: https://github.com/flutter/flutter/issues/68331 #include "vulkan_window.h" #include #include #include #include "flutter/flutter_vma/flutter_skia_vma.h" #include "flutter/vulkan/vulkan_skia_proc_table.h" #include "vulkan_application.h" #include "vulkan_device.h" #include "vulkan_native_surface.h" #include "vulkan_surface.h" #include "vulkan_swapchain.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" #include "third_party/skia/include/gpu/ganesh/vk/GrVkDirectContext.h" #include "third_party/skia/include/gpu/vk/VulkanExtensions.h" namespace vulkan { VulkanWindow::VulkanWindow(fml::RefPtr proc_table, std::unique_ptr native_surface) : VulkanWindow(/*context/*/ nullptr, std::move(proc_table), std::move(native_surface)) {} VulkanWindow::VulkanWindow(const sk_sp& context, fml::RefPtr proc_table, std::unique_ptr native_surface) : valid_(false), vk_(std::move(proc_table)), skia_gr_context_(context) { if (!vk_ || !vk_->HasAcquiredMandatoryProcAddresses()) { FML_DLOG(INFO) << "Proc table has not acquired mandatory proc addresses."; return; } if (native_surface && !native_surface->IsValid()) { FML_DLOG(INFO) << "Native surface is invalid."; return; } // Create the application instance. std::vector extensions = { VK_KHR_SURFACE_EXTENSION_NAME, // parent extension native_surface->GetExtensionName() // child extension }; application_ = std::make_unique(*vk_, "Flutter", std::move(extensions)); if (!application_->IsValid() || !vk_->AreInstanceProcsSetup()) { // Make certain the application instance was created and it set up the // instance proc table entries. FML_DLOG(INFO) << "Instance proc addresses have not been set up."; return; } // 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_DLOG(INFO) << "Device proc addresses have not been set up."; return; } if (!native_surface) { return; } // Create the logical surface from the native platform surface. surface_ = std::make_unique(*vk_, *application_, std::move(native_surface)); if (!surface_->IsValid()) { FML_DLOG(INFO) << "Vulkan surface is invalid."; return; } // Needs to happen before GrDirectContext is created. memory_allocator_ = flutter::FlutterSkiaVulkanMemoryAllocator::Make( application_->GetAPIVersion(), application_->GetInstance(), logical_device_->GetPhysicalDeviceHandle(), logical_device_->GetHandle(), vk_, true); // Create the Skia GrDirectContext. if (!skia_gr_context_ && !CreateSkiaGrContext()) { FML_DLOG(INFO) << "Could not create Skia context."; return; } // Create the swapchain. if (!RecreateSwapchain()) { FML_DLOG(INFO) << "Could not set up the swapchain initially."; return; } valid_ = true; } VulkanWindow::~VulkanWindow() = default; bool VulkanWindow::IsValid() const { return valid_; } GrDirectContext* VulkanWindow::GetSkiaGrContext() { return skia_gr_context_.get(); } bool VulkanWindow::CreateSkiaGrContext() { #ifdef SK_VULKAN skgpu::VulkanBackendContext backend_context; VkPhysicalDeviceFeatures features; skgpu::VulkanExtensions extensions; if (!this->CreateSkiaBackendContext(&backend_context, &features, &extensions)) { return false; } GrContextOptions options; options.fReduceOpsTaskSplitting = GrContextOptions::Enable::kNo; sk_sp context = GrDirectContexts::MakeVulkan(backend_context, options); if (context == nullptr) { return false; } context->setResourceCacheLimit(kGrCacheMaxByteSize); skia_gr_context_ = context; return true; #else return false; #endif // SK_VULKAN } bool VulkanWindow::CreateSkiaBackendContext( skgpu::VulkanBackendContext* context, VkPhysicalDeviceFeatures* features, skgpu::VulkanExtensions* extensions) { #ifdef SK_VULKAN FML_CHECK(context); FML_CHECK(features); FML_CHECK(extensions); auto getProc = CreateSkiaGetProc(vk_); if (getProc == nullptr) { return false; } if (!logical_device_->GetPhysicalDeviceFeatures(features)) { return false; } context->fInstance = application_->GetInstance(); context->fPhysicalDevice = logical_device_->GetPhysicalDeviceHandle(); context->fDevice = logical_device_->GetHandle(); context->fQueue = logical_device_->GetQueueHandle(); context->fGraphicsQueueIndex = logical_device_->GetGraphicsQueueIndex(); context->fMaxAPIVersion = application_->GetAPIVersion(); context->fDeviceFeatures = features; context->fGetProc = std::move(getProc); context->fMemoryAllocator = memory_allocator_; constexpr uint32_t instance_extension_count = 2; const char* instance_extensions[instance_extension_count] = { // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_surface.html VK_KHR_SURFACE_EXTENSION_NAME, surface_->GetNativeSurface().GetExtensionName(), }; constexpr uint32_t device_extension_count = 1; const char* device_extensions[device_extension_count] = { // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_swapchain.html VK_KHR_SWAPCHAIN_EXTENSION_NAME, }; extensions->init(context->fGetProc, context->fInstance, context->fPhysicalDevice, instance_extension_count, instance_extensions, device_extension_count, device_extensions); context->fVkExtensions = extensions; return true; #else return false; #endif } sk_sp VulkanWindow::AcquireSurface() { if (!IsValid()) { FML_DLOG(INFO) << "Surface is invalid."; return nullptr; } auto surface_size = surface_->GetSize(); // This check is theoretically unnecessary as the swapchain should report that // the surface is out-of-date and perform swapchain recreation at the new // configuration. However, on Android, the swapchain never reports that it is // of date. Hence this extra check. Platforms that don't have this issue, or, // cant report this information (which is optional anyway), report a zero // size. if (surface_size != SkISize::Make(0, 0) && surface_size != swapchain_->GetSize()) { FML_DLOG(INFO) << "Swapchain and surface sizes are out of sync. Recreating " "swapchain."; if (!RecreateSwapchain()) { FML_DLOG(INFO) << "Could not recreate swapchain."; valid_ = false; return nullptr; } } while (true) { sk_sp surface; auto acquire_result = VulkanSwapchain::AcquireStatus::ErrorSurfaceLost; std::tie(acquire_result, surface) = swapchain_->AcquireSurface(); if (acquire_result == VulkanSwapchain::AcquireStatus::Success) { // Successfully acquired a surface from the swapchain. Nothing more to do. return surface; } if (acquire_result == VulkanSwapchain::AcquireStatus::ErrorSurfaceLost) { // Surface is lost. This is an unrecoverable error. FML_DLOG(INFO) << "Swapchain reported surface was lost."; return nullptr; } if (acquire_result == VulkanSwapchain::AcquireStatus::ErrorSurfaceOutOfDate) { // Surface out of date. Recreate the swapchain at the new configuration. if (RecreateSwapchain()) { // Swapchain was recreated, try surface acquisition again. continue; } else { // Could not recreate the swapchain at the new configuration. FML_DLOG(INFO) << "Swapchain reported surface was out of date but " "could not recreate the swapchain at the new " "configuration."; valid_ = false; return nullptr; } } break; } FML_DCHECK(false) << "Unhandled VulkanSwapchain::AcquireResult"; return nullptr; } bool VulkanWindow::SwapBuffers() { if (!IsValid()) { FML_DLOG(INFO) << "Window was invalid."; return false; } return swapchain_->Submit(); } bool VulkanWindow::RecreateSwapchain() { // This way, we always lose our reference to the old swapchain. Even if we // cannot create a new one to replace it. auto old_swapchain = std::move(swapchain_); if (!vk_->IsValid()) { return false; } if (logical_device_ == nullptr || !logical_device_->IsValid()) { return false; } if (surface_ == nullptr || !surface_->IsValid()) { return false; } if (skia_gr_context_ == nullptr) { return false; } auto swapchain = std::make_unique( *vk_, *logical_device_, *surface_, skia_gr_context_.get(), std::move(old_swapchain), logical_device_->GetGraphicsQueueIndex()); if (!swapchain->IsValid()) { return false; } swapchain_ = std::move(swapchain); return true; } } // namespace vulkan