blob: 8c67d2888fb544bdcdb6249f8c5347c4b53cedb1 [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/engine/engine.h"
#include <lib/async/default.h>
#include <lib/fdio/directory.h>
#include <zircon/pixelformat.h>
#include <vector>
#include "src/ui/lib/escher/util/trace_macros.h"
#include "src/ui/scenic/lib/flatland/buffers/util.h"
#include "src/ui/scenic/lib/flatland/global_image_data.h"
namespace flatland {
const zx_pixel_format_t kDefaultImageFormat = ZX_PIXEL_FORMAT_ARGB_8888;
Engine::Engine(std::shared_ptr<fuchsia::hardware::display::ControllerSyncPtr> display_controller,
const std::shared_ptr<Renderer>& renderer, RenderDataFunc render_data_func)
: display_controller_(std::move(display_controller)),
renderer_(renderer),
render_data_func_(render_data_func) {
FX_DCHECK(renderer_);
FX_DCHECK(render_data_func_);
}
Engine::~Engine() {
// Destroy all of the display layers.
DiscardConfig();
for (const auto& [_, data] : display_engine_data_map_) {
for (const auto& layer : data.layers) {
(*display_controller_.get())->DestroyLayer(layer);
}
for (const auto& event_data : data.frame_event_datas) {
(*display_controller_.get())->ReleaseEvent(event_data.wait_id);
(*display_controller_.get())->ReleaseEvent(event_data.signal_id);
}
}
}
bool Engine::ImportBufferCollection(
sysmem_util::GlobalBufferCollectionId collection_id,
fuchsia::sysmem::Allocator_Sync* sysmem_allocator,
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) {
FX_DCHECK(display_controller_);
auto sync_token = token.BindSync();
// Create a duped renderer token.
fuchsia::sysmem::BufferCollectionTokenSyncPtr renderer_token;
zx_status_t status =
sync_token->Duplicate(std::numeric_limits<uint32_t>::max(), renderer_token.NewRequest());
FX_DCHECK(status == ZX_OK);
// Import the collection to the renderer.
if (!renderer_->ImportBufferCollection(collection_id, sysmem_allocator,
std::move(renderer_token))) {
FX_LOGS(INFO) << "Renderer could not import buffer collection.";
return false;
}
// TODO(fxbug.dev/61974): Find a way to query what formats are compatible with a particular
// display.
fuchsia::hardware::display::ImageConfig image_config = {.pixel_format = kDefaultImageFormat};
std::unique_lock<std::mutex> lock(lock_);
auto result = scenic_impl::ImportBufferCollection(collection_id, *display_controller_.get(),
std::move(sync_token), image_config);
return result;
}
void Engine::ReleaseBufferCollection(sysmem_util::GlobalBufferCollectionId collection_id) {
std::unique_lock<std::mutex> lock(lock_);
FX_DCHECK(display_controller_);
(*display_controller_.get())->ReleaseBufferCollection(collection_id);
renderer_->ReleaseBufferCollection(collection_id);
}
bool Engine::ImportBufferImage(const ImageMetadata& metadata) {
FX_DCHECK(display_controller_);
if (metadata.identifier == 0) {
FX_LOGS(ERROR) << "ImageMetadata identifier is invalid.";
return false;
}
if (metadata.collection_id == sysmem_util::kInvalidId) {
FX_LOGS(ERROR) << "ImageMetadata collection ID is invalid.";
return false;
}
if (metadata.width == 0 || metadata.height == 0) {
FX_LOGS(ERROR) << "ImageMetadata has a null dimension: "
<< "(" << metadata.width << ", " << metadata.height << ").";
return false;
}
if (!renderer_->ImportBufferImage(metadata)) {
FX_LOGS(ERROR) << "Renderer could not import image.";
return false;
}
uint64_t display_image_id;
fuchsia::hardware::display::ImageConfig image_config = {
.width = metadata.width,
.height = metadata.height,
// TODO(fxbug.dev/61974): Find a way to query what formats are compatible with a particular
// display. Right now we hardcode ARGB_8888 to match the format used in tests for textures.
.pixel_format = kDefaultImageFormat};
zx_status_t import_image_status = ZX_OK;
// Scope the lock.
{
std::unique_lock<std::mutex> lock(lock_);
auto status = (*display_controller_.get())
->ImportImage(image_config, metadata.collection_id, metadata.vmo_index,
&import_image_status, &display_image_id);
FX_DCHECK(status == ZX_OK);
if (import_image_status != ZX_OK) {
FX_LOGS(ERROR) << "Display controller could not import the image.";
return false;
}
// Add the display-specific ID to the global map.
image_id_map_[metadata.identifier] = display_image_id;
return true;
}
}
void Engine::ReleaseBufferImage(sysmem_util::GlobalImageId image_id) {
auto display_image_id = InternalImageId(image_id);
// Locks the rest of the function.
std::unique_lock<std::mutex> lock(lock_);
FX_DCHECK(display_controller_);
(*display_controller_.get())->ReleaseImage(display_image_id);
// Release image from the renderer.
renderer_->ReleaseBufferImage(image_id);
}
uint64_t Engine::CreateDisplayLayer() {
std::unique_lock<std::mutex> lock(lock_);
uint64_t layer_id;
zx_status_t create_layer_status;
zx_status_t transport_status =
(*display_controller_.get())->CreateLayer(&create_layer_status, &layer_id);
if (create_layer_status != ZX_OK || transport_status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create layer, " << create_layer_status;
return 0;
}
return layer_id;
}
void Engine::SetDisplayLayers(uint64_t display_id, const std::vector<uint64_t>& layers) {
// Set all of the layers for each of the images on the display.
std::unique_lock<std::mutex> lock(lock_);
auto status = (*display_controller_.get())->SetDisplayLayers(display_id, layers);
FX_DCHECK(status == ZX_OK);
}
bool Engine::SetRenderDataOnDisplay(const RenderData& data) {
// Every rectangle should have an associated image.
uint32_t num_images = data.images.size();
// Since we map 1 image to 1 layer, if there are more images than layers available for
// the given display, then they cannot be directly composited to the display in hardware.
std::vector<uint64_t> layers;
{
std::unique_lock<std::mutex> lock(lock_);
layers = display_engine_data_map_[data.display_id].layers;
if (layers.size() < num_images) {
return false;
}
}
// We only set as many layers as needed for the images we have.
SetDisplayLayers(data.display_id,
std::vector<uint64_t>(layers.begin(), layers.begin() + num_images));
for (uint32_t i = 0; i < num_images; i++) {
ApplyLayerImage(layers[i], data.rectangles[i], data.images[i], /*wait_id*/ 0, /*signal_id*/ 0);
}
return true;
}
void Engine::ApplyLayerImage(uint32_t layer_id, escher::Rectangle2D rectangle, ImageMetadata image,
scenic_impl::DisplayEventId wait_id,
scenic_impl::DisplayEventId signal_id) {
auto display_image_id = InternalImageId(image.identifier);
auto [src, dst] = DisplaySrcDstFrames::New(rectangle, image);
std::unique_lock<std::mutex> lock(lock_);
// We just use the identity transform because the rectangles have already been rotated by
// the flatland code.
auto transform = fuchsia::hardware::display::Transform::IDENTITY;
fuchsia::hardware::display::ImageConfig image_config = {
.width = src.width, .height = src.height, .pixel_format = kDefaultImageFormat};
(*display_controller_.get())->SetLayerPrimaryConfig(layer_id, image_config);
(*display_controller_.get())->SetLayerPrimaryPosition(layer_id, transform, src, dst);
auto alpha_mode = image.has_transparency ? fuchsia::hardware::display::AlphaMode::PREMULTIPLIED
: fuchsia::hardware::display::AlphaMode::DISABLE;
(*display_controller_.get())->SetLayerPrimaryAlpha(layer_id, alpha_mode, 1.f);
// Set the imported image on the layer.
// TODO(fxbug.dev/59646): Add wait and signal events.
(*display_controller_.get())->SetLayerImage(layer_id, display_image_id, wait_id, signal_id);
}
Engine::DisplayConfigResponse Engine::CheckConfig() {
TRACE_DURATION("flatland", "EngineRenderer::CheckConfig");
fuchsia::hardware::display::ConfigResult result;
std::vector<fuchsia::hardware::display::ClientCompositionOp> ops;
std::unique_lock<std::mutex> lock(lock_);
(*display_controller_.get())->CheckConfig(/*discard*/ false, &result, &ops);
return {.result = result, .ops = ops};
}
void Engine::DiscardConfig() {
TRACE_DURATION("flatland", "EngineRenderer::DiscardConfig");
fuchsia::hardware::display::ConfigResult result;
std::vector<fuchsia::hardware::display::ClientCompositionOp> ops;
std::unique_lock<std::mutex> lock(lock_);
(*display_controller_.get())->CheckConfig(/*discard*/ true, &result, &ops);
}
void Engine::ApplyConfig() {
TRACE_DURATION("flatland", "EngineRenderer::ApplyConfig");
std::unique_lock<std::mutex> lock(lock_);
auto status = (*display_controller_.get())->ApplyConfig();
FX_DCHECK(status == ZX_OK);
}
void Engine::RenderFrame() {
TRACE_DURATION("flatland", "EngineRenderer::RenderFrame");
auto render_data_list = render_data_func_(display_info_map_);
// Config should be reset before doing anything new.
DiscardConfig();
// Create and set layers, one per image/rectangle, set the layer images and the
// layer transforms. Afterwards we check the config, if it fails for whatever reason,
// such as there being too many layers, then we fall back to software composition.
bool hardware_fail = false;
for (auto& data : render_data_list) {
if (!SetRenderDataOnDisplay(data)) {
hardware_fail = true;
break;
}
}
auto [result, ops] = CheckConfig();
// If the results are not okay, we have to not do gpu composition using the renderer.
if (hardware_fail || result != fuchsia::hardware::display::ConfigResult::OK) {
DiscardConfig();
for (const auto& data : render_data_list) {
auto& display_engine_data = display_engine_data_map_[data.display_id];
const auto& pixel_scale = display_info_map_[data.display_id].pixel_scale;
auto& curr_vmo = display_engine_data.curr_vmo;
const auto& render_target = display_engine_data.targets[curr_vmo];
// Reset the event data.
auto& event_data = display_engine_data.frame_event_datas[curr_vmo];
// We expect the retired event to already have been signaled. Verify this without waiting.
if (event_data.signal_event.wait_one(ZX_EVENT_SIGNALED, zx::time(), nullptr) != ZX_OK) {
FX_LOGS(ERROR) << "flatland::Engine::RenderFrame rendering into in-use backbuffer";
}
event_data.wait_event.signal(ZX_EVENT_SIGNALED, 0);
event_data.signal_event.signal(ZX_EVENT_SIGNALED, 0);
std::vector<zx::event> render_fences;
render_fences.push_back(std::move(event_data.wait_event));
renderer_->Render(render_target, data.rectangles, data.images, render_fences);
curr_vmo = (curr_vmo + 1) % display_engine_data.vmo_count;
event_data.wait_event = std::move(render_fences[0]);
auto layer = display_engine_data.layers[0];
SetDisplayLayers(data.display_id, {layer});
ApplyLayerImage(layer, {glm::vec2(0), pixel_scale}, render_target, event_data.wait_id,
event_data.signal_id);
auto [result, /*ops*/ _] = CheckConfig();
if (result != fuchsia::hardware::display::ConfigResult::OK) {
FX_LOGS(ERROR) << "Both display hardware composition and GPU rendering have failed.";
// TODO(fxbug.dev/59646): Figure out how we really want to handle this case here.
return;
}
}
}
ApplyConfig();
}
Engine::FrameEventData Engine::NewFrameEventData() {
FrameEventData result;
std::unique_lock<std::mutex> lock(lock_);
// The DC waits on this to be signaled by the renderer.
auto status = zx::event::create(0, &result.wait_event);
FX_DCHECK(status == ZX_OK);
result.wait_id = scenic_impl::ImportEvent(*display_controller_.get(), result.wait_event);
FX_DCHECK(result.wait_id != fuchsia::hardware::display::INVALID_DISP_ID);
// The DC signals this once it has set the layer image.
status = zx::event::create(0, &result.signal_event);
FX_DCHECK(status == ZX_OK);
result.signal_id = scenic_impl::ImportEvent(*display_controller_.get(), result.signal_event);
FX_DCHECK(result.signal_id != fuchsia::hardware::display::INVALID_DISP_ID);
return result;
}
sysmem_util::GlobalBufferCollectionId Engine::AddDisplay(
uint64_t display_id, DisplayInfo info, fuchsia::sysmem::Allocator_Sync* sysmem_allocator,
uint32_t num_vmos, fuchsia::sysmem::BufferCollectionInfo_2* collection_info) {
FX_DCHECK(sysmem_allocator);
const uint32_t kWidth = info.pixel_scale.x;
const uint32_t kHeight = info.pixel_scale.y;
display_info_map_[display_id] = std::move(info);
auto& display_engine_data = display_engine_data_map_[display_id];
// When we add in a new display, we create a couple of layers for that display upfront to be
// used when we directly composite render data in hardware via the display controller.
// TODO(fx.dev/66499): Right now we're just hardcoding the number of layers per display
// uniformly, but this should probably be handled more dynamically in the future when we're
// dealing with displays that can potentially handle many more layers. Although Astro can only
// handle 1 layer right now, we create 2 layers in order to do more complicated unit testing
// with the mock display controller.
for (uint32_t i = 0; i < 2; i++) {
display_engine_data.layers.push_back(CreateDisplayLayer());
}
// Exit early if there are no vmos to create.
if (num_vmos == 0) {
return 0;
}
FX_DCHECK(collection_info);
// Create the buffer collection token to be used for frame buffers.
fuchsia::sysmem::BufferCollectionTokenSyncPtr engine_token;
auto status = sysmem_allocator->AllocateSharedCollection(engine_token.NewRequest());
FX_DCHECK(status == ZX_OK) << status;
// Dup the token for the renderer.
fuchsia::sysmem::BufferCollectionTokenSyncPtr renderer_token;
status =
engine_token->Duplicate(std::numeric_limits<uint32_t>::max(), renderer_token.NewRequest());
FX_DCHECK(status == ZX_OK);
// Dup the token for the display.
fuchsia::sysmem::BufferCollectionTokenSyncPtr display_token;
status =
engine_token->Duplicate(std::numeric_limits<uint32_t>::max(), display_token.NewRequest());
FX_DCHECK(status == ZX_OK);
// Register the buffer collection with the renderer
auto collection_id = sysmem_util::GenerateUniqueBufferCollectionId();
auto result = renderer_->RegisterRenderTargetCollection(collection_id, sysmem_allocator,
std::move(renderer_token));
FX_DCHECK(result);
// TODO(fxbug.dev/61974): Find a way to query what formats are compatible with a particular
// display.
fuchsia::hardware::display::ImageConfig image_config = {.pixel_format = kDefaultImageFormat};
result = scenic_impl::ImportBufferCollection(collection_id, *display_controller_.get(),
std::move(display_token), image_config);
FX_DCHECK(result);
// Finally set the engine constraints.
auto [buffer_usage, memory_constraints] = GetUsageAndMemoryConstraintsForCpuWriteOften();
fuchsia::sysmem::BufferCollectionSyncPtr collection_ptr =
CreateClientPointerWithConstraints(sysmem_allocator, std::move(engine_token), num_vmos,
kWidth, kHeight, buffer_usage, memory_constraints);
// Have the client wait for buffers allocated so it can populate its information
// struct with the vmo data.
{
zx_status_t allocation_status = ZX_OK;
auto status = collection_ptr->WaitForBuffersAllocated(&allocation_status, collection_info);
FX_DCHECK(status == ZX_OK);
FX_DCHECK(allocation_status == ZX_OK);
status = collection_ptr->Close();
FX_DCHECK(status == ZX_OK);
}
// Import the images as well.
for (uint32_t i = 0; i < num_vmos; i++) {
ImageMetadata target = {.collection_id = collection_id,
.identifier = sysmem_util::GenerateUniqueImageId(),
.vmo_index = i,
.width = kWidth,
.height = kHeight,
.has_transparency = false,
.is_render_target = true};
display_engine_data.frame_event_datas.push_back(NewFrameEventData());
display_engine_data.targets.push_back(target);
bool res = ImportBufferImage(target);
FX_DCHECK(res);
}
display_engine_data.vmo_count = num_vmos;
display_engine_data.curr_vmo = 0;
return collection_id;
}
uint64_t Engine::InternalImageId(sysmem_util::GlobalImageId image_id) const {
// Lock the whole function.
std::unique_lock<std::mutex> lock(lock_);
auto itr = image_id_map_.find(image_id);
FX_DCHECK(itr != image_id_map_.end());
return itr->second;
}
} // namespace flatland