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
5#include <cassert>
6#include <memory>
7#include <optional>
8
9#include "flutter/flutter_vma/flutter_skia_vma.h"
10#include "flutter/fml/logging.h"
11#include "flutter/shell/common/context_options.h"
12#include "flutter/testing/test_vulkan_context.h"
13#include "flutter/vulkan/vulkan_skia_proc_table.h"
14
15#include "flutter/fml/memory/ref_ptr.h"
16#include "flutter/fml/native_library.h"
17#include "third_party/skia/include/core/SkSurface.h"
18#include "third_party/skia/include/gpu/GrDirectContext.h"
19#include "third_party/skia/include/gpu/vk/GrVkExtensions.h"
20#include "vulkan/vulkan_core.h"
21
22#ifdef FML_OS_MACOSX
23#define VULKAN_SO_PATH "libvk_swiftshader.dylib"
24#elif FML_OS_WIN
25#define VULKAN_SO_PATH "vk_swiftshader.dll"
26#else
27#define VULKAN_SO_PATH "libvk_swiftshader.so"
28#endif
29
30namespace flutter {
31namespace testing {
32
33TestVulkanContext::TestVulkanContext() {
34 // ---------------------------------------------------------------------------
35 // Initialize basic Vulkan state using the Swiftshader ICD.
36 // ---------------------------------------------------------------------------
37
38 const char* vulkan_icd = VULKAN_SO_PATH;
39
40 // TODO(96949): Clean this up and pass a native library directly to
41 // VulkanProcTable.
42 if (!fml::NativeLibrary::Create(VULKAN_SO_PATH)) {
43 FML_LOG(ERROR) << "Couldn't find Vulkan ICD \"" << vulkan_icd
44 << "\", trying \"libvulkan.so\" instead.";
45 vulkan_icd = "libvulkan.so";
46 }
47
48 FML_LOG(INFO) << "Using Vulkan ICD: " << vulkan_icd;
49
50 vk_ = fml::MakeRefCounted<vulkan::VulkanProcTable>(args&: vulkan_icd);
51 if (!vk_ || !vk_->HasAcquiredMandatoryProcAddresses()) {
52 FML_LOG(ERROR) << "Proc table has not acquired mandatory proc addresses.";
53 return;
54 }
55
56 application_ = std::make_unique<vulkan::VulkanApplication>(
57 args&: *vk_, args: "Flutter Unittests", args: std::vector<std::string>{},
58 VK_MAKE_VERSION(1, 0, 0), VK_MAKE_VERSION(1, 0, 0), args: true);
59 if (!application_->IsValid()) {
60 FML_LOG(ERROR) << "Failed to initialize basic Vulkan state.";
61 return;
62 }
63 if (!vk_->AreInstanceProcsSetup()) {
64 FML_LOG(ERROR) << "Failed to acquire full proc table.";
65 return;
66 }
67
68 device_ = application_->AcquireFirstCompatibleLogicalDevice();
69 if (!device_ || !device_->IsValid()) {
70 FML_LOG(ERROR) << "Failed to create compatible logical device.";
71 return;
72 }
73
74 // ---------------------------------------------------------------------------
75 // Create a Skia context.
76 // For creating SkSurfaces from VkImages and snapshotting them, etc.
77 // ---------------------------------------------------------------------------
78
79 uint32_t skia_features = 0;
80 if (!device_->GetPhysicalDeviceFeaturesSkia(features: &skia_features)) {
81 FML_LOG(ERROR) << "Failed to get physical device features.";
82
83 return;
84 }
85
86 auto get_proc = vulkan::CreateSkiaGetProc(vk: vk_);
87 if (get_proc == nullptr) {
88 FML_LOG(ERROR) << "Failed to create Vulkan getProc for Skia.";
89 return;
90 }
91
92 sk_sp<skgpu::VulkanMemoryAllocator> allocator =
93 flutter::FlutterSkiaVulkanMemoryAllocator::Make(
94 VK_MAKE_VERSION(1, 0, 0), instance: application_->GetInstance(),
95 physicalDevice: device_->GetPhysicalDeviceHandle(), device: device_->GetHandle(), vk: vk_, mustUseCoherentHostVisibleMemory: true);
96
97 GrVkExtensions extensions;
98
99 GrVkBackendContext backend_context = {};
100 backend_context.fInstance = application_->GetInstance();
101 backend_context.fPhysicalDevice = device_->GetPhysicalDeviceHandle();
102 backend_context.fDevice = device_->GetHandle();
103 backend_context.fQueue = device_->GetQueueHandle();
104 backend_context.fGraphicsQueueIndex = device_->GetGraphicsQueueIndex();
105 backend_context.fMinAPIVersion = VK_MAKE_VERSION(1, 0, 0);
106 backend_context.fMaxAPIVersion = VK_MAKE_VERSION(1, 0, 0);
107 backend_context.fFeatures = skia_features;
108 backend_context.fVkExtensions = &extensions;
109 backend_context.fGetProc = get_proc;
110 backend_context.fOwnsInstanceAndDevice = false;
111 backend_context.fMemoryAllocator = allocator;
112
113 GrContextOptions options =
114 MakeDefaultContextOptions(type: ContextType::kRender, api: GrBackendApi::kVulkan);
115 options.fReduceOpsTaskSplitting = GrContextOptions::Enable::kNo;
116 context_ = GrDirectContext::MakeVulkan(backend_context, options);
117}
118
119TestVulkanContext::~TestVulkanContext() {
120 if (context_) {
121 context_->releaseResourcesAndAbandonContext();
122 }
123}
124
125std::optional<TestVulkanImage> TestVulkanContext::CreateImage(
126 const SkISize& size) const {
127 TestVulkanImage result;
128
129 VkImageCreateInfo info = {
130 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
131 .pNext = nullptr,
132 .flags = 0,
133 .imageType = VK_IMAGE_TYPE_2D,
134 .format = VK_FORMAT_R8G8B8A8_UNORM,
135 .extent = VkExtent3D{.width: static_cast<uint32_t>(size.width()),
136 .height: static_cast<uint32_t>(size.height()), .depth: 1},
137 .mipLevels = 1,
138 .arrayLayers = 1,
139 .samples = VK_SAMPLE_COUNT_1_BIT,
140 .tiling = VK_IMAGE_TILING_OPTIMAL,
141 .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
142 VK_IMAGE_USAGE_TRANSFER_DST_BIT |
143 VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
144 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
145 .queueFamilyIndexCount = 0,
146 .pQueueFamilyIndices = nullptr,
147 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
148 };
149
150 VkImage image;
151 if (VK_CALL_LOG_ERROR(VK_CALL_LOG_ERROR(
152 vk_->CreateImage(device_->GetHandle(), &info, nullptr, &image)))) {
153 return std::nullopt;
154 }
155
156 result.image_ = vulkan::VulkanHandle<VkImage>(
157 image, [&vk = vk_, &device = device_](VkImage image) {
158 vk->DestroyImage(device->GetHandle(), image, nullptr);
159 });
160
161 VkMemoryRequirements mem_req;
162 vk_->GetImageMemoryRequirements(device_->GetHandle(), image, &mem_req);
163 VkMemoryAllocateInfo alloc_info{};
164 alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
165 alloc_info.allocationSize = mem_req.size;
166 alloc_info.memoryTypeIndex = static_cast<uint32_t>(__builtin_ctz(
167 mem_req.memoryTypeBits & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT));
168
169 VkDeviceMemory memory;
170 if (VK_CALL_LOG_ERROR(vk_->AllocateMemory(device_->GetHandle(), &alloc_info,
171 nullptr, &memory)) != VK_SUCCESS) {
172 return std::nullopt;
173 }
174
175 result.memory_ = vulkan::VulkanHandle<VkDeviceMemory>{
176 memory, [&vk = vk_, &device = device_](VkDeviceMemory memory) {
177 vk->FreeMemory(device->GetHandle(), memory, nullptr);
178 }};
179
180 if (VK_CALL_LOG_ERROR(VK_CALL_LOG_ERROR(vk_->BindImageMemory(
181 device_->GetHandle(), result.image_, result.memory_, 0)))) {
182 return std::nullopt;
183 }
184
185 result.context_ =
186 fml::RefPtr<TestVulkanContext>(const_cast<TestVulkanContext*>(this));
187
188 return result;
189}
190
191sk_sp<GrDirectContext> TestVulkanContext::GetGrDirectContext() const {
192 return context_;
193}
194
195} // namespace testing
196} // namespace flutter
197

source code of flutter_engine/flutter/testing/test_vulkan_context.cc