| // 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 "virtual-layer.h" | |
| #include <fuchsia/hardware/display/llcpp/fidl.h> | |
| #include <math.h> | |
| #include <stdio.h> | |
| #include <zircon/pixelformat.h> | |
| #include <algorithm> | |
| #include <iterator> | |
| #include <fbl/algorithm.h> | |
| #include "utils.h" | |
| namespace fhd = ::llcpp::fuchsia::hardware::display; | |
| namespace testing { | |
| namespace display { | |
| static constexpr uint32_t kSrcFrameBouncePeriod = 90; | |
| static constexpr uint32_t kDestFrameBouncePeriod = 60; | |
| static constexpr uint32_t kRotationPeriod = 24; | |
| static constexpr uint32_t kScalePeriod = 45; | |
| static uint32_t get_fg_color() { | |
| static uint32_t layer_count = 0; | |
| static uint32_t colors[] = { | |
| 0xffff0000, | |
| 0xff00ff00, | |
| 0xff0000ff, | |
| }; | |
| return colors[layer_count++ % std::size(colors)]; | |
| } | |
| // Checks if two rectangles intersect, and if so, returns their intersection. | |
| static bool compute_intersection(const fhd::Frame& a, const fhd::Frame& b, | |
| fhd::Frame* intersection) { | |
| uint32_t left = std::max(a.x_pos, b.x_pos); | |
| uint32_t right = std::min(a.x_pos + a.width, b.x_pos + b.width); | |
| uint32_t top = std::max(a.y_pos, b.y_pos); | |
| uint32_t bottom = std::min(a.y_pos + a.height, b.y_pos + b.height); | |
| if (left >= right || top >= bottom) { | |
| return false; | |
| } | |
| intersection->x_pos = left; | |
| intersection->y_pos = top; | |
| intersection->width = right - left; | |
| intersection->height = bottom - top; | |
| return true; | |
| } | |
| static uint32_t interpolate_scaling(uint32_t x, uint32_t frame_num) { | |
| return x / 2 + interpolate(x / 2, frame_num, kScalePeriod); | |
| } | |
| VirtualLayer::VirtualLayer(Display* display) { | |
| displays_.push_back(display); | |
| width_ = display->mode().horizontal_resolution; | |
| height_ = display->mode().vertical_resolution; | |
| } | |
| VirtualLayer::VirtualLayer(const fbl::Vector<Display>& displays, bool tiled) { | |
| for (auto& d : displays) { | |
| displays_.push_back(&d); | |
| } | |
| width_ = 0; | |
| height_ = 0; | |
| for (auto* d : displays_) { | |
| if (tiled) { | |
| width_ += d->mode().horizontal_resolution; | |
| } else { | |
| width_ = std::max(width_, d->mode().horizontal_resolution); | |
| } | |
| height_ = std::max(height_, d->mode().vertical_resolution); | |
| } | |
| } | |
| custom_layer_t* VirtualLayer::CreateLayer(fhd::Controller::SyncClient* dc) { | |
| layers_.push_back(custom_layer_t()); | |
| layers_[layers_.size() - 1].active = false; | |
| auto result = dc->CreateLayer(); | |
| if (!result.ok() || result->res != ZX_OK) { | |
| printf("Creating layer failed\n"); | |
| return nullptr; | |
| } | |
| layers_[layers_.size() - 1].id = result->layer_id; | |
| return &layers_[layers_.size() - 1]; | |
| } | |
| PrimaryLayer::PrimaryLayer(Display* display) : VirtualLayer(display) { | |
| image_format_ = display->format(); | |
| } | |
| PrimaryLayer::PrimaryLayer(const fbl::Vector<Display>& displays, bool mirrors) | |
| : VirtualLayer(displays, !mirrors), mirrors_(mirrors) { | |
| image_format_ = displays_[0]->format(); | |
| SetImageDimens(width_, height_); | |
| } | |
| PrimaryLayer::PrimaryLayer(const fbl::Vector<Display>& displays, uint32_t fgcolor, uint32_t bgcolor, | |
| bool mirrors) | |
| : VirtualLayer(displays, !mirrors), fgcolor_(fgcolor), bgcolor_(bgcolor), mirrors_(mirrors) { | |
| override_colors_ = true; | |
| image_format_ = displays_[0]->format(); | |
| SetImageDimens(width_, height_); | |
| } | |
| bool PrimaryLayer::Init(fhd::Controller::SyncClient* dc) { | |
| if ((displays_.size() > 1 || rotates_) && scaling_) { | |
| printf("Unsupported config\n"); | |
| return false; | |
| } | |
| uint32_t fg_color = override_colors_ ? fgcolor_ : get_fg_color(); | |
| uint32_t bg_color = alpha_enable_ ? 0x3fffffff : 0xffffffff; | |
| if (override_colors_) { | |
| bg_color = bgcolor_; | |
| } | |
| images_[0] = | |
| Image::Create(dc, image_width_, image_height_, image_format_, fg_color, bg_color, modifier_); | |
| if (layer_flipping_) { | |
| images_[1] = Image::Create(dc, image_width_, image_height_, image_format_, fg_color, bg_color, | |
| modifier_); | |
| } | |
| if (!images_[0] || (layer_flipping_ && !images_[1])) { | |
| return false; | |
| } | |
| if (!layer_flipping_) { | |
| images_[0]->Render(-1, -1); | |
| } | |
| for (unsigned i = 0; i < displays_.size(); i++) { | |
| custom_layer_t* layer = CreateLayer(dc); | |
| if (layer == nullptr) { | |
| return false; | |
| } | |
| if (!images_[0]->Import(dc, &layer->import_info[0])) { | |
| return false; | |
| } | |
| if (layer_flipping_) { | |
| if (!images_[1]->Import(dc, &layer->import_info[1])) { | |
| return false; | |
| } | |
| } else { | |
| layer->import_info[alt_image_].events[WAIT_EVENT].signal(0, ZX_EVENT_SIGNALED); | |
| } | |
| fhd::ImageConfig image_config; | |
| images_[0]->GetConfig(&image_config); | |
| auto set_config_result = dc->SetLayerPrimaryConfig(layer->id, image_config); | |
| if (!set_config_result.ok()) { | |
| printf("Setting layer config failed\n"); | |
| return false; | |
| } | |
| auto set_alpha_result = dc->SetLayerPrimaryAlpha( | |
| layer->id, alpha_enable_ ? fhd::AlphaMode::HW_MULTIPLY : fhd::AlphaMode::DISABLE, | |
| alpha_val_); | |
| if (!set_alpha_result.ok()) { | |
| printf("Setting layer alpha config failed\n"); | |
| return false; | |
| } | |
| } | |
| StepLayout(0); | |
| if (!layer_flipping_) { | |
| SetLayerImages(dc, false); | |
| } | |
| if (!(pan_src_ || pan_dest_)) { | |
| SetLayerPositions(dc); | |
| } | |
| return true; | |
| } | |
| void* PrimaryLayer::GetCurrentImageBuf() { return images_[alt_image_]->buffer(); } | |
| size_t PrimaryLayer::GetCurrentImageSize() { | |
| return images_[alt_image_]->height() * images_[alt_image_]->stride() * | |
| ZX_PIXEL_FORMAT_BYTES(images_[alt_image_]->format()); | |
| } | |
| void PrimaryLayer::StepLayout(int32_t frame_num) { | |
| if (layer_flipping_) { | |
| alt_image_ = frame_num % 2; | |
| } | |
| if (pan_src_) { | |
| src_frame_.x_pos = | |
| interpolate(image_width_ - src_frame_.width, frame_num, kSrcFrameBouncePeriod); | |
| } | |
| if (pan_dest_) { | |
| dest_frame_.x_pos = interpolate(width_ - dest_frame_.width, frame_num, kDestFrameBouncePeriod); | |
| } | |
| if (rotates_) { | |
| switch ((frame_num / kRotationPeriod) % 4) { | |
| case 0: | |
| rotation_ = fhd::Transform::IDENTITY; | |
| break; | |
| case 1: | |
| rotation_ = fhd::Transform::ROT_90; | |
| break; | |
| case 2: | |
| rotation_ = fhd::Transform::ROT_180; | |
| break; | |
| case 3: | |
| rotation_ = fhd::Transform::ROT_270; | |
| break; | |
| } | |
| if (frame_num % kRotationPeriod == 0 && frame_num != 0) { | |
| uint32_t tmp = dest_frame_.width; | |
| dest_frame_.width = dest_frame_.height; | |
| dest_frame_.height = tmp; | |
| } | |
| } | |
| fhd::Frame display = {}; | |
| for (unsigned i = 0; i < displays_.size(); i++) { | |
| display.height = displays_[i]->mode().vertical_resolution; | |
| display.width = displays_[i]->mode().horizontal_resolution; | |
| if (mirrors_) { | |
| layers_[i].src.x_pos = 0; | |
| layers_[i].src.y_pos = 0; | |
| layers_[i].src.width = image_width_; | |
| layers_[i].src.height = image_height_; | |
| layers_[i].dest.x_pos = 0; | |
| layers_[i].dest.y_pos = 0; | |
| layers_[i].dest.width = display.width; | |
| layers_[i].dest.height = display.height; | |
| layers_[i].active = true; | |
| continue; | |
| } | |
| // Calculate the portion of the dest frame which shows up on this display | |
| if (compute_intersection(display, dest_frame_, &layers_[i].dest)) { | |
| // Find the subset of the src region which shows up on this display | |
| if (rotation_ == fhd::Transform::IDENTITY || rotation_ == fhd::Transform::ROT_180) { | |
| if (!scaling_) { | |
| layers_[i].src.x_pos = src_frame_.x_pos + (layers_[i].dest.x_pos - dest_frame_.x_pos); | |
| layers_[i].src.y_pos = src_frame_.y_pos; | |
| layers_[i].src.width = layers_[i].dest.width; | |
| layers_[i].src.height = layers_[i].dest.height; | |
| } else { | |
| layers_[i].src.x_pos = | |
| src_frame_.x_pos + | |
| interpolate_scaling(layers_[i].dest.x_pos - dest_frame_.x_pos, frame_num); | |
| layers_[i].src.y_pos = src_frame_.y_pos; | |
| layers_[i].src.width = interpolate_scaling(layers_[i].dest.width, frame_num); | |
| layers_[i].src.height = interpolate_scaling(layers_[i].dest.height, frame_num); | |
| } | |
| } else { | |
| layers_[i].src.x_pos = src_frame_.x_pos; | |
| layers_[i].src.y_pos = src_frame_.y_pos + (layers_[i].dest.y_pos - dest_frame_.y_pos); | |
| layers_[i].src.height = layers_[i].dest.width; | |
| layers_[i].src.width = layers_[i].dest.height; | |
| } | |
| // Put the dest frame coordinates in the display's coord space | |
| layers_[i].dest.x_pos -= display.x_pos; | |
| layers_[i].active = true; | |
| } else { | |
| layers_[i].active = false; | |
| } | |
| display.x_pos += display.width; | |
| } | |
| if (layer_toggle_) { | |
| for (auto& layer : layers_) { | |
| layer.active = !(frame_num % 2); | |
| } | |
| } | |
| } | |
| void PrimaryLayer::SendLayout(fhd::Controller::SyncClient* dc) { | |
| if (layer_flipping_) { | |
| SetLayerImages(dc, alt_image_); | |
| } | |
| if (scaling_ || pan_src_ || pan_dest_) { | |
| SetLayerPositions(dc); | |
| } | |
| } | |
| bool PrimaryLayer::WaitForReady() { return Wait(SIGNAL_EVENT); } | |
| void PrimaryLayer::Render(int32_t frame_num) { | |
| if (!layer_flipping_) { | |
| return; | |
| } | |
| images_[alt_image_]->Render(frame_num < 2 ? 0 : frame_num - 2, frame_num); | |
| for (auto& layer : layers_) { | |
| layer.import_info[alt_image_].events[WAIT_EVENT].signal(0, ZX_EVENT_SIGNALED); | |
| } | |
| } | |
| void PrimaryLayer::SetLayerPositions(fhd::Controller::SyncClient* dc) { | |
| for (auto& layer : layers_) { | |
| ZX_ASSERT(dc->SetLayerPrimaryPosition(layer.id, rotation_, layer.src, layer.dest).ok()); | |
| } | |
| } | |
| void VirtualLayer::SetLayerImages(fhd::Controller::SyncClient* dc, bool alt_image) { | |
| for (auto& layer : layers_) { | |
| const auto& image = layer.import_info[alt_image]; | |
| auto result = dc->SetLayerImage(layer.id, image.id, image.event_ids[WAIT_EVENT], | |
| image.event_ids[SIGNAL_EVENT]); | |
| ZX_ASSERT(result.ok()); | |
| } | |
| } | |
| bool PrimaryLayer::Wait(uint32_t idx) { | |
| zx_time_t deadline = zx_deadline_after(ZX_MSEC(100)); | |
| for (auto& layer : layers_) { | |
| uint32_t observed; | |
| if (!layer.active) { | |
| continue; | |
| } | |
| auto& event = layer.import_info[alt_image_].events[idx]; | |
| zx_status_t res; | |
| if ((res = event.wait_one(ZX_EVENT_SIGNALED, zx::time(deadline), &observed)) == ZX_OK) { | |
| if (layer_flipping_) { | |
| event.signal(ZX_EVENT_SIGNALED, 0); | |
| } | |
| } else { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| CursorLayer::CursorLayer(Display* display) : VirtualLayer(display) {} | |
| CursorLayer::CursorLayer(const fbl::Vector<Display>& displays) : VirtualLayer(displays) {} | |
| bool CursorLayer::Init(fhd::Controller::SyncClient* dc) { | |
| fhd::CursorInfo info = displays_[0]->cursor(); | |
| uint32_t bg_color = 0xffffffff; | |
| image_ = Image::Create(dc, info.width, info.height, info.pixel_format, get_fg_color(), bg_color, | |
| false); | |
| if (!image_) { | |
| return false; | |
| } | |
| image_->Render(-1, -1); | |
| for (unsigned i = 0; i < displays_.size(); i++) { | |
| custom_layer_t* layer = CreateLayer(dc); | |
| if (layer == nullptr) { | |
| return false; | |
| } | |
| layer->active = true; | |
| if (!image_->Import(dc, &layer->import_info[0])) { | |
| return false; | |
| } | |
| layer->import_info[0].events[WAIT_EVENT].signal(0, ZX_EVENT_SIGNALED); | |
| fhd::ImageConfig image_config = {}; | |
| image_config.height = info.height; | |
| image_config.width = info.width; | |
| image_config.pixel_format = info.pixel_format; | |
| image_config.type = fhd::TYPE_SIMPLE; | |
| auto result = dc->SetLayerCursorConfig(layer->id, image_config); | |
| if (!result.ok()) { | |
| printf("Setting layer config failed\n"); | |
| return false; | |
| } | |
| } | |
| SetLayerImages(dc, false); | |
| return true; | |
| } | |
| void CursorLayer::StepLayout(int32_t frame_num) { | |
| fhd::CursorInfo info = displays_[0]->cursor(); | |
| x_pos_ = interpolate(width_ + info.width, frame_num, kDestFrameBouncePeriod) - info.width; | |
| y_pos_ = interpolate(height_ + info.height, frame_num, kDestFrameBouncePeriod) - info.height; | |
| } | |
| void CursorLayer::SendLayout(fhd::Controller::SyncClient* dc) { | |
| uint32_t display_start = 0; | |
| for (unsigned i = 0; i < displays_.size(); i++) { | |
| ZX_ASSERT(dc->SetLayerCursorPosition(layers_[i].id, x_pos_ - display_start, y_pos_).ok()); | |
| display_start += displays_[i]->mode().horizontal_resolution; | |
| } | |
| } | |
| ColorLayer::ColorLayer(Display* display) : VirtualLayer(display) {} | |
| ColorLayer::ColorLayer(const fbl::Vector<Display>& displays) : VirtualLayer(displays) {} | |
| bool ColorLayer::Init(fhd::Controller::SyncClient* dc) { | |
| for (unsigned i = 0; i < displays_.size(); i++) { | |
| custom_layer_t* layer = CreateLayer(dc); | |
| if (layer == nullptr) { | |
| return false; | |
| } | |
| layer->active = true; | |
| constexpr uint32_t kColorLayerFormat = ZX_PIXEL_FORMAT_ARGB_8888; | |
| uint32_t kColorLayerColor = get_fg_color(); | |
| uint32_t size = FIDL_ALIGN(ZX_PIXEL_FORMAT_BYTES(kColorLayerFormat)); | |
| uint8_t data[size]; | |
| *reinterpret_cast<uint32_t*>(data) = kColorLayerColor; | |
| auto result = dc->SetLayerColorConfig( | |
| layer->id, kColorLayerFormat, | |
| ::fidl::VectorView<uint8_t>(fidl::unowned_ptr(data), | |
| ZX_PIXEL_FORMAT_BYTES(kColorLayerFormat))); | |
| if (!result.ok()) { | |
| printf("Setting layer config failed\n"); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| } // namespace display | |
| } // namespace testing |