| // 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/controller.h" |
| |
| #include <lib/async/cpp/task.h> |
| #include <lib/driver/logging/cpp/logger.h> |
| #include <lib/fdf/cpp/dispatcher.h> |
| #include <lib/fit/function.h> |
| #include <lib/trace/event.h> |
| #include <lib/zbi-format/graphics.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/clock.h> |
| #include <lib/zx/result.h> |
| #include <lib/zx/time.h> |
| #include <threads.h> |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/threads.h> |
| #include <zircon/time.h> |
| #include <zircon/types.h> |
| |
| #include <algorithm> |
| #include <cinttypes> |
| #include <cstdint> |
| #include <cstdlib> |
| #include <cstring> |
| #include <memory> |
| #include <optional> |
| #include <span> |
| #include <utility> |
| #include <vector> |
| |
| #include <fbl/alloc_checker.h> |
| #include <fbl/array.h> |
| #include <fbl/ref_ptr.h> |
| #include <fbl/static_vector.h> |
| #include <fbl/vector.h> |
| |
| #include "src/graphics/display/drivers/coordinator/added-display-info.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/display-info.h" |
| #include "src/graphics/display/drivers/coordinator/image.h" |
| #include "src/graphics/display/drivers/coordinator/layer.h" |
| #include "src/graphics/display/drivers/coordinator/post-display-task.h" |
| #include "src/graphics/display/drivers/coordinator/vsync-monitor.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/pixel-format.h" |
| |
| namespace fidl_display = fuchsia_hardware_display; |
| |
| namespace display_coordinator { |
| |
| void Controller::AddDisplay(std::unique_ptr<AddedDisplayInfo> added_display_info) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| zx::result<std::unique_ptr<DisplayInfo>> display_info_result = |
| DisplayInfo::Create(std::move(*added_display_info)); |
| if (display_info_result.is_error()) { |
| // DisplayInfo::Create() has already logged the error. |
| return; |
| } |
| std::unique_ptr<DisplayInfo> display_info = std::move(display_info_result).value(); |
| |
| display::DisplayId display_id = display_info->id(); |
| const std::array<display::DisplayId, 1> added_id_candidates = {display_id}; |
| std::span<const display::DisplayId> added_ids(added_id_candidates); |
| |
| // TODO(https://fxbug.dev/339311596): Do not trigger the client's |
| // `OnDisplaysChanged` if an added display is ignored. |
| // |
| // Dropping some add events can result in spurious removes, but |
| // those are filtered out in the clients. |
| if (!display_info->preferred_modes.is_empty()) { |
| display_info->InitializeInspect(&root_); |
| } else { |
| fdf::warn("Ignoring display with no usable display preferred modes"); |
| added_ids = {}; |
| } |
| |
| auto display_it = displays_.find(display_id); |
| if (display_it != displays_.end()) { |
| fdf::warn("Display {} is already created; add display request ignored", display_id.value()); |
| return; |
| } |
| displays_.insert(std::move(display_info)); |
| |
| // TODO(https://fxbug.dev/317914671): Pass parsed display metadata to driver. |
| |
| if (virtcon_client_ready_) { |
| ZX_DEBUG_ASSERT(virtcon_client_ != nullptr); |
| virtcon_client_->OnDisplaysChanged(added_ids, /*removed_display_ids=*/{}); |
| } |
| if (primary_client_ready_) { |
| ZX_DEBUG_ASSERT(primary_client_ != nullptr); |
| primary_client_->OnDisplaysChanged(added_ids, /*removed_display_ids=*/{}); |
| } |
| } |
| |
| void Controller::RemoveDisplay(display::DisplayId removed_display_id) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| std::unique_ptr<DisplayInfo> removed_display = displays_.erase(removed_display_id); |
| if (!removed_display) { |
| fdf::warn("Display removal references unknown display ID: {}", removed_display_id.value()); |
| return; |
| } |
| |
| // Release references to all images on the display. |
| while (removed_display->images.pop_front()) { |
| } |
| |
| const std::array<display::DisplayId, 1> removed_display_ids = { |
| removed_display_id, |
| }; |
| if (virtcon_client_ready_) { |
| ZX_DEBUG_ASSERT(virtcon_client_ != nullptr); |
| virtcon_client_->OnDisplaysChanged(/*added_display_ids=*/{}, removed_display_ids); |
| } |
| if (primary_client_ready_) { |
| ZX_DEBUG_ASSERT(primary_client_ != nullptr); |
| primary_client_->OnDisplaysChanged(/*added_display_ids=*/{}, removed_display_ids); |
| } |
| } |
| |
| void Controller::OnDisplayAdded(std::unique_ptr<AddedDisplayInfo> added_display_info) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| // TODO(https://fxbug.dev/438325925): Remove the PostTask after this call |
| // is guaranteed not to block an engine driver thread. |
| zx::result<> post_task_result = display::PostTask<kDisplayTaskTargetSize>( |
| *driver_dispatcher()->async_dispatcher(), |
| [this, added_display_info = std::move(added_display_info)]() mutable { |
| AddDisplay(std::move(added_display_info)); |
| }); |
| if (post_task_result.is_error()) { |
| fdf::error("Failed to dispatch AddDisplay task: {}", post_task_result); |
| } |
| } |
| |
| void Controller::OnDisplayRemoved(display::DisplayId removed_display_id) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| // TODO(https://fxbug.dev/438325925): Remove the PostTask after this call |
| // is guaranteed not to block an engine driver thread. |
| zx::result<> post_task_result = display::PostTask<kDisplayTaskTargetSize>( |
| *driver_dispatcher()->async_dispatcher(), |
| [this, removed_display_id]() { RemoveDisplay(removed_display_id); }); |
| if (post_task_result.is_error()) { |
| fdf::error("Failed to dispatch RemoveDisplay task: {}", post_task_result); |
| } |
| } |
| |
| void Controller::OnCaptureComplete() { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| ZX_DEBUG_ASSERT_MSG(engine_info_.has_value(), |
| "OnCaptureComplete() called before engine connection completed"); |
| |
| if (!engine_info_->is_capture_supported()) { |
| fdf::error("OnCaptureComplete() called by a display engine without display capture support"); |
| return; |
| } |
| |
| // TODO(https://fxbug.dev/438325925): Remove the PostTask after this call |
| // is guaranteed not to block an engine driver thread. |
| zx::result<> post_task_result = |
| display::PostTask<kDisplayTaskTargetSize>(*driver_dispatcher()->async_dispatcher(), [this]() { |
| // Free an image that was previously used by the hardware. |
| if (pending_release_capture_image_id_ != display::kInvalidDriverCaptureImageId) { |
| ReleaseCaptureImage(pending_release_capture_image_id_); |
| pending_release_capture_image_id_ = display::kInvalidDriverCaptureImageId; |
| } |
| |
| if (virtcon_client_ready_) { |
| ZX_DEBUG_ASSERT(virtcon_client_ != nullptr); |
| virtcon_client_->OnCaptureComplete(); |
| } |
| if (primary_client_ready_) { |
| ZX_DEBUG_ASSERT(primary_client_ != nullptr); |
| primary_client_->OnCaptureComplete(); |
| } |
| }); |
| if (post_task_result.is_error()) { |
| fdf::error("Failed to dispatch capture complete task: {}", post_task_result); |
| } |
| } |
| |
| void Controller::OnDisplayVsync(display::DisplayId display_id, zx::time_monotonic timestamp, |
| display::DriverConfigStamp driver_config_stamp) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| // TODO(https://fxbug.dev/438325925): Remove the PostTask after this call |
| // is guaranteed not to block an engine driver thread. |
| zx::result<> post_task_result = display::PostTask<kDisplayTaskTargetSize>( |
| *driver_dispatcher()->async_dispatcher(), |
| [this, display_id, timestamp, driver_config_stamp]() { |
| ProcessDisplayVsync(display_id, timestamp, driver_config_stamp); |
| }); |
| if (post_task_result.is_error()) { |
| fdf::error("Failed to dispatch ProcessVsync task: {}", post_task_result); |
| } |
| } |
| |
| void Controller::ProcessDisplayVsync(display::DisplayId display_id, zx::time_monotonic timestamp, |
| display::DriverConfigStamp driver_config_stamp) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| // TODO(https://fxbug.dev/402445178): This trace event is load bearing for fps trace processor. |
| // Remove it after changing the dependency. |
| TRACE_INSTANT("gfx", "VSYNC", TRACE_SCOPE_THREAD, "display_id", display_id.value()); |
| // Emit a counter called "VSYNC" for visualization in the Trace Viewer. `vsync_edge_flag` |
| // switching between 0 and 1 counts represents one vsync period. |
| static bool vsync_edge_flag = false; |
| TRACE_COUNTER("gfx", "VSYNC", display_id.value(), "", |
| TA_UINT32(vsync_edge_flag = !vsync_edge_flag)); |
| TRACE_DURATION("gfx", "Display::Controller::OnDisplayVsync", "display_id", display_id.value()); |
| |
| vsync_monitor_.OnVsync(timestamp, driver_config_stamp); |
| |
| auto displays_it = displays_.find(display_id); |
| if (!displays_it.IsValid()) { |
| fdf::error("Dropping VSync for unknown display ID: {}", display_id.value()); |
| return; |
| } |
| DisplayInfo& display_info = *displays_it; |
| |
| // See ::ApplyConfig for more explanation of how vsync image tracking works. |
| // |
| // If there's a pending layer change, don't process any present/retire actions |
| // until the change is complete. |
| if (display_info.pending_layer_change) { |
| bool done = driver_config_stamp >= display_info.pending_layer_change_driver_config_stamp; |
| if (done) { |
| display_info.pending_layer_change = false; |
| display_info.pending_layer_change_driver_config_stamp = display::kInvalidDriverConfigStamp; |
| display_info.switching_client = false; |
| } |
| } |
| |
| // The display configuration associated with the VSync event can come |
| // from one of the currently connected clients, or from a previously |
| // connected client that is now disconnected. |
| std::optional<ClientPriority> config_stamp_source = std::nullopt; |
| ClientProxy* const client_proxies[] = {primary_client_, virtcon_client_}; |
| for (ClientProxy* client_proxy : client_proxies) { |
| if (client_proxy == nullptr) { |
| continue; |
| } |
| |
| const std::list<ClientProxy::ConfigStampPair>& pending_stamps = |
| client_proxy->pending_applied_config_stamps(); |
| auto it = std::ranges::find_if(pending_stamps, |
| [&](const ClientProxy::ConfigStampPair& pending_stamp) { |
| return pending_stamp.driver_stamp >= driver_config_stamp; |
| }); |
| if (it != pending_stamps.end() && it->driver_stamp == driver_config_stamp) { |
| config_stamp_source = std::make_optional(client_proxy->client_priority()); |
| // Obsolete stamps will be removed in `Client::OnDisplayVsync()`. |
| break; |
| } |
| }; |
| |
| if (!display_info.pending_layer_change) { |
| // Each image in the `info->images` set can fall into one of the following |
| // cases: |
| // - being displayed (its `latest_controller_config_stamp` matches the |
| // incoming `controller_config_stamp` from display driver); |
| // - older than the current displayed image (its |
| // `latest_controller_config_stamp` is less than the incoming |
| // `controller_config_stamp`) and should be retired; |
| // - newer than the current displayed image (its |
| // `latest_controller_config_stamp` is greater than the incoming |
| // `controller_config_stamp`) and yet to be presented. |
| for (auto it = display_info.images.begin(); it != display_info.images.end();) { |
| bool should_retire = it->latest_driver_config_stamp() < driver_config_stamp; |
| |
| // Retire any images which are older than whatever is currently in their |
| // layer. |
| if (should_retire) { |
| fbl::RefPtr<Image> image_to_retire = display_info.images.erase(it++); |
| // Older images may not be presented. Ending their flows here |
| // ensures the correctness of traces. |
| // |
| // NOTE: If changing this flow name or ID, please also do so in the |
| // corresponding FLOW_BEGIN. |
| TRACE_FLOW_END("gfx", "present_image", image_to_retire->id().value()); |
| } else { |
| it++; |
| } |
| } |
| } |
| |
| // Evict retired configurations from the queue. |
| auto& config_image_queue = display_info.config_image_queue; |
| while (!config_image_queue.empty() && |
| config_image_queue.front().config_stamp < driver_config_stamp) { |
| config_image_queue.pop(); |
| } |
| |
| // Since the stamps sent from Controller to drivers are in chronological |
| // order, the Vsync signals Controller receives should also be in |
| // chronological order as well. |
| // |
| // Applying empty configs won't create entries in |config_image_queue|. |
| // Otherwise, we'll get the list of images used at ApplyConfig() with |
| // the given |config_stamp|. |
| if (!config_image_queue.empty() && |
| config_image_queue.front().config_stamp == driver_config_stamp) { |
| for (const auto& image : config_image_queue.front().images) { |
| // End of the flow for the image going to be presented. |
| // |
| // NOTE: If changing this flow name or ID, please also do so in the |
| // corresponding FLOW_BEGIN. |
| TRACE_FLOW_END("gfx", "present_image", image.image_id.value()); |
| } |
| } |
| |
| if (!config_stamp_source.has_value()) { |
| // The config was applied by a client that is no longer connected. |
| fdf::debug("VSync event dropped; the config owner disconnected"); |
| return; |
| } |
| |
| switch (config_stamp_source.value()) { |
| case ClientPriority::kPrimary: |
| primary_client_->OnDisplayVsync(display_id, timestamp.get(), driver_config_stamp); |
| break; |
| case ClientPriority::kVirtcon: |
| virtcon_client_->OnDisplayVsync(display_id, timestamp.get(), driver_config_stamp); |
| break; |
| } |
| } |
| |
| void Controller::ApplyConfig(DisplayConfig& display_config, |
| display::ConfigStamp client_config_stamp, ClientId client_id) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| zx_instant_mono_t timestamp = zx_clock_get_monotonic(); |
| last_valid_apply_config_timestamp_ns_property_.Set(timestamp); |
| last_valid_apply_config_interval_ns_property_.Set(timestamp - last_valid_apply_config_timestamp_); |
| last_valid_apply_config_timestamp_ = timestamp; |
| |
| last_valid_apply_config_config_stamp_property_.Set(client_config_stamp.value()); |
| |
| // The applied configuration's stamp. |
| display::DriverConfigStamp driver_config_stamp = {}; |
| fbl::static_vector<display::DriverLayer, display::EngineInfo::kMaxAllowedMaxLayerCount> |
| driver_layers; |
| |
| { |
| bool switching_client = client_id != applied_client_id_; |
| |
| ++last_issued_driver_config_stamp_; |
| driver_config_stamp = last_issued_driver_config_stamp_; |
| |
| auto displays_it = displays_.find(display_config.id()); |
| if (!displays_it.IsValid()) { |
| fdf::warn("ApplyConfig(): Cannot find display with id {}", display_config.id()); |
| return; |
| } |
| DisplayInfo& display_info = *displays_it; |
| |
| display_info.config_image_queue.push({.config_stamp = driver_config_stamp, .images = {}}); |
| |
| display_info.switching_client = switching_client; |
| display_info.pending_layer_change = display_config.apply_layer_change(); |
| if (display_info.pending_layer_change) { |
| display_info.pending_layer_change_driver_config_stamp = driver_config_stamp; |
| } |
| display_info.layer_count = display_config.applied_config().layer_count; |
| |
| if (display_info.layer_count == 0) { |
| // TODO(https://fxbug.dev/336394440): Make this a fatal error. |
| fdf::warn("ApplyConfig(): config doesn't have any valid layer; skipped"); |
| return; |
| } |
| |
| ZX_DEBUG_ASSERT(driver_layers.empty()); |
| for (const LayerNode& applied_layer_node : display_config.get_applied_layers()) { |
| const Layer* applied_layer = applied_layer_node.layer; |
| driver_layers.push_back(applied_layer->applied_driver_layer_config()); |
| fbl::RefPtr<Image> applied_image = applied_layer->applied_image(); |
| |
| if (applied_layer->is_skipped() || applied_image == nullptr) { |
| continue; |
| } |
| |
| // Set the image controller config stamp so vsync knows what config the |
| // image was used at. |
| applied_image->set_latest_driver_config_stamp(driver_config_stamp); |
| |
| // NOTE: If changing this flow name or ID, please also do so in the |
| // corresponding FLOW_END. |
| TRACE_FLOW_BEGIN("gfx", "present_image", applied_image->id().value()); |
| |
| // It's possible that the image's layer was moved between displays. The logic around |
| // pending_layer_change guarantees that the old display will be done with the image |
| // before the new display is, so deleting it from the old list is fine. |
| // |
| // Even if we're on the same display, the entry needs to be moved to the end of the |
| // list to ensure that the last config->current.layer_count elements in the queue |
| // are the current images. |
| // |
| // TODO(https://fxbug.dev/317914671): investigate whether storing Images in doubly-linked |
| // lists continues to be desirable. |
| if (applied_image->InDoublyLinkedList()) { |
| applied_image->RemoveFromDoublyLinkedList(); |
| } |
| display_info.images.push_back(applied_image); |
| display_info.config_image_queue.back().images.push_back( |
| {.image_id = applied_image->id(), .client_id = applied_image->client_id()}); |
| } |
| |
| applied_client_id_ = client_id; |
| |
| if (client_owning_displays_ != nullptr) { |
| if (switching_client) { |
| client_owning_displays_->ReapplySpecialConfigs(); |
| } |
| |
| client_owning_displays_->UpdateConfigStampMapping({ |
| .driver_stamp = driver_config_stamp, |
| .client_stamp = client_config_stamp, |
| }); |
| } |
| } |
| |
| DriverDisplayConfig driver_display_config = display_config.applied_config(); |
| // Populated by Client::ApplyConfig(). |
| ZX_DEBUG_ASSERT(static_cast<size_t>(driver_display_config.layer_count) == driver_layers.size()); |
| |
| engine_driver_client_->ApplyConfiguration(driver_display_config, driver_layers, |
| driver_config_stamp); |
| } |
| |
| void Controller::ImageWillBeDestroyed(display::DriverImageId driver_image_id) { |
| engine_driver_client_->ReleaseImage(driver_image_id); |
| } |
| |
| void Controller::ReleaseCaptureImage(display::DriverCaptureImageId driver_capture_image_id) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| ZX_DEBUG_ASSERT_MSG(engine_info_.has_value(), |
| "CaptureImage created before engine connection completed"); |
| ZX_DEBUG_ASSERT_MSG(engine_info_->is_capture_supported(), |
| "CaptureImage created by engine without capture support"); |
| |
| if (driver_capture_image_id == display::kInvalidDriverCaptureImageId) { |
| return; |
| } |
| |
| const zx::result<> result = engine_driver_client_->ReleaseCapture(driver_capture_image_id); |
| if (result.is_error() && result.error_value() == ZX_ERR_SHOULD_WAIT) { |
| ZX_DEBUG_ASSERT_MSG(pending_release_capture_image_id_ == display::kInvalidDriverCaptureImageId, |
| "multiple pending releases for capture images"); |
| // Delay the image release until the hardware is done. |
| pending_release_capture_image_id_ = driver_capture_image_id; |
| } |
| } |
| |
| void Controller::SetVirtconMode(fuchsia_hardware_display::wire::VirtconMode virtcon_mode) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| virtcon_mode_ = virtcon_mode; |
| HandleClientOwnershipChanges(); |
| } |
| |
| void Controller::HandleClientOwnershipChanges() { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| ClientProxy* new_client_owning_displays; |
| if (virtcon_mode_ == fidl_display::wire::VirtconMode::kForced || |
| (virtcon_mode_ == fidl_display::wire::VirtconMode::kFallback && primary_client_ == nullptr)) { |
| new_client_owning_displays = virtcon_client_; |
| } else { |
| new_client_owning_displays = primary_client_; |
| } |
| |
| if (new_client_owning_displays != client_owning_displays_) { |
| if (client_owning_displays_ != nullptr) { |
| client_owning_displays_->SetOwnership(false); |
| } |
| if (new_client_owning_displays) { |
| new_client_owning_displays->SetOwnership(true); |
| } |
| client_owning_displays_ = new_client_owning_displays; |
| } |
| } |
| |
| void Controller::OnClientDead(ClientProxy* client) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| ZX_DEBUG_ASSERT(client != nullptr); |
| |
| fdf::info("Client {} dead", client->client_id().value()); |
| |
| if (unbinding_) { |
| return; |
| } |
| |
| if (client == virtcon_client_) { |
| virtcon_client_ = nullptr; |
| virtcon_mode_ = fidl_display::wire::VirtconMode::kFallback; |
| virtcon_client_ready_ = false; |
| } else if (client == primary_client_) { |
| primary_client_ = nullptr; |
| primary_client_ready_ = false; |
| } else { |
| ZX_DEBUG_ASSERT_MSG(false, "Dead client is neither Virtcon nor Primary\n"); |
| } |
| |
| // Avoid trying to tell the disconnected client that it lost display ownership. |
| if (client == client_owning_displays_) { |
| client_owning_displays_ = nullptr; |
| } |
| clients_.remove_if( |
| [client](std::unique_ptr<ClientProxy>& list_client) { return list_client.get() == client; }); |
| |
| HandleClientOwnershipChanges(); |
| } |
| |
| zx::result<std::span<const display::ModeAndId>> Controller::GetDisplayPreferredModes( |
| display::DisplayId display_id) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| if (unbinding_) { |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| auto displays_it = displays_.find(display_id); |
| if (!displays_it.IsValid()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| const DisplayInfo& display_info = *displays_it; |
| return zx::ok(std::span(display_info.preferred_modes)); |
| } |
| |
| zx::result<fbl::Vector<display::PixelFormat>> Controller::GetSupportedPixelFormats( |
| display::DisplayId display_id) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| auto displays_it = displays_.find(display_id); |
| if (!displays_it.IsValid()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| const DisplayInfo& display_info = *displays_it; |
| |
| fbl::AllocChecker alloc_checker; |
| fbl::Vector<display::PixelFormat> pixel_formats; |
| pixel_formats.reserve(display_info.pixel_formats.size(), &alloc_checker); |
| if (!alloc_checker.check()) { |
| return zx::error(ZX_ERR_NO_MEMORY); |
| } |
| std::ranges::copy(display_info.pixel_formats, std::back_inserter(pixel_formats)); |
| ZX_DEBUG_ASSERT(pixel_formats.size() == display_info.pixel_formats.size()); |
| |
| return zx::ok(std::move(pixel_formats)); |
| } |
| |
| namespace { |
| |
| void PrintChannelKoids(ClientPriority client_priority, const zx::channel& channel) { |
| zx_info_handle_basic_t info{}; |
| size_t actual, avail; |
| zx_status_t status = channel.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), &actual, &avail); |
| if (status != ZX_OK || info.type != ZX_OBJ_TYPE_CHANNEL) { |
| fdf::debug("Could not get koids for handle(type={}): {}", info.type, status); |
| return; |
| } |
| ZX_DEBUG_ASSERT(actual == avail); |
| fdf::info("{} client connecting on channel (c=0x{:x}, s=0x{:x})", |
| DebugStringFromClientPriority(client_priority), info.related_koid, info.koid); |
| } |
| |
| } // namespace |
| |
| zx_status_t Controller::CreateClient( |
| ClientPriority client_priority, |
| fidl::ServerEnd<fidl_display::Coordinator> coordinator_server_end, |
| fidl::ClientEnd<fuchsia_hardware_display::CoordinatorListener> |
| coordinator_listener_client_end) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| PrintChannelKoids(client_priority, coordinator_server_end.channel()); |
| |
| fbl::AllocChecker alloc_checker; |
| auto post_task_state = fbl::make_unique_checked<DisplayTaskState>(&alloc_checker); |
| if (!alloc_checker.check()) { |
| fdf::debug("Failed to alloc client task"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| if (unbinding_) { |
| fdf::debug("Client connected during unbind"); |
| return ZX_ERR_UNAVAILABLE; |
| } |
| |
| if ((client_priority == ClientPriority::kVirtcon && virtcon_client_ != nullptr) || |
| (client_priority == ClientPriority::kPrimary && primary_client_ != nullptr)) { |
| fdf::debug("{} client already bound", DebugStringFromClientPriority(client_priority)); |
| return ZX_ERR_ALREADY_BOUND; |
| } |
| |
| ClientId client_id = next_client_id_; |
| ++next_client_id_; |
| auto client = std::make_unique<ClientProxy>(this, client_priority, client_id); |
| |
| zx_status_t status = client->Init(&root_, std::move(coordinator_server_end), |
| std::move(coordinator_listener_client_end)); |
| if (status != ZX_OK) { |
| fdf::debug("Failed to init client {}", status); |
| return status; |
| } |
| |
| ClientProxy* client_ptr = client.get(); |
| clients_.push_back(std::move(client)); |
| |
| fdf::debug("New {} client [{}] connected.", DebugStringFromClientPriority(client_priority), |
| client_ptr->client_id().value()); |
| |
| switch (client_priority) { |
| case ClientPriority::kVirtcon: |
| ZX_DEBUG_ASSERT(virtcon_client_ == nullptr); |
| ZX_DEBUG_ASSERT(!virtcon_client_ready_); |
| virtcon_client_ = client_ptr; |
| break; |
| case ClientPriority::kPrimary: |
| ZX_DEBUG_ASSERT(primary_client_ == nullptr); |
| ZX_DEBUG_ASSERT(!primary_client_ready_); |
| primary_client_ = client_ptr; |
| } |
| HandleClientOwnershipChanges(); |
| |
| zx::result<> post_task_result = display::PostTask( |
| std::move(post_task_state), *driver_dispatcher()->async_dispatcher(), [this, client_id]() { |
| if (unbinding_) { |
| return; |
| } |
| |
| ClientProxy* client_proxy; |
| if (virtcon_client_ != nullptr && virtcon_client_->client_id() == client_id) { |
| client_proxy = virtcon_client_; |
| } else if (primary_client_ != nullptr && primary_client_->client_id() == client_id) { |
| client_proxy = primary_client_; |
| } else { |
| return; |
| } |
| |
| // Add all existing displays to the client |
| if (displays_.size() > 0) { |
| display::DisplayId current_displays[displays_.size()]; |
| int initialized_display_count = 0; |
| for (const DisplayInfo& display : displays_) { |
| current_displays[initialized_display_count] = display.id(); |
| ++initialized_display_count; |
| } |
| std::span<display::DisplayId> removed_display_ids = {}; |
| client_proxy->OnDisplaysChanged( |
| std::span<display::DisplayId>(current_displays, initialized_display_count), |
| removed_display_ids); |
| } |
| |
| if (virtcon_client_ == client_proxy) { |
| ZX_DEBUG_ASSERT(!virtcon_client_ready_); |
| virtcon_client_ready_ = true; |
| } else { |
| ZX_DEBUG_ASSERT(primary_client_ == client_proxy); |
| ZX_DEBUG_ASSERT(!primary_client_ready_); |
| primary_client_ready_ = true; |
| } |
| }); |
| return post_task_result.status_value(); |
| } |
| |
| display::DriverBufferCollectionId Controller::GetNextDriverBufferCollectionId() { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| return next_driver_buffer_collection_id_++; |
| } |
| |
| void Controller::OpenCoordinatorWithListenerForVirtcon( |
| OpenCoordinatorWithListenerForVirtconRequestView request, |
| OpenCoordinatorWithListenerForVirtconCompleter::Sync& completer) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| ZX_DEBUG_ASSERT(request->has_coordinator()); |
| ZX_DEBUG_ASSERT(request->has_coordinator_listener()); |
| |
| zx_status_t create_status = |
| CreateClient(ClientPriority::kVirtcon, std::move(request->coordinator()), |
| std::move(request->coordinator_listener())); |
| if (create_status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(create_status); |
| } |
| } |
| |
| void Controller::OpenCoordinatorWithListenerForPrimary( |
| OpenCoordinatorWithListenerForPrimaryRequestView request, |
| OpenCoordinatorWithListenerForPrimaryCompleter::Sync& completer) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| ZX_DEBUG_ASSERT(request->has_coordinator()); |
| ZX_DEBUG_ASSERT(request->has_coordinator_listener()); |
| |
| zx_status_t create_status = |
| CreateClient(ClientPriority::kPrimary, std::move(request->coordinator()), |
| std::move(request->coordinator_listener())); |
| if (create_status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(create_status); |
| } |
| } |
| |
| // static |
| zx::result<std::unique_ptr<Controller>> Controller::Create( |
| std::unique_ptr<EngineDriverClient> engine_driver_client, |
| fdf::UnownedSynchronizedDispatcher driver_dispatcher) { |
| fbl::AllocChecker alloc_checker; |
| |
| auto controller = fbl::make_unique_checked<Controller>( |
| &alloc_checker, std::move(engine_driver_client), std::move(driver_dispatcher)); |
| if (!alloc_checker.check()) { |
| fdf::error("Failed to allocate memory for Controller"); |
| return zx::error(ZX_ERR_NO_MEMORY); |
| } |
| |
| zx::result<> initialize_result = controller->Initialize(); |
| if (initialize_result.is_error()) { |
| fdf::error("Failed to initialize the Controller device: {}", initialize_result); |
| return initialize_result.take_error(); |
| } |
| |
| return zx::ok(std::move(controller)); |
| } |
| |
| zx::result<> Controller::Initialize() { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| |
| zx::result<> vsync_monitor_init_result = vsync_monitor_.Initialize(); |
| if (vsync_monitor_init_result.is_error()) { |
| // VsyncMonitor::Init() logged the error. |
| return vsync_monitor_init_result.take_error(); |
| } |
| |
| auto [fidl_listener_client, fidl_listener_server] = |
| fdf::Endpoints<fuchsia_hardware_display_engine::EngineListener>::Create(); |
| engine_listener_fidl_adapter_.CreateHandler()(std::move(fidl_listener_server)); |
| |
| engine_info_ = |
| engine_driver_client_->CompleteCoordinatorConnection(std::move(fidl_listener_client)); |
| fdf::info("Engine capabilities - max layers: {}, max displays: {}, display capture: {}", |
| engine_info_->max_layer_count(), engine_info_->max_connected_display_count(), |
| engine_info_->is_capture_supported() ? "yes" : "no"); |
| |
| return zx::ok(); |
| } |
| |
| void Controller::PrepareStop() { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| fdf::info("Controller::PrepareStop started"); |
| |
| { |
| unbinding_ = true; |
| |
| // Tear down all existing clients. This ensures that all clients will not |
| // send `ImportImage()` and `ApplyConfiguration()` requests. |
| for (auto& client : clients_) { |
| client->TearDown(); |
| } |
| |
| vsync_monitor_.Deinitialize(); |
| |
| // Once this call completes, the engine driver will no longer send events. |
| // This means it's safe to stop keeping track of imported resources. |
| engine_driver_client_->UnsetListener(); |
| |
| // Dispose of all images without calling ReleaseImage(). |
| for (DisplayInfo& display : displays_) { |
| while (fbl::RefPtr<Image> displayed_image = display.images.pop_front()) { |
| displayed_image->MarkDisposed(); |
| } |
| } |
| } |
| |
| fdf::info("Controller::PrepareStop finished"); |
| } |
| |
| Controller::Controller(std::unique_ptr<EngineDriverClient> engine_driver_client, |
| fdf::UnownedSynchronizedDispatcher driver_dispatcher) |
| : root_(inspector_.GetRoot().CreateChild("display")), |
| driver_dispatcher_(std::move(driver_dispatcher)), |
| engine_listener_fidl_adapter_(this, driver_dispatcher_->borrow()), |
| vsync_monitor_(root_.CreateChild("vsync_monitor"), driver_dispatcher_->async_dispatcher()), |
| engine_driver_client_(std::move(engine_driver_client)) { |
| ZX_DEBUG_ASSERT(IsRunningOnDriverDispatcher()); |
| ZX_DEBUG_ASSERT(engine_driver_client_ != nullptr); |
| |
| last_valid_apply_config_timestamp_ns_property_ = |
| root_.CreateUint("last_valid_apply_config_timestamp_ns", 0); |
| last_valid_apply_config_interval_ns_property_ = |
| root_.CreateUint("last_valid_apply_config_interval_ns", 0); |
| last_valid_apply_config_config_stamp_property_ = |
| root_.CreateUint("last_valid_apply_config_stamp", display::kInvalidConfigStamp.value()); |
| } |
| |
| Controller::~Controller() { fdf::info("Controller::~Controller"); } |
| |
| } // namespace display_coordinator |