1 // Copyright 2019 The Chromium 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 "ui/ozone/platform/scenic/sysmem_buffer_collection.h"
6
7 #include "base/bits.h"
8 #include "base/fuchsia/fuchsia_logging.h"
9 #include "build/build_config.h"
10 #include "gpu/vulkan/vulkan_device_queue.h"
11 #include "gpu/vulkan/vulkan_function_pointers.h"
12 #include "ui/gfx/buffer_format_util.h"
13 #include "ui/ozone/platform/scenic/scenic_surface_factory.h"
14 #include "ui/ozone/platform/scenic/sysmem_native_pixmap.h"
15
16 namespace ui {
17
18 namespace {
19
RoundUp(size_t value,size_t alignment)20 size_t RoundUp(size_t value, size_t alignment) {
21 return ((value + alignment - 1) / alignment) * alignment;
22 }
23
VkFormatForBufferFormat(gfx::BufferFormat buffer_format)24 VkFormat VkFormatForBufferFormat(gfx::BufferFormat buffer_format) {
25 switch (buffer_format) {
26 case gfx::BufferFormat::YVU_420:
27 return VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM;
28
29 case gfx::BufferFormat::YUV_420_BIPLANAR:
30 return VK_FORMAT_G8_B8R8_2PLANE_420_UNORM;
31
32 case gfx::BufferFormat::R_8:
33 return VK_FORMAT_R8_UNORM;
34
35 case gfx::BufferFormat::RG_88:
36 return VK_FORMAT_R8G8_UNORM;
37
38 case gfx::BufferFormat::BGRA_8888:
39 case gfx::BufferFormat::BGRX_8888:
40 return VK_FORMAT_B8G8R8A8_UNORM;
41
42 case gfx::BufferFormat::RGBA_8888:
43 case gfx::BufferFormat::RGBX_8888:
44 return VK_FORMAT_R8G8B8A8_UNORM;
45
46 default:
47 NOTREACHED();
48 return VK_FORMAT_UNDEFINED;
49 }
50 }
51
52 } // namespace
53
54 // static
IsNativePixmapConfigSupported(gfx::BufferFormat format,gfx::BufferUsage usage)55 bool SysmemBufferCollection::IsNativePixmapConfigSupported(
56 gfx::BufferFormat format,
57 gfx::BufferUsage usage) {
58 switch (format) {
59 case gfx::BufferFormat::YUV_420_BIPLANAR:
60 case gfx::BufferFormat::R_8:
61 case gfx::BufferFormat::RG_88:
62 case gfx::BufferFormat::RGBA_8888:
63 case gfx::BufferFormat::RGBX_8888:
64 case gfx::BufferFormat::BGRA_8888:
65 case gfx::BufferFormat::BGRX_8888:
66 break;
67
68 default:
69 return false;
70 }
71 switch (usage) {
72 case gfx::BufferUsage::SCANOUT:
73 case gfx::BufferUsage::GPU_READ:
74 break;
75
76 case gfx::BufferUsage::SCANOUT_CPU_READ_WRITE:
77 case gfx::BufferUsage::GPU_READ_CPU_READ_WRITE:
78 #if defined(ARCH_CPU_X86_64)
79 // SwiftShader currently doesn't support liner image layouts (b/171299814)
80 // required for images accessed by CPU, so these formats cannot be
81 // supported with Goldfish Vulkan drivers running under emulator.It's not
82 // straightforward to detect format support here because this code runs in
83 // the renderer process. Disable these formats for all X64 devices for
84 // now.
85 // TODO(crbug.com/1141538): remove this workaround.
86 return false;
87 #endif
88 break;
89
90 default:
91 return false;
92 }
93 return true;
94 }
95
SysmemBufferCollection()96 SysmemBufferCollection::SysmemBufferCollection()
97 : SysmemBufferCollection(gfx::SysmemBufferCollectionId::Create()) {}
98
SysmemBufferCollection(gfx::SysmemBufferCollectionId id)99 SysmemBufferCollection::SysmemBufferCollection(gfx::SysmemBufferCollectionId id)
100 : id_(id) {}
101
Initialize(fuchsia::sysmem::Allocator_Sync * allocator,ScenicSurfaceFactory * scenic_surface_factory,zx::channel token_handle,gfx::Size size,gfx::BufferFormat format,gfx::BufferUsage usage,VkDevice vk_device,size_t min_buffer_count,bool force_protected,bool register_with_image_pipe)102 bool SysmemBufferCollection::Initialize(
103 fuchsia::sysmem::Allocator_Sync* allocator,
104 ScenicSurfaceFactory* scenic_surface_factory,
105 zx::channel token_handle,
106 gfx::Size size,
107 gfx::BufferFormat format,
108 gfx::BufferUsage usage,
109 VkDevice vk_device,
110 size_t min_buffer_count,
111 bool force_protected,
112 bool register_with_image_pipe) {
113 DCHECK(IsNativePixmapConfigSupported(format, usage));
114 DCHECK(!collection_);
115 DCHECK(!vk_buffer_collection_);
116
117 // Currently all supported |usage| values require GPU access, which requires
118 // a valid VkDevice.
119 if (vk_device == VK_NULL_HANDLE)
120 return false;
121
122 if (size.IsEmpty()) {
123 // Buffer collection that doesn't have explicit size is expected to be
124 // shared with other participants, who will determine the actual image size.
125 DCHECK(token_handle);
126
127 // Set nominal size of 1x1, which will be used only for
128 // vkSetBufferCollectionConstraintsFUCHSIA(). The actual size of the
129 // allocated buffers is determined by constraints set by other sysmem
130 // clients for the same collection. Size of the Vulkan image is determined
131 // by the values passed to CreateVkImage().
132 min_size_ = gfx::Size(1, 1);
133 } else {
134 min_size_ = size;
135 }
136
137 format_ = format;
138 usage_ = usage;
139 vk_device_ = vk_device;
140 is_protected_ = force_protected;
141
142 if (register_with_image_pipe) {
143 scenic_overlay_view_.emplace(scenic_surface_factory->CreateScenicSession(),
144 scenic_surface_factory);
145 surface_factory_ = scenic_surface_factory;
146 }
147
148 fuchsia::sysmem::BufferCollectionTokenSyncPtr collection_token;
149 if (token_handle) {
150 collection_token.Bind(std::move(token_handle));
151 } else {
152 zx_status_t status =
153 allocator->AllocateSharedCollection(collection_token.NewRequest());
154 if (status != ZX_OK) {
155 ZX_DLOG(ERROR, status)
156 << "fuchsia.sysmem.Allocator.AllocateSharedCollection()";
157 return false;
158 }
159 }
160
161 return InitializeInternal(allocator, std::move(collection_token),
162 min_buffer_count);
163 }
164
CreateNativePixmap(size_t buffer_index)165 scoped_refptr<gfx::NativePixmap> SysmemBufferCollection::CreateNativePixmap(
166 size_t buffer_index) {
167 CHECK_LT(buffer_index, num_buffers());
168
169 gfx::NativePixmapHandle handle;
170 handle.buffer_collection_id = id();
171 handle.buffer_index = buffer_index;
172 handle.ram_coherency =
173 buffers_info_.settings.buffer_settings.coherency_domain ==
174 fuchsia::sysmem::CoherencyDomain::RAM;
175
176 zx::vmo main_plane_vmo;
177 if (is_mappable()) {
178 DCHECK(buffers_info_.buffers[buffer_index].vmo.is_valid());
179 zx_status_t status = buffers_info_.buffers[buffer_index].vmo.duplicate(
180 ZX_RIGHT_SAME_RIGHTS, &main_plane_vmo);
181 if (status != ZX_OK) {
182 ZX_DLOG(ERROR, status) << "zx_handle_duplicate";
183 return nullptr;
184 }
185 }
186
187 const fuchsia::sysmem::ImageFormatConstraints& format =
188 buffers_info_.settings.image_format_constraints;
189
190 // The logic should match LogicalBufferCollection::Allocate().
191 size_t stride = RoundUp(
192 std::max(static_cast<size_t>(format.min_bytes_per_row),
193 gfx::RowSizeForBufferFormat(image_size_.width(), format_, 0)),
194 format.bytes_per_row_divisor);
195 size_t plane_offset = buffers_info_.buffers[buffer_index].vmo_usable_start;
196 size_t plane_size = stride * image_size_.height();
197 handle.planes.emplace_back(stride, plane_offset, plane_size,
198 std::move(main_plane_vmo));
199
200 // For YUV images add a second plane.
201 if (format_ == gfx::BufferFormat::YUV_420_BIPLANAR) {
202 size_t uv_plane_offset = plane_offset + plane_size;
203 size_t uv_plane_size = plane_size / 2;
204 handle.planes.emplace_back(stride, uv_plane_offset, uv_plane_size,
205 zx::vmo());
206 DCHECK_LE(uv_plane_offset + uv_plane_size, buffer_size_);
207 }
208
209 return new SysmemNativePixmap(this, std::move(handle));
210 }
211
CreateVkImage(size_t buffer_index,VkDevice vk_device,gfx::Size size,VkImage * vk_image,VkImageCreateInfo * vk_image_info,VkDeviceMemory * vk_device_memory,VkDeviceSize * mem_allocation_size,base::Optional<gpu::VulkanYCbCrInfo> * ycbcr_info)212 bool SysmemBufferCollection::CreateVkImage(
213 size_t buffer_index,
214 VkDevice vk_device,
215 gfx::Size size,
216 VkImage* vk_image,
217 VkImageCreateInfo* vk_image_info,
218 VkDeviceMemory* vk_device_memory,
219 VkDeviceSize* mem_allocation_size,
220 base::Optional<gpu::VulkanYCbCrInfo>* ycbcr_info) {
221 DCHECK_CALLED_ON_VALID_THREAD(vulkan_thread_checker_);
222
223 if (vk_device_ != vk_device) {
224 DLOG(FATAL) << "Tried to import NativePixmap that was created for a "
225 "different VkDevice.";
226 return false;
227 }
228
229 VkBufferCollectionPropertiesFUCHSIA properties = {
230 VK_STRUCTURE_TYPE_BUFFER_COLLECTION_PROPERTIES_FUCHSIA};
231 if (vkGetBufferCollectionPropertiesFUCHSIA(vk_device_, vk_buffer_collection_,
232 &properties) != VK_SUCCESS) {
233 DLOG(ERROR) << "vkGetBufferCollectionPropertiesFUCHSIA failed";
234 return false;
235 }
236
237 InitializeImageCreateInfo(vk_image_info, size);
238
239 VkBufferCollectionImageCreateInfoFUCHSIA image_format_fuchsia = {
240 VK_STRUCTURE_TYPE_BUFFER_COLLECTION_IMAGE_CREATE_INFO_FUCHSIA,
241 };
242 image_format_fuchsia.collection = vk_buffer_collection_;
243 image_format_fuchsia.index = buffer_index;
244 vk_image_info->pNext = &image_format_fuchsia;
245
246 if (vkCreateImage(vk_device_, vk_image_info, nullptr, vk_image) !=
247 VK_SUCCESS) {
248 DLOG(ERROR) << "Failed to create VkImage.";
249 return false;
250 }
251
252 vk_image_info->pNext = nullptr;
253
254 VkMemoryRequirements requirements;
255 vkGetImageMemoryRequirements(vk_device, *vk_image, &requirements);
256
257 uint32_t viable_memory_types =
258 properties.memoryTypeBits & requirements.memoryTypeBits;
259 uint32_t memory_type = base::bits::CountTrailingZeroBits(viable_memory_types);
260
261 VkImportMemoryBufferCollectionFUCHSIA buffer_collection_info = {
262 VK_STRUCTURE_TYPE_IMPORT_MEMORY_BUFFER_COLLECTION_FUCHSIA};
263 buffer_collection_info.collection = vk_buffer_collection_;
264 buffer_collection_info.index = buffer_index;
265
266 VkMemoryAllocateInfo alloc_info = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
267 &buffer_collection_info};
268 alloc_info.allocationSize = requirements.size;
269 alloc_info.memoryTypeIndex = memory_type;
270
271 if (vkAllocateMemory(vk_device_, &alloc_info, nullptr, vk_device_memory) !=
272 VK_SUCCESS) {
273 DLOG(ERROR) << "Failed to create VkMemory from sysmem buffer.";
274 vkDestroyImage(vk_device_, *vk_image, nullptr);
275 *vk_image = VK_NULL_HANDLE;
276 return false;
277 }
278
279 if (vkBindImageMemory(vk_device_, *vk_image, *vk_device_memory, 0u) !=
280 VK_SUCCESS) {
281 DLOG(ERROR) << "Failed to bind sysmem buffer to a VkImage.";
282 vkDestroyImage(vk_device_, *vk_image, nullptr);
283 *vk_image = VK_NULL_HANDLE;
284 vkFreeMemory(vk_device_, *vk_device_memory, nullptr);
285 *vk_device_memory = VK_NULL_HANDLE;
286 return false;
287 }
288
289 *mem_allocation_size = requirements.size;
290
291 auto color_space =
292 buffers_info_.settings.image_format_constraints.color_space[0].type;
293 switch (color_space) {
294 case fuchsia::sysmem::ColorSpaceType::SRGB:
295 *ycbcr_info = base::nullopt;
296 break;
297
298 case fuchsia::sysmem::ColorSpaceType::REC709: {
299 // Currently sysmem doesn't specify location of chroma samples relative to
300 // luma (see fxb/13677). Assume they are cosited with luma. YCbCr info
301 // here must match the values passed for the same buffer in
302 // FuchsiaVideoDecoder. |format_features| are resolved later in the GPU
303 // process before the ycbcr info is passed to Skia.
304 *ycbcr_info = gpu::VulkanYCbCrInfo(
305 vk_image_info->format, /*external_format=*/0,
306 VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709,
307 VK_SAMPLER_YCBCR_RANGE_ITU_NARROW, VK_CHROMA_LOCATION_COSITED_EVEN,
308 VK_CHROMA_LOCATION_COSITED_EVEN, /*format_features=*/0);
309 break;
310 }
311
312 default:
313 DLOG(ERROR) << "Sysmem allocated buffer with unsupported color space: "
314 << static_cast<int>(color_space);
315 return false;
316 }
317
318 return true;
319 }
320
SetOnDeletedCallback(base::OnceClosure on_deleted)321 void SysmemBufferCollection::SetOnDeletedCallback(
322 base::OnceClosure on_deleted) {
323 DCHECK(!on_deleted_);
324 on_deleted_ = std::move(on_deleted);
325 }
326
~SysmemBufferCollection()327 SysmemBufferCollection::~SysmemBufferCollection() {
328 if (vk_buffer_collection_ != VK_NULL_HANDLE) {
329 vkDestroyBufferCollectionFUCHSIA(vk_device_, vk_buffer_collection_,
330 nullptr);
331 }
332
333 if (collection_)
334 collection_->Close();
335
336 if (on_deleted_)
337 std::move(on_deleted_).Run();
338 }
339
InitializeInternal(fuchsia::sysmem::Allocator_Sync * allocator,fuchsia::sysmem::BufferCollectionTokenSyncPtr collection_token,size_t min_buffer_count)340 bool SysmemBufferCollection::InitializeInternal(
341 fuchsia::sysmem::Allocator_Sync* allocator,
342 fuchsia::sysmem::BufferCollectionTokenSyncPtr collection_token,
343 size_t min_buffer_count) {
344 fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>
345 collection_token_for_vulkan;
346 collection_token->Duplicate(ZX_RIGHT_SAME_RIGHTS,
347 collection_token_for_vulkan.NewRequest());
348
349 // Duplicate one more token for Scenic if this collection can be used as an
350 // overlay.
351 fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>
352 collection_token_for_scenic;
353 if (scenic_overlay_view_.has_value()) {
354 collection_token->Duplicate(ZX_RIGHT_SAME_RIGHTS,
355 collection_token_for_scenic.NewRequest());
356 }
357
358 zx_status_t status = collection_token->Sync();
359 if (status != ZX_OK) {
360 ZX_DLOG(ERROR, status) << "fuchsia.sysmem.BufferCollectionToken.Sync()";
361 return false;
362 }
363
364 if (scenic_overlay_view_.has_value()) {
365 scenic_overlay_view_->Initialize(std::move(collection_token_for_scenic));
366 }
367
368 status = allocator->BindSharedCollection(std::move(collection_token),
369 collection_.NewRequest());
370 if (status != ZX_OK) {
371 ZX_DLOG(ERROR, status) << "fuchsia.sysmem.Allocator.BindSharedCollection()";
372 return false;
373 }
374
375 fuchsia::sysmem::BufferCollectionConstraints constraints;
376 if (is_mappable()) {
377 constraints.usage.cpu =
378 fuchsia::sysmem::cpuUsageRead | fuchsia::sysmem::cpuUsageWrite;
379
380 constraints.has_buffer_memory_constraints = true;
381 constraints.buffer_memory_constraints.ram_domain_supported = true;
382 constraints.buffer_memory_constraints.cpu_domain_supported = true;
383 } else {
384 constraints.usage.none = fuchsia::sysmem::noneUsage;
385 }
386
387 constraints.min_buffer_count = min_buffer_count;
388 constraints.image_format_constraints_count = 0;
389
390 status = collection_->SetConstraints(/*has_constraints=*/true,
391 std::move(constraints));
392 if (status != ZX_OK) {
393 ZX_DLOG(ERROR, status)
394 << "fuchsia.sysmem.BufferCollection.SetConstraints()";
395 return false;
396 }
397
398 zx::channel token_channel = collection_token_for_vulkan.TakeChannel();
399 VkBufferCollectionCreateInfoFUCHSIA buffer_collection_create_info = {
400 VK_STRUCTURE_TYPE_BUFFER_COLLECTION_CREATE_INFO_FUCHSIA};
401 buffer_collection_create_info.collectionToken = token_channel.get();
402 if (vkCreateBufferCollectionFUCHSIA(vk_device_,
403 &buffer_collection_create_info, nullptr,
404 &vk_buffer_collection_) != VK_SUCCESS) {
405 vk_buffer_collection_ = VK_NULL_HANDLE;
406 DLOG(ERROR) << "vkCreateBufferCollectionFUCHSIA() failed";
407 return false;
408 }
409
410 // vkCreateBufferCollectionFUCHSIA() takes ownership of the token on success.
411 ignore_result(token_channel.release());
412
413 VkImageCreateInfo image_create_info;
414 InitializeImageCreateInfo(&image_create_info, min_size_);
415
416 if (vkSetBufferCollectionConstraintsFUCHSIA(vk_device_, vk_buffer_collection_,
417 &image_create_info) !=
418 VK_SUCCESS) {
419 DLOG(ERROR) << "vkSetBufferCollectionConstraintsFUCHSIA() failed";
420 return false;
421 }
422
423 zx_status_t wait_status;
424 status = collection_->WaitForBuffersAllocated(&wait_status, &buffers_info_);
425 if (status != ZX_OK) {
426 ZX_DLOG(ERROR, status) << "fuchsia.sysmem.BufferCollection failed";
427 return false;
428 }
429
430 if (wait_status != ZX_OK) {
431 ZX_DLOG(ERROR, status) << "fuchsia.sysmem.BufferCollection::"
432 "WaitForBuffersAllocated() failed.";
433 return false;
434 }
435
436 DCHECK_GE(buffers_info_.buffer_count, min_buffer_count);
437 DCHECK(buffers_info_.settings.has_image_format_constraints);
438
439 // The logic should match LogicalBufferCollection::Allocate().
440 const fuchsia::sysmem::ImageFormatConstraints& format =
441 buffers_info_.settings.image_format_constraints;
442 size_t width =
443 RoundUp(std::max(format.min_coded_width, format.required_max_coded_width),
444 format.coded_width_divisor);
445 size_t height = RoundUp(
446 std::max(format.min_coded_height, format.required_max_coded_height),
447 format.coded_height_divisor);
448 image_size_ = gfx::Size(width, height);
449 buffer_size_ = buffers_info_.settings.buffer_settings.size_bytes;
450 is_protected_ = buffers_info_.settings.buffer_settings.is_secure;
451
452 // Add all images to Image pipe for presentation later.
453 if (scenic_overlay_view_.has_value()) {
454 scenic_overlay_view_->AddImages(buffers_info_.buffer_count, image_size_);
455 }
456
457 // CreateVkImage() should always be called on the same thread, but it may be
458 // different from the thread that called Initialize().
459 DETACH_FROM_THREAD(vulkan_thread_checker_);
460
461 return true;
462 }
463
InitializeImageCreateInfo(VkImageCreateInfo * vk_image_info,gfx::Size size)464 void SysmemBufferCollection::InitializeImageCreateInfo(
465 VkImageCreateInfo* vk_image_info,
466 gfx::Size size) {
467 *vk_image_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
468 vk_image_info->flags = is_protected_ ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u;
469 vk_image_info->imageType = VK_IMAGE_TYPE_2D;
470 vk_image_info->format = VkFormatForBufferFormat(format_);
471 vk_image_info->extent = VkExtent3D{size.width(), size.height(), 1};
472 vk_image_info->mipLevels = 1;
473 vk_image_info->arrayLayers = 1;
474 vk_image_info->samples = VK_SAMPLE_COUNT_1_BIT;
475 vk_image_info->tiling =
476 is_mappable() ? VK_IMAGE_TILING_LINEAR : VK_IMAGE_TILING_OPTIMAL;
477
478 vk_image_info->usage = VK_IMAGE_USAGE_SAMPLED_BIT |
479 VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
480 VK_IMAGE_USAGE_TRANSFER_DST_BIT;
481 if (usage_ == gfx::BufferUsage::SCANOUT ||
482 usage_ == gfx::BufferUsage::SCANOUT_CPU_READ_WRITE) {
483 vk_image_info->usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
484 }
485
486 vk_image_info->sharingMode = VK_SHARING_MODE_EXCLUSIVE;
487 vk_image_info->initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
488 }
489
490 } // namespace ui
491