blob: fab9eb84bab9672df8c7c8d376e86395ea2459f5 [file] [log] [blame]
// Copyright 2018 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 "image_pipe_surface_async.h"
#include <lib/async/cpp/task.h>
#include <lib/fdio/directory.h>
#include <lib/trace/event.h>
#include "src/lib/fsl/handles/object_info.h"
#include "vk_dispatch_table_helper.h"
#include "vulkan/vk_layer.h"
namespace image_pipe_swapchain {
bool ImagePipeSurfaceAsync::Init() {
zx_status_t status = fdio_service_connect("/svc/fuchsia.sysmem.Allocator",
sysmem_allocator_.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
fprintf(stderr, "Couldn't connect to sysmem service\n");
return false;
}
sysmem_allocator_->SetDebugClientInfo(fsl::GetCurrentProcessName(), fsl::GetCurrentProcessKoid());
return true;
}
bool ImagePipeSurfaceAsync::CreateImage(VkDevice device, VkLayerDispatchTable* pDisp,
VkFormat format, VkImageUsageFlags usage,
VkSwapchainCreateFlagsKHR swapchain_flags,
VkExtent2D extent, uint32_t image_count,
const VkAllocationCallbacks* pAllocator,
std::vector<ImageInfo>* image_info_out) {
// Allocate token for BufferCollection.
fuchsia::sysmem::BufferCollectionTokenSyncPtr local_token;
zx_status_t status = sysmem_allocator_->AllocateSharedCollection(local_token.NewRequest());
if (status != ZX_OK) {
fprintf(stderr, "Swapchain: AllocateSharedCollection failed: %d\n", status);
return false;
}
// Duplicate tokens to pass around.
auto scenic_token = std::make_unique<fuchsia::sysmem::BufferCollectionTokenSyncPtr>();
status = local_token->Duplicate(std::numeric_limits<uint32_t>::max(), scenic_token->NewRequest());
if (status != ZX_OK) {
fprintf(stderr, "Swapchain: Duplicate failed: %d\n", status);
return false;
}
fuchsia::sysmem::BufferCollectionTokenSyncPtr vulkan_token;
status = local_token->Duplicate(std::numeric_limits<uint32_t>::max(), vulkan_token.NewRequest());
if (status != ZX_OK) {
fprintf(stderr, "Swapchain: Duplicate failed: %d\n", status);
return false;
}
status = local_token->Sync();
if (status != ZX_OK) {
fprintf(stderr, "Swapchain: Sync failed: %d\n", status);
return false;
}
async::PostTask(loop_.dispatcher(), [this, scenic_token = std::move(scenic_token),
new_buffer_id = ++current_buffer_id_]() {
// Pass |scenic_token| to Scenic to collect constraints.
if (image_pipe_.is_bound())
image_pipe_->AddBufferCollection(new_buffer_id, scenic_token->Unbind());
});
// Set swapchain constraints |vulkan_token|.
VkBufferCollectionCreateInfoFUCHSIA import_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_COLLECTION_CREATE_INFO_FUCHSIA,
.pNext = nullptr,
.collectionToken = vulkan_token.Unbind().TakeChannel().release(),
};
VkBufferCollectionFUCHSIA collection;
VkResult result =
pDisp->CreateBufferCollectionFUCHSIA(device, &import_info, pAllocator, &collection);
if (result != VK_SUCCESS) {
fprintf(stderr, "Failed to import buffer collection: %d\n", result);
return false;
}
uint32_t image_flags = 0;
if (swapchain_flags & VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR)
image_flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
if (swapchain_flags & VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR)
image_flags |= VK_IMAGE_CREATE_PROTECTED_BIT;
VkImageCreateInfo image_create_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
.flags = image_flags,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = VkExtent3D{extent.width, extent.height, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
result = pDisp->SetBufferCollectionConstraintsFUCHSIA(device, collection, &image_create_info);
if (result != VK_SUCCESS) {
fprintf(stderr, "Failed to set buffer collection constraints: %d\n", result);
return false;
}
// Set |image_count| constraints on the |local_token|.
fuchsia::sysmem::BufferCollectionSyncPtr buffer_collection;
status = sysmem_allocator_->BindSharedCollection(std::move(local_token),
buffer_collection.NewRequest());
if (status != ZX_OK) {
fprintf(stderr, "Swapchain: BindSharedCollection failed: %d\n", status);
return false;
}
fuchsia::sysmem::BufferCollectionConstraints constraints;
constraints.min_buffer_count = image_count;
constraints.usage.vulkan = fuchsia::sysmem::vulkanUsageSampled;
status = buffer_collection->SetConstraints(true, constraints);
if (status != ZX_OK) {
fprintf(stderr, "Swapchain: SetConstraints failed: %d %d\n", image_count, status);
return false;
}
// Wait for buffer to be allocated.
zx_status_t allocation_status = ZX_OK;
fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info = {};
status = buffer_collection->WaitForBuffersAllocated(&allocation_status, &buffer_collection_info);
if (status != ZX_OK || allocation_status != ZX_OK) {
fprintf(stderr, "Swapchain: WaitForBuffersAllocated failed: %d\n", status);
return false;
}
if (buffer_collection_info.buffer_count < image_count) {
fprintf(stderr, "Swapchain: Failed to allocate %d buffers: %d\n", image_count, status);
return false;
}
// Insert width and height information while adding images because it wasnt passed in
// AddBufferCollection().
fuchsia::sysmem::ImageFormat_2 image_format = {};
image_format.coded_width = extent.width;
image_format.coded_height = extent.height;
for (uint32_t i = 0; i < image_count; ++i) {
// Create Vk image.
VkBufferCollectionImageCreateInfoFUCHSIA image_format_fuchsia = {
.sType = VK_STRUCTURE_TYPE_BUFFER_COLLECTION_IMAGE_CREATE_INFO_FUCHSIA,
.pNext = nullptr,
.collection = collection,
.index = i};
image_create_info.pNext = &image_format_fuchsia;
VkImage image;
result = pDisp->CreateImage(device, &image_create_info, pAllocator, &image);
if (result != VK_SUCCESS) {
fprintf(stderr, "Swapchain: vkCreateImage failed: %d\n", result);
return false;
}
// Extract memory handles from BufferCollection.
VkMemoryRequirements memory_requirements;
pDisp->GetImageMemoryRequirements(device, image, &memory_requirements);
VkBufferCollectionPropertiesFUCHSIA properties = {
.sType = VK_STRUCTURE_TYPE_BUFFER_COLLECTION_PROPERTIES_FUCHSIA};
result = pDisp->GetBufferCollectionPropertiesFUCHSIA(device, collection, &properties);
if (result != VK_SUCCESS) {
fprintf(stderr, "Swapchain: GetBufferCollectionPropertiesFUCHSIA failed: %d\n", status);
return false;
}
uint32_t memory_type_index =
__builtin_ctz(memory_requirements.memoryTypeBits & properties.memoryTypeBits);
VkImportMemoryBufferCollectionFUCHSIA import_info = {
.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_BUFFER_COLLECTION_FUCHSIA,
.collection = collection,
.index = i,
};
VkMemoryAllocateInfo alloc_info{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &import_info,
.allocationSize = memory_requirements.size,
.memoryTypeIndex = memory_type_index,
};
VkDeviceMemory memory;
result = pDisp->AllocateMemory(device, &alloc_info, pAllocator, &memory);
if (result != VK_SUCCESS) {
fprintf(stderr, "Swapchain: vkAllocateMemory failed: %d\n", result);
return false;
}
result = pDisp->BindImageMemory(device, image, memory, 0);
if (result != VK_SUCCESS) {
fprintf(stderr, "Swapchain: vkBindImageMemory failed: %d\n", result);
return false;
}
ImageInfo info = {
.image = image,
.memory = memory,
.image_id = next_image_id(),
};
image_info_out->push_back(info);
async::PostTask(loop_.dispatcher(),
[this, info, current_buffer_id = current_buffer_id_, i, image_format]() {
std::lock_guard<std::mutex> lock(mutex_);
if (image_pipe_.is_bound())
image_pipe_->AddImage(info.image_id, current_buffer_id, i, image_format);
});
image_id_to_buffer_id_.emplace(info.image_id, current_buffer_id_);
}
buffer_counts_.emplace(current_buffer_id_, image_count);
pDisp->DestroyBufferCollectionFUCHSIA(device, collection, pAllocator);
buffer_collection->Close();
return true;
}
bool ImagePipeSurfaceAsync::IsLost() {
std::lock_guard<std::mutex> lock(mutex_);
return channel_closed_;
}
// Disable thread safety analysis because it can't handle unique_lock.
void ImagePipeSurfaceAsync::RemoveImage(uint32_t image_id)
__attribute__((no_thread_safety_analysis)) {
std::unique_lock<std::mutex> lock(mutex_);
for (auto iter = queue_.begin(); iter != queue_.end();) {
if (iter->image_id == image_id) {
iter = queue_.erase(iter);
} else {
iter++;
}
}
// TODO(fxbug.dev/24315) - remove this workaround
static constexpr bool kUseWorkaround = true;
while (kUseWorkaround && present_pending_ && !channel_closed_) {
lock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(5));
lock.lock();
}
async::PostTask(loop_.dispatcher(), [this, image_id]() {
if (image_pipe_.is_bound())
image_pipe_->RemoveImage(image_id);
});
// We do not expect same image to be removed multiple times.
auto buffer_it = buffer_counts_.find(image_id_to_buffer_id_[image_id]);
if (--buffer_it->second == 0) {
uint32_t collection_id = buffer_it->first;
async::PostTask(loop_.dispatcher(), [this, collection_id]() {
if (image_pipe_.is_bound())
image_pipe_->RemoveBufferCollection(collection_id);
});
}
}
void ImagePipeSurfaceAsync::PresentImage(uint32_t image_id,
std::vector<std::unique_ptr<PlatformEvent>> acquire_fences,
std::vector<std::unique_ptr<PlatformEvent>> release_fences,
VkQueue queue) {
std::lock_guard<std::mutex> lock(mutex_);
TRACE_FLOW_BEGIN("gfx", "image_pipe_swapchain_to_present", image_id);
std::vector<std::unique_ptr<FenceSignaler>> release_fence_signalers;
release_fence_signalers.reserve(release_fences.size());
for (auto& fence : release_fences) {
zx::event event = static_cast<FuchsiaEvent*>(fence.get())->Take();
release_fence_signalers.push_back(std::make_unique<FenceSignaler>(std::move(event)));
}
if (channel_closed_)
return;
std::vector<zx::event> acquire_events;
acquire_events.reserve(acquire_fences.size());
for (auto& fence : acquire_fences) {
zx::event event = static_cast<FuchsiaEvent*>(fence.get())->Take();
acquire_events.push_back(std::move(event));
}
queue_.push_back({image_id, std::move(acquire_events), std::move(release_fence_signalers)});
if (!present_pending_) {
async::PostTask(loop_.dispatcher(), [this]() {
std::lock_guard<std::mutex> lock(mutex_);
PresentNextImageLocked();
});
}
}
SupportedImageProperties& ImagePipeSurfaceAsync::GetSupportedImageProperties() {
return supported_image_properties_;
}
void ImagePipeSurfaceAsync::PresentNextImageLocked() {
if (present_pending_)
return;
if (queue_.empty())
return;
TRACE_DURATION("gfx", "ImagePipeSurfaceAsync::PresentNextImageLocked");
// To guarantee FIFO mode, we can't have Scenic drop any of our frames.
// We accomplish that sending the next one only when we receive the callback
// for the previous one. We don't use the presentation info timing
// parameters because we really just want to push out the next image asap.
uint64_t presentation_time = zx_clock_get_monotonic();
auto& present = queue_.front();
TRACE_FLOW_END("gfx", "image_pipe_swapchain_to_present", present.image_id);
TRACE_FLOW_BEGIN("gfx", "image_pipe_present_image", present.image_id);
if (image_pipe_.is_bound()) {
std::vector<zx::event> release_events;
release_events.reserve(present.release_fences.size());
for (auto& signaler : present.release_fences) {
zx::event event;
signaler->event().duplicate(ZX_RIGHT_SAME_RIGHTS, &event);
release_events.push_back(std::move(event));
}
image_pipe_->PresentImage(present.image_id, presentation_time,
std::move(present.acquire_fences), std::move(release_events),
// Called on the async loop.
[this, release_fences = std::move(present.release_fences)](
fuchsia::images::PresentationInfo pinfo) {
std::lock_guard<std::mutex> lock(mutex_);
present_pending_ = false;
for (auto& fence : release_fences) {
fence->reset();
}
PresentNextImageLocked();
});
}
queue_.erase(queue_.begin());
present_pending_ = true;
}
} // namespace image_pipe_swapchain