| // 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 <memory> | |
| #include <string> | |
| #include <utility> | |
| #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/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<VulkanProcTable> proc_table, | |
| std::unique_ptr<VulkanNativeSurface> native_surface) | |
| : VulkanWindow(/*context/*/ nullptr, | |
| std::move(proc_table), | |
| std::move(native_surface)) {} | |
| VulkanWindow::VulkanWindow(const sk_sp<GrDirectContext>& context, | |
| fml::RefPtr<VulkanProcTable> proc_table, | |
| std::unique_ptr<VulkanNativeSurface> 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<std::string> extensions = { | |
| VK_KHR_SURFACE_EXTENSION_NAME, // parent extension | |
| native_surface->GetExtensionName() // child extension | |
| }; | |
| application_ = std::make_unique<VulkanApplication>(*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<VulkanSurface>(*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 | |
| GrVkBackendContext 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<GrDirectContext> 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( | |
| GrVkBackendContext* 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<SkSurface> 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<SkSurface> 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<VulkanSwapchain>( | |
| *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 |