blob: 0e365eb97311355bbeae1e6c09c6b131f4dd1701 [file] [log] [blame]
// Copyright 2020 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/flatland/renderer/vk_renderer.h"
#include <zircon/pixelformat.h>
#include "src/ui/lib/escher/escher.h"
#include "src/ui/lib/escher/impl/vulkan_utils.h"
#include "src/ui/lib/escher/renderer/render_funcs.h"
#include "src/ui/lib/escher/util/image_utils.h"
#include "src/ui/lib/escher/util/trace_macros.h"
namespace escher {
namespace {
TexturePtr CreateDepthTexture(Escher* escher, const ImagePtr& output_image) {
TexturePtr depth_buffer;
RenderFuncs::ObtainDepthTexture(
escher, output_image->use_protected_memory(), output_image->info(),
escher->device()->caps().GetMatchingDepthStencilFormat().value, depth_buffer);
return depth_buffer;
}
} // anonymous namespace
} // namespace escher
namespace flatland {
VkRenderer::VkRenderer(escher::EscherWeakPtr escher)
: escher_(std::move(escher)), compositor_(escher::RectangleCompositor(escher_.get())) {}
VkRenderer::~VkRenderer() {
auto vk_device = escher_->vk_device();
auto vk_loader = escher_->device()->dispatch_loader();
for (auto& [_, vk_collection] : vk_collection_map_) {
vk_device.destroyBufferCollectionFUCHSIA(vk_collection, nullptr, vk_loader);
}
}
bool VkRenderer::ImportBufferCollection(
sysmem_util::GlobalBufferCollectionId collection_id,
fuchsia::sysmem::Allocator_Sync* sysmem_allocator,
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) {
return RegisterCollection(collection_id, sysmem_allocator, std::move(token),
escher::RectangleCompositor::kTextureUsageFlags);
}
void VkRenderer::ReleaseBufferCollection(sysmem_util::GlobalBufferCollectionId collection_id) {
// Multiple threads may be attempting to read/write from the various maps,
// lock this function here.
// TODO(fxbug.dev/44335): Convert this to a lock-free structure.
std::unique_lock<std::mutex> lock(lock_);
auto collection_itr = collection_map_.find(collection_id);
// If the collection is not in the map, then there's nothing to do.
if (collection_itr == collection_map_.end()) {
return;
}
// Erase the sysmem collection from the map.
collection_map_.erase(collection_id);
// Grab the vulkan collection and DCHECK since there should always be
// a vk collection to correspond with the buffer collection. We then
// delete it using the vk device.
auto vk_itr = vk_collection_map_.find(collection_id);
FX_DCHECK(vk_itr != vk_collection_map_.end());
auto vk_device = escher_->vk_device();
auto vk_loader = escher_->device()->dispatch_loader();
vk_device.destroyBufferCollectionFUCHSIA(vk_itr->second, nullptr, vk_loader);
vk_collection_map_.erase(collection_id);
}
bool VkRenderer::RegisterCollection(
sysmem_util::GlobalBufferCollectionId collection_id,
fuchsia::sysmem::Allocator_Sync* sysmem_allocator,
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token,
vk::ImageUsageFlags usage) {
TRACE_DURATION("flatland", "VkRenderer::RegisterCollection");
auto vk_device = escher_->vk_device();
auto vk_loader = escher_->device()->dispatch_loader();
FX_DCHECK(vk_device);
FX_DCHECK(collection_id != sysmem_util::kInvalidId);
// Check for a null token here before we try to duplicate it to get the
// vulkan token.
if (!token.is_valid()) {
FX_LOGS(ERROR) << "Token is invalid.";
return sysmem_util::kInvalidId;
}
auto vk_constraints =
escher::RectangleCompositor::GetDefaultImageConstraints(vk::Format::eB8G8R8A8Unorm, usage);
// Create a duped vulkan token.
fuchsia::sysmem::BufferCollectionTokenSyncPtr vulkan_token;
{
// TODO(fxbug.dev/51213): See if this can become asynchronous.
fuchsia::sysmem::BufferCollectionTokenSyncPtr sync_token = token.BindSync();
zx_status_t status =
sync_token->Duplicate(std::numeric_limits<uint32_t>::max(), vulkan_token.NewRequest());
FX_DCHECK(status == ZX_OK);
// Reassign the channel to the non-sync interface handle.
token = sync_token.Unbind();
}
// Create the sysmem buffer collection. We do this before creating the vulkan collection below,
// since New() checks if the incoming token is of the wrong type/malicious.
auto result = BufferCollectionInfo::New(sysmem_allocator, std::move(token));
if (result.is_error()) {
FX_LOGS(ERROR) << "Unable to register collection.";
return false;
}
// Create the vk_collection and set its constraints.
vk::BufferCollectionFUCHSIA vk_collection;
{
vk::BufferCollectionCreateInfoFUCHSIA buffer_collection_create_info;
buffer_collection_create_info.collectionToken = vulkan_token.Unbind().TakeChannel().release();
vk_collection = escher::ESCHER_CHECKED_VK_RESULT(
vk_device.createBufferCollectionFUCHSIA(buffer_collection_create_info, nullptr, vk_loader));
auto vk_result =
vk_device.setBufferCollectionConstraintsFUCHSIA(vk_collection, vk_constraints, vk_loader);
FX_DCHECK(vk_result == vk::Result::eSuccess);
}
// Multiple threads may be attempting to read/write from |collection_map_| so we
// lock this function here.
// TODO(fxbug.dev/44335): Convert this to a lock-free structure.
std::unique_lock<std::mutex> lock(lock_);
collection_map_[collection_id] = std::move(result.value());
vk_collection_map_[collection_id] = std::move(vk_collection);
return true;
}
bool VkRenderer::ImportBufferImage(const ImageMetadata& metadata) {
std::unique_lock<std::mutex> lock(lock_);
const auto& collection_itr = collection_map_.find(metadata.collection_id);
if (collection_itr == collection_map_.end()) {
FX_LOGS(ERROR) << "Collection with id " << metadata.collection_id << " does not exist.";
return false;
}
auto& collection = collection_itr->second;
if (!collection.BuffersAreAllocated()) {
FX_LOGS(ERROR) << "Buffers for collection " << metadata.collection_id
<< " have not been allocated.";
return false;
}
const auto& sysmem_info = collection.GetSysmemInfo();
const auto vmo_count = sysmem_info.buffer_count;
auto image_constraints = sysmem_info.settings.image_format_constraints;
if (texture_map_.find(metadata.identifier) != texture_map_.end() ||
render_target_map_.find(metadata.identifier) != render_target_map_.end()) {
FX_LOGS(ERROR) << "An image with this identifier already exists.";
return false;
}
if (metadata.vmo_index >= vmo_count) {
FX_LOGS(ERROR) << "CreateImage failed, vmo_index " << metadata.vmo_index
<< " must be less than vmo_count " << vmo_count;
return false;
}
if (metadata.width < image_constraints.min_coded_width ||
metadata.width > image_constraints.max_coded_width) {
FX_LOGS(ERROR) << "CreateImage failed, width " << metadata.width
<< " is not within valid range [" << image_constraints.min_coded_width << ","
<< image_constraints.max_coded_width << "]";
return false;
}
if (metadata.height < image_constraints.min_coded_height ||
metadata.height > image_constraints.max_coded_height) {
FX_LOGS(ERROR) << "CreateImage failed, height " << metadata.height
<< " is not within valid range [" << image_constraints.min_coded_height << ","
<< image_constraints.max_coded_height << "]";
return false;
}
if (metadata.is_render_target) {
auto image = ExtractImage(metadata, escher::RectangleCompositor::kRenderTargetUsageFlags);
image->set_swapchain_layout(vk::ImageLayout::eColorAttachmentOptimal);
render_target_map_[metadata.identifier] = image;
depth_target_map_[metadata.identifier] = escher::CreateDepthTexture(escher_.get(), image);
pending_render_targets_.insert(metadata.identifier);
} else {
texture_map_[metadata.identifier] = ExtractTexture(metadata);
pending_textures_.insert(metadata.identifier);
}
return true;
}
void VkRenderer::ReleaseBufferImage(sysmem_util::GlobalImageId image_id) {
std::unique_lock<std::mutex> lock(lock_);
if (texture_map_.find(image_id) != texture_map_.end()) {
texture_map_.erase(image_id);
} else if (render_target_map_.find(image_id) != render_target_map_.end()) {
render_target_map_.erase(image_id);
depth_target_map_.erase(image_id);
}
}
escher::ImagePtr VkRenderer::ExtractImage(ImageMetadata metadata, vk::ImageUsageFlags usage) {
TRACE_DURATION("flatland", "VkRenderer::ExtractImage");
auto vk_device = escher_->vk_device();
auto vk_loader = escher_->device()->dispatch_loader();
GpuImageInfo gpu_info;
auto collection_itr = collection_map_.find(metadata.collection_id);
FX_DCHECK(collection_itr != collection_map_.end());
auto& collection = collection_itr->second;
auto vk_itr = vk_collection_map_.find(metadata.collection_id);
FX_DCHECK(vk_itr != vk_collection_map_.end());
auto vk_collection = vk_itr->second;
// Create the GPU info from the server side collection.
gpu_info = GpuImageInfo::New(vk_device, vk_loader, collection.GetSysmemInfo(), vk_collection,
metadata.vmo_index);
FX_DCHECK(gpu_info.GetGpuMem());
// Create and return an escher image.
auto image_ptr = escher::image_utils::NewImage(
vk_device, gpu_info.NewVkImageCreateInfo(metadata.width, metadata.height, usage),
gpu_info.GetGpuMem(), escher_->resource_recycler());
FX_DCHECK(image_ptr);
return image_ptr;
}
bool VkRenderer::RegisterRenderTargetCollection(
sysmem_util::GlobalBufferCollectionId collection_id,
fuchsia::sysmem::Allocator_Sync* sysmem_allocator,
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) {
return RegisterCollection(collection_id, sysmem_allocator, std::move(token),
escher::RectangleCompositor::kRenderTargetUsageFlags);
}
void VkRenderer::DeregisterRenderTargetCollection(
sysmem_util::GlobalBufferCollectionId collection_id) {
ReleaseBufferCollection(collection_id);
}
escher::TexturePtr VkRenderer::ExtractTexture(ImageMetadata metadata) {
auto image = ExtractImage(metadata, escher::RectangleCompositor::kTextureUsageFlags);
auto texture = escher::Texture::New(escher_->resource_recycler(), image, vk::Filter::eNearest);
FX_DCHECK(texture);
return texture;
}
void VkRenderer::Render(const ImageMetadata& render_target,
const std::vector<Rectangle2D>& rectangles,
const std::vector<ImageMetadata>& images,
const std::vector<zx::event>& release_fences) {
TRACE_DURATION("flatland", "VkRenderer::Render");
// Escher's frame class acts as a command buffer manager that we use to create a
// command buffer and submit it to the device queue once we are done.
auto frame = escher_->NewFrame("flatland::VkRenderer", ++frame_number_);
auto command_buffer = frame->cmds();
// Copy over the texture and render target data to local containers that do not need
// to be accessed via a lock. We're just doing a shallow copy via the copy assignment
// operator since the texture and render target data is just referenced through pointers.
// We manually unlock the lock after copying over the data.
std::unique_lock<std::mutex> lock(lock_);
const auto local_texture_map = texture_map_;
const auto local_render_target_map = render_target_map_;
const auto local_depth_target_map = depth_target_map_;
// After moving, the original containers are emptied.
const auto local_pending_textures = std::move(pending_textures_);
const auto local_pending_render_targets = std::move(pending_render_targets_);
lock.unlock();
// Transition pending images to their correct layout
// TODO(fxbug.dev/52196): The way we are transitioning image layouts here and in the rest of
// scenic is incorrect for "external" images. It just happens to be working by luck on our current
// hardware.
for (auto texture_id : local_pending_textures) {
FX_DCHECK(local_texture_map.find(texture_id) != local_texture_map.end());
const auto texture = local_texture_map.at(texture_id);
command_buffer->impl()->TransitionImageLayout(texture->image(), vk::ImageLayout::eUndefined,
vk::ImageLayout::eShaderReadOnlyOptimal);
}
for (auto target_id : local_pending_render_targets) {
FX_DCHECK(local_render_target_map.find(target_id) != local_render_target_map.end());
const auto target = local_render_target_map.at(target_id);
command_buffer->impl()->TransitionImageLayout(target, vk::ImageLayout::eUndefined,
vk::ImageLayout::eColorAttachmentOptimal);
}
std::vector<const escher::TexturePtr> textures;
std::vector<escher::RectangleCompositor::ColorData> color_data;
for (const auto& image : images) {
// Pass the texture into the above vector to keep it alive outside of this loop.
FX_DCHECK(local_texture_map.find(image.identifier) != local_texture_map.end());
textures.emplace_back(local_texture_map.at(image.identifier));
// TODO(fxbug.dev/52632): We are hardcoding the multiply color and transparency flag for now.
// Eventually these will be exposed in the API.
color_data.emplace_back(
escher::RectangleCompositor::ColorData(glm::vec4(1.f), image.has_transparency));
}
// Grab the output image and use it to generate a depth texture. The depth texture needs to
// be the same width and height as the output image.
FX_DCHECK(local_render_target_map.find(render_target.identifier) !=
local_render_target_map.end());
const auto output_image = local_render_target_map.at(render_target.identifier);
const auto depth_texture = local_depth_target_map.at(render_target.identifier);
// Now the compositor can finally draw.
compositor_.DrawBatch(command_buffer, rectangles, textures, color_data, output_image,
depth_texture);
// Create vk::semaphores from the zx::events.
std::vector<escher::SemaphorePtr> semaphores;
for (auto& fence_original : release_fences) {
// Since the original fences are passed in by const reference, we
// duplicate them here so that the duped fences can be moved into
// the create info struct of the semaphore.
zx::event fence_copy;
auto status = fence_original.duplicate(ZX_RIGHT_SAME_RIGHTS, &fence_copy);
FX_DCHECK(status == ZX_OK);
auto sema = escher::Semaphore::New(escher_->vk_device());
vk::ImportSemaphoreZirconHandleInfoFUCHSIA info;
info.semaphore = sema->vk_semaphore();
info.handle = fence_copy.release();
info.handleType = vk::ExternalSemaphoreHandleTypeFlagBits::eTempZirconEventFUCHSIA;
auto result = escher_->vk_device().importSemaphoreZirconHandleFUCHSIA(
info, escher_->device()->dispatch_loader());
FX_DCHECK(result == vk::Result::eSuccess);
semaphores.emplace_back(sema);
}
// Submit the commands and wait for them to finish.
frame->EndFrame(semaphores, nullptr);
}
void VkRenderer::WaitIdle() { escher_->vk_device().waitIdle(); }
} // namespace flatland