1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4// FLUTTER_NOLINT
5
6#include "vulkan_window.h"
7
8#include <memory>
9#include <string>
10
11#include "third_party/skia/include/gpu/GrDirectContext.h"
12#include "vulkan_application.h"
13#include "vulkan_device.h"
14#include "vulkan_native_surface.h"
15#include "vulkan_surface.h"
16#include "vulkan_swapchain.h"
17
18namespace vulkan {
19
20VulkanWindow::VulkanWindow(fml::RefPtr<VulkanProcTable> proc_table,
21 std::unique_ptr<VulkanNativeSurface> native_surface,
22 bool render_to_surface)
23 : valid_(false), vk(std::move(proc_table)) {
24 if (!vk || !vk->HasAcquiredMandatoryProcAddresses()) {
25 FML_DLOG(INFO) << "Proc table has not acquired mandatory proc addresses.";
26 return;
27 }
28
29 if (native_surface == nullptr || !native_surface->IsValid()) {
30 FML_DLOG(INFO) << "Native surface is invalid.";
31 return;
32 }
33
34 // Create the application instance.
35
36 std::vector<std::string> extensions = {
37 VK_KHR_SURFACE_EXTENSION_NAME, // parent extension
38 native_surface->GetExtensionName() // child extension
39 };
40
41 application_ = std::make_unique<VulkanApplication>(*vk, "Flutter",
42 std::move(extensions));
43
44 if (!application_->IsValid() || !vk->AreInstanceProcsSetup()) {
45 // Make certain the application instance was created and it setup the
46 // instance proc table entries.
47 FML_DLOG(INFO) << "Instance proc addresses have not been setup.";
48 return;
49 }
50
51 // Create the device.
52
53 logical_device_ = application_->AcquireFirstCompatibleLogicalDevice();
54
55 if (logical_device_ == nullptr || !logical_device_->IsValid() ||
56 !vk->AreDeviceProcsSetup()) {
57 // Make certain the device was created and it setup the device proc table
58 // entries.
59 FML_DLOG(INFO) << "Device proc addresses have not been setup.";
60 return;
61 }
62
63 // TODO(38466): Refactor GPU surface APIs take into account the fact that an
64 // external view embedder may want to render to the root surface.
65 if (!render_to_surface) {
66 return;
67 }
68
69 // Create the logical surface from the native platform surface.
70 surface_ = std::make_unique<VulkanSurface>(*vk, *application_,
71 std::move(native_surface));
72
73 if (!surface_->IsValid()) {
74 FML_DLOG(INFO) << "Vulkan surface is invalid.";
75 return;
76 }
77
78 // Create the Skia GrDirectContext.
79
80 if (!CreateSkiaGrContext()) {
81 FML_DLOG(INFO) << "Could not create Skia context.";
82 return;
83 }
84
85 // Create the swapchain.
86
87 if (!RecreateSwapchain()) {
88 FML_DLOG(INFO) << "Could not setup the swapchain initially.";
89 return;
90 }
91
92 valid_ = true;
93}
94
95VulkanWindow::~VulkanWindow() = default;
96
97bool VulkanWindow::IsValid() const {
98 return valid_;
99}
100
101GrDirectContext* VulkanWindow::GetSkiaGrContext() {
102 return skia_gr_context_.get();
103}
104
105bool VulkanWindow::CreateSkiaGrContext() {
106 GrVkBackendContext backend_context;
107
108 if (!CreateSkiaBackendContext(&backend_context)) {
109 return false;
110 }
111
112 sk_sp<GrDirectContext> context = GrDirectContext::MakeVulkan(backend_context);
113
114 if (context == nullptr) {
115 return false;
116 }
117
118 context->setResourceCacheLimits(kGrCacheMaxCount, kGrCacheMaxByteSize);
119
120 skia_gr_context_ = context;
121
122 return true;
123}
124
125bool VulkanWindow::CreateSkiaBackendContext(GrVkBackendContext* context) {
126 auto getProc = vk->CreateSkiaGetProc();
127
128 if (getProc == nullptr) {
129 return false;
130 }
131
132 uint32_t skia_features = 0;
133 if (!logical_device_->GetPhysicalDeviceFeaturesSkia(&skia_features)) {
134 return false;
135 }
136
137 context->fInstance = application_->GetInstance();
138 context->fPhysicalDevice = logical_device_->GetPhysicalDeviceHandle();
139 context->fDevice = logical_device_->GetHandle();
140 context->fQueue = logical_device_->GetQueueHandle();
141 context->fGraphicsQueueIndex = logical_device_->GetGraphicsQueueIndex();
142 context->fMinAPIVersion = application_->GetAPIVersion();
143 context->fExtensions = kKHR_surface_GrVkExtensionFlag |
144 kKHR_swapchain_GrVkExtensionFlag |
145 surface_->GetNativeSurface().GetSkiaExtensionName();
146 context->fFeatures = skia_features;
147 context->fGetProc = std::move(getProc);
148 context->fOwnsInstanceAndDevice = false;
149 return true;
150}
151
152sk_sp<SkSurface> VulkanWindow::AcquireSurface() {
153 if (!IsValid()) {
154 FML_DLOG(INFO) << "Surface is invalid.";
155 return nullptr;
156 }
157
158 auto surface_size = surface_->GetSize();
159
160 // This check is theoretically unnecessary as the swapchain should report that
161 // the surface is out-of-date and perform swapchain recreation at the new
162 // configuration. However, on Android, the swapchain never reports that it is
163 // of date. Hence this extra check. Platforms that don't have this issue, or,
164 // cant report this information (which is optional anyway), report a zero
165 // size.
166 if (surface_size != SkISize::Make(0, 0) &&
167 surface_size != swapchain_->GetSize()) {
168 FML_DLOG(INFO) << "Swapchain and surface sizes are out of sync. Recreating "
169 "swapchain.";
170 if (!RecreateSwapchain()) {
171 FML_DLOG(INFO) << "Could not recreate swapchain.";
172 valid_ = false;
173 return nullptr;
174 }
175 }
176
177 while (true) {
178 sk_sp<SkSurface> surface;
179 auto acquire_result = VulkanSwapchain::AcquireStatus::ErrorSurfaceLost;
180
181 std::tie(acquire_result, surface) = swapchain_->AcquireSurface();
182
183 if (acquire_result == VulkanSwapchain::AcquireStatus::Success) {
184 // Successfully acquired a surface from the swapchain. Nothing more to do.
185 return surface;
186 }
187
188 if (acquire_result == VulkanSwapchain::AcquireStatus::ErrorSurfaceLost) {
189 // Surface is lost. This is an unrecoverable error.
190 FML_DLOG(INFO) << "Swapchain reported surface was lost.";
191 return nullptr;
192 }
193
194 if (acquire_result ==
195 VulkanSwapchain::AcquireStatus::ErrorSurfaceOutOfDate) {
196 // Surface out of date. Recreate the swapchain at the new configuration.
197 if (RecreateSwapchain()) {
198 // Swapchain was recreated, try surface acquisition again.
199 continue;
200 } else {
201 // Could not recreate the swapchain at the new configuration.
202 FML_DLOG(INFO) << "Swapchain reported surface was out of date but "
203 "could not recreate the swapchain at the new "
204 "configuration.";
205 valid_ = false;
206 return nullptr;
207 }
208 }
209
210 break;
211 }
212
213 FML_DCHECK(false) << "Unhandled VulkanSwapchain::AcquireResult";
214 return nullptr;
215}
216
217bool VulkanWindow::SwapBuffers() {
218 if (!IsValid()) {
219 FML_DLOG(INFO) << "Window was invalid.";
220 return false;
221 }
222
223 return swapchain_->Submit();
224}
225
226bool VulkanWindow::RecreateSwapchain() {
227 // This way, we always lose our reference to the old swapchain. Even if we
228 // cannot create a new one to replace it.
229 auto old_swapchain = std::move(swapchain_);
230
231 if (!vk->IsValid()) {
232 return false;
233 }
234
235 if (logical_device_ == nullptr || !logical_device_->IsValid()) {
236 return false;
237 }
238
239 if (surface_ == nullptr || !surface_->IsValid()) {
240 return false;
241 }
242
243 if (skia_gr_context_ == nullptr) {
244 return false;
245 }
246
247 auto swapchain = std::make_unique<VulkanSwapchain>(
248 *vk, *logical_device_, *surface_, skia_gr_context_.get(),
249 std::move(old_swapchain), logical_device_->GetGraphicsQueueIndex());
250
251 if (!swapchain->IsValid()) {
252 return false;
253 }
254
255 swapchain_ = std::move(swapchain);
256 return true;
257}
258
259} // namespace vulkan
260