| // 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/fdio/directory.h> |
| #include <zircon/pixelformat.h> |
| |
| #include <vector> |
| |
| #include "src/ui/scenic/lib/display/util.h" |
| #include "src/ui/scenic/lib/flatland/global_image_data.h" |
| |
| namespace flatland { |
| |
| namespace { |
| |
| // Struct to combine the source and destination frames used to set a layer's |
| // position on the display. The src frame represents the (cropped) UV coordinates |
| // of the image and the dst frame represents the position in screen space that |
| // the layer will be placed. |
| struct DisplayFrameData { |
| fuchsia::hardware::display::Frame src; |
| fuchsia::hardware::display::Frame dst; |
| }; |
| |
| constexpr fuchsia::sysmem::BufferUsage kNoneUsage = {.none = fuchsia::sysmem::noneUsage}; |
| |
| // TODO(fxbug.dev/59646) : Move this somewhere else maybe. |
| void SetClientConstraintsAndWaitForAllocated( |
| fuchsia::sysmem::Allocator_Sync* sysmem_allocator, |
| fuchsia::sysmem::BufferCollectionTokenSyncPtr token, uint32_t image_count, uint32_t width, |
| uint32_t height, fuchsia::sysmem::BufferUsage usage, |
| std::optional<fuchsia::sysmem::BufferMemoryConstraints> memory_constraints) { |
| fuchsia::sysmem::BufferCollectionSyncPtr buffer_collection; |
| zx_status_t status = |
| sysmem_allocator->BindSharedCollection(std::move(token), buffer_collection.NewRequest()); |
| FX_DCHECK(status == ZX_OK); |
| fuchsia::sysmem::BufferCollectionConstraints constraints; |
| if (memory_constraints) { |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = std::move(*memory_constraints); |
| } else { |
| constraints.has_buffer_memory_constraints = false; |
| } |
| constraints.usage = usage; |
| constraints.min_buffer_count = image_count; |
| |
| constraints.image_format_constraints_count = 1; |
| auto& image_constraints = constraints.image_format_constraints[0]; |
| image_constraints.color_spaces_count = 1; |
| image_constraints.color_space[0] = |
| fuchsia::sysmem::ColorSpace{.type = fuchsia::sysmem::ColorSpaceType::SRGB}; |
| image_constraints.pixel_format.type = fuchsia::sysmem::PixelFormatType::BGRA32; |
| image_constraints.pixel_format.has_format_modifier = true; |
| image_constraints.pixel_format.format_modifier.value = fuchsia::sysmem::FORMAT_MODIFIER_LINEAR; |
| |
| image_constraints.required_min_coded_width = width; |
| image_constraints.required_min_coded_height = height; |
| image_constraints.required_max_coded_width = width; |
| image_constraints.required_max_coded_height = height; |
| image_constraints.max_coded_width = width * 4 /*num channels*/; |
| image_constraints.max_coded_height = height; |
| image_constraints.max_bytes_per_row = 0xffffffff; |
| |
| status = buffer_collection->SetConstraints(true, constraints); |
| FX_DCHECK(status == ZX_OK); |
| |
| // Have the client wait for allocation. |
| zx_status_t allocation_status = ZX_OK; |
| fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info = {}; |
| status = buffer_collection->WaitForBuffersAllocated(&allocation_status, &buffer_collection_info); |
| FX_DCHECK(status == ZX_OK); |
| FX_DCHECK(allocation_status == ZX_OK); |
| |
| status = buffer_collection->Close(); |
| FX_DCHECK(status == ZX_OK); |
| } |
| |
| uint64_t InitializeDisplayLayer(fuchsia::hardware::display::ControllerSyncPtr& display_controller) { |
| uint64_t layer_id; |
| zx_status_t create_layer_status; |
| zx_status_t transport_status = display_controller->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; |
| } |
| |
| std::vector<uint64_t> CreateAndSetDisplayLayers( |
| fuchsia::hardware::display::ControllerSyncPtr& display_controller, uint64_t display_id, |
| uint64_t num_layers) { |
| std::vector<uint64_t> layers; |
| for (uint32_t i = 0; i < num_layers; i++) { |
| auto curr_layer_id = InitializeDisplayLayer(display_controller); |
| layers.push_back(curr_layer_id); |
| } |
| |
| // Set all of the layers for each of the images on the display. |
| auto status = display_controller->SetDisplayLayers(display_id, layers); |
| FX_DCHECK(status == ZX_OK); |
| |
| return layers; |
| } |
| |
| // When setting an image on a layer in the display, you have to specify the "source" |
| // and "destination", where the source represents the pixel offsets and dimensions to |
| // use from the image and the destination represents where on the display the (cropped) |
| // image will go in pixel coordinates. This exactly mirrors the setup we have in the |
| // Rectangle2D struct and ImageMetadata struct, so we just need to convert that over to |
| // the proper display controller readable format. |
| DisplayFrameData RectangleDataToDisplayFrames(escher::Rectangle2D rectangle, ImageMetadata image) { |
| fuchsia::hardware::display::Frame src_frame = { |
| .x_pos = static_cast<uint32_t>(rectangle.clockwise_uvs[0].x * image.width), |
| .y_pos = static_cast<uint32_t>(rectangle.clockwise_uvs[0].y * image.height), |
| .width = static_cast<uint32_t>((rectangle.clockwise_uvs[2].x - rectangle.clockwise_uvs[0].x) * |
| image.width), |
| .height = static_cast<uint32_t>( |
| (rectangle.clockwise_uvs[2].y - rectangle.clockwise_uvs[0].y) * image.height), |
| }; |
| |
| fuchsia::hardware::display::Frame dst_frame = { |
| .x_pos = static_cast<uint32_t>(rectangle.origin.x), |
| .y_pos = static_cast<uint32_t>(rectangle.origin.y), |
| .width = static_cast<uint32_t>(rectangle.extent.x), |
| .height = static_cast<uint32_t>(rectangle.extent.y), |
| }; |
| return {.src = src_frame, .dst = dst_frame}; |
| } |
| |
| } // namespace |
| |
| Engine::Engine(std::unique_ptr<fuchsia::hardware::display::ControllerSyncPtr> display_controller, |
| const std::shared_ptr<Renderer>& renderer, |
| const std::shared_ptr<LinkSystem>& link_system, |
| const std::shared_ptr<UberStructSystem>& uber_struct_system) |
| : display_controller_(std::move(display_controller)), |
| renderer_(renderer), |
| link_system_(link_system), |
| uber_struct_system_(uber_struct_system) { |
| FX_DCHECK(renderer_); |
| FX_DCHECK(link_system_); |
| FX_DCHECK(uber_struct_system_); |
| } |
| |
| 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(); |
| |
| // TODO(61974): Find a way to query what formats are compatible with a particular display. |
| fuchsia::hardware::display::ImageConfig image_config = {.pixel_format = ZX_PIXEL_FORMAT_RGB_x888}; |
| |
| // Scope the lock. |
| { |
| 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); |
| } |
| |
| bool Engine::ImportImage(const ImageMetadata& meta_data) { |
| FX_DCHECK(display_controller_); |
| |
| if (meta_data.identifier == 0) { |
| FX_LOGS(ERROR) << "ImageMetadata identifier is invalid."; |
| return false; |
| } |
| |
| if (meta_data.collection_id == sysmem_util::kInvalidId) { |
| FX_LOGS(ERROR) << "ImageMetadata collection ID is invalid."; |
| return false; |
| } |
| |
| if (meta_data.width == 0 || meta_data.height == 0) { |
| FX_LOGS(ERROR) << "ImageMetadata has a null dimension: " |
| << "(" << meta_data.width << ", " << meta_data.height << ")."; |
| return false; |
| } |
| |
| uint64_t display_image_id; |
| fuchsia::hardware::display::ImageConfig image_config = { |
| .width = meta_data.width, |
| .height = meta_data.height, |
| // TODO(61974): Find a way to query what formats are compatible with a particular display. |
| .pixel_format = ZX_PIXEL_FORMAT_RGB_x888}; |
| 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, meta_data.collection_id, meta_data.vmo_idx, |
| &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_[meta_data.identifier] = display_image_id; |
| return true; |
| } |
| } |
| |
| void Engine::ReleaseImage(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); |
| } |
| |
| std::vector<Engine::RenderData> Engine::ComputeRenderData() { |
| const auto snapshot = uber_struct_system_->Snapshot(); |
| const auto links = link_system_->GetResolvedTopologyLinks(); |
| const auto link_system_id = link_system_->GetInstanceId(); |
| |
| // Gather the flatland data into a vector of rectangle and image data that can be passed to |
| // either the display controller directly or to the software renderer. |
| std::vector<RenderData> image_list_per_display; |
| for (const auto& [display_id, display_info] : display_map_) { |
| const auto& transform = display_info.transform; |
| const auto& resolution = display_info.pixel_scale; |
| |
| const auto topology_data = |
| GlobalTopologyData::ComputeGlobalTopologyData(snapshot, links, link_system_id, transform); |
| const auto global_matrices = ComputeGlobalMatrices(topology_data.topology_vector, |
| topology_data.parent_indices, snapshot); |
| const auto [image_indices, images] = |
| ComputeGlobalImageData(topology_data.topology_vector, snapshot); |
| |
| const auto image_rectangles = |
| ComputeGlobalRectangles(SelectMatrices(global_matrices, image_indices)); |
| |
| link_system_->UpdateLinks(topology_data.topology_vector, topology_data.live_handles, |
| global_matrices, resolution, snapshot); |
| |
| FX_DCHECK(image_rectangles.size() == images.size()); |
| image_list_per_display.push_back({.rectangles = std::move(image_rectangles), |
| .images = std::move(images), |
| .display_id = display_id}); |
| } |
| return image_list_per_display; |
| } |
| |
| void Engine::RenderFrame() { |
| auto render_data_list = ComputeRenderData(); |
| |
| // 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. |
| for (auto& render_data : render_data_list) { |
| // Every rectangle should have an associated image. |
| uint32_t num_images = render_data.images.size(); |
| uint32_t num_rectangles = render_data.rectangles.size(); |
| auto& display_id = render_data.display_id; |
| |
| // TODO(fxbug.dev/59646): This should eventually be cached. We don't want to recreate the layers |
| // every single time we call RenderFrame(). |
| std::vector<uint64_t> layers; |
| |
| // Scope the lock. |
| { |
| std::unique_lock<std::mutex> lock(lock_); |
| layers = CreateAndSetDisplayLayers(*display_controller_.get(), display_id, num_images); |
| } |
| |
| for (uint32_t i = 0; i < num_images; i++) { |
| const auto& rectangle = render_data.rectangles[i]; |
| const auto& image = render_data.images[i]; |
| const auto& curr_layer_id = layers[i]; |
| |
| #if 0 |
| auto display_image_id = InternalImageId(image.identifier); |
| |
| // Set the imported image on the layer. |
| // TODO(fxbug.dev/59646): Add wait and signal events. |
| { |
| std::unique_lock<std::mutex> lock(lock_); |
| (*display_controller_.get())->SetLayerImage(curr_layer_id, display_image_id, |
| /*wait_event*/ 0, /*signal_event*/ 0); |
| } |
| #endif |
| |
| // Convert rectangle and image data into display controller source and destination frames. |
| auto [src_frame, dst_frame] = RectangleDataToDisplayFrames(rectangle, image); |
| |
| // We just use the identity transform because the rectangles have already been rotated by |
| // the flatland code. |
| auto transform = fuchsia::hardware::display::Transform::IDENTITY; |
| |
| // Scope the lock. |
| { |
| std::unique_lock<std::mutex> lock(lock_); |
| (*display_controller_.get()) |
| ->SetLayerPrimaryPosition(curr_layer_id, transform, src_frame, dst_frame); |
| } |
| } |
| |
| // TODO(fxbug.dev/59646): Add back in when we have tests. |
| #if 0 |
| // // Check that the display is capable of compositing all of the images we told it to. |
| // fuchsia::hardware::display::ConfigResult result; |
| // std::vector<fuchsia::hardware::display::ClientCompositionOp> ops; |
| // display_controller()->CheckConfig(/*discard=*/false, &result, &ops); |
| |
| // // If the results are ok, we can apply the config directly, else we have to composite ourselves. |
| // if (result == fuchsia::hardware::display::ConfigResult::OK) { |
| // auto status = display_controller()->ApplyConfig(); |
| // FX_DCHECK(status == ZX_OK); |
| // } else { |
| // // TODO(fxbug.dev/59646): Here is where we'd actually have to render using the 2D renderer. |
| // } |
| // } |
| #endif |
| } |
| } |
| |
| void Engine::AddDisplay(uint64_t display_id, TransformHandle transform, glm::uvec2 pixel_scale) { |
| display_map_[display_id] = {.transform = std::move(transform), |
| .pixel_scale = std::move(pixel_scale)}; |
| } |
| |
| sysmem_util::GlobalBufferCollectionId Engine::RegisterTargetCollection( |
| fuchsia::sysmem::Allocator_Sync* sysmem_allocator, uint64_t display_id, uint32_t num_vmos) { |
| FX_DCHECK(sysmem_allocator); |
| auto iter = display_map_.find(display_id); |
| if (iter == display_map_.end() || num_vmos == 0) { |
| return sysmem_util::kInvalidId; |
| } |
| |
| auto display_info = iter->second; |
| |
| const uint32_t width = display_info.pixel_scale.x; |
| const uint32_t height = display_info.pixel_scale.y; |
| |
| // 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 renderer_collection_id = sysmem_util::GenerateUniqueBufferCollectionId(); |
| auto result = renderer_->RegisterRenderTargetCollection(renderer_collection_id, sysmem_allocator, |
| std::move(renderer_token)); |
| FX_DCHECK(result); |
| |
| // Register the buffer collection with the display controller. |
| result = |
| ImportBufferCollection(renderer_collection_id, sysmem_allocator, std::move(display_token)); |
| FX_DCHECK(result); |
| |
| // Finally set the engine constraints. |
| SetClientConstraintsAndWaitForAllocated(sysmem_allocator, std::move(engine_token), num_vmos, |
| width, height, kNoneUsage, std::nullopt); |
| |
| return renderer_collection_id; |
| } |
| |
| uint64_t Engine::InternalImageId(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 |