| // 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/fake/fake-display.h" |
| |
| #include <fidl/fuchsia.hardware.sysmem/cpp/fidl.h> |
| #include <fidl/fuchsia.sysmem/cpp/fidl.h> |
| #include <fidl/fuchsia.sysmem2/cpp/fidl.h> |
| #include <fuchsia/hardware/display/controller/cpp/banjo.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/fzl/vmo-mapper.h> |
| #include <lib/inspect/cpp/inspector.h> |
| #include <lib/zx/channel.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/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/threads.h> |
| #include <zircon/types.h> |
| |
| #include <array> |
| #include <atomic> |
| #include <cinttypes> |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstring> |
| #include <iterator> |
| #include <limits> |
| #include <string> |
| #include <utility> |
| |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/vector.h> |
| |
| #include "src/graphics/display/drivers/coordinator/preferred-scanout-image-type.h" |
| #include "src/graphics/display/drivers/fake/image-info.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/display-timing.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/driver-image-id.h" |
| #include "src/lib/fsl/handles/object_info.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace fake_display { |
| |
| namespace { |
| // List of supported pixel formats |
| constexpr fuchsia_images2_pixel_format_enum_value_t kSupportedPixelFormats[] = { |
| static_cast<fuchsia_images2_pixel_format_enum_value_t>( |
| fuchsia_images2::wire::PixelFormat::kB8G8R8A8), |
| static_cast<fuchsia_images2_pixel_format_enum_value_t>( |
| fuchsia_images2::wire::PixelFormat::kR8G8B8A8), |
| }; |
| // Arbitrary dimensions - the same as sherlock. |
| constexpr uint32_t kWidth = 1280; |
| constexpr uint32_t kHeight = 800; |
| |
| constexpr display::DisplayId kDisplayId(1); |
| |
| constexpr uint32_t kRefreshRateFps = 60; |
| // Arbitrary slowdown for testing purposes |
| // TODO(payamm): Randomizing the delay value is more value |
| constexpr uint64_t kNumOfVsyncsForCapture = 5; // 5 * 16ms = 80ms |
| } // namespace |
| |
| FakeDisplay::FakeDisplay(FakeDisplayDeviceConfig device_config, |
| fidl::ClientEnd<fuchsia_sysmem::Allocator> sysmem_allocator, |
| inspect::Inspector inspector) |
| : display_controller_impl_banjo_protocol_({&display_controller_impl_protocol_ops_, this}), |
| device_config_(device_config), |
| sysmem_(std::move(sysmem_allocator)), |
| inspector_(std::move(inspector)) {} |
| |
| FakeDisplay::~FakeDisplay() { Deinitialize(); } |
| |
| zx_status_t FakeDisplay::DisplayControllerImplSetMinimumRgb(uint8_t minimum_rgb) { |
| fbl::AutoLock lock(&capture_mutex_); |
| |
| clamp_rgb_value_ = minimum_rgb; |
| return ZX_OK; |
| } |
| |
| void FakeDisplay::PopulateAddedDisplayArgs(added_display_args_t* args) { |
| args->display_id = display::ToBanjoDisplayId(kDisplayId); |
| args->panel_capabilities_source = PANEL_CAPABILITIES_SOURCE_DISPLAY_MODE; |
| |
| const int32_t pixel_clock_hz = kWidth * kHeight * kRefreshRateFps; |
| ZX_DEBUG_ASSERT(pixel_clock_hz >= 0); |
| ZX_DEBUG_ASSERT(pixel_clock_hz <= display::kMaxPixelClockHz); |
| |
| const display::DisplayTiming timing = { |
| .horizontal_active_px = static_cast<int32_t>(kWidth), |
| .horizontal_front_porch_px = 0, |
| .horizontal_sync_width_px = 0, |
| .horizontal_back_porch_px = 0, |
| .vertical_active_lines = static_cast<int32_t>(kHeight), |
| .vertical_front_porch_lines = 0, |
| .vertical_sync_width_lines = 0, |
| .vertical_back_porch_lines = 0, |
| .pixel_clock_frequency_hz = pixel_clock_hz, |
| .fields_per_frame = display::FieldsPerFrame::kProgressive, |
| .hsync_polarity = display::SyncPolarity::kNegative, |
| .vsync_polarity = display::SyncPolarity::kNegative, |
| .vblank_alternates = false, |
| .pixel_repetition = 0, |
| }; |
| args->panel.mode = display::ToBanjoDisplayMode(timing); |
| args->pixel_format_list = kSupportedPixelFormats; |
| args->pixel_format_count = std::size(kSupportedPixelFormats); |
| } |
| |
| zx_status_t FakeDisplay::InitSysmemAllocatorClient() { |
| std::string debug_name = fxl::StringPrintf("fake-display[%lu]", fsl::GetCurrentProcessKoid()); |
| fuchsia_sysmem::AllocatorSetDebugClientInfoRequest request; |
| request.name() = std::move(debug_name); |
| request.id() = fsl::GetCurrentProcessKoid(); |
| auto set_debug_status = sysmem_->SetDebugClientInfo(std::move(request)); |
| if (!set_debug_status.is_ok()) { |
| zxlogf(ERROR, "Cannot set sysmem allocator debug info: %s", |
| set_debug_status.error_value().status_string()); |
| return set_debug_status.error_value().status(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| void FakeDisplay::DisplayControllerImplSetDisplayControllerInterface( |
| const display_controller_interface_protocol_t* intf) { |
| fbl::AutoLock interface_lock(&interface_mutex_); |
| controller_interface_client_ = ddk::DisplayControllerInterfaceProtocolClient(intf); |
| added_display_args_t args; |
| PopulateAddedDisplayArgs(&args); |
| controller_interface_client_.OnDisplaysChanged(&args, 1, nullptr, 0); |
| } |
| |
| void FakeDisplay::DisplayControllerImplResetDisplayControllerInterface() { |
| fbl::AutoLock interface_lock(&interface_mutex_); |
| controller_interface_client_ = ddk::DisplayControllerInterfaceProtocolClient(); |
| } |
| |
| zx::result<display::DriverImageId> FakeDisplay::ImportVmoImageForTesting(zx::vmo vmo, |
| size_t offset) { |
| fbl::AllocChecker alloc_checker; |
| fbl::AutoLock lock(&image_mutex_); |
| |
| display::DriverImageId driver_image_id = next_imported_display_driver_image_id_++; |
| // Image metadata for testing only and may not reflect the actual image |
| // buffer format. |
| ImageMetadata display_image_metadata = { |
| .pixel_format = fuchsia_sysmem::wire::PixelFormatType::kBgra32, |
| .coherency_domain = fuchsia_sysmem::wire::CoherencyDomain::kCpu, |
| }; |
| |
| auto import_info = fbl::make_unique_checked<DisplayImageInfo>( |
| &alloc_checker, driver_image_id, display_image_metadata, std::move(vmo)); |
| if (!alloc_checker.check()) { |
| return zx::error(ZX_ERR_NO_MEMORY); |
| } |
| |
| imported_images_.insert(std::move(import_info)); |
| return zx::ok(driver_image_id); |
| } |
| |
| namespace { |
| |
| bool IsAcceptableImageTilingType(uint32_t image_tiling_type) { |
| return image_tiling_type == IMAGE_TILING_TYPE_PREFERRED_SCANOUT || |
| image_tiling_type == IMAGE_TILING_TYPE_LINEAR; |
| } |
| |
| } // namespace |
| |
| zx_status_t FakeDisplay::DisplayControllerImplImportBufferCollection( |
| uint64_t banjo_driver_buffer_collection_id, zx::channel collection_token) { |
| const display::DriverBufferCollectionId driver_buffer_collection_id = |
| display::ToDriverBufferCollectionId(banjo_driver_buffer_collection_id); |
| if (buffer_collections_.find(driver_buffer_collection_id) != buffer_collections_.end()) { |
| zxlogf(ERROR, "Buffer Collection (id=%lu) already exists", driver_buffer_collection_id.value()); |
| return ZX_ERR_ALREADY_EXISTS; |
| } |
| |
| auto [collection_client_endpoint, collection_server_endpoint] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| |
| fuchsia_sysmem::AllocatorBindSharedCollectionRequest bind_request; |
| bind_request.token() = |
| fidl::ClientEnd<fuchsia_sysmem::BufferCollectionToken>(std::move(collection_token)); |
| bind_request.buffer_collection_request() = std::move(collection_server_endpoint); |
| auto bind_result = sysmem_->BindSharedCollection(std::move(bind_request)); |
| if (bind_result.is_error()) { |
| zxlogf(ERROR, "Cannot complete FIDL call BindSharedCollection: %s", |
| bind_result.error_value().status_string()); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| buffer_collections_[driver_buffer_collection_id] = |
| fidl::SyncClient(std::move(collection_client_endpoint)); |
| return ZX_OK; |
| } |
| |
| zx_status_t FakeDisplay::DisplayControllerImplReleaseBufferCollection( |
| uint64_t banjo_driver_buffer_collection_id) { |
| const display::DriverBufferCollectionId driver_buffer_collection_id = |
| display::ToDriverBufferCollectionId(banjo_driver_buffer_collection_id); |
| if (buffer_collections_.find(driver_buffer_collection_id) == buffer_collections_.end()) { |
| zxlogf(ERROR, "Cannot release buffer collection %lu: buffer collection doesn't exist", |
| driver_buffer_collection_id.value()); |
| return ZX_ERR_NOT_FOUND; |
| } |
| buffer_collections_.erase(driver_buffer_collection_id); |
| return ZX_OK; |
| } |
| |
| zx_status_t FakeDisplay::DisplayControllerImplImportImage( |
| const image_metadata_t* image_metadata, uint64_t banjo_driver_buffer_collection_id, |
| uint32_t index, uint64_t* out_image_handle) { |
| const display::DriverBufferCollectionId driver_buffer_collection_id = |
| display::ToDriverBufferCollectionId(banjo_driver_buffer_collection_id); |
| const auto it = buffer_collections_.find(driver_buffer_collection_id); |
| if (it == buffer_collections_.end()) { |
| zxlogf(ERROR, "ImportImage: Cannot find imported buffer collection (id=%lu)", |
| driver_buffer_collection_id.value()); |
| return ZX_ERR_NOT_FOUND; |
| } |
| const fidl::SyncClient<fuchsia_sysmem::BufferCollection>& collection = it->second; |
| |
| fbl::AutoLock lock(&image_mutex_); |
| if (!IsAcceptableImageTilingType(image_metadata->tiling_type)) { |
| zxlogf(INFO, "ImportImage() will fail due to invalid Image tiling type %" PRIu32, |
| image_metadata->tiling_type); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fidl::Result check_result = collection->CheckBuffersAllocated(); |
| // TODO(https://fxbug.dev/42072690): The sysmem FIDL error logging patterns are |
| // inconsistent across drivers. The FIDL error handling and logging should be |
| // unified. |
| if (check_result.is_error()) { |
| return check_result.error_value().status(); |
| } |
| const auto& check_response = check_result.value(); |
| if (check_response.status() == ZX_ERR_UNAVAILABLE) { |
| return ZX_ERR_SHOULD_WAIT; |
| } |
| if (check_response.status() != ZX_OK) { |
| return check_response.status(); |
| } |
| |
| fidl::Result wait_result = collection->WaitForBuffersAllocated(); |
| // TODO(https://fxbug.dev/42072690): The sysmem FIDL error logging patterns are |
| // inconsistent across drivers. The FIDL error handling and logging should be |
| // unified. |
| if (wait_result.is_error()) { |
| return wait_result.error_value().status(); |
| } |
| auto& wait_response = wait_result.value(); |
| if (wait_response.status() != ZX_OK) { |
| return wait_response.status(); |
| } |
| auto& collection_info = wait_response.buffer_collection_info(); |
| |
| fbl::Vector<zx::vmo> vmos; |
| for (uint32_t i = 0; i < collection_info.buffer_count(); ++i) { |
| vmos.push_back(std::move(collection_info.buffers()[i].vmo())); |
| } |
| |
| if (!collection_info.settings().has_image_format_constraints() || index >= vmos.size()) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| // TODO(https://fxbug.dev/42079320): When capture is enabled |
| // (IsCaptureSupported() is true), we should perform a check to ensure that |
| // the display images should not be of "inaccessible" coherency domain. |
| |
| display::DriverImageId driver_image_id = next_imported_display_driver_image_id_++; |
| ImageMetadata display_image_metadata = { |
| .pixel_format = collection_info.settings().image_format_constraints().pixel_format().type(), |
| .coherency_domain = collection_info.settings().buffer_settings().coherency_domain(), |
| }; |
| |
| fbl::AllocChecker alloc_checker; |
| auto import_info = fbl::make_unique_checked<DisplayImageInfo>( |
| &alloc_checker, driver_image_id, std::move(display_image_metadata), std::move(vmos[index])); |
| if (!alloc_checker.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| *out_image_handle = display::ToBanjoDriverImageId(driver_image_id); |
| imported_images_.insert(std::move(import_info)); |
| return ZX_OK; |
| } |
| |
| void FakeDisplay::DisplayControllerImplReleaseImage(uint64_t image_handle) { |
| fbl::AutoLock lock(&image_mutex_); |
| display::DriverImageId driver_image_id = display::ToDriverImageId(image_handle); |
| if (imported_images_.erase(driver_image_id) == nullptr) { |
| zxlogf(ERROR, "Failed to release display Image (handle %" PRIu64 ")", driver_image_id.value()); |
| } |
| } |
| |
| config_check_result_t FakeDisplay::DisplayControllerImplCheckConfiguration( |
| const display_config_t* display_configs, size_t display_count, |
| client_composition_opcode_t* out_client_composition_opcodes_list, |
| size_t client_composition_opcodes_count, size_t* out_client_composition_opcodes_actual) { |
| if (out_client_composition_opcodes_actual != nullptr) { |
| *out_client_composition_opcodes_actual = 0; |
| } |
| |
| if (display_count != 1) { |
| ZX_DEBUG_ASSERT(display_count == 0); |
| return CONFIG_CHECK_RESULT_OK; |
| } |
| ZX_DEBUG_ASSERT(display::ToDisplayId(display_configs[0].display_id) == kDisplayId); |
| |
| ZX_DEBUG_ASSERT(client_composition_opcodes_count >= display_configs[0].layer_count); |
| cpp20::span<client_composition_opcode_t> client_composition_opcodes( |
| out_client_composition_opcodes_list, display_configs[0].layer_count); |
| std::fill(client_composition_opcodes.begin(), client_composition_opcodes.end(), 0); |
| if (out_client_composition_opcodes_actual != nullptr) { |
| *out_client_composition_opcodes_actual = client_composition_opcodes.size(); |
| } |
| |
| bool success; |
| if (display_configs[0].layer_count != 1) { |
| success = display_configs[0].layer_count == 0; |
| } else { |
| const primary_layer_t& layer = display_configs[0].layer_list[0].cfg.primary; |
| frame_t frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = kWidth, |
| .height = kHeight, |
| }; |
| success = display_configs[0].layer_list[0].type == LAYER_TYPE_PRIMARY && |
| layer.transform_mode == FRAME_TRANSFORM_IDENTITY && |
| layer.image_metadata.width == kWidth && layer.image_metadata.height == kHeight && |
| memcmp(&layer.dest_frame, &frame, sizeof(frame_t)) == 0 && |
| memcmp(&layer.src_frame, &frame, sizeof(frame_t)) == 0 && |
| layer.alpha_mode == ALPHA_DISABLE; |
| } |
| if (!success) { |
| client_composition_opcodes[0] = CLIENT_COMPOSITION_OPCODE_MERGE_BASE; |
| for (unsigned i = 1; i < display_configs[0].layer_count; i++) { |
| client_composition_opcodes[i] = CLIENT_COMPOSITION_OPCODE_MERGE_SRC; |
| } |
| } |
| return CONFIG_CHECK_RESULT_OK; |
| } |
| |
| void FakeDisplay::DisplayControllerImplApplyConfiguration( |
| const display_config_t* display_configs, size_t display_count, |
| const config_stamp_t* banjo_config_stamp) { |
| ZX_DEBUG_ASSERT(display_configs); |
| ZX_DEBUG_ASSERT(banjo_config_stamp != nullptr); |
| { |
| fbl::AutoLock lock(&image_mutex_); |
| if (display_count == 1 && display_configs[0].layer_count) { |
| // Only support one display. |
| current_image_to_capture_id_ = |
| display::ToDriverImageId(display_configs[0].layer_list[0].cfg.primary.image_handle); |
| } else { |
| current_image_to_capture_id_ = display::kInvalidDriverImageId; |
| } |
| } |
| |
| // The `current_config_stamp_` is stored by ApplyConfiguration() on the |
| // display coordinator's controller loop thread, and loaded only by the |
| // driver Vsync thread to notify coordinator for a new frame being displayed. |
| // After that, captures will be triggered on the controller loop thread by |
| // StartCapture(), which is synchronized with the capture thread (via mutex). |
| // |
| // Thus, for `current_config_stamp_`, there's no need for acquire-release |
| // memory model (to synchronize `current_image_to_capture_` changes between |
| // controller loop thread and Vsync thread), and relaxed memory order should |
| // be sufficient to guarantee that both value changes in ApplyConfiguration() |
| // will be visible to the capture thread for captures triggered after the |
| // Vsync with this config stamp. |
| // |
| // As long as a client requests a capture after it sees the Vsync event of a |
| // given config, the captured contents can only be contents applied no earlier |
| // than that config (which can be that config itself, or a config applied |
| // after that config). |
| const display::ConfigStamp config_stamp = display::ToConfigStamp(*banjo_config_stamp); |
| current_config_stamp_.store(config_stamp, std::memory_order_relaxed); |
| } |
| |
| void FakeDisplay::DisplayControllerImplSetEld(uint64_t display_id, const uint8_t* raw_eld_list, |
| size_t raw_eld_count) {} |
| |
| enum class FakeDisplay::BufferCollectionUsage { |
| kPrimaryLayer = 1, |
| kCapture = 2, |
| }; |
| |
| fuchsia_sysmem::BufferCollectionConstraints FakeDisplay::CreateBufferCollectionConstraints( |
| BufferCollectionUsage usage) { |
| fuchsia_sysmem::BufferCollectionConstraints constraints; |
| switch (usage) { |
| case BufferCollectionUsage::kCapture: |
| constraints.usage().cpu() = |
| fuchsia_sysmem::kCpuUsageReadOften | fuchsia_sysmem::kCpuUsageWriteOften; |
| break; |
| case BufferCollectionUsage::kPrimaryLayer: |
| constraints.usage().display() = fuchsia_sysmem::kDisplayUsageLayer; |
| break; |
| } |
| |
| // TODO(https://fxbug.dev/42079320): In order to support capture, both capture sources |
| // and capture targets must not be in the "inaccessible" coherency domain. |
| constraints.has_buffer_memory_constraints() = true; |
| SetBufferMemoryConstraints(constraints.buffer_memory_constraints()); |
| |
| // When we have C++20, we can use std::to_array to avoid specifying the array |
| // size twice. |
| static constexpr std::array<fuchsia_sysmem::PixelFormatType, 2> kPixelFormats = { |
| fuchsia_sysmem::PixelFormatType::kR8G8B8A8, fuchsia_sysmem::PixelFormatType::kBgra32}; |
| static constexpr std::array<uint64_t, 2> kFormatModifiers = { |
| fuchsia_sysmem::kFormatModifierLinear, fuchsia_sysmem::kFormatModifierGoogleGoldfishOptimal}; |
| |
| size_t format_constraints_count = 0; |
| for (fuchsia_sysmem::PixelFormatType pixel_format : kPixelFormats) { |
| for (uint64_t format_modifier : kFormatModifiers) { |
| fuchsia_sysmem::ImageFormatConstraints& image_constraints = |
| constraints.image_format_constraints()[format_constraints_count]; |
| ++format_constraints_count; |
| |
| SetCommonImageFormatConstraints(pixel_format, fuchsia_sysmem::FormatModifier(format_modifier), |
| image_constraints); |
| switch (usage) { |
| case BufferCollectionUsage::kCapture: |
| SetCaptureImageFormatConstraints(image_constraints); |
| break; |
| case BufferCollectionUsage::kPrimaryLayer: |
| SetLayerImageFormatConstraints(image_constraints); |
| break; |
| } |
| } |
| } |
| ZX_DEBUG_ASSERT(format_constraints_count <= std::numeric_limits<uint32_t>::max()); |
| constraints.image_format_constraints_count() = static_cast<uint32_t>(format_constraints_count); |
| return constraints; |
| } |
| |
| void FakeDisplay::SetBufferMemoryConstraints(fuchsia_sysmem::BufferMemoryConstraints& constraints) { |
| constraints.min_size_bytes() = 0; |
| constraints.max_size_bytes() = std::numeric_limits<uint32_t>::max(); |
| constraints.physically_contiguous_required() = false; |
| constraints.secure_required() = false; |
| constraints.ram_domain_supported() = true; |
| constraints.cpu_domain_supported() = true; |
| constraints.inaccessible_domain_supported() = true; |
| } |
| |
| void FakeDisplay::SetCommonImageFormatConstraints( |
| fuchsia_sysmem::PixelFormatType pixel_format_type, |
| fuchsia_sysmem::FormatModifier format_modifier, |
| fuchsia_sysmem::ImageFormatConstraints& constraints) { |
| constraints.pixel_format().type() = pixel_format_type; |
| constraints.pixel_format().has_format_modifier() = true; |
| constraints.pixel_format().format_modifier() = format_modifier; |
| |
| constraints.color_spaces_count() = 1; |
| constraints.color_space()[0].type() = fuchsia_sysmem::ColorSpaceType::kSrgb; |
| |
| constraints.layers() = 1; |
| constraints.coded_width_divisor() = 1; |
| constraints.coded_height_divisor() = 1; |
| constraints.bytes_per_row_divisor() = 1; |
| constraints.start_offset_divisor() = 1; |
| constraints.display_width_divisor() = 1; |
| constraints.display_height_divisor() = 1; |
| } |
| |
| void FakeDisplay::SetCaptureImageFormatConstraints( |
| fuchsia_sysmem::ImageFormatConstraints& constraints) { |
| constraints.min_coded_width() = kWidth; |
| constraints.max_coded_width() = kWidth; |
| constraints.min_coded_height() = kHeight; |
| constraints.max_coded_height() = kHeight; |
| constraints.min_bytes_per_row() = kWidth * 4; |
| constraints.max_bytes_per_row() = kWidth * 4; |
| constraints.max_coded_width_times_coded_height() = kWidth * kHeight; |
| } |
| |
| void FakeDisplay::SetLayerImageFormatConstraints( |
| fuchsia_sysmem::ImageFormatConstraints& constraints) { |
| constraints.min_coded_width() = 0; |
| constraints.max_coded_width() = std::numeric_limits<uint32_t>::max(); |
| constraints.min_coded_height() = 0; |
| constraints.max_coded_height() = std::numeric_limits<uint32_t>::max(); |
| constraints.min_bytes_per_row() = 0; |
| constraints.max_bytes_per_row() = std::numeric_limits<uint32_t>::max(); |
| constraints.max_coded_width_times_coded_height() = std::numeric_limits<uint32_t>::max(); |
| } |
| |
| zx_status_t FakeDisplay::DisplayControllerImplSetBufferCollectionConstraints( |
| const image_buffer_usage_t* usage, uint64_t banjo_driver_buffer_collection_id) { |
| const display::DriverBufferCollectionId driver_buffer_collection_id = |
| display::ToDriverBufferCollectionId(banjo_driver_buffer_collection_id); |
| const auto it = buffer_collections_.find(driver_buffer_collection_id); |
| if (it == buffer_collections_.end()) { |
| zxlogf(ERROR, "ImportImage: Cannot find imported buffer collection (id=%lu)", |
| driver_buffer_collection_id.value()); |
| return ZX_ERR_NOT_FOUND; |
| } |
| const fidl::SyncClient<fuchsia_sysmem::BufferCollection>& collection = it->second; |
| |
| BufferCollectionUsage buffer_collection_usage = (usage->tiling_type == IMAGE_TILING_TYPE_CAPTURE) |
| ? BufferCollectionUsage::kCapture |
| : BufferCollectionUsage::kPrimaryLayer; |
| auto set_result = collection->SetConstraints( |
| {true, CreateBufferCollectionConstraints(buffer_collection_usage)}); |
| if (set_result.is_error()) { |
| zxlogf(ERROR, "Failed to set constraints on a sysmem BufferCollection: %s", |
| set_result.error_value().status_string()); |
| return set_result.error_value().status(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t FakeDisplay::DisplayControllerImplSetDisplayPower(uint64_t display_id, bool power_on) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t FakeDisplay::DisplayControllerImplImportImageForCapture( |
| uint64_t banjo_driver_buffer_collection_id, uint32_t index, uint64_t* out_capture_handle) { |
| if (!IsCaptureSupported()) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| const display::DriverBufferCollectionId driver_buffer_collection_id = |
| display::ToDriverBufferCollectionId(banjo_driver_buffer_collection_id); |
| const auto it = buffer_collections_.find(driver_buffer_collection_id); |
| if (it == buffer_collections_.end()) { |
| zxlogf(ERROR, "ImportImage: Cannot find imported buffer collection (id=%lu)", |
| driver_buffer_collection_id.value()); |
| return ZX_ERR_NOT_FOUND; |
| } |
| const fidl::SyncClient<fuchsia_sysmem::BufferCollection>& collection = it->second; |
| |
| fbl::AutoLock lock(&capture_mutex_); |
| |
| fidl::Result check_result = collection->CheckBuffersAllocated(); |
| // TODO(https://fxbug.dev/42072690): The sysmem FIDL error logging patterns are |
| // inconsistent across drivers. The FIDL error handling and logging should be |
| // unified. |
| if (check_result.is_error()) { |
| return check_result.error_value().status(); |
| } |
| const auto& check_response = check_result.value(); |
| if (check_response.status() == ZX_ERR_UNAVAILABLE) { |
| return ZX_ERR_SHOULD_WAIT; |
| } |
| if (check_response.status() != ZX_OK) { |
| return check_response.status(); |
| } |
| |
| fidl::Result wait_result = collection->WaitForBuffersAllocated(); |
| // TODO(https://fxbug.dev/42072690): The sysmem FIDL error logging patterns are |
| // inconsistent across drivers. The FIDL error handling and logging should be |
| // unified. |
| if (wait_result.is_error()) { |
| return wait_result.error_value().status(); |
| } |
| auto& wait_response = wait_result.value(); |
| if (wait_response.status() != ZX_OK) { |
| return wait_response.status(); |
| } |
| fuchsia_sysmem::BufferCollectionInfo2& collection_info = wait_response.buffer_collection_info(); |
| |
| if (!collection_info.settings().has_image_format_constraints()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (index >= collection_info.buffer_count()) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| // TODO(https://fxbug.dev/42079320): Capture target images should not be of |
| // "inaccessible" coherency domain. We should add a check here. |
| display::DriverCaptureImageId driver_capture_image_id = next_imported_driver_capture_image_id_++; |
| ImageMetadata capture_image_metadata = { |
| .pixel_format = collection_info.settings().image_format_constraints().pixel_format().type(), |
| .coherency_domain = collection_info.settings().buffer_settings().coherency_domain(), |
| }; |
| zx::vmo vmo = std::move(collection_info.buffers()[index].vmo()); |
| |
| fbl::AllocChecker alloc_checker; |
| auto capture_image_info = fbl::make_unique_checked<CaptureImageInfo>( |
| &alloc_checker, driver_capture_image_id, std::move(capture_image_metadata), std::move(vmo)); |
| if (!alloc_checker.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| *out_capture_handle = display::ToBanjoDriverCaptureImageId(driver_capture_image_id); |
| imported_captures_.insert(std::move(capture_image_info)); |
| return ZX_OK; |
| } |
| |
| bool FakeDisplay::DisplayControllerImplIsCaptureSupported() { return IsCaptureSupported(); } |
| |
| zx_status_t FakeDisplay::DisplayControllerImplStartCapture(uint64_t capture_handle) { |
| if (!IsCaptureSupported()) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| fbl::AutoLock lock(&capture_mutex_); |
| if (current_capture_target_image_id_ != display::kInvalidDriverCaptureImageId) { |
| return ZX_ERR_SHOULD_WAIT; |
| } |
| |
| // Confirm the handle was previously imported (hence valid) |
| display::DriverCaptureImageId driver_capture_image_id = |
| display::ToDriverCaptureImageId(capture_handle); |
| auto it = imported_captures_.find(driver_capture_image_id); |
| if (it == imported_captures_.end()) { |
| // invalid handle |
| return ZX_ERR_INVALID_ARGS; |
| } |
| current_capture_target_image_id_ = driver_capture_image_id; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t FakeDisplay::DisplayControllerImplReleaseCapture(uint64_t capture_handle) { |
| if (!IsCaptureSupported()) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| fbl::AutoLock lock(&capture_mutex_); |
| display::DriverCaptureImageId driver_capture_image_id = |
| display::ToDriverCaptureImageId(capture_handle); |
| if (current_capture_target_image_id_ == driver_capture_image_id) { |
| return ZX_ERR_SHOULD_WAIT; |
| } |
| |
| if (imported_captures_.erase(driver_capture_image_id) == nullptr) { |
| // invalid handle |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ZX_OK; |
| } |
| |
| bool FakeDisplay::DisplayControllerImplIsCaptureCompleted() { |
| fbl::AutoLock lock(&capture_mutex_); |
| return current_capture_target_image_id_ == display::kInvalidDriverCaptureImageId; |
| } |
| |
| zx_status_t FakeDisplay::SetupDisplayInterface() { |
| { |
| fbl::AutoLock image_lock(&image_mutex_); |
| current_image_to_capture_id_ = display::kInvalidDriverImageId; |
| } |
| |
| { |
| fbl::AutoLock interface_lock(&interface_mutex_); |
| if (controller_interface_client_.is_valid()) { |
| added_display_args_t args; |
| PopulateAddedDisplayArgs(&args); |
| |
| controller_interface_client_.OnDisplaysChanged(&args, /*added_display_count=*/1, |
| /*removed_display_list=*/nullptr, |
| /*removed_display_count=*/0); |
| } |
| } |
| |
| if (IsCaptureSupported()) { |
| fbl::AutoLock capture_lock(&capture_mutex_); |
| current_capture_target_image_id_ = display::kInvalidDriverCaptureImageId; |
| } |
| return ZX_OK; |
| } |
| |
| bool FakeDisplay::IsCaptureSupported() const { return !device_config_.no_buffer_access; } |
| |
| int FakeDisplay::CaptureThread() { |
| ZX_DEBUG_ASSERT(IsCaptureSupported()); |
| while (true) { |
| zx::nanosleep(zx::deadline_after(zx::sec(1) / kRefreshRateFps)); |
| if (capture_shutdown_flag_.load()) { |
| break; |
| } |
| { |
| fbl::AutoLock interface_lock_(&interface_mutex_); |
| // `current_capture_target_image_` is a pointer to DisplayImageInfo stored in |
| // `imported_captures_` (guarded by `capture_mutex_`). So `capture_mutex_` |
| // must be locked while the capture image is being used. |
| fbl::AutoLock capture_lock(&capture_mutex_); |
| if (controller_interface_client_.is_valid() && |
| (current_capture_target_image_id_ != display::kInvalidDriverCaptureImageId) && |
| ++capture_complete_signal_count_ >= kNumOfVsyncsForCapture) { |
| { |
| auto dst = imported_captures_.find(current_capture_target_image_id_); |
| |
| // `dst` should be always valid. |
| // |
| // The current capture target image is guaranteed to be valid in |
| // `imported_captures_` in StartCapture(), and can only be released |
| // (i.e. removed from `imported_captures_`) after the active capture |
| // is done. |
| ZX_DEBUG_ASSERT(dst.IsValid()); |
| |
| // `current_image_to_capture_id_` is a key to DisplayImageInfo stored |
| // in `imported_images_` (guarded by `image_mutex_`). So `image_mutex_` |
| // must be locked while the source image is being used. |
| fbl::AutoLock image_lock(&image_mutex_); |
| if (current_image_to_capture_id_ != display::kInvalidDriverImageId) { |
| // We have a valid image being displayed. Let's capture it. |
| auto src = imported_images_.find(current_image_to_capture_id_); |
| |
| // `src` should be always valid. |
| // |
| // The "current image to capture" is guaranteed to be valid in |
| // `imported_images_` in ApplyConfig(), and can only be released |
| // (i.e. removed from `imported_images_`) after there's an vsync |
| // not containing the image anymore. |
| ZX_ASSERT(src.IsValid()); |
| |
| if (src->metadata().pixel_format != dst->metadata().pixel_format) { |
| zxlogf(ERROR, "Trying to capture format=%u as format=%u\n", |
| static_cast<uint32_t>(src->metadata().pixel_format), |
| static_cast<uint32_t>(dst->metadata().pixel_format)); |
| continue; |
| } |
| size_t src_vmo_size; |
| auto status = src->vmo().get_size(&src_vmo_size); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to get the size of the displayed image VMO: %s", |
| zx_status_get_string(status)); |
| continue; |
| } |
| size_t dst_vmo_size; |
| status = dst->vmo().get_size(&dst_vmo_size); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to get the size of the VMO for the captured image: %s", |
| zx_status_get_string(status)); |
| continue; |
| } |
| if (dst_vmo_size != src_vmo_size) { |
| zxlogf(ERROR, |
| "Capture will fail; the displayed image VMO size %zu does not match the " |
| "captured image VMO size %zu", |
| src_vmo_size, dst_vmo_size); |
| continue; |
| } |
| fzl::VmoMapper mapped_src; |
| status = mapped_src.Map(src->vmo(), 0, src_vmo_size, ZX_VM_PERM_READ); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Capture thread will exit; failed to map displayed image VMO: %s", |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| fzl::VmoMapper mapped_dst; |
| status = |
| mapped_dst.Map(dst->vmo(), 0, dst_vmo_size, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Capture thread will exit; failed to map capture image VMO: %s", |
| zx_status_get_string(status)); |
| return status; |
| } |
| if (src->metadata().coherency_domain == fuchsia_sysmem::wire::CoherencyDomain::kRam) { |
| zx_cache_flush(mapped_src.start(), src_vmo_size, |
| ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE); |
| } |
| std::memcpy(mapped_dst.start(), mapped_src.start(), dst_vmo_size); |
| if (dst->metadata().coherency_domain == fuchsia_sysmem::wire::CoherencyDomain::kRam) { |
| zx_cache_flush(mapped_dst.start(), dst_vmo_size, |
| ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE); |
| } |
| } |
| } |
| controller_interface_client_.OnCaptureComplete(); |
| current_capture_target_image_id_ = display::kInvalidDriverCaptureImageId; |
| capture_complete_signal_count_ = 0; |
| } |
| } |
| } |
| return ZX_OK; |
| } |
| |
| int FakeDisplay::VSyncThread() { |
| while (true) { |
| zx::nanosleep(zx::deadline_after(zx::sec(1) / kRefreshRateFps)); |
| if (vsync_shutdown_flag_.load()) { |
| break; |
| } |
| SendVsync(); |
| } |
| return ZX_OK; |
| } |
| |
| void FakeDisplay::SendVsync() { |
| fbl::AutoLock interface_lock(&interface_mutex_); |
| if (controller_interface_client_.is_valid()) { |
| // See the discussion in `DisplayControllerImplApplyConfiguration()` about |
| // the reason we use relaxed memory order here. |
| const display::ConfigStamp current_config_stamp = |
| current_config_stamp_.load(std::memory_order_relaxed); |
| const config_stamp_t banjo_current_config_stamp = |
| display::ToBanjoConfigStamp(current_config_stamp); |
| controller_interface_client_.OnDisplayVsync( |
| ToBanjoDisplayId(kDisplayId), zx_clock_get_monotonic(), &banjo_current_config_stamp); |
| } |
| } |
| |
| void FakeDisplay::RecordDisplayConfigToInspectRootNode() { |
| inspect::Node& root_node = inspector_.GetRoot(); |
| ZX_ASSERT(root_node); |
| root_node.RecordChild("device_config", [&](inspect::Node& config_node) { |
| config_node.RecordInt("width_px", kWidth); |
| config_node.RecordInt("height_px", kHeight); |
| config_node.RecordDouble("refresh_rate_hz", kRefreshRateFps); |
| config_node.RecordBool("manual_vsync_trigger", device_config_.manual_vsync_trigger); |
| config_node.RecordBool("no_buffer_access", device_config_.no_buffer_access); |
| }); |
| } |
| |
| zx_status_t FakeDisplay::Initialize() { |
| ZX_DEBUG_ASSERT(!initialized_); |
| |
| zx_status_t status = InitSysmemAllocatorClient(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to initialize sysmem Allocator client: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Setup Display Interface |
| status = SetupDisplayInterface(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "SetupDisplayInterface() failed: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| if (!device_config_.manual_vsync_trigger) { |
| status = thrd_status_to_zx_status(thrd_create_with_name( |
| &vsync_thread_, |
| [](void* context) { return static_cast<FakeDisplay*>(context)->VSyncThread(); }, this, |
| "vsync_thread")); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to create VSync thread: %s", zx_status_get_string(status)); |
| return status; |
| } |
| vsync_thread_running_ = true; |
| } |
| |
| if (IsCaptureSupported()) { |
| status = thrd_status_to_zx_status(thrd_create_with_name( |
| &capture_thread_, |
| [](void* context) { return static_cast<FakeDisplay*>(context)->CaptureThread(); }, this, |
| "capture_thread")); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to not create image capture thread: %s", zx_status_get_string(status)); |
| return status; |
| } |
| } |
| |
| RecordDisplayConfigToInspectRootNode(); |
| |
| initialized_ = true; |
| |
| return ZX_OK; |
| } |
| |
| void FakeDisplay::Deinitialize() { |
| if (!initialized_) { |
| return; |
| } |
| |
| vsync_shutdown_flag_.store(true); |
| if (vsync_thread_running_) { |
| // Ignore return value here in case the vsync_thread_ isn't running. |
| thrd_join(vsync_thread_, nullptr); |
| } |
| if (IsCaptureSupported()) { |
| capture_shutdown_flag_.store(true); |
| thrd_join(capture_thread_, nullptr); |
| } |
| |
| initialized_ = false; |
| } |
| |
| } // namespace fake_display |