blob: 7567b7f652029d2d62aa90dbf5039002cd514382 [file] [log] [blame]
// Copyright 2019 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 "display.h"
#include <fidl/fuchsia.hardware.goldfish/cpp/wire.h>
#include <fidl/fuchsia.sysmem/cpp/fidl.h>
#include <fuchsia/hardware/goldfish/control/cpp/banjo.h>
#include <lib/async/cpp/task.h>
#include <lib/async/cpp/time.h>
#include <lib/async/cpp/wait.h>
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/trace/event.h>
#include <lib/fidl/cpp/wire/channel.h>
#include <lib/fidl/cpp/wire/connect_service.h>
#include <lib/fit/defer.h>
#include <lib/image-format/image_format.h>
#include <lib/zircon-internal/align.h>
#include <zircon/status.h>
#include <zircon/threads.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <optional>
#include <sstream>
#include <vector>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include "fidl/fuchsia.sysmem/cpp/common_types.h"
#include "fidl/fuchsia.sysmem/cpp/markers.h"
#include "fidl/fuchsia.sysmem/cpp/natural_types.h"
#include "fuchsia/hardware/display/controller/c/banjo.h"
#include "lib/fidl/cpp/channel.h"
#include "lib/fidl/cpp/wire/internal/transport_channel.h"
#include "lib/fidl/cpp/wire/traits.h"
#include "src/devices/lib/goldfish/pipe_headers/include/base.h"
#include "src/graphics/display/drivers/goldfish-display/render_control.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/lib/fsl/handles/object_info.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace goldfish {
namespace {
const char* kTag = "goldfish-display";
constexpr display::DisplayId kPrimaryDisplayId(1);
constexpr fuchsia_images2_pixel_format_enum_value_t kPixelFormats[] = {
static_cast<fuchsia_images2_pixel_format_enum_value_t>(
fuchsia_images2::wire::PixelFormat::kBgra32),
static_cast<fuchsia_images2_pixel_format_enum_value_t>(
fuchsia_images2::wire::PixelFormat::kR8G8B8A8),
};
constexpr uint32_t FB_WIDTH = 1;
constexpr uint32_t FB_HEIGHT = 2;
constexpr uint32_t FB_FPS = 5;
constexpr uint32_t GL_RGBA = 0x1908;
constexpr uint32_t GL_BGRA_EXT = 0x80E1;
} // namespace
// static
zx_status_t Display::Create(void* ctx, zx_device_t* device) {
auto display = std::make_unique<Display>(device);
zx_status_t status = display->Bind();
if (status == ZX_OK) {
// devmgr now owns device.
[[maybe_unused]] auto* dev = display.release();
}
return status;
}
Display::Display(zx_device_t* parent)
: DisplayType(parent), loop_(&kAsyncLoopConfigNeverAttachToThread) {
if (parent) {
control_ = parent;
}
}
Display::~Display() {
loop_.Shutdown();
for (auto& it : devices_) {
TeardownDisplay(it.first);
}
}
zx_status_t Display::Bind() {
fbl::AutoLock lock(&lock_);
if (!control_.is_valid()) {
zxlogf(ERROR, "%s: no control protocol", kTag);
return ZX_ERR_NOT_SUPPORTED;
}
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_goldfish_pipe::GoldfishPipe>();
if (endpoints.is_error()) {
return endpoints.status_value();
}
auto channel = endpoints->server.TakeChannel();
zx_status_t status = control_.ConnectToPipeDevice(std::move(channel));
if (status != ZX_OK) {
zxlogf(ERROR, "%s: could not connect to pipe device: %s", kTag, zx_status_get_string(status));
return status;
}
pipe_ = fidl::WireSyncClient(std::move(endpoints->client));
if (!pipe_.is_valid()) {
zxlogf(ERROR, "%s: no pipe protocol", kTag);
return ZX_ERR_NOT_SUPPORTED;
}
status = InitSysmemAllocatorClientLocked();
if (status != ZX_OK) {
zxlogf(ERROR, "%s: cannot initialize sysmem allocator: %s", kTag, zx_status_get_string(status));
return status;
}
// Create a second FIDL connection for use by RenderControl.
endpoints = fidl::CreateEndpoints<fuchsia_hardware_goldfish_pipe::GoldfishPipe>();
if (endpoints.is_error()) {
return endpoints.status_value();
}
channel = endpoints->server.TakeChannel();
status = control_.ConnectToPipeDevice(std::move(channel));
if (status != ZX_OK) {
zxlogf(ERROR, "%s: could not connect to pipe device: %s", kTag, zx_status_get_string(status));
return status;
}
fidl::WireSyncClient pipe_client{std::move(endpoints->client)};
rc_ = std::make_unique<RenderControl>();
status = rc_->InitRcPipe(std::move(pipe_client));
if (status != ZX_OK) {
zxlogf(ERROR, "%s: RenderControl failed to initialize: %d", kTag, status);
return ZX_ERR_NOT_SUPPORTED;
}
display::DisplayId next_display_id = kPrimaryDisplayId;
// Parse optional display params. This is comma seperated list of
// display devices. The format is:
//
// widthxheight[-xpos+ypos][@refresh][%scale]
char* flag = getenv("driver.goldfish.displays");
if (flag) {
std::stringstream devices_stream(flag);
std::string device_string;
while (std::getline(devices_stream, device_string, ',')) {
Device device;
char delim = 0;
std::stringstream device_stream(device_string);
do {
switch (delim) {
case 0:
device_stream >> device.width;
break;
case 'x':
device_stream >> device.height;
break;
case '-':
device_stream >> device.x;
break;
case '+':
device_stream >> device.y;
break;
case '@':
device_stream >> device.refresh_rate_hz;
break;
case '%':
device_stream >> device.scale;
break;
}
} while (device_stream >> delim);
if (!device.width || !device.height) {
zxlogf(ERROR, "%s: skip device=%s, missing size", kTag, device_string.c_str());
continue;
}
if (!device.refresh_rate_hz) {
zxlogf(ERROR, "%s: skip device=%s, refresh rate is zero", kTag, device_string.c_str());
continue;
}
if (device.scale < 0.1f || device.scale > 100.f) {
zxlogf(ERROR, "%s: skip device=%s, scale is not in range 0.1-100", kTag,
device_string.c_str());
continue;
}
auto& new_device = devices_[next_display_id++];
new_device.width = device.width;
new_device.height = device.height;
new_device.x = device.x;
new_device.y = device.y;
new_device.refresh_rate_hz = device.refresh_rate_hz;
new_device.scale = device.scale;
}
}
// Create primary device if needed.
if (devices_.empty()) {
auto& device = devices_[kPrimaryDisplayId];
device.width = static_cast<uint32_t>(rc_->GetFbParam(FB_WIDTH, 1024));
device.height = static_cast<uint32_t>(rc_->GetFbParam(FB_HEIGHT, 768));
device.refresh_rate_hz = static_cast<uint32_t>(rc_->GetFbParam(FB_FPS, 60));
}
// Set up display and set up flush task for each device.
for (auto& it : devices_) {
zx_status_t status = SetupDisplay(it.first);
ZX_DEBUG_ASSERT(status == ZX_OK);
async::PostTask(loop_.dispatcher(), [this, display_id = it.first] {
FlushDisplay(loop_.dispatcher(), display_id);
});
}
// Start async event thread.
loop_.StartThread("goldfish_display_event_thread");
return DdkAdd("goldfish-display");
}
void Display::DdkRelease() { delete this; }
void Display::DisplayControllerImplSetDisplayControllerInterface(
const display_controller_interface_protocol_t* interface) {
std::vector<added_display_args_t> args;
for (auto& it : devices_) {
added_display_args_t display = {
.display_id = display::ToBanjoDisplayId(it.first),
.edid_present = false,
.panel =
{
.params =
{
.width = it.second.width,
.height = it.second.height,
.refresh_rate_e2 = it.second.refresh_rate_hz * 100,
},
},
.pixel_format_list = kPixelFormats,
.pixel_format_count = sizeof(kPixelFormats) / sizeof(kPixelFormats[0]),
.cursor_info_list = nullptr,
.cursor_info_count = 0,
};
args.push_back(display);
}
{
fbl::AutoLock lock(&flush_lock_);
dc_intf_ = ddk::DisplayControllerInterfaceProtocolClient(interface);
dc_intf_.OnDisplaysChanged(args.data(), args.size(), nullptr, 0, nullptr, 0, nullptr);
}
}
zx_status_t Display::InitSysmemAllocatorClientLocked() {
auto endpoints = fidl::CreateEndpoints<fuchsia_sysmem::Allocator>();
if (!endpoints.is_ok()) {
zxlogf(ERROR, "Cannot create sysmem allocator endpoints: %s", endpoints.status_string());
return endpoints.status_value();
}
auto& [client, server] = endpoints.value();
auto connect_result = pipe_->ConnectSysmem(server.TakeChannel());
if (!connect_result.ok()) {
zxlogf(ERROR, "Cannot connect to sysmem Allocator protocol: %s",
connect_result.status_string());
return connect_result.status();
}
sysmem_allocator_client_ = fidl::WireSyncClient(std::move(client));
std::string debug_name = fxl::StringPrintf("goldfish-display[%lu]", fsl::GetCurrentProcessKoid());
auto set_debug_status = sysmem_allocator_client_->SetDebugClientInfo(
fidl::StringView::FromExternal(debug_name), fsl::GetCurrentProcessKoid());
if (!set_debug_status.ok()) {
zxlogf(ERROR, "Cannot set sysmem allocator debug info: %s", set_debug_status.status_string());
return set_debug_status.status();
}
return ZX_OK;
}
namespace {
uint32_t GetColorBufferFormatFromSysmemPixelFormat(
const fuchsia_sysmem::PixelFormat& pixel_format) {
switch (pixel_format.type()) {
case fuchsia_sysmem::PixelFormatType::kR8G8B8A8:
return GL_BGRA_EXT;
case fuchsia_sysmem::PixelFormatType::kBgra32:
return GL_RGBA;
default:
// This should not happen. The sysmem-negotiated pixel format must be supported.
ZX_ASSERT_MSG(false, "Import unsupported image: %u",
static_cast<uint32_t>(pixel_format.type()));
}
}
} // namespace
zx_status_t Display::ImportVmoImage(image_t* image, const fuchsia_sysmem::PixelFormat& pixel_format,
zx::vmo vmo, size_t offset) {
auto color_buffer = std::make_unique<ColorBuffer>();
const uint32_t color_buffer_format = GetColorBufferFormatFromSysmemPixelFormat(pixel_format);
fidl::Arena unused_arena;
const uint32_t bytes_per_pixel =
ImageFormatStrideBytesPerWidthPixel(fidl::ToWire(unused_arena, pixel_format));
color_buffer->size = fbl::round_up(image->width * image->height * bytes_per_pixel,
static_cast<uint32_t>(PAGE_SIZE));
// Linear images must be pinned.
color_buffer->pinned_vmo =
rc_->pipe_io()->PinVmo(vmo, ZX_BTI_PERM_READ | ZX_BTI_CONTIGUOUS, offset, color_buffer->size);
color_buffer->vmo = std::move(vmo);
color_buffer->width = image->width;
color_buffer->height = image->height;
color_buffer->format = color_buffer_format;
auto status = rc_->CreateColorBuffer(image->width, image->height, color_buffer_format);
if (status.is_error()) {
zxlogf(ERROR, "%s: failed to create color buffer", kTag);
return status.error_value();
}
color_buffer->host_color_buffer_id = status.value();
image->handle = reinterpret_cast<uint64_t>(color_buffer.release());
return ZX_OK;
}
zx_status_t Display::DisplayControllerImplImportBufferCollection(uint64_t collection_id,
zx::channel collection_token) {
if (buffer_collections_.find(collection_id) != buffer_collections_.end()) {
zxlogf(ERROR, "Buffer Collection (id=%lu) already exists", collection_id);
return ZX_ERR_ALREADY_EXISTS;
}
ZX_DEBUG_ASSERT_MSG(sysmem_allocator_client_.is_valid(), "sysmem allocator is not initialized");
auto endpoints = fidl::CreateEndpoints<fuchsia_sysmem::BufferCollection>();
if (!endpoints.is_ok()) {
zxlogf(ERROR, "Cannot create sysmem BufferCollection endpoints: %s", endpoints.status_string());
return ZX_ERR_INTERNAL;
}
auto& [collection_client_endpoint, collection_server_endpoint] = endpoints.value();
auto bind_result = sysmem_allocator_client_->BindSharedCollection(
fidl::ClientEnd<fuchsia_sysmem::BufferCollectionToken>(std::move(collection_token)),
std::move(collection_server_endpoint));
if (!bind_result.ok()) {
zxlogf(ERROR, "Cannot complete FIDL call BindSharedCollection: %s",
bind_result.status_string());
return ZX_ERR_INTERNAL;
}
buffer_collections_[collection_id] = fidl::SyncClient(std::move(collection_client_endpoint));
return ZX_OK;
}
zx_status_t Display::DisplayControllerImplReleaseBufferCollection(uint64_t collection_id) {
if (buffer_collections_.find(collection_id) == buffer_collections_.end()) {
zxlogf(ERROR, "Cannot release buffer collection %lu: buffer collection doesn't exist",
collection_id);
return ZX_ERR_NOT_FOUND;
}
buffer_collections_.erase(collection_id);
return ZX_OK;
}
zx_status_t Display::DisplayControllerImplImportImage(image_t* image, uint64_t collection_id,
uint32_t index) {
const auto it = buffer_collections_.find(collection_id);
if (it == buffer_collections_.end()) {
zxlogf(ERROR, "ImportImage: Cannot find imported buffer collection (id=%lu)", collection_id);
return ZX_ERR_NOT_FOUND;
}
const fidl::SyncClient<fuchsia_sysmem::BufferCollection>& collection_client = it->second;
fidl::Result check_result = collection_client->CheckBuffersAllocated();
// TODO(fxbug.dev/121691): 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_client->WaitForBuffersAllocated();
// TODO(fxbug.dev/121691): 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();
zx::vmo vmo;
if (index < collection_info.buffer_count()) {
vmo = std::move(collection_info.buffers()[index].vmo());
ZX_DEBUG_ASSERT(!collection_info.buffers()[index].vmo().is_valid());
}
if (!vmo.is_valid()) {
zxlogf(ERROR, "%s: invalid index", kTag);
return ZX_ERR_OUT_OF_RANGE;
}
if (!collection_info.settings().has_image_format_constraints()) {
zxlogf(ERROR, "Buffer collection doesn't have valid image format constraints");
return ZX_ERR_NOT_SUPPORTED;
}
uint64_t offset = collection_info.buffers()[index].vmo_usable_start();
if (collection_info.settings().buffer_settings().heap() !=
fuchsia_sysmem::HeapType::kGoldfishDeviceLocal) {
const auto& pixel_format = collection_info.settings().image_format_constraints().pixel_format();
return ImportVmoImage(image, pixel_format, std::move(vmo), offset);
}
if (offset != 0) {
zxlogf(ERROR, "VMO offset (%lu) not supported for Goldfish device local color buffers", offset);
return ZX_ERR_NOT_SUPPORTED;
}
auto color_buffer = std::make_unique<ColorBuffer>();
color_buffer->vmo = std::move(vmo);
image->handle = reinterpret_cast<uint64_t>(color_buffer.release());
return ZX_OK;
}
void Display::DisplayControllerImplReleaseImage(image_t* image) {
auto color_buffer = reinterpret_cast<ColorBuffer*>(image->handle);
// Color buffer is owned by image in the linear case.
if (image->type == IMAGE_TYPE_SIMPLE) {
rc_->CloseColorBuffer(color_buffer->host_color_buffer_id);
}
async::PostTask(loop_.dispatcher(), [this, color_buffer] {
for (auto& kv : devices_) {
if (kv.second.incoming_config.has_value() &&
kv.second.incoming_config->color_buffer == color_buffer) {
kv.second.incoming_config = std::nullopt;
}
}
delete color_buffer;
});
}
config_check_result_t Display::DisplayControllerImplCheckConfiguration(
const display_config_t** display_configs, size_t display_count, uint32_t** layer_cfg_results,
size_t* layer_cfg_result_count) {
if (display_count == 0) {
return CONFIG_CHECK_RESULT_OK;
}
for (unsigned i = 0; i < display_count; i++) {
const size_t layer_count = display_configs[i]->layer_count;
const display::DisplayId display_id = display::ToDisplayId(display_configs[i]->display_id);
if (layer_count > 0) {
ZX_DEBUG_ASSERT(devices_.find(display_id) != devices_.end());
const Device& device = devices_[display_id];
if (display_configs[i]->cc_flags != 0) {
// Color Correction is not supported, but we will pretend we do.
// TODO(fxbug.dev/36184): Returning error will cause blank screen if scenic requests
// color correction. For now, lets pretend we support it, until a proper
// fix is done (either from scenic or from core display)
zxlogf(WARNING, "%s: Color Correction not support. No error reported", __func__);
}
if (display_configs[i]->layer_list[0]->type != LAYER_TYPE_PRIMARY) {
// We only support PRIMARY layer. Notify client to convert layer to
// primary type.
layer_cfg_results[i][0] |= CLIENT_USE_PRIMARY;
layer_cfg_result_count[i] = 1;
} else {
primary_layer_t* layer = &display_configs[i]->layer_list[0]->cfg.primary;
// Scaling is allowed if destination frame match display and
// source frame match image.
frame_t dest_frame = {
.x_pos = 0,
.y_pos = 0,
.width = device.width,
.height = device.height,
};
frame_t src_frame = {
.x_pos = 0,
.y_pos = 0,
.width = layer->image.width,
.height = layer->image.height,
};
if (memcmp(&layer->dest_frame, &dest_frame, sizeof(frame_t)) != 0) {
// TODO(fxbug.dev/36222): Need to provide proper flag to indicate driver only
// accepts full screen dest frame.
layer_cfg_results[i][0] |= CLIENT_FRAME_SCALE;
}
if (memcmp(&layer->src_frame, &src_frame, sizeof(frame_t)) != 0) {
layer_cfg_results[i][0] |= CLIENT_SRC_FRAME;
}
if (layer->alpha_mode != ALPHA_DISABLE) {
// Alpha is not supported.
layer_cfg_results[i][0] |= CLIENT_ALPHA;
}
if (layer->transform_mode != FRAME_TRANSFORM_IDENTITY) {
// Transformation is not supported.
layer_cfg_results[i][0] |= CLIENT_TRANSFORM;
}
// Check if any changes to the base layer were required.
if (layer_cfg_results[i][0] != 0) {
layer_cfg_result_count[i] = 1;
}
}
// If there is more than one layer, the rest need to be merged into the base layer.
if (layer_count > 1) {
layer_cfg_results[i][0] |= CLIENT_MERGE_BASE;
for (unsigned j = 1; j < layer_count; j++) {
layer_cfg_results[i][j] |= CLIENT_MERGE_SRC;
}
layer_cfg_result_count[i] = layer_count;
}
}
}
return CONFIG_CHECK_RESULT_OK;
}
zx_status_t Display::PresentDisplayConfig(display::DisplayId display_id,
const DisplayConfig& display_config) {
auto* color_buffer = display_config.color_buffer;
if (!color_buffer) {
return ZX_OK;
}
zx::eventpair event_display, event_sync_device;
zx_status_t status = zx::eventpair::create(0u, &event_display, &event_sync_device);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_eventpair_create failed: %d", kTag, status);
return status;
}
auto& device = devices_[display_id];
// Set up async wait for the goldfish sync event. The zx::eventpair will be
// stored in the async wait callback, which will be destroyed only when the
// event is signaled or the wait is cancelled.
device.pending_config_waits.emplace_back(event_display.get(), ZX_EVENTPAIR_SIGNALED, 0);
auto& wait = device.pending_config_waits.back();
wait.Begin(loop_.dispatcher(), [event = std::move(event_display), &device,
pending_config_stamp = display_config.config_stamp](
async_dispatcher_t* dispatcher, async::WaitOnce* current_wait,
zx_status_t status, const zx_packet_signal_t*) {
TRACE_DURATION("gfx", "Display::SyncEventHandler", "config_stamp",
pending_config_stamp.value());
if (status == ZX_ERR_CANCELED) {
zxlogf(INFO, "Wait for config stamp %lu cancelled.", pending_config_stamp.value());
return;
}
ZX_DEBUG_ASSERT_MSG(status == ZX_OK, "Invalid wait status: %d\n", status);
// When the eventpair in |current_wait| is signalled, all the pending waits
// that are queued earlier than that eventpair will be removed from the list
// and the async WaitOnce will be cancelled.
// Note that the cancelled waits will return early and will not reach here.
ZX_DEBUG_ASSERT(std::any_of(device.pending_config_waits.begin(),
device.pending_config_waits.end(),
[current_wait](const async::WaitOnce& wait) {
return wait.object() == current_wait->object();
}));
// Remove all the pending waits that are queued earlier than the current
// wait, and the current wait itself. In WaitOnce, the callback is moved to
// stack before current wait is removed, so it's safe to remove any item in
// the list.
for (auto it = device.pending_config_waits.begin(); it != device.pending_config_waits.end();) {
if (it->object() == current_wait->object()) {
device.pending_config_waits.erase(it);
break;
}
it = device.pending_config_waits.erase(it);
}
device.latest_config_stamp = std::max(device.latest_config_stamp, pending_config_stamp);
});
// Update host-writeable display buffers before presenting.
if (color_buffer->pinned_vmo.region_count() > 0) {
auto status = rc_->UpdateColorBuffer(
color_buffer->host_color_buffer_id, color_buffer->pinned_vmo, color_buffer->width,
color_buffer->height, color_buffer->format, color_buffer->size);
if (status.is_error() || status.value()) {
zxlogf(ERROR, "%s : color buffer update failed: %d:%u", kTag, status.status_value(),
status.value_or(0));
return status.is_error() ? status.status_value() : ZX_ERR_INTERNAL;
}
}
// Present the buffer.
{
HostDisplayId host_display_id = devices_[display_id].host_display_id;
if (host_display_id != kInvalidHostDisplayId) {
// Set color buffer for secondary displays.
auto status = rc_->SetDisplayColorBuffer(host_display_id, color_buffer->host_color_buffer_id);
if (status.is_error() || status.value()) {
zxlogf(ERROR, "%s: failed to set display color buffer", kTag);
return status.is_error() ? status.status_value() : ZX_ERR_INTERNAL;
}
} else {
status = rc_->FbPost(color_buffer->host_color_buffer_id);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: FbPost failed: %d", kTag, status);
return status;
}
}
fbl::AutoLock lock(&lock_);
status = control_.CreateSyncFence(std::move(event_sync_device));
if (status != ZX_OK) {
zxlogf(ERROR, "%s: CreateSyncFence failed: %d", kTag, status);
return status;
}
}
return ZX_OK;
}
void Display::DisplayControllerImplApplyConfiguration(const display_config_t** display_configs,
size_t display_count,
const config_stamp_t* banjo_config_stamp) {
ZX_DEBUG_ASSERT(banjo_config_stamp != nullptr);
display::ConfigStamp config_stamp = display::ToConfigStamp(*banjo_config_stamp);
for (const auto& it : devices_) {
uint64_t handle = 0;
for (unsigned i = 0; i < display_count; i++) {
if (display::ToDisplayId(display_configs[i]->display_id) == it.first) {
if (display_configs[i]->layer_count) {
handle = display_configs[i]->layer_list[0]->cfg.primary.image.handle;
}
break;
}
}
if (handle == 0u) {
// The display doesn't have any active layers right now. For layers that
// previously existed, we should cancel waiting events on the pending
// color buffer and remove references to both pending and current color
// buffers.
async::PostTask(loop_.dispatcher(), [this, display_id = it.first, config_stamp] {
if (devices_.find(display_id) != devices_.end()) {
auto& device = devices_[display_id];
device.pending_config_waits.clear();
device.incoming_config = std::nullopt;
device.latest_config_stamp = std::max(device.latest_config_stamp, config_stamp);
}
});
return;
}
auto color_buffer = reinterpret_cast<ColorBuffer*>(handle);
if (color_buffer && color_buffer->host_color_buffer_id != kInvalidHostColorBufferId) {
zx::vmo vmo;
zx_status_t status = color_buffer->vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to duplicate vmo: %d", kTag, status);
} else {
fbl::AutoLock lock(&lock_);
uint32_t render_control_encoded_color_buffer_id = kInvalidHostColorBufferId.value();
status = control_.GetColorBuffer(std::move(vmo), &render_control_encoded_color_buffer_id);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to get color buffer: %d", kTag, status);
}
color_buffer->host_color_buffer_id =
ToHostColorBufferId(render_control_encoded_color_buffer_id);
// Color buffers are in vulkan-only mode by default as that avoids
// unnecessary copies on the host in some cases. The color buffer
// needs to be moved out of vulkan-only mode before being used for
// presentation.
if (color_buffer->host_color_buffer_id != kInvalidHostColorBufferId) {
auto status = rc_->SetColorBufferVulkanMode(color_buffer->host_color_buffer_id, 0);
if (status.is_error() || status.value()) {
zxlogf(ERROR, "%s: failed to set vulkan mode: %d %d", kTag, status.status_value(),
status.value_or(0));
}
}
}
}
if (color_buffer) {
async::PostTask(loop_.dispatcher(),
[this, config_stamp, color_buffer, display_id = it.first] {
devices_[display_id].incoming_config = {
.color_buffer = color_buffer,
.config_stamp = config_stamp,
};
});
}
}
}
zx_status_t Display::DisplayControllerImplGetSysmemConnection(zx::channel connection) {
fbl::AutoLock lock(&lock_);
auto result = pipe_->ConnectSysmem(std::move(connection));
zx_status_t status = result.status();
if (!result.ok()) {
zxlogf(ERROR, "%s: failed to connect to sysmem: %s", kTag, result.status_string());
}
return status;
}
zx_status_t Display::DisplayControllerImplSetBufferCollectionConstraints(const image_t* config,
uint64_t collection_id) {
const auto it = buffer_collections_.find(collection_id);
if (it == buffer_collections_.end()) {
zxlogf(ERROR, "ImportImage: Cannot find imported buffer collection (id=%lu)", collection_id);
return ZX_ERR_NOT_FOUND;
}
const fidl::SyncClient<fuchsia_sysmem::BufferCollection>& collection = it->second;
fuchsia_sysmem::BufferCollectionConstraints constraints;
constraints.usage().display() = fuchsia_sysmem::kDisplayUsageLayer;
constraints.has_buffer_memory_constraints() = true;
auto& buffer_constraints = constraints.buffer_memory_constraints();
buffer_constraints.min_size_bytes() = 0;
buffer_constraints.max_size_bytes() = 0xffffffff;
buffer_constraints.physically_contiguous_required() = true;
buffer_constraints.secure_required() = false;
buffer_constraints.ram_domain_supported() = true;
buffer_constraints.cpu_domain_supported() = true;
buffer_constraints.inaccessible_domain_supported() = true;
buffer_constraints.heap_permitted_count() = 2;
buffer_constraints.heap_permitted()[0] = fuchsia_sysmem::HeapType::kSystemRam;
buffer_constraints.heap_permitted()[1] = fuchsia_sysmem::HeapType::kGoldfishDeviceLocal;
constraints.image_format_constraints_count() = 4;
for (uint32_t i = 0; i < constraints.image_format_constraints_count(); i++) {
auto& image_constraints = constraints.image_format_constraints()[i];
image_constraints.pixel_format().type() = i & 0b01 ? fuchsia_sysmem::PixelFormatType::kR8G8B8A8
: fuchsia_sysmem::PixelFormatType::kBgra32;
image_constraints.pixel_format().has_format_modifier() = true;
image_constraints.pixel_format().format_modifier().value() =
i & 0b10 ? fuchsia_sysmem::kFormatModifierLinear
: fuchsia_sysmem::kFormatModifierGoogleGoldfishOptimal;
image_constraints.color_spaces_count() = 1;
image_constraints.color_space()[0].type() = fuchsia_sysmem::ColorSpaceType::kSrgb;
image_constraints.min_coded_width() = 0;
image_constraints.max_coded_width() = 0xffffffff;
image_constraints.min_coded_height() = 0;
image_constraints.max_coded_height() = 0xffffffff;
image_constraints.min_bytes_per_row() = 0;
image_constraints.max_bytes_per_row() = 0xffffffff;
image_constraints.max_coded_width_times_coded_height() = 0xffffffff;
image_constraints.layers() = 1;
image_constraints.coded_width_divisor() = 1;
image_constraints.coded_height_divisor() = 1;
image_constraints.bytes_per_row_divisor() = 1;
image_constraints.start_offset_divisor() = 1;
image_constraints.display_width_divisor() = 1;
image_constraints.display_height_divisor() = 1;
}
auto set_result = collection->SetConstraints({true, std::move(constraints)});
if (set_result.is_error()) {
zxlogf(ERROR, "%s: failed to set constraints", kTag);
return set_result.error_value().status();
}
return ZX_OK;
}
zx_status_t Display::SetupDisplay(display::DisplayId display_id) {
Device& device = devices_[display_id];
// Create secondary displays.
if (display_id != kPrimaryDisplayId) {
auto status = rc_->CreateDisplay();
if (status.is_error()) {
return status.error_value();
}
device.host_display_id = status.value();
}
uint32_t width = static_cast<uint32_t>(static_cast<float>(device.width) * device.scale);
uint32_t height = static_cast<uint32_t>(static_cast<float>(device.height) * device.scale);
auto status = rc_->SetDisplayPose(device.host_display_id, device.x, device.y, width, height);
if (status.is_error() || status.value()) {
zxlogf(ERROR, "%s: failed to set display pose: %d %d", kTag, status.status_value(),
status.value_or(0));
return status.is_error() ? status.error_value() : ZX_ERR_INTERNAL;
}
device.expected_next_flush = async::Now(loop_.dispatcher());
return ZX_OK;
}
void Display::TeardownDisplay(display::DisplayId display_id) {
Device& device = devices_[display_id];
if (device.host_display_id != kInvalidHostDisplayId) {
zx::result<uint32_t> status = rc_->DestroyDisplay(device.host_display_id);
ZX_DEBUG_ASSERT(status.is_ok());
ZX_DEBUG_ASSERT(!status.value());
}
}
void Display::FlushDisplay(async_dispatcher_t* dispatcher, display::DisplayId display_id) {
Device& device = devices_[display_id];
zx::duration period = zx::sec(1) / device.refresh_rate_hz;
zx::time expected_next_flush = device.expected_next_flush + period;
if (device.incoming_config.has_value()) {
zx_status_t status = PresentDisplayConfig(display_id, *device.incoming_config);
ZX_DEBUG_ASSERT(status == ZX_OK || status == ZX_ERR_SHOULD_WAIT);
}
{
fbl::AutoLock lock(&flush_lock_);
if (dc_intf_.is_valid()) {
zx::time now = async::Now(dispatcher);
const uint64_t banjo_display_id = display::ToBanjoDisplayId(display_id);
const config_stamp_t banjo_config_stamp =
display::ToBanjoConfigStamp(device.latest_config_stamp);
dc_intf_.OnDisplayVsync(banjo_display_id, now.get(), &banjo_config_stamp);
}
}
// If we've already passed the |expected_next_flush| deadline, skip the
// Vsync and adjust the deadline to the earliest next available frame.
zx::time now = async::Now(dispatcher);
if (now > expected_next_flush) {
expected_next_flush +=
period * (((now - expected_next_flush + period).get() - 1L) / period.get());
}
device.expected_next_flush = expected_next_flush;
async::PostTaskForTime(
dispatcher, [this, dispatcher, display_id] { FlushDisplay(dispatcher, display_id); },
expected_next_flush);
}
} // namespace goldfish
static constexpr zx_driver_ops_t goldfish_display_driver_ops = []() -> zx_driver_ops_t {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = goldfish::Display::Create;
return ops;
}();
ZIRCON_DRIVER(goldfish_display, goldfish_display_driver_ops, "zircon", "0.1");