blob: 16d180f49494b36115b50348c0c150a3e3b451c1 [file] [log] [blame] [edit]
// 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 "src/graphics/display/drivers/coordinator/client.h"
#include <fidl/fuchsia.hardware.display.types/cpp/wire.h>
#include <fidl/fuchsia.hardware.display/cpp/wire.h>
#include <fidl/fuchsia.images2/cpp/wire.h>
#include <lib/async/cpp/task.h>
#include <lib/async/dispatcher.h>
#include <lib/driver/logging/cpp/logger.h>
#include <lib/fit/defer.h>
#include <lib/image-format/image_format.h>
#include <lib/inspect/cpp/inspect.h>
#include <lib/sync/completion.h>
#include <lib/sysmem-version/sysmem-version.h>
#include <lib/trace/event.h>
#include <lib/zx/clock.h>
#include <lib/zx/result.h>
#include <lib/zx/time.h>
#include <lib/zx/vmo.h>
#include <threads.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstring>
#include <memory>
#include <utility>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include <fbl/ref_ptr.h>
#include <fbl/static_vector.h>
#include <fbl/string_printf.h>
#include <fbl/vector.h>
#include "src/graphics/display/drivers/coordinator/capture-image.h"
#include "src/graphics/display/drivers/coordinator/client-id.h"
#include "src/graphics/display/drivers/coordinator/client-priority.h"
#include "src/graphics/display/drivers/coordinator/client-proxy.h"
#include "src/graphics/display/drivers/coordinator/engine-driver-client.h"
#include "src/graphics/display/drivers/coordinator/fence.h"
#include "src/graphics/display/drivers/coordinator/image.h"
#include "src/graphics/display/lib/api-types/cpp/buffer-collection-id.h"
#include "src/graphics/display/lib/api-types/cpp/color-conversion.h"
#include "src/graphics/display/lib/api-types/cpp/config-stamp.h"
#include "src/graphics/display/lib/api-types/cpp/display-id.h"
#include "src/graphics/display/lib/api-types/cpp/driver-buffer-collection-id.h"
#include "src/graphics/display/lib/api-types/cpp/driver-capture-image-id.h"
#include "src/graphics/display/lib/api-types/cpp/event-id.h"
#include "src/graphics/display/lib/api-types/cpp/image-buffer-usage.h"
#include "src/graphics/display/lib/api-types/cpp/image-id.h"
#include "src/graphics/display/lib/api-types/cpp/image-metadata.h"
#include "src/graphics/display/lib/api-types/cpp/layer-id.h"
#include "src/graphics/display/lib/api-types/cpp/mode-id.h"
#include "src/graphics/display/lib/api-types/cpp/rectangle.h"
#include "src/graphics/display/lib/api-types/cpp/vsync-ack-cookie.h"
namespace fhd = fuchsia_hardware_display;
namespace fhdt = fuchsia_hardware_display_types;
namespace {
constexpr uint32_t kFallbackHorizontalSizeMm = 160;
constexpr uint32_t kFallbackVerticalSizeMm = 90;
// True iff `inner` is entirely contained within `outer`.
//
// `outer` must be positioned at the coordinate system's origin. Both `inner` and `outer` must be
// non-empty.
constexpr bool OriginRectangleContains(const display::Rectangle& outer,
const display::Rectangle& inner) {
ZX_DEBUG_ASSERT(outer.x() == 0);
ZX_DEBUG_ASSERT(outer.y() == 0);
ZX_DEBUG_ASSERT(outer.width() > 0);
ZX_DEBUG_ASSERT(outer.height() > 0);
ZX_DEBUG_ASSERT(inner.width() > 0);
ZX_DEBUG_ASSERT(inner.height() > 0);
return inner.x() < outer.width() && inner.y() < outer.height() &&
inner.x() + inner.width() <= outer.width() && inner.y() + inner.height() <= outer.height();
}
// We allocate some variable sized stack allocations based on the number of
// layers, so we limit the total number of layers to prevent blowing the stack.
constexpr uint64_t kMaxLayers = 65536;
} // namespace
namespace display_coordinator {
void Client::ImportImage(ImportImageRequestView request, ImportImageCompleter::Sync& completer) {
TRACE_DURATION("gfx", "Display::Client::ImportImage");
const display::ImageId image_id = display::ImageId(request->image_id);
if (image_id == display::kInvalidImageId) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
auto images_it = images_.find(image_id);
if (images_it.IsValid()) {
completer.ReplyError(ZX_ERR_ALREADY_EXISTS);
return;
}
auto capture_image_it = capture_images_.find(image_id);
if (capture_image_it.IsValid()) {
completer.ReplyError(ZX_ERR_ALREADY_EXISTS);
return;
}
if (!display::ImageMetadata::IsValid(request->image_metadata)) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
const display::ImageMetadata image_metadata(request->image_metadata);
const display::BufferCollectionId buffer_collection_id(request->buffer_collection_id);
const uint32_t buffer_index = request->buffer_index;
if (image_metadata.tiling_type() == display::ImageTilingType::kCapture) {
zx_status_t import_status =
ImportImageForCapture(image_metadata, buffer_collection_id, buffer_index, image_id);
if (import_status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(import_status);
}
return;
}
zx_status_t import_status =
ImportImageForDisplay(image_metadata, buffer_collection_id, buffer_index, image_id);
if (import_status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(import_status);
}
}
zx_status_t Client::ImportImageForDisplay(const display::ImageMetadata& image_metadata,
display::BufferCollectionId buffer_collection_id,
uint32_t buffer_index, display::ImageId image_id) {
ZX_DEBUG_ASSERT(image_metadata.tiling_type() != display::ImageTilingType::kCapture);
ZX_DEBUG_ASSERT(!images_.find(image_id).IsValid());
ZX_DEBUG_ASSERT(!capture_images_.find(image_id).IsValid());
auto collection_map_it = collection_map_.find(buffer_collection_id);
if (collection_map_it == collection_map_.end()) {
return ZX_ERR_INVALID_ARGS;
}
const Collections& collections = collection_map_it->second;
zx::result<display::DriverImageId> result = controller_.engine_driver_client()->ImportImage(
image_metadata, collections.driver_buffer_collection_id, buffer_index);
if (result.is_error()) {
return result.error_value();
}
const display::DriverImageId driver_image_id = result.value();
auto release_image =
fit::defer([this, driver_image_id]() { controller_.ImageWillBeDestroyed(driver_image_id); });
fbl::AllocChecker alloc_checker;
fbl::RefPtr<Image> image = fbl::AdoptRef(new (&alloc_checker) Image(
&controller_, image_metadata, image_id, driver_image_id, &proxy_.node(), id_));
if (!alloc_checker.check()) {
fdf::debug("Alloc checker failed while constructing Image.\n");
return ZX_ERR_NO_MEMORY;
}
// `dc_image` is now owned by the Image instance.
release_image.cancel();
images_.insert(std::move(image));
return ZX_OK;
}
void Client::ReleaseImage(ReleaseImageRequestView request,
ReleaseImageCompleter::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::ReleaseImage");
const display::ImageId image_id = display::ImageId(request->image_id);
auto image = images_.find(image_id);
if (image.IsValid()) {
if (CleanUpImage(*image)) {
ApplyConfigImpl();
}
return;
}
auto capture_image = capture_images_.find(image_id);
if (capture_image.IsValid()) {
// Ensure we are not releasing an active capture.
if (current_capture_image_id_ == image_id) {
// We have an active capture; release it when capture is completed.
fdf::warn("Capture is active. Will release after capture is complete");
pending_release_capture_image_id_ = current_capture_image_id_;
} else {
// Release image now.
capture_images_.erase(capture_image);
}
return;
}
fdf::error("Invalid Image ID requested for release");
}
void Client::ImportEvent(ImportEventRequestView request,
ImportEventCompleter::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::ImportEvent");
const display::EventId event_id = display::EventId(request->id);
if (event_id == display::kInvalidEventId) {
fdf::error("Cannot import events with an invalid ID #{}", event_id.value());
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
zx::result<> import_result = fences_.ImportEvent(std::move(request->event), event_id);
if (import_result.is_error()) {
fdf::error("Failed to import event: {}", import_result);
TearDown(import_result.error_value());
return;
}
}
void Client::ImportBufferCollection(ImportBufferCollectionRequestView request,
ImportBufferCollectionCompleter::Sync& completer) {
TRACE_DURATION("gfx", "Display::Client::ImportBufferCollection");
const display::BufferCollectionId buffer_collection_id =
display::BufferCollectionId(request->buffer_collection_id);
// TODO: Switch to .contains() when C++20.
if (collection_map_.count(buffer_collection_id)) {
completer.ReplyError(ZX_ERR_ALREADY_EXISTS);
return;
}
const display::DriverBufferCollectionId driver_buffer_collection_id =
controller_.GetNextDriverBufferCollectionId();
zx::result<> import_result = controller_.engine_driver_client()->ImportBufferCollection(
driver_buffer_collection_id, std::move(request->buffer_collection_token));
if (import_result.is_error()) {
fdf::warn("Cannot import BufferCollection to display driver: {}", import_result);
completer.ReplyError(ZX_ERR_INTERNAL);
return;
}
collection_map_[buffer_collection_id] = Collections{
.driver_buffer_collection_id = driver_buffer_collection_id,
};
completer.ReplySuccess();
}
void Client::ReleaseBufferCollection(ReleaseBufferCollectionRequestView request,
ReleaseBufferCollectionCompleter::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::ReleaseBufferCollection");
const display::BufferCollectionId buffer_collection_id =
display::BufferCollectionId(request->buffer_collection_id);
auto it = collection_map_.find(buffer_collection_id);
if (it == collection_map_.end()) {
return;
}
[[maybe_unused]] zx::result<> result =
controller_.engine_driver_client()->ReleaseBufferCollection(
it->second.driver_buffer_collection_id);
if (result.is_error()) {
// TODO(https://fxbug.dev/42180237) Consider handling the error instead of ignoring it.
}
collection_map_.erase(it);
}
void Client::SetBufferCollectionConstraints(
SetBufferCollectionConstraintsRequestView request,
SetBufferCollectionConstraintsCompleter::Sync& completer) {
TRACE_DURATION("gfx", "Display::Client::SetBufferCollectionConstraints");
const display::BufferCollectionId buffer_collection_id =
display::BufferCollectionId(request->buffer_collection_id);
auto it = collection_map_.find(buffer_collection_id);
if (it == collection_map_.end()) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
auto& collections = it->second;
const display::ImageBufferUsage image_buffer_usage(request->buffer_usage);
zx::result<> result = controller_.engine_driver_client()->SetBufferCollectionConstraints(
image_buffer_usage, collections.driver_buffer_collection_id);
if (result.is_error()) {
fdf::warn(
"Cannot set BufferCollection constraints using imported buffer collection (id={}) {}.",
buffer_collection_id.value(), result);
completer.ReplyError(ZX_ERR_INTERNAL);
}
completer.ReplySuccess();
}
void Client::ReleaseEvent(ReleaseEventRequestView request,
ReleaseEventCompleter::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::ReleaseEvent");
const display::EventId event_id = display::EventId(request->id);
// TODO(https://fxbug.dev/42080337): Check if the ID is valid (i.e. imported but not
// yet released) before calling `ReleaseEvent()`.
fences_.ReleaseEvent(event_id);
}
void Client::CreateLayer(CreateLayerCompleter::Sync& completer) {
TRACE_DURATION("gfx", "Display::Client::CreateLayer");
// TODO(https://fxbug.dev/42079482): Layer IDs should be client-managed.
if (layers_.size() == kMaxLayers) {
completer.ReplyError(ZX_ERR_NO_RESOURCES);
return;
}
fbl::AllocChecker alloc_checker;
display::LayerId layer_id = next_layer_id_;
auto new_layer = fbl::make_unique_checked<Layer>(&alloc_checker, layer_id);
if (!alloc_checker.check()) {
completer.ReplyError(ZX_ERR_NO_MEMORY);
return;
}
++next_layer_id_;
layers_.insert(std::move(new_layer));
completer.ReplySuccess(layer_id.ToFidl());
}
void Client::DestroyLayer(DestroyLayerRequestView request,
DestroyLayerCompleter::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::DestroyLayer");
display::LayerId layer_id = display::LayerId(request->layer_id);
auto layers_it = layers_.find(layer_id);
if (!layers_it.IsValid()) {
fdf::error("Tried to destroy invalid layer {}", layer_id.value());
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
Layer& layer = *layers_it;
if (layer.in_use()) {
fdf::error("Destroyed layer {} which was in use", layer_id.value());
TearDown(ZX_ERR_BAD_STATE);
return;
}
layers_.erase(layers_it);
}
namespace {
// Returns `ModeId` that corresponds to the provided `target_mode` in
// `display_preferred_modes`.
//
// Returns `kInvalidModeId` if the `target_mode` cannot be found.
display::ModeId GetPreferredModeIdForMode(
std::span<const display::ModeAndId> display_preferred_modes, const display::Mode& target_mode) {
auto mode_it = std::ranges::find_if(
display_preferred_modes,
[&](const display::ModeAndId& mode_and_id) { return mode_and_id.mode() == target_mode; });
if (mode_it != display_preferred_modes.end()) {
return mode_it->id();
}
return display::kInvalidModeId;
}
} // namespace
void Client::SetDisplayMode(SetDisplayModeRequestView request,
SetDisplayModeCompleter::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::SetDisplayMode");
const display::DisplayId display_id = display::DisplayId(request->display_id);
auto display_configs_it = display_configs_.find(display_id);
if (!display_configs_it.IsValid()) {
fdf::warn("SetDisplayMode called with unknown display ID: {}", display_id.value());
return;
}
DisplayConfig& display_config = *display_configs_it;
if (!display::Mode::IsValid(request->mode)) {
fdf::error("SetDisplayMode called with invalid mode: {}x{} @ {}.{:03} Hz",
request->mode.active_area.width, request->mode.active_area.height,
request->mode.refresh_rate_millihertz / 1000,
request->mode.refresh_rate_millihertz % 1000);
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
display::Mode target_mode = display::Mode::From(request->mode);
zx::result<std::span<const display::ModeAndId>> display_preferred_modes_result =
controller_.GetDisplayPreferredModes(display_id);
if (display_preferred_modes_result.is_error()) {
fdf::error("Failed to get display preferred modes for display ID {}: {}", display_id.value(),
display_preferred_modes_result);
TearDown(display_preferred_modes_result.status_value());
return;
}
std::span<const display::ModeAndId> display_preferred_modes =
std::move(display_preferred_modes_result).value();
display::ModeId mode_id = GetPreferredModeIdForMode(display_preferred_modes, target_mode);
if (mode_id == display::kInvalidModeId) {
fdf::error("Failed to find supported mode for: {}", target_mode);
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
fdf::info("Found supported display mode {} in the preferred modes list", target_mode);
if (display_preferred_modes.size() == 1) {
// If there is only one mode, the coordinator doesn't need to set
// the display mode on engine.
fdf::info("The display has only one mode. Skip setting display mode.");
return;
}
display_config.draft_.mode_id = mode_id;
display_config.has_draft_nonlayer_config_change_ = true;
draft_display_config_was_validated_ = false;
}
void Client::SetDisplayColorConversion(SetDisplayColorConversionRequestView request,
SetDisplayColorConversionCompleter::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::SetDisplayColorConversion");
const display::DisplayId display_id = display::DisplayId(request->display_id);
auto display_configs_it = display_configs_.find(display_id);
if (!display_configs_it.IsValid()) {
fdf::warn("SetDisplayColorConversion called with unknown display ID: {}", display_id.value());
return;
}
DisplayConfig& display_config = *display_configs_it;
fidl::Array<float, 3>& fidl_preoffsets = request->preoffsets;
fidl::Array<float, 9>& fidl_coefficients = request->coefficients;
fidl::Array<float, 3>& fidl_postoffsets = request->postoffsets;
if (std::ranges::any_of(fidl_preoffsets, [](float x) { return !std::isfinite(x); })) {
fdf::warn("SetDisplayColorConversion called with invalid preoffsets: {}", display_id.value());
return;
}
if (std::ranges::any_of(fidl_coefficients, [](float x) { return !std::isfinite(x); })) {
fdf::warn("SetDisplayColorConversion called with invalid coefficients: {}", display_id.value());
return;
}
if (std::ranges::any_of(fidl_postoffsets, [](float x) { return !std::isfinite(x); })) {
fdf::warn("SetDisplayColorConversion called with invalid postoffsets: {}", display_id.value());
return;
}
display::ColorConversion color_conversion({
.preoffsets = {fidl_preoffsets[0], fidl_preoffsets[1], fidl_preoffsets[2]},
.coefficients =
{
std::array<float, 3>{fidl_coefficients[0], fidl_coefficients[1],
fidl_coefficients[2]},
std::array<float, 3>{fidl_coefficients[3], fidl_coefficients[4],
fidl_coefficients[5]},
std::array<float, 3>{fidl_coefficients[6], fidl_coefficients[7],
fidl_coefficients[8]},
},
.postoffsets = {fidl_postoffsets[0], fidl_postoffsets[1], fidl_postoffsets[2]},
});
display_config.draft_.color_conversion = std::move(color_conversion);
display_config.has_draft_nonlayer_config_change_ = true;
draft_display_config_was_validated_ = false;
// One-way call. No reply required.
}
void Client::SetDisplayLayers(SetDisplayLayersRequestView request,
SetDisplayLayersCompleter::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::SetDisplayLayers");
if (request->layer_ids.empty()) {
fdf::error("SetDisplayLayers called with an empty layer list");
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
const display::DisplayId display_id = display::DisplayId(request->display_id);
auto display_configs_it = display_configs_.find(display_id);
if (!display_configs_it.IsValid()) {
fdf::warn("SetDisplayLayers called with unknown display ID: {}", display_id.value());
return;
}
DisplayConfig& display_config = *display_configs_it;
display_config.draft_has_layer_list_change_ = true;
display_config.draft_has_layer_list_change_property_.Set(true);
display_config.draft_layers_.clear();
for (fuchsia_hardware_display::wire::LayerId fidl_layer_id : request->layer_ids) {
display::LayerId layer_id = display::LayerId(fidl_layer_id);
auto layers_it = layers_.find(layer_id);
if (!layers_it.IsValid()) {
fdf::error("SetDisplayLayers called with unknown layer ID: {}", layer_id.value());
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
Layer& layer = *layers_it;
if (!layer.AppendToConfigLayerList(display_config.draft_layers_)) {
fdf::error("Tried to reuse an in-use layer");
TearDown(ZX_ERR_BAD_STATE);
return;
}
}
display_config.draft_.layer_count = static_cast<int>(request->layer_ids.size());
draft_display_config_was_validated_ = false;
// One-way call. No reply required.
}
void Client::SetLayerPrimaryConfig(SetLayerPrimaryConfigRequestView request,
SetLayerPrimaryConfigCompleter::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::SetLayerPrimaryConfig");
display::LayerId layer_id = display::LayerId(request->layer_id);
auto layers_it = layers_.find(layer_id);
if (!layers_it.IsValid()) {
fdf::error("SetLayerPrimaryConfig called with unknown layer ID: {}", layer_id.value());
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
Layer& layer = *layers_it;
if (!display::ImageMetadata::IsValid(request->image_metadata)) {
fdf::error("SetLayerPrimaryConfig called with invalid image metadata");
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
display::ImageMetadata image_metadata(request->image_metadata);
layer.SetPrimaryConfig(image_metadata);
// TODO(https://fxbug.dev/397427767): Check if the layer belongs to the draft
// config first.
draft_display_config_was_validated_ = false;
// One-way call. No reply required.
}
void Client::SetLayerPrimaryPosition(SetLayerPrimaryPositionRequestView request,
SetLayerPrimaryPositionCompleter::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::SetLayerPrimaryPosition");
display::LayerId layer_id = display::LayerId(request->layer_id);
auto layers_it = layers_.find(layer_id);
if (!layers_it.IsValid()) {
fdf::error("SetLayerPrimaryPosition called with unknown layer ID: {}", layer_id.value());
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
Layer& layer = *layers_it;
if (!display::CoordinateTransformation::IsValid(request->image_source_transformation)) {
fdf::error("SetLayerPrimaryPosition called with invalid image_source_transformation");
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
display::CoordinateTransformation image_source_transformation(
request->image_source_transformation);
if (!display::Rectangle::IsValid(request->image_source)) {
fdf::error("SetLayerPrimaryPosition called with invalid image_source");
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
display::Rectangle image_source = display::Rectangle::From(request->image_source);
if (!display::Rectangle::IsValid(request->display_destination)) {
fdf::error("SetLayerPrimaryPosition called with invalid display_destination");
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
display::Rectangle display_destination = display::Rectangle::From(request->display_destination);
layer.SetPrimaryPosition(image_source_transformation, image_source, display_destination);
// TODO(https://fxbug.dev/397427767): Check if the layer belongs to the draft
// config first.
draft_display_config_was_validated_ = false;
// One-way call. No reply required.
}
void Client::SetLayerPrimaryAlpha(SetLayerPrimaryAlphaRequestView request,
SetLayerPrimaryAlphaCompleter::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::SetLayerPrimaryAlpha");
display::LayerId layer_id = display::LayerId(request->layer_id);
auto layers_it = layers_.find(layer_id);
if (!layers_it.IsValid()) {
fdf::error("SetLayerPrimaryAlpha called with unknown layer ID: {}", layer_id.value());
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
Layer& layer = *layers_it;
if (!display::AlphaMode::IsValid(request->mode)) {
fdf::error("Invalid alpha mode {}", static_cast<uint8_t>(request->mode));
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
display::AlphaMode alpha_mode(request->mode);
if ((!isnan(request->val) && (request->val < 0 || request->val > 1))) {
fdf::error("Invalid alpha value {}", request->val);
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
layer.SetPrimaryAlpha(alpha_mode, /*alpha_coefficient=*/request->val);
// TODO(https://fxbug.dev/397427767): Check if the layer belongs to the draft
// config first.
draft_display_config_was_validated_ = false;
// One-way call. No reply required.
}
void Client::SetLayerColorConfig(SetLayerColorConfigRequestView request,
SetLayerColorConfigCompleter::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::SetLayerColorConfig");
display::LayerId layer_id = display::LayerId(request->layer_id);
auto layers_it = layers_.find(layer_id);
if (!layers_it.IsValid()) {
fdf::error("SetLayerColorConfig called with unknown layer ID: {}", layer_id.value());
return;
}
Layer& layer = *layers_it;
if (!display::Color::IsValid(request->color)) {
fdf::error("SetLayerColorConfig with invalid pixel format");
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
display::Color color = display::Color::From(request->color);
if (!display::Rectangle::IsValid(request->display_destination)) {
fdf::error("SetLayerColorConfig called with invalid display_destination");
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
display::Rectangle display_destination = display::Rectangle::From(request->display_destination);
layer.SetColorConfig(color, display_destination);
// TODO(https://fxbug.dev/397427767): Check if the layer belongs to the draft
// config first.
draft_display_config_was_validated_ = false;
// One-way call. No reply required.
}
void Client::SetLayerImage2(SetLayerImage2RequestView request,
SetLayerImage2Completer::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::SetLayerImage2");
SetLayerImageImpl(display::LayerId(request->layer_id), display::ImageId(request->image_id),
display::EventId(request->wait_event_id));
}
void Client::SetLayerImageImpl(display::LayerId layer_id, display::ImageId image_id,
display::EventId wait_event_id) {
auto layers_it = layers_.find(layer_id);
if (!layers_it.IsValid()) {
fdf::error("SetLayerImage called with unknown layer ID: {}", layer_id.value());
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
Layer& layer = *layers_it;
auto images_it = images_.find(image_id);
if (!images_it.IsValid()) {
fdf::error("SetLayerImage called with unknown image ID: {}", image_id.value());
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
Image& image = *images_it;
// TODO(https://fxbug.dev/42076907): Currently this logic only compares size
// and usage type between the current `Image` and a given `Layer`'s accepted
// configuration.
//
// We don't set the pixel format a `Layer` can accept, and we don't compare the
// `Image` pixel format against any accepted pixel format, assuming that all
// image buffers allocated by sysmem can always be used for scanout in any
// `Layer`. Currently, this assumption works for all our existing display engine
// drivers. However, switching pixel formats in a `Layer` may cause performance
// reduction, or might be not supported by new display engines / new display
// formats.
//
// We should figure out a mechanism to indicate pixel format / modifiers
// support for a `Layer`'s image configuration (as opposed of using image_t),
// and compare this Image's sysmem buffer collection information against the
// `Layer`'s format support.
if (image.metadata() != display::ImageMetadata(layer.draft_image_metadata())) {
fdf::error("SetLayerImage with mismatching layer and image metadata");
TearDown(ZX_ERR_BAD_STATE);
return;
}
// TODO(https://fxbug.dev/42080337): Check if the IDs are valid (i.e. imported but not
// yet released) before calling `SetImage()`.
layer.SetImage(images_it.CopyPointer(), wait_event_id);
// One-way call. No reply required.
}
void Client::CheckConfig(CheckConfigCompleter::Sync& completer) {
display::ConfigCheckResult config_check_result = CheckConfigImpl();
draft_display_config_was_validated_ = config_check_result == display::ConfigCheckResult::kOk;
completer.Reply(config_check_result.ToFidl());
}
void Client::DiscardConfig(DiscardConfigCompleter::Sync& /*_completer*/) { DiscardConfig(); }
void Client::ApplyConfig3(ApplyConfig3RequestView request,
ApplyConfig3Completer::Sync& _completer) {
TRACE_DURATION("gfx", "Display::Client::ApplyConfig3");
if (!request->has_stamp()) {
fdf::error("ApplyConfig3 called without a config stamp");
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
const display::ConfigStamp new_config_stamp(request->stamp().value);
if (layers_.is_empty()) {
FDF_LOG(ERROR, "ApplyConfig3 called before SetDisplayLayers");
TearDown(ZX_ERR_BAD_STATE);
return;
}
if (!draft_display_config_was_validated_) {
// TODO(https://fxbug.dev/397427767): TearDown(ZX_ERR_BAD_STATE) instead of
// calling CheckConfig() and silently failing.
draft_display_config_was_validated_ = CheckConfigImpl() == display::ConfigCheckResult::kOk;
if (!draft_display_config_was_validated_) {
fdf::info("ApplyConfig3 called with invalid configuration; dropping the request");
return;
}
}
// Now that we can guarantee that the configuration will be applied, it is
// safe to update the config stamp.
if (new_config_stamp <= latest_config_stamp_) {
fdf::error(
"ApplyConfig3 config stamp not monotonically increasing; new stamp: {}, previous stamp: {}",
new_config_stamp.value(), latest_config_stamp_.value());
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
latest_config_stamp_ = new_config_stamp;
// Empty applied layer lists for all displays whose layer lists are changing.
//
// This guarantees that layers moved between displays don't end up in two
// layer lists while each display's applied configuration is updated to match
// its draft configuration.
for (DisplayConfig& display_config : display_configs_) {
if (display_config.draft_has_layer_list_change_) {
display_config.applied_layers_.clear();
}
}
for (DisplayConfig& display_config : display_configs_) {
if (display_config.has_draft_nonlayer_config_change_) {
display_config.applied_ = display_config.draft_;
display_config.has_draft_nonlayer_config_change_ = false;
}
// Update any image layers. This needs to be done before migrating layers, as
// that needs to know if there are any waiting images.
for (LayerNode& draft_layer_node : display_config.draft_layers_) {
if (!draft_layer_node.layer->ResolveDraftLayerProperties()) {
fdf::error("Failed to resolve draft layer properties for layer {}",
draft_layer_node.layer->id().value());
TearDown(ZX_ERR_BAD_STATE);
return;
}
if (!draft_layer_node.layer->ResolveDraftImage(&fences_, latest_config_stamp_)) {
fdf::error("Failed to resolve draft image for layer {}",
draft_layer_node.layer->id().value());
TearDown(ZX_ERR_BAD_STATE);
return;
}
}
// Build applied layer lists that were emptied above.
if (display_config.draft_has_layer_list_change_) {
// Rebuild the applied layer list from the draft layer list.
for (LayerNode& draft_layer_node : display_config.draft_layers_) {
Layer* draft_layer = draft_layer_node.layer;
display_config.applied_layers_.push_back(&draft_layer->applied_display_config_list_node_);
}
for (LayerNode& applied_layer_node : display_config.applied_layers_) {
Layer* applied_layer = applied_layer_node.layer;
// Don't migrate images between displays if there are pending images. See
// `Controller::ApplyConfig` for more details.
if (applied_layer->applied_to_display_id_ != display_config.id() &&
applied_layer->applied_image_ != nullptr && applied_layer->HasWaitingImages()) {
applied_layer->applied_image_ = nullptr;
// This doesn't need to be reset anywhere, since we really care about the last
// display this layer was shown on. Ignoring the 'null' display could cause
// unusual layer changes to trigger this unnecessary, but that's not wrong.
applied_layer->applied_to_display_id_ = display_config.id();
}
}
display_config.draft_has_layer_list_change_ = false;
display_config.draft_has_layer_list_change_property_.Set(false);
display_config.pending_apply_layer_change_ = true;
display_config.pending_apply_layer_change_property_.Set(true);
}
// Apply any draft configuration changes to active layers.
for (LayerNode& applied_layer_node : display_config.applied_layers_) {
applied_layer_node.layer->ApplyChanges();
}
}
ApplyConfigImpl();
// No reply defined.
}
void Client::GetLatestAppliedConfigStamp(GetLatestAppliedConfigStampCompleter::Sync& completer) {
TRACE_DURATION("gfx", "Display::Client::GetLatestAppliedConfigStamp");
completer.Reply(latest_config_stamp_.ToFidl());
}
void Client::SetVirtconMode(SetVirtconModeRequestView request,
SetVirtconModeCompleter::Sync& /*_completer*/) {
TRACE_DURATION("gfx", "Display::Client::SetVirtconMode");
if (priority_ != ClientPriority::kVirtcon) {
fdf::error("SetVirtconMode() called by {} client", DebugStringFromClientPriority(priority_));
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
controller_.SetVirtconMode(request->mode);
// No reply defined.
}
void Client::IsCaptureSupported(IsCaptureSupportedCompleter::Sync& completer) {
TRACE_DURATION("gfx", "Display::Client::IsCaptureSupported");
completer.ReplySuccess(controller_.supports_capture());
}
zx_status_t Client::ImportImageForCapture(const display::ImageMetadata& image_metadata,
display::BufferCollectionId buffer_collection_id,
uint32_t buffer_index, display::ImageId image_id) {
ZX_DEBUG_ASSERT(image_metadata.tiling_type() == display::ImageTilingType::kCapture);
ZX_DEBUG_ASSERT(!images_.find(image_id).IsValid());
ZX_DEBUG_ASSERT(!capture_images_.find(image_id).IsValid());
// Ensure display driver supports/implements capture.
if (!controller_.supports_capture()) {
return ZX_ERR_NOT_SUPPORTED;
}
// Ensure a previously imported collection id is being used for import.
auto it = collection_map_.find(buffer_collection_id);
if (it == collection_map_.end()) {
return ZX_ERR_INVALID_ARGS;
}
const Client::Collections& collections = it->second;
zx::result<display::DriverCaptureImageId> import_result =
controller_.engine_driver_client()->ImportImageForCapture(
collections.driver_buffer_collection_id, buffer_index);
if (import_result.is_error()) {
return import_result.error_value();
}
const display::DriverCaptureImageId driver_capture_image_id = import_result.value();
auto release_image = fit::defer([this, driver_capture_image_id]() {
// TODO(https://fxbug.dev/42180237): Consider handling the error instead of ignoring it.
[[maybe_unused]] zx::result<> result =
controller_.engine_driver_client()->ReleaseCapture(driver_capture_image_id);
});
fbl::AllocChecker alloc_checker;
fbl::RefPtr<CaptureImage> capture_image = fbl::AdoptRef(new (&alloc_checker) CaptureImage(
&controller_, image_id, driver_capture_image_id, &proxy_.node(), id_));
if (!alloc_checker.check()) {
return ZX_ERR_NO_MEMORY;
}
// `driver_capture_image_id` is now owned by the CaptureImage instance.
release_image.cancel();
capture_images_.insert(std::move(capture_image));
return ZX_OK;
}
void Client::StartCapture(StartCaptureRequestView request, StartCaptureCompleter::Sync& completer) {
TRACE_DURATION("gfx", "Display::Client::StartCapture");
// Ensure display driver supports/implements capture.
if (!controller_.supports_capture()) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
return;
}
// Don't start capture if one is in progress.
if (current_capture_image_id_ != display::kInvalidImageId) {
completer.ReplyError(ZX_ERR_SHOULD_WAIT);
return;
}
// Ensure we have a capture fence for the request signal event.
auto signal_fence = fences_.GetFence(display::EventId(request->signal_event_id));
if (signal_fence == nullptr) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
// Ensure we are capturing into a valid image buffer.
const display::ImageId capture_image_id = display::ImageId(request->image_id);
auto image = capture_images_.find(capture_image_id);
if (!image.IsValid()) {
fdf::error("Invalid Capture Image ID requested for capture");
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
capture_fence_id_ = display::EventId(request->signal_event_id);
zx::result<> result =
controller_.engine_driver_client()->StartCapture(image->driver_capture_image_id());
if (result.is_error()) {
completer.ReplyError(result.error_value());
return;
}
proxy_.EnableCapture(true);
completer.ReplySuccess();
// Keep track of currently active capture image.
current_capture_image_id_ = capture_image_id; // TODO: Is this right?
}
void Client::SetMinimumRgb(SetMinimumRgbRequestView request,
SetMinimumRgbCompleter::Sync& completer) {
TRACE_DURATION("gfx", "Display::Client::SetMinimumRgb");
if (!is_owner_) {
completer.ReplyError(ZX_ERR_NOT_CONNECTED);
return;
}
zx::result<> result = controller_.engine_driver_client()->SetMinimumRgb(request->minimum_rgb);
if (result.is_error()) {
completer.ReplyError(result.error_value());
return;
}
client_minimum_rgb_ = request->minimum_rgb;
completer.ReplySuccess();
}
void Client::SetDisplayPower(SetDisplayPowerRequestView request,
SetDisplayPowerCompleter::Sync& completer) {
TRACE_DURATION("gfx", "Display::Client::SetDisplayPower");
const display::DisplayId display_id = display::DisplayId(request->display_id);
auto display_configs_it = display_configs_.find(display_id);
if (!display_configs_it.IsValid()) {
fdf::warn("SetDisplayPower called with unknown display ID: {}", display_id.value());
completer.ReplyError(ZX_ERR_NOT_FOUND);
}
zx::result<> result =
controller_.engine_driver_client()->SetDisplayPower(display_id, request->power_on);
if (result.is_error()) {
completer.ReplyError(result.error_value());
return;
}
completer.ReplySuccess();
}
display::ConfigCheckResult Client::CheckConfigImpl() {
TRACE_DURATION("gfx", "Display::Client::CheckConfig");
for (const DisplayConfig& display_config : display_configs_) {
if (display_config.draft_layers_.is_empty()) {
// `SetDisplayLayers()` prevents the client from directly specifying an
// empty layer list for a display. However, this can still happen if the
// client put together a display configuration, a new display was added to
// the system, and the client called CheckConfig() or ApplyConfig() before
// it received the display change event.
//
// Skipping over the newly added display is appropriate, because display
// engine drivers must support operating the hardware between the moment a
// display is added and the moment it receives its first configuration.
continue;
}
// Required to get display preferred modes.
zx::result<std::span<const display::ModeAndId>> preferred_modes_result =
controller_.GetDisplayPreferredModes(display_config.id());
if (preferred_modes_result.is_error()) {
fdf::error("Failed to get display preferred modes for display ID {}: {}",
display_config.id().value(), preferred_modes_result);
return display::ConfigCheckResult::kUnsupportedConfig;
}
std::span<const display::ModeAndId> preferred_modes = preferred_modes_result.value();
return CheckConfigForDisplay(display_config, preferred_modes);
}
// The client needs to process display changes and prepare a configuration
// that accounts for the added / removed displays.
return display::ConfigCheckResult::kEmptyConfig;
}
display::ConfigCheckResult Client::CheckConfigForDisplay(
const DisplayConfig& display_config, std::span<const display::ModeAndId> preferred_modes) {
ZX_DEBUG_ASSERT(!display_config.draft_layers_.is_empty());
// The cast will not result in UB because the maximum layer count is
// guaranteed to be positive.
const size_t max_layer_count = display_config.engine_max_layer_count();
ZX_DEBUG_ASSERT_MSG(max_layer_count > 0,
"DisplayConfig contract broken: engine_max_layer_count() must be positive");
// Frame used for checking that each layer's `display_destination` lies
// entirely within the display output.
const display::ModeId draft_mode_id(display_config.draft_.mode_id);
if (draft_mode_id == display::kInvalidModeId) {
return display::ConfigCheckResult::kInvalidConfig;
}
auto mode_it = std::ranges::find_if(preferred_modes, [&](const display::ModeAndId& mode_and_id) {
return mode_and_id.id() == draft_mode_id;
});
if (mode_it == preferred_modes.end()) {
fdf::error("SetDisplayMode called with unknown mode ID: {}", draft_mode_id.value());
return display::ConfigCheckResult::kUnsupportedDisplayModes;
}
display::Rectangle display_area({});
display_area = {{
.x = 0,
.y = 0,
.width = mode_it->mode().active_area().width(),
.height = mode_it->mode().active_area().height(),
}};
fbl::static_vector<display::DriverLayer, display::EngineInfo::kMaxAllowedMaxLayerCount>
driver_layers;
// Normalize the display configuration, and perform Coordinator-level
// checks. The engine drivers API contract does not allow passing
// configurations that fail these checks.
for (const LayerNode& draft_layer_node : display_config.draft_layers_) {
const display::DriverLayer& driver_layer = draft_layer_node.layer->draft_layer_config_;
driver_layers.push_back(driver_layer);
if (driver_layers.size() > max_layer_count) {
return display::ConfigCheckResult::kUnsupportedConfig;
}
if (driver_layer.image_source().width() != 0 && driver_layer.image_source().height() != 0) {
// Frame for checking that the layer's `image_source` lies entirely within
// the source image.
const display::Rectangle image_area({
.x = 0,
.y = 0,
.width = driver_layer.image_metadata().dimensions().width(),
.height = driver_layer.image_metadata().dimensions().height(),
});
if (!OriginRectangleContains(image_area, driver_layer.image_source())) {
return display::ConfigCheckResult::kInvalidConfig;
}
// The formats of layer images are negotiated by sysmem between clients
// and display engine drivers when being imported, so they are always
// accepted by the display coordinator.
}
if (!OriginRectangleContains(display_area, driver_layer.display_destination())) {
return display::ConfigCheckResult::kInvalidConfig;
}
}
DriverDisplayConfig draft_driver_display_config = display_config.draft_;
ZX_DEBUG_ASSERT_MSG(
static_cast<size_t>(draft_driver_display_config.layer_count) == driver_layers.size(),
"Draft configuration layer count %d does not agree with list size %zu",
draft_driver_display_config.layer_count, driver_layers.size());
{
TRACE_DURATION("gfx", "Display::Client::CheckConfig engine_driver_client");
return controller_.engine_driver_client()->CheckConfiguration(draft_driver_display_config,
driver_layers);
}
}
void Client::ReapplyConfig() {
if (latest_config_stamp_ != display::kInvalidConfigStamp) {
ApplyConfigImpl();
}
}
void Client::ApplyConfigImpl() {
ZX_DEBUG_ASSERT(controller_.IsRunningOnDriverDispatcher());
TRACE_DURATION("gfx", "Display::Client::ApplyConfig internal");
ZX_DEBUG_ASSERT_MSG(!layers_.is_empty(), "Empty layers during ApplyConfigImpl");
bool config_missing_image = false;
// Layers may have pending images, and it is possible that a layer still
// uses images from previous configurations. We should take this into account
// when sending the config_stamp to `Controller`.
//
// We keep track of the "current client config stamp" for each image, the
// value of which is only updated when a configuration uses an image that is
// ready on application, or when the image's wait fence has been signaled and
// `ActivateLatestReadyImage()` activates the new image.
//
// The final config_stamp sent to `Controller` will be the minimum of all
// per-layer stamps.
display::ConfigStamp applied_config_stamp = latest_config_stamp_;
for (DisplayConfig& display_config : display_configs_) {
display_config.applied_.layer_count = 0;
// Displays with no current layers are filtered out in `Controller::ApplyConfig`,
// after it updates its own image tracking logic.
for (LayerNode& applied_layer_node : display_config.applied_layers_) {
display_config.applied_.layer_count++;
Layer* applied_layer = applied_layer_node.layer;
const bool activated = applied_layer->ActivateLatestReadyImage();
if (activated && applied_layer->applied_image()) {
display_config.pending_apply_layer_change_ = true;
display_config.pending_apply_layer_change_property_.Set(true);
}
// This is subtle. Compute the config stamp for this config as the *earliest* stamp of any
// `Image` that appears on a `Layer` in this config. The goal is to satisfy the contract of
// the `applied_config_stamp` field of `CoordinatorListener.OnVsync()`, which returns the
// config stamp of the latest *fully applied* config. For example, a config is not fully
// applied if one of the images in the config is still waiting on a fence, even if the other
// images in the config have appeared on-screen.
std::optional<display::ConfigStamp> applied_layer_client_config_stamp =
applied_layer->GetCurrentClientConfigStamp();
if (applied_layer_client_config_stamp != std::nullopt) {
applied_config_stamp = std::min(applied_config_stamp, *applied_layer_client_config_stamp);
}
bool is_solid_color_fill = applied_layer->applied_layer_config_.image_source().width() == 0 ||
applied_layer->applied_layer_config_.image_source().height() == 0;
if (!is_solid_color_fill) {
if (applied_layer->applied_image() == nullptr) {
config_missing_image = true;
}
}
}
}
if (!config_missing_image && is_owner_) {
for (DisplayConfig& display_config : display_configs_) {
controller_.ApplyConfig(display_config, applied_config_stamp, id_);
}
}
}
void Client::SetOwnership(bool is_owner) {
ZX_DEBUG_ASSERT(controller_.IsRunningOnDriverDispatcher());
is_owner_ = is_owner;
NotifyOwnershipChange(/*client_has_ownership=*/is_owner);
// Only apply the current config if the client has previously applied a config.
ReapplyConfig();
}
void Client::NotifyDisplayChanges(
std::span<const fuchsia_hardware_display::wire::Info> added_display_infos,
std::span<const fuchsia_hardware_display_types::wire::DisplayId> removed_display_ids) {
if (!coordinator_listener_.is_valid()) {
return;
}
// TODO(https://fxbug.dev/42052765): `OnDisplayChanged()` takes `VectorView`s
// of non-const display `Info` and `display::DisplayId` types though it doesn't modify
// the vectors. We have to perform a `const_cast` to drop their constness.
std::span<fuchsia_hardware_display::wire::Info> non_const_added_display_infos(
const_cast<fuchsia_hardware_display::wire::Info*>(added_display_infos.data()),
added_display_infos.size());
std::span<fuchsia_hardware_display_types::wire::DisplayId> non_const_removed_display_ids(
const_cast<fuchsia_hardware_display_types::wire::DisplayId*>(removed_display_ids.data()),
removed_display_ids.size());
fidl::OneWayStatus fidl_transport_status = coordinator_listener_->OnDisplaysChanged(
fidl::VectorView<fuchsia_hardware_display::wire::Info>::FromExternal(
non_const_added_display_infos.data(), non_const_added_display_infos.size()),
fidl::VectorView<fuchsia_hardware_display_types::wire::DisplayId>::FromExternal(
non_const_removed_display_ids.data(), non_const_removed_display_ids.size()));
if (!fidl_transport_status.ok()) {
fdf::error("FIDL error calling OnDisplaysChanged: {}", fidl_transport_status.error());
}
}
void Client::NotifyOwnershipChange(bool client_has_ownership) {
if (!coordinator_listener_.is_valid()) {
return;
}
fidl::OneWayStatus fidl_transport_status =
coordinator_listener_->OnClientOwnershipChange(client_has_ownership);
if (!fidl_transport_status.ok()) {
fdf::error("FIDL error calling OnClientOwnershipChange: {}", fidl_transport_status.error());
}
}
void Client::NotifyVsync(display::DisplayId display_id, zx::time_monotonic timestamp,
display::ConfigStamp config_stamp,
display::VsyncAckCookie vsync_ack_cookie) {
if (!coordinator_listener_.is_valid()) {
return;
}
fidl::OneWayStatus fidl_transport_status = coordinator_listener_->OnVsync(
display_id.ToFidl(), timestamp, config_stamp.ToFidl(), vsync_ack_cookie.ToFidl());
if (!fidl_transport_status.ok()) {
fdf::error("FIDL error calling OnVsync: {}", fidl_transport_status.error());
}
}
void Client::OnDisplaysChanged(std::span<const display::DisplayId> added_display_ids,
std::span<const display::DisplayId> removed_display_ids) {
ZX_DEBUG_ASSERT(controller_.IsRunningOnDriverDispatcher());
for (display::DisplayId added_display_id : added_display_ids) {
zx::result get_supported_pixel_formats_result =
controller_.GetSupportedPixelFormats(added_display_id);
if (get_supported_pixel_formats_result.is_error()) {
fdf::warn("Failed to get pixel formats when processing hotplug: {}",
get_supported_pixel_formats_result);
continue;
}
fbl::AllocChecker alloc_checker;
auto display_config = fbl::make_unique_checked<DisplayConfig>(
&alloc_checker, added_display_id, std::move(get_supported_pixel_formats_result).value(),
controller_.engine_info().max_layer_count());
if (!alloc_checker.check()) {
fdf::warn("Out of memory when processing hotplug");
continue;
}
zx::result<std::span<const display::ModeAndId>> display_preferred_modes_result =
controller_.GetDisplayPreferredModes(display_config->id());
if (display_preferred_modes_result.is_error()) {
fdf::warn("Failed to get display preferred modes when processing hotplug: {}",
display_preferred_modes_result);
continue;
}
std::span<const display::ModeAndId> display_preferred_modes =
std::move(display_preferred_modes_result).value();
ZX_DEBUG_ASSERT(!display_preferred_modes.empty());
const display::ModeAndId preferred_mode_and_id = display_preferred_modes[0];
ZX_DEBUG_ASSERT(preferred_mode_and_id.id() != display::kInvalidModeId);
display_config->applied_ = DriverDisplayConfig{
.display_id = display_config->id(),
.mode_id = preferred_mode_and_id.id(),
.color_conversion = display::ColorConversion::kIdentity,
.layer_count = 0,
};
display_config->draft_ = display_config->applied_;
display_config->InitializeInspect(&proxy_.node());
display_configs_.insert(std::move(display_config));
}
// We need 2 loops, since we need to make sure we allocate the
// correct size array in the FIDL response.
std::vector<fhd::wire::Info> coded_configs;
coded_configs.reserve(added_display_ids.size());
// Hang on to modes values until we send the message.
std::vector<std::vector<fuchsia_hardware_display_types::wire::Mode>> modes_vector;
fidl::Arena arena;
for (display::DisplayId added_display_id : added_display_ids) {
auto display_configs_it = display_configs_.find(added_display_id);
if (!display_configs_it.IsValid()) {
// The display got removed before the display addition was processed and
// reported to the client.
continue;
}
const DisplayConfig& display_config = *display_configs_it;
fhd::wire::Info fidl_display_info;
fidl_display_info.id = display_config.id().ToFidl();
zx::result<std::span<const display::ModeAndId>> display_preferred_modes_result =
controller_.GetDisplayPreferredModes(display_config.id());
ZX_DEBUG_ASSERT(display_preferred_modes_result.is_ok());
std::span<const display::ModeAndId> display_preferred_modes =
std::move(display_preferred_modes_result).value();
ZX_DEBUG_ASSERT(!display_preferred_modes.empty());
std::vector<fuchsia_hardware_display_types::wire::Mode> fidl_modes;
fidl_modes.reserve(display_preferred_modes.size());
for (const display::ModeAndId& mode_and_id : display_preferred_modes) {
fidl_modes.emplace_back(mode_and_id.mode().ToFidl());
}
modes_vector.emplace_back(std::move(fidl_modes));
fidl_display_info.modes =
fidl::VectorView<fuchsia_hardware_display_types::wire::Mode>::FromExternal(
modes_vector.back());
fidl_display_info.pixel_format = fidl::VectorView<fuchsia_images2::wire::PixelFormat>(
arena, display_config.pixel_formats_.size());
for (size_t pixel_format_index = 0; pixel_format_index < fidl_display_info.pixel_format.size();
++pixel_format_index) {
fidl_display_info.pixel_format[pixel_format_index] =
display_config.pixel_formats_[pixel_format_index].ToFidl();
}
const bool found_display_info =
controller_.FindDisplayInfo(added_display_id, [&](const DisplayInfo& display_info) {
fidl_display_info.manufacturer_name =
fidl::StringView::FromExternal(display_info.GetManufacturerName());
fidl_display_info.monitor_name = fidl::StringView(arena, display_info.GetMonitorName());
fidl_display_info.monitor_serial =
fidl::StringView(arena, display_info.GetMonitorSerial());
// The return value of `GetHorizontalSizeMm()` is guaranteed to be `0 <= value < 2^16`,
// so it can be safely cast to `uint32_t`.
fidl_display_info.horizontal_size_mm =
static_cast<uint32_t>(display_info.GetHorizontalSizeMm());
// The return value of `GetVerticalSizeMm()` is guaranteed to be `0 <= value < 2^16`,
// so it can be safely cast to uint32_t.
fidl_display_info.vertical_size_mm =
static_cast<uint32_t>(display_info.GetVerticalSizeMm());
});
if (!found_display_info) {
fdf::error("Failed to get DisplayInfo for display {}", added_display_id.value());
ZX_DEBUG_ASSERT(false);
}
fidl_display_info.using_fallback_size = false;
if (fidl_display_info.horizontal_size_mm == 0 || fidl_display_info.vertical_size_mm == 0) {
fidl_display_info.horizontal_size_mm = kFallbackHorizontalSizeMm;
fidl_display_info.vertical_size_mm = kFallbackVerticalSizeMm;
fidl_display_info.using_fallback_size = true;
}
coded_configs.push_back(fidl_display_info);
}
std::vector<fhdt::wire::DisplayId> fidl_removed_display_ids;
fidl_removed_display_ids.reserve(removed_display_ids.size());
for (display::DisplayId removed_display_id : removed_display_ids) {
std::unique_ptr<DisplayConfig> display_config = display_configs_.erase(removed_display_id);
if (display_config != nullptr) {
display_config->draft_layers_.clear();
display_config->applied_layers_.clear();
fidl_removed_display_ids.push_back(display_config->id().ToFidl());
}
}
if (!coded_configs.empty() || !fidl_removed_display_ids.empty()) {
NotifyDisplayChanges(coded_configs, fidl_removed_display_ids);
}
}
void Client::OnFenceSignaled(Fence& fence) {
bool new_image_ready = false;
for (auto& layer : layers_) {
new_image_ready |= layer.MarkFenceReady(fence);
}
if (new_image_ready) {
ApplyConfigImpl();
}
}
void Client::CaptureCompleted() {
auto signal_fence = fences_.GetFence(capture_fence_id_);
if (signal_fence != nullptr) {
signal_fence->Signal();
}
// Release the pending capture image, if there is one.
if (pending_release_capture_image_id_ != display::kInvalidImageId) {
auto image = capture_images_.find(pending_release_capture_image_id_);
if (image.IsValid()) {
capture_images_.erase(image);
}
pending_release_capture_image_id_ = display::kInvalidImageId;
}
current_capture_image_id_ = display::kInvalidImageId;
}
void Client::TearDown(zx_status_t epitaph) {
TRACE_DURATION("gfx", "Display::Client::TearDown");
fdf::info("Tearing down Client 0x{:x} (ID = {})", reinterpret_cast<uintptr_t>(this), id_.value());
ZX_DEBUG_ASSERT(controller_.IsRunningOnDriverDispatcher());
draft_display_config_was_validated_ = false;
// See `fuchsia.hardware.display/Coordinator` protocol documentation in `coordinator.fidl`,
// which describes the epitaph values that will be set when the channel closes.
switch (epitaph) {
case ZX_ERR_INVALID_ARGS:
case ZX_ERR_BAD_STATE:
case ZX_ERR_NO_MEMORY:
fdf::info("TearDown() called with epitaph {}", zx::make_result(epitaph));
break;
default:
fdf::info("TearDown() called with epitaph {}; using catchall ZX_ERR_INTERNAL instead",
zx::make_result(epitaph));
epitaph = ZX_ERR_INTERNAL;
}
// Teardown stops events from the channel, but not from the ddk, so we
// need to make sure we don't try to teardown multiple times.
if (!IsValid()) {
return;
}
valid_ = false;
// Break FIDL connections.
binding_->Close(epitaph);
binding_.reset();
coordinator_listener_.AsyncTeardown();
CleanUpAllImages();
fdf::info("Releasing {} capture images cur={}, pending={}", capture_images_.size(),
current_capture_image_id_.value(), pending_release_capture_image_id_.value());
current_capture_image_id_ = pending_release_capture_image_id_ = display::kInvalidImageId;
capture_images_.clear();
fences_.Clear();
for (DisplayConfig& display_config : display_configs_) {
display_config.draft_layers_.clear();
display_config.applied_layers_.clear();
}
// The layer's images have already been handled in `CleanUpImageLayerState`.
layers_.clear();
// Release all imported buffer collections on display drivers.
for (const auto& [k, v] : collection_map_) {
// TODO(https://fxbug.dev/42180237): Consider handling the error instead of ignoring it.
[[maybe_unused]] zx::result<> result =
controller_.engine_driver_client()->ReleaseBufferCollection(v.driver_buffer_collection_id);
}
collection_map_.clear();
}
bool Client::CleanUpAllImages() {
// Clean up any layer state associated with the images.
bool current_config_changed = [&] {
// We need to clean up images for all layers and thus should not
// short-circuit here.
bool any_layer_changed = false;
for (Layer& layer : layers_) {
any_layer_changed |= layer.CleanUpAllImages();
}
return any_layer_changed;
}();
images_.clear();
return current_config_changed;
}
bool Client::CleanUpImage(Image& image) {
// Clean up any layer state associated with the images.
bool current_config_changed = [&] {
// We need to clean up images for all layers and thus should not
// short-circuit here.
bool any_layer_changed = false;
for (Layer& layer : layers_) {
any_layer_changed |= layer.CleanUpImage(image);
}
return any_layer_changed;
}();
images_.erase(image);
return current_config_changed;
}
void Client::CleanUpCaptureImage(display::ImageId id) {
if (id == display::kInvalidImageId) {
return;
}
// If the image is currently active, the underlying driver will retain a
// handle to it until the hardware can be reprogrammed.
auto image = capture_images_.find(id);
if (image.IsValid()) {
capture_images_.erase(image);
}
}
void Client::SetAllConfigDraftLayersToAppliedLayers() {
// Layers may have been moved between displays, so we must be extra careful
// to avoid inserting a Layer in a display's draft list while it's
// already moved to another Display's draft list.
//
// We side-step this problem by clearing all draft lists before inserting
// any Layer in them, so that we can guarantee that for every Layer, its
// `draft_node_` is not in any Display's draft list.
for (DisplayConfig& display_config : display_configs_) {
display_config.draft_layers_.clear();
}
for (DisplayConfig& display_config : display_configs_) {
// Rebuild the draft layers list from applied layers list.
for (LayerNode& layer_node : display_config.applied_layers_) {
display_config.draft_layers_.push_back(&layer_node.layer->draft_display_config_list_node_);
}
}
}
void Client::DiscardConfig() {
TRACE_DURATION("gfx", "Display::Client::DiscardConfig");
// Go through layers and release any resources claimed by draft configs.
for (Layer& layer : layers_) {
layer.DiscardChanges();
}
// Discard layer list changes.
SetAllConfigDraftLayersToAppliedLayers();
// Discard the rest of the Display changes.
for (DisplayConfig& display_config : display_configs_) {
display_config.DiscardNonLayerDraftConfig();
}
draft_display_config_was_validated_ = true;
}
void Client::AcknowledgeVsync(AcknowledgeVsyncRequestView request,
AcknowledgeVsyncCompleter::Sync& /*_completer*/) {
display::VsyncAckCookie ack_cookie = display::VsyncAckCookie(request->cookie);
if (ack_cookie == display::kInvalidVsyncAckCookie) {
fdf::error("AcknowledgeVsync() called with invalid cookie");
TearDown(ZX_ERR_INVALID_ARGS);
return;
}
proxy_.AcknowledgeVsync(ack_cookie);
fdf::trace("Cookie {} Acked\n", ack_cookie.value());
}
void Client::Bind(
fidl::ServerEnd<fuchsia_hardware_display::Coordinator> coordinator_server_end,
fidl::ClientEnd<fuchsia_hardware_display::CoordinatorListener> coordinator_listener_client_end,
fidl::OnUnboundFn<Client> unbound_callback) {
ZX_DEBUG_ASSERT(!valid_);
ZX_DEBUG_ASSERT(coordinator_server_end.is_valid());
ZX_DEBUG_ASSERT(coordinator_listener_client_end.is_valid());
valid_ = true;
// Keep a copy of FIDL binding so we can safely unbind from it during shutdown.
binding_ = fidl::BindServer(controller_.driver_dispatcher()->async_dispatcher(),
std::move(coordinator_server_end), this, std::move(unbound_callback));
coordinator_listener_.Bind(std::move(coordinator_listener_client_end),
controller_.driver_dispatcher()->async_dispatcher());
}
Client::Client(Controller* controller, ClientProxy* proxy, ClientPriority priority,
ClientId client_id)
: controller_(*controller),
proxy_(*proxy),
priority_(priority),
id_(client_id),
fences_(this, controller->driver_dispatcher()->borrow()) {
ZX_DEBUG_ASSERT(controller != nullptr);
ZX_DEBUG_ASSERT(proxy != nullptr);
ZX_DEBUG_ASSERT(client_id != kInvalidClientId);
}
Client::~Client() {
ZX_DEBUG_ASSERT(!valid_);
ZX_DEBUG_ASSERT(layers_.size() == 0);
}
} // namespace display_coordinator