| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/ui/scenic/lib/gfx/swapchain/buffer_pool.h" |
| |
| #include <fuchsia/sysmem/cpp/fidl.h> |
| #include <lib/async/default.h> |
| #include <lib/fit/defer.h> |
| #include <lib/trace/event.h> |
| |
| #include "src/graphics/display/drivers/display/preferred-scanout-image-type.h" |
| #include "src/ui/lib/escher/escher.h" |
| #include "src/ui/lib/escher/flib/fence.h" |
| #include "src/ui/lib/escher/impl/naive_image.h" |
| #include "src/ui/lib/escher/util/bit_ops.h" |
| #include "src/ui/lib/escher/util/fuchsia_utils.h" |
| #include "src/ui/lib/escher/util/image_utils.h" |
| #include "src/ui/lib/escher/vk/chained_semaphore_generator.h" |
| #include "src/ui/lib/escher/vk/gpu_mem.h" |
| #include "src/ui/lib/escher/vk/image_layout_updater.h" |
| #include "src/ui/scenic/lib/allocation/id.h" |
| #include "src/ui/scenic/lib/display/util.h" |
| |
| #define VK_CHECK_RESULT(XXX) FX_CHECK(XXX.result == vk::Result::eSuccess) |
| |
| namespace scenic_impl { |
| namespace gfx { |
| namespace { |
| |
| // Highest priority format first. |
| const vk::Format kPreferredImageFormats[] = { |
| vk::Format::eR8G8B8A8Srgb, |
| vk::Format::eB8G8R8A8Srgb, |
| }; |
| |
| } // namespace |
| |
| BufferPool::BufferPool(size_t count, Environment* environment, bool use_protected_memory) { |
| FX_CHECK(CreateBuffers(count, environment, use_protected_memory)); |
| } |
| |
| BufferPool& BufferPool::operator=(BufferPool&& rhs) { |
| buffers_ = std::move(rhs.buffers_); |
| used_ = std::move(rhs.used_); |
| image_config_ = rhs.image_config_; |
| image_format_ = rhs.image_format_; |
| return *this; |
| } |
| |
| BufferPool::~BufferPool() { FX_CHECK(buffers_.empty()); } |
| |
| BufferPool::Framebuffer* BufferPool::GetUnused() { |
| for (size_t i = 0; i < buffers_.size(); ++i) { |
| if (!used_[i]) { |
| used_[i] = true; |
| return &buffers_[i]; |
| } |
| } |
| return nullptr; |
| } |
| |
| void BufferPool::Put(BufferPool::Framebuffer* f) { |
| for (size_t i = 0; i < buffers_.size(); ++i) { |
| if (&buffers_[i] == f) { |
| used_[i] = false; |
| return; |
| } |
| } |
| FX_CHECK(false) << "Tried to release a buffer not owned by this pool"; |
| } |
| |
| void BufferPool::Clear( |
| std::shared_ptr<fuchsia::hardware::display::ControllerSyncPtr> display_controller) { |
| for (size_t i = 0; i < buffers_.size(); ++i) { |
| if ((*display_controller)->ReleaseImage(buffers_[i].id) != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to release image id=" << buffers_[i].id; |
| } |
| } |
| buffers_.clear(); |
| } |
| |
| static vk::ImageUsageFlags GetFramebufferImageUsage() { |
| return vk::ImageUsageFlagBits::eColorAttachment | |
| // For blitting frame #. |
| vk::ImageUsageFlagBits::eTransferDst; |
| } |
| |
| static vk::Format GetDisplayImageFormat(zx_pixel_format_t pixel_format) { |
| switch (pixel_format) { |
| case ZX_PIXEL_FORMAT_RGB_x888: |
| case ZX_PIXEL_FORMAT_ARGB_8888: |
| return vk::Format::eB8G8R8A8Srgb; |
| case ZX_PIXEL_FORMAT_BGR_888x: |
| case ZX_PIXEL_FORMAT_ABGR_8888: |
| return vk::Format::eR8G8B8A8Srgb; |
| } |
| FX_CHECK(false) << "Unsupported pixel format: " << pixel_format; |
| return vk::Format::eUndefined; |
| } |
| |
| // Create a number of synced tokens that can be imported into collections. |
| static std::vector<fuchsia::sysmem::BufferCollectionTokenSyncPtr> DuplicateToken( |
| const fuchsia::sysmem::BufferCollectionTokenSyncPtr& input, uint32_t count) { |
| std::vector<fuchsia::sysmem::BufferCollectionTokenSyncPtr> output; |
| for (uint32_t i = 0; i < count; ++i) { |
| fuchsia::sysmem::BufferCollectionTokenSyncPtr new_token; |
| zx_status_t status = |
| input->Duplicate(std::numeric_limits<uint32_t>::max(), new_token.NewRequest()); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Unable to duplicate sysmem token:" << status; |
| return std::vector<fuchsia::sysmem::BufferCollectionTokenSyncPtr>(); |
| } |
| output.push_back(std::move(new_token)); |
| } |
| zx_status_t status = input->Sync(); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Unable to sync sysmem token:" << status; |
| return std::vector<fuchsia::sysmem::BufferCollectionTokenSyncPtr>(); |
| } |
| return output; |
| } |
| |
| bool BufferPool::CreateBuffers(size_t count, BufferPool::Environment* environment, |
| bool use_protected_memory) { |
| if (count == 0) { |
| return true; |
| } |
| |
| FX_CHECK(environment->escher); |
| FX_CHECK(buffers_.empty()); |
| buffers_.resize(count); |
| used_.resize(count); |
| vk::ImageUsageFlags image_usage = GetFramebufferImageUsage(); |
| image_format_ = vk::Format::eUndefined; |
| |
| const uint32_t width_in_px = environment->display->width_in_px(); |
| const uint32_t height_in_px = environment->display->height_in_px(); |
| |
| zx_pixel_format_t pixel_format = ZX_PIXEL_FORMAT_NONE; |
| for (auto preferred_format : kPreferredImageFormats) { |
| for (zx_pixel_format_t format : environment->display->pixel_formats()) { |
| vk::Format vk_format = GetDisplayImageFormat(format); |
| if (vk_format == preferred_format) { |
| pixel_format = format; |
| image_format_ = vk_format; |
| break; |
| } |
| } |
| if (pixel_format != ZX_PIXEL_FORMAT_NONE) { |
| break; |
| } |
| } |
| |
| if (pixel_format == ZX_PIXEL_FORMAT_NONE) { |
| FX_LOGS(ERROR) << "Unable to find usable pixel format."; |
| return false; |
| } |
| |
| image_config_.height = height_in_px; |
| image_config_.width = width_in_px; |
| image_config_.pixel_format = pixel_format; |
| image_config_.type = IMAGE_TYPE_PREFERRED_SCANOUT; |
| |
| // Create all the tokens. |
| fuchsia::sysmem::BufferCollectionTokenSyncPtr local_token = |
| environment->sysmem->CreateBufferCollection(); |
| if (!local_token) { |
| FX_LOGS(ERROR) << "Sysmem tokens couldn't be allocated"; |
| return false; |
| } |
| |
| local_token->SetName(100u, use_protected_memory ? "Display-Protected" : "Display"); |
| local_token->SetDebugClientInfo("buffer_pool", 0u); |
| |
| auto tokens = DuplicateToken(local_token, 2); |
| if (tokens.empty()) { |
| FX_LOGS(ERROR) << "Sysmem tokens failed to be duped."; |
| return false; |
| } |
| |
| // Set display buffer constraints. |
| tokens[1]->SetDebugClientInfo("scenic", 0u); |
| auto display_collection_id = allocation::GenerateUniqueBufferCollectionId(); |
| auto result = scenic_impl::ImportBufferCollection(display_collection_id, |
| *environment->display_controller.get(), |
| std::move(tokens[1]), image_config_); |
| if (!result) { |
| FX_LOGS(ERROR) << "Setting buffer collection constraints failed."; |
| return false; |
| } |
| |
| auto collection_closer = fit::defer([environment, display_collection_id]() { |
| if ((*environment->display_controller)->ReleaseBufferCollection(display_collection_id) != |
| ZX_OK) { |
| FX_LOGS(ERROR) << "ReleaseBufferCollection failed."; |
| } |
| }); |
| |
| // Set Vulkan buffer constraints. |
| vk::ImageCreateInfo create_info; |
| create_info.flags = |
| use_protected_memory ? vk::ImageCreateFlagBits::eProtected : vk::ImageCreateFlags(); |
| create_info.imageType = vk::ImageType::e2D; |
| create_info.format = image_format_; |
| create_info.extent = vk::Extent3D{width_in_px, height_in_px, 1}; |
| create_info.mipLevels = 1; |
| create_info.arrayLayers = 1; |
| create_info.samples = vk::SampleCountFlagBits::e1; |
| create_info.tiling = vk::ImageTiling::eOptimal; |
| create_info.usage = image_usage; |
| create_info.sharingMode = vk::SharingMode::eExclusive; |
| create_info.initialLayout = vk::ImageLayout::eUndefined; |
| |
| tokens[0]->SetDebugClientInfo("vulkan", 0u); |
| vk::BufferCollectionCreateInfoFUCHSIA import_collection; |
| import_collection.collectionToken = tokens[0].Unbind().TakeChannel().release(); |
| auto import_result = environment->vk_device.createBufferCollectionFUCHSIA( |
| import_collection, nullptr, environment->escher->device()->dispatch_loader()); |
| if (import_result.result != vk::Result::eSuccess) { |
| FX_LOGS(ERROR) << "VkImportBufferCollectionFUCHSIA failed: " |
| << vk::to_string(import_result.result); |
| return false; |
| } |
| |
| auto vulkan_collection_closer = fit::defer([environment, import_result]() { |
| environment->vk_device.destroyBufferCollectionFUCHSIA( |
| import_result.value, nullptr, environment->escher->device()->dispatch_loader()); |
| }); |
| |
| auto constraints_result = environment->vk_device.setBufferCollectionConstraintsFUCHSIA( |
| import_result.value, create_info, environment->escher->device()->dispatch_loader()); |
| if (constraints_result != vk::Result::eSuccess) { |
| FX_LOGS(ERROR) << "VkSetBufferCollectionConstraints failed: " |
| << vk::to_string(constraints_result); |
| return false; |
| } |
| |
| // Use the local collection so we can read out the error if allocation |
| // fails, and to ensure everything's allocated before trying to import it |
| // into another process. |
| fuchsia::sysmem::BufferCollectionSyncPtr sysmem_collection = |
| environment->sysmem->GetCollectionFromToken(std::move(local_token)); |
| if (!sysmem_collection) { |
| return false; |
| } |
| fuchsia::sysmem::BufferCollectionConstraints constraints; |
| constraints.min_buffer_count = count; |
| constraints.usage.vulkan = fuchsia::sysmem::noneUsage; |
| zx_status_t status = sysmem_collection->SetConstraints(true, constraints); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Unable to set constraints:" << status; |
| return false; |
| } |
| |
| fuchsia::sysmem::BufferCollectionInfo_2 info; |
| zx_status_t allocation_status = ZX_OK; |
| // Wait for the buffers to be allocated. |
| status = sysmem_collection->WaitForBuffersAllocated(&allocation_status, &info); |
| if (status != ZX_OK || allocation_status != ZX_OK) { |
| FX_LOGS(ERROR) << "Waiting for buffers failed:" << status << " " << allocation_status; |
| return false; |
| } |
| |
| // Import the collection into a vulkan image. |
| if (info.buffer_count < count) { |
| FX_LOGS(ERROR) << "Incorrect buffer collection count: " << info.buffer_count; |
| return false; |
| } |
| |
| escher::ImageLayoutUpdater layout_updater(environment->escher->GetWeakPtr()); |
| |
| for (uint32_t i = 0; i < count; ++i) { |
| vk::BufferCollectionImageCreateInfoFUCHSIA collection_image_info; |
| collection_image_info.collection = import_result.value; |
| collection_image_info.index = i; |
| create_info.setPNext(&collection_image_info); |
| |
| auto image_result = environment->vk_device.createImage(create_info); |
| if (image_result.result != vk::Result::eSuccess) { |
| FX_LOGS(ERROR) << "VkCreateImage failed: " << vk::to_string(image_result.result); |
| return false; |
| } |
| |
| auto memory_requirements = |
| environment->vk_device.getImageMemoryRequirements(image_result.value); |
| auto collection_properties = environment->vk_device.getBufferCollectionPropertiesFUCHSIA( |
| import_result.value, environment->escher->device()->dispatch_loader()); |
| if (collection_properties.result != vk::Result::eSuccess) { |
| FX_LOGS(ERROR) << "VkGetBufferCollectionProperties failed: " |
| << vk::to_string(collection_properties.result); |
| return false; |
| } |
| |
| FX_CHECK(memory_requirements.memoryTypeBits & collection_properties.value.memoryTypeBits) |
| << memory_requirements.memoryTypeBits << " " << collection_properties.value.memoryTypeBits; |
| uint32_t memory_type_index = escher::CountTrailingZeros( |
| memory_requirements.memoryTypeBits & collection_properties.value.memoryTypeBits); |
| vk::StructureChain<vk::MemoryAllocateInfo, vk::ImportMemoryBufferCollectionFUCHSIA, |
| vk::MemoryDedicatedAllocateInfoKHR> |
| alloc_info(vk::MemoryAllocateInfo() |
| .setAllocationSize(memory_requirements.size) |
| .setMemoryTypeIndex(memory_type_index), |
| vk::ImportMemoryBufferCollectionFUCHSIA() |
| .setCollection(import_result.value) |
| .setIndex(i), |
| vk::MemoryDedicatedAllocateInfoKHR().setImage(image_result.value)); |
| |
| auto mem_result = |
| environment->vk_device.allocateMemory(alloc_info.get<vk::MemoryAllocateInfo>()); |
| |
| if (mem_result.result != vk::Result::eSuccess) { |
| FX_LOGS(ERROR) << "vkAllocMemory failed: " << vk::to_string(mem_result.result); |
| return false; |
| } |
| |
| Framebuffer buffer; |
| buffer.device_memory = |
| escher::GpuMem::AdoptVkMemory(environment->vk_device, mem_result.value, |
| memory_requirements.size, false /* needs_mapped_ptr */); |
| FX_CHECK(buffer.device_memory); |
| |
| // Wrap the image and device memory in a escher::Image. |
| escher::ImageInfo image_info; |
| image_info.format = image_format_; |
| image_info.width = width_in_px; |
| image_info.height = height_in_px; |
| image_info.usage = image_usage; |
| image_info.memory_flags = |
| use_protected_memory ? vk::MemoryPropertyFlagBits::eProtected : vk::MemoryPropertyFlags(); |
| |
| // escher::NaiveImage::AdoptVkImage() binds the memory to the image. |
| buffer.escher_image = escher::impl::NaiveImage::AdoptVkImage( |
| environment->recycler, image_info, image_result.value, buffer.device_memory, |
| create_info.initialLayout); |
| |
| if (!buffer.escher_image) { |
| FX_LOGS(ERROR) << "Creating escher::EscherImage failed."; |
| environment->vk_device.destroyImage(image_result.value); |
| return false; |
| } else { |
| vk::ImageLayout kSwapchainLayout = vk::ImageLayout::eColorAttachmentOptimal; |
| buffer.escher_image->set_swapchain_layout(kSwapchainLayout); |
| layout_updater.ScheduleSetImageInitialLayout(buffer.escher_image, kSwapchainLayout); |
| } |
| zx_status_t import_image_status = ZX_OK; |
| zx_status_t transport_status = (*environment->display_controller) |
| ->ImportImage(image_config_, display_collection_id, i, |
| &import_image_status, &buffer.id); |
| if (transport_status != ZX_OK) { |
| buffer.id = fuchsia::hardware::display::INVALID_DISP_ID; |
| FX_PLOGS(ERROR, transport_status) << "Importing image FIDL call failed."; |
| return false; |
| } |
| if (import_image_status != ZX_OK) { |
| buffer.id = fuchsia::hardware::display::INVALID_DISP_ID; |
| FX_PLOGS(ERROR, import_image_status) |
| << "Importing image failed (" << image_info |
| << " use_protected_memory=" << (use_protected_memory ? "true" : "false") << ")."; |
| return false; |
| } |
| |
| buffers_[i] = std::move(buffer); |
| used_[i] = false; |
| } |
| |
| auto semaphore_pair = environment->escher->semaphore_chain()->TakeLastAndCreateNextSemaphore(); |
| layout_updater.AddWaitSemaphore(std::move(semaphore_pair.semaphore_to_wait), |
| vk::PipelineStageFlagBits::eColorAttachmentOutput); |
| layout_updater.AddSignalSemaphore(std::move(semaphore_pair.semaphore_to_signal)); |
| layout_updater.Submit(); |
| |
| sysmem_collection->Close(); |
| |
| return true; |
| } |
| |
| } // namespace gfx |
| } // namespace scenic_impl |