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