| // 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 <fuchsia/hardware/goldfish/llcpp/fidl.h> |
| #include <fuchsia/sysmem/c/fidl.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/cpp/time.h> |
| #include <lib/async/cpp/wait.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/ddk/trace/event.h> |
| #include <lib/zircon-internal/align.h> |
| #include <zircon/pixelformat.h> |
| #include <zircon/threads.h> |
| |
| #include <memory> |
| #include <sstream> |
| #include <vector> |
| |
| #include <fbl/auto_lock.h> |
| |
| #include "src/graphics/display/drivers/goldfish-display/goldfish-display-bind.h" |
| |
| namespace goldfish { |
| namespace { |
| |
| const char* kTag = "goldfish-display"; |
| |
| const char* kPipeName = "pipe:opengles"; |
| |
| constexpr uint64_t kPrimaryDisplayId = 1; |
| |
| constexpr uint32_t kClientFlags = 0; |
| |
| constexpr zx_pixel_format_t kPixelFormats[] = { |
| ZX_PIXEL_FORMAT_RGB_x888, |
| ZX_PIXEL_FORMAT_ARGB_8888, |
| ZX_PIXEL_FORMAT_BGR_888x, |
| ZX_PIXEL_FORMAT_ABGR_8888, |
| }; |
| |
| 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; |
| constexpr uint32_t GL_UNSIGNED_BYTE = 0x1401; |
| |
| struct GetFbParamCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t param; |
| }; |
| constexpr uint32_t kOP_rcGetFbParam = 10007; |
| constexpr uint32_t kSize_rcGetFbParam = 12; |
| |
| struct CreateColorBufferCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t width; |
| uint32_t height; |
| uint32_t internalformat; |
| }; |
| constexpr uint32_t kOP_rcCreateColorBuffer = 10012; |
| constexpr uint32_t kSize_rcCreateColorBuffer = 20; |
| |
| struct OpenColorBufferCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t id; |
| }; |
| constexpr uint32_t kOP_rcOpenColorBuffer = 10013; |
| constexpr uint32_t kSize_rcOpenColorBuffer = 12; |
| |
| struct CloseColorBufferCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t id; |
| }; |
| constexpr uint32_t kOP_rcCloseColorBuffer = 10014; |
| constexpr uint32_t kSize_rcCloseColorBuffer = 12; |
| |
| struct SetColorBufferVulkanModeCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t id; |
| uint32_t mode; |
| }; |
| constexpr uint32_t kOP_rcSetColorBufferVulkanMode = 10045; |
| constexpr uint32_t kSize_rcSetColorBufferVulkanMode = 16; |
| |
| struct UpdateColorBufferCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t id; |
| uint32_t x; |
| uint32_t y; |
| uint32_t width; |
| uint32_t height; |
| uint32_t format; |
| uint32_t type; |
| uint32_t size_pixels; |
| }; |
| constexpr uint32_t kOP_rcUpdateColorBuffer = 10024; |
| constexpr uint32_t kSize_rcUpdateColorBuffer = 40; |
| |
| struct FbPostCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t id; |
| }; |
| constexpr uint32_t kOP_rcFbPost = 10018; |
| constexpr uint32_t kSize_rcFbPost = 12; |
| |
| struct CreateDisplayCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t size_display_id; |
| }; |
| constexpr uint32_t kOP_rcCreateDisplay = 10038; |
| constexpr uint32_t kSize_rcCreateDisplay = 12; |
| |
| struct DestroyDisplayCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t display_id; |
| }; |
| constexpr uint32_t kOP_rcDestroyDisplay = 10039; |
| constexpr uint32_t kSize_rcDestroyDisplay = 12; |
| |
| struct SetDisplayColorBufferCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t display_id; |
| uint32_t id; |
| }; |
| constexpr uint32_t kOP_rcSetDisplayColorBuffer = 10040; |
| constexpr uint32_t kSize_rcSetDisplayColorBuffer = 16; |
| |
| struct SetDisplayPoseCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t display_id; |
| int32_t x; |
| int32_t y; |
| uint32_t w; |
| uint32_t h; |
| }; |
| constexpr uint32_t kOP_rcSetDisplayPose = 10044; |
| constexpr uint32_t kSize_rcSetDisplayPose = 28; |
| |
| } // 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. |
| __UNUSED auto* dev = display.release(); |
| } |
| return status; |
| } |
| |
| Display::Display(zx_device_t* parent) |
| : DisplayType(parent), loop_(&kAsyncLoopConfigNeverAttachToThread) { |
| if (parent) { |
| control_ = parent; |
| pipe_ = parent; |
| } |
| } |
| |
| Display::~Display() { |
| loop_.Shutdown(); |
| |
| for (auto& it : devices_) { |
| TeardownDisplay(it.first); |
| } |
| |
| if (id_) { |
| fbl::AutoLock lock(&lock_); |
| if (cmd_buffer_.is_valid()) { |
| auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt()); |
| buffer->id = id_; |
| buffer->cmd = PIPE_CMD_CODE_CLOSE; |
| buffer->status = PIPE_ERROR_INVAL; |
| |
| pipe_.Exec(id_); |
| ZX_DEBUG_ASSERT(!buffer->status); |
| } |
| pipe_.Destroy(id_); |
| } |
| } |
| |
| 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; |
| } |
| |
| if (!pipe_.is_valid()) { |
| zxlogf(ERROR, "%s: no pipe protocol", kTag); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t status = pipe_.GetBti(&bti_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: GetBti failed: %d", kTag, status); |
| return status; |
| } |
| |
| status = io_buffer_.Init(bti_.get(), PAGE_SIZE, IO_BUFFER_RW | IO_BUFFER_CONTIG); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: io_buffer_init failed: %d", kTag, status); |
| return status; |
| } |
| |
| status = zx::event::create(0u, &pipe_event_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: zx_event_create failed: %d", kTag, status); |
| return status; |
| } |
| |
| zx::event pipe_event_dup; |
| status = pipe_event_.duplicate(ZX_RIGHT_SAME_RIGHTS, &pipe_event_dup); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: zx_handle_duplicate failed: %d", kTag, status); |
| return status; |
| } |
| |
| zx::vmo vmo; |
| status = pipe_.Create(&id_, &vmo); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Create failed: %d", kTag, status); |
| return status; |
| } |
| status = pipe_.SetEvent(id_, std::move(pipe_event_dup)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: SetEvent failed: %d", kTag, status); |
| return status; |
| } |
| |
| status = cmd_buffer_.InitVmo(bti_.get(), vmo.get(), 0, IO_BUFFER_RW); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: io_buffer_init_vmo failed: %d", kTag, status); |
| return status; |
| } |
| |
| auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt()); |
| buffer->id = id_; |
| buffer->cmd = PIPE_CMD_CODE_OPEN; |
| buffer->status = PIPE_ERROR_INVAL; |
| |
| pipe_.Open(id_); |
| if (buffer->status) { |
| zxlogf(ERROR, "%s: Open failed: %d", kTag, buffer->status); |
| cmd_buffer_.release(); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| size_t length = strlen(kPipeName) + 1; |
| memcpy(io_buffer_.virt(), kPipeName, length); |
| status = WriteLocked(static_cast<uint32_t>(length)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Pipe name write failed: %d", kTag, status); |
| return status; |
| } |
| |
| memcpy(io_buffer_.virt(), &kClientFlags, sizeof(kClientFlags)); |
| status = WriteLocked(sizeof(kClientFlags)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Client flags write failed: %d", kTag, status); |
| return status; |
| } |
| |
| uint64_t 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; |
| } |
| |
| devices_[next_display_id++] = device; |
| } |
| } |
| |
| // Create primary device if needed. |
| if (devices_.empty()) { |
| Device device; |
| device.width = static_cast<uint32_t>(GetFbParamLocked(FB_WIDTH, 1024)); |
| device.height = static_cast<uint32_t>(GetFbParamLocked(FB_HEIGHT, 768)); |
| device.refresh_rate_hz = static_cast<uint32_t>(GetFbParamLocked(FB_FPS, 60)); |
| devices_[kPrimaryDisplayId] = device; |
| } |
| |
| // Set up display and set up flush task for each device. |
| for (auto& it : devices_) { |
| zx_status_t status = SetupDisplayLocked(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::DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); } |
| |
| 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 = 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::ImportVmoImage(image_t* image, zx::vmo vmo, size_t offset) { |
| auto color_buffer = std::make_unique<ColorBuffer>(); |
| |
| // Linear images must be pinned. |
| unsigned pixel_size = ZX_PIXEL_FORMAT_BYTES(image->pixel_format); |
| color_buffer->size = ZX_ROUNDUP(image->width * image->height * pixel_size, PAGE_SIZE); |
| zx_status_t status = bti_.pin(ZX_BTI_PERM_READ | ZX_BTI_CONTIGUOUS, vmo, offset, |
| color_buffer->size, &color_buffer->paddr, 1, &color_buffer->pmt); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: failed to pin VMO: %d", kTag, status); |
| return status; |
| } |
| |
| uint32_t format = (image->pixel_format == ZX_PIXEL_FORMAT_RGB_x888 || |
| image->pixel_format == ZX_PIXEL_FORMAT_ARGB_8888) |
| ? GL_BGRA_EXT |
| : GL_RGBA; |
| |
| color_buffer->vmo = std::move(vmo); |
| color_buffer->width = image->width; |
| color_buffer->height = image->height; |
| color_buffer->format = format; |
| |
| { |
| fbl::AutoLock lock(&lock_); |
| status = CreateColorBufferLocked(image->width, image->height, format, &color_buffer->id); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: failed to create color buffer", kTag); |
| return status; |
| } |
| } |
| |
| image->handle = reinterpret_cast<uint64_t>(color_buffer.release()); |
| return ZX_OK; |
| } |
| |
| zx_status_t Display::DisplayControllerImplImportImage(image_t* image, zx_unowned_handle_t handle, |
| uint32_t index) { |
| zx_status_t status, status2; |
| fuchsia_sysmem_BufferCollectionInfo_2 collection_info; |
| status = |
| fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(handle, &status2, &collection_info); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (status2 != ZX_OK) { |
| return status2; |
| } |
| |
| zx::vmo vmo; |
| if (index < collection_info.buffer_count) { |
| vmo = zx::vmo(collection_info.buffers[index].vmo); |
| collection_info.buffers[index].vmo = ZX_HANDLE_INVALID; |
| } |
| for (uint32_t i = 0; i < collection_info.buffer_count; ++i) { |
| zx_handle_close(collection_info.buffers[i].vmo); |
| } |
| |
| if (!vmo.is_valid()) { |
| zxlogf(ERROR, "%s: invalid index", kTag); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| uint64_t offset = collection_info.buffers[index].vmo_usable_start; |
| |
| if (collection_info.settings.buffer_settings.heap != |
| fuchsia_sysmem_HeapType_GOLDFISH_DEVICE_LOCAL) { |
| return ImportVmoImage(image, std::move(vmo), offset); |
| } |
| |
| if (!collection_info.settings.has_image_format_constraints || offset) { |
| zxlogf(ERROR, "%s: invalid image format or offset", kTag); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| 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) { |
| fbl::AutoLock lock(&lock_); |
| CloseColorBufferLocked(color_buffer->id); |
| } |
| |
| async::PostTask(loop_.dispatcher(), [this, color_buffer] { |
| const auto color_buffer_maps = {¤t_cb_, &pending_cb_}; |
| for (const auto map : color_buffer_maps) { |
| for (const auto& kv : *map) { |
| if (kv.second == color_buffer) { |
| map->at(kv.first) = nullptr; |
| } |
| } |
| } |
| delete color_buffer; |
| }); |
| } |
| |
| uint32_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_DISPLAY_OK; |
| } |
| for (unsigned i = 0; i < display_count; i++) { |
| const size_t layer_count = display_configs[i]->layer_count; |
| if (layer_count > 0) { |
| ZX_DEBUG_ASSERT(devices_.find(display_configs[i]->display_id) != devices_.end()); |
| const Device& device = devices_[display_configs[i]->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_DISPLAY_OK; |
| } |
| |
| zx_status_t Display::PresentColorBuffer(uint32_t display_id, ColorBuffer* color_buffer) { |
| if (!color_buffer) { |
| return ZX_HANDLE_INVALID; |
| } |
| if (color_buffer->sync_event.is_valid()) { |
| return ZX_ERR_SHOULD_WAIT; |
| } |
| |
| 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; |
| } |
| color_buffer->sync_event = std::move(event_display); |
| |
| // Set up async wait. |
| color_buffer->async_wait = |
| std::make_unique<async::WaitOnce>(color_buffer->sync_event.get(), ZX_EVENTPAIR_SIGNALED, 0u); |
| color_buffer->async_wait->Begin( |
| loop_.dispatcher(), |
| [this, color_buffer, display_id](async_dispatcher_t* dispatcher, async::WaitOnce* wait, |
| zx_status_t status, const zx_packet_signal_t* signal) { |
| TRACE_DURATION("gfx", "Display::SyncEventHandler", "color_buffer", color_buffer->id); |
| if (status == ZX_ERR_CANCELED) { |
| zxlogf(INFO, "Wait cancelled.\n"); |
| return; |
| } |
| ZX_DEBUG_ASSERT_MSG(status == ZX_OK, "Invalid wait status: %d\n", status); |
| |
| color_buffer->async_wait.reset(); |
| color_buffer->sync_event.reset(); |
| current_cb_[display_id] = color_buffer; |
| }); |
| |
| // Update host-writeable display buffers before presenting. |
| if (color_buffer->paddr) { |
| fbl::AutoLock lock(&lock_); |
| uint32_t result; |
| zx_status_t status = UpdateColorBufferLocked(color_buffer->id, color_buffer->paddr, |
| color_buffer->width, color_buffer->height, |
| color_buffer->format, color_buffer->size, &result); |
| if (status != ZX_OK || result) { |
| zxlogf(ERROR, "%s : color buffer update failed: %d:%u", kTag, status, result); |
| return status; |
| } |
| } |
| |
| // Present the buffer. |
| { |
| fbl::AutoLock lock(&lock_); |
| |
| uint32_t host_display_id = devices_[display_id].host_display_id; |
| if (host_display_id) { |
| // Set color buffer for secondary displays. |
| uint32_t result; |
| status = SetDisplayColorBufferLocked(host_display_id, color_buffer->id, &result); |
| if (status != ZX_OK || result) { |
| zxlogf(ERROR, "%s: failed to set display color buffer", kTag); |
| return status; |
| } |
| } else { |
| status = FbPostLocked(color_buffer->id); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: FbPost failed: %d", kTag, status); |
| return status; |
| } |
| } |
| |
| 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) { |
| for (auto it : devices_) { |
| uint64_t handle = 0; |
| for (unsigned i = 0; i < display_count; i++) { |
| if (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; |
| } |
| } |
| auto color_buffer = reinterpret_cast<ColorBuffer*>(handle); |
| if (color_buffer && !color_buffer->id) { |
| 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_); |
| |
| status = control_.GetColorBuffer(std::move(vmo), &color_buffer->id); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: failed to get color buffer: %d", kTag, status); |
| } |
| |
| // 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->id) { |
| uint32_t result = 0; |
| status = SetColorBufferVulkanModeLocked(color_buffer->id, 0, &result); |
| if (status != ZX_OK || result) { |
| zxlogf(ERROR, "%s: failed to set vulkan mode: %d %d", kTag, status, result); |
| } |
| } |
| } |
| } |
| |
| if (color_buffer) { |
| async::PostTask(loop_.dispatcher(), [this, color_buffer, display_id = it.first] { |
| pending_cb_[display_id] = color_buffer; |
| }); |
| } |
| } |
| } |
| |
| zx_status_t Display::DisplayControllerImplGetSysmemConnection(zx::channel connection) { |
| fbl::AutoLock lock(&lock_); |
| |
| zx_status_t status = pipe_.ConnectSysmem(std::move(connection)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: failed to connect to sysmem: %d", kTag, status); |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Display::DisplayControllerImplSetBufferCollectionConstraints(const image_t* config, |
| uint32_t collection) { |
| fuchsia_sysmem_BufferCollectionConstraints constraints = {}; |
| constraints.usage.display = fuchsia_sysmem_displayUsageLayer; |
| constraints.has_buffer_memory_constraints = true; |
| fuchsia_sysmem_BufferMemoryConstraints& 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_SYSTEM_RAM; |
| buffer_constraints.heap_permitted[1] = fuchsia_sysmem_HeapType_GOLDFISH_DEVICE_LOCAL; |
| constraints.image_format_constraints_count = 4; |
| for (uint32_t i = 0; i < constraints.image_format_constraints_count; i++) { |
| fuchsia_sysmem_ImageFormatConstraints& image_constraints = |
| constraints.image_format_constraints[i]; |
| image_constraints.pixel_format.type = |
| i & 0b01 ? fuchsia_sysmem_PixelFormatType_R8G8B8A8 : fuchsia_sysmem_PixelFormatType_BGRA32; |
| image_constraints.pixel_format.has_format_modifier = true; |
| image_constraints.pixel_format.format_modifier.value = |
| i & 0b10 ? fuchsia_sysmem_FORMAT_MODIFIER_LINEAR |
| : fuchsia_sysmem_FORMAT_MODIFIER_GOOGLE_GOLDFISH_OPTIMAL; |
| image_constraints.color_spaces_count = 1; |
| image_constraints.color_space[0].type = fuchsia_sysmem_ColorSpaceType_SRGB; |
| 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; |
| } |
| |
| zx_status_t status = |
| fuchsia_sysmem_BufferCollectionSetConstraints(collection, true, &constraints); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: failed to set constraints", kTag); |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Display::DisplayControllerImplGetSingleBufferFramebuffer(zx::vmo* out_vmo, |
| uint32_t* out_stride) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t Display::WriteLocked(uint32_t cmd_size) { |
| TRACE_DURATION("gfx", "Display::Write", "cmd_size", cmd_size); |
| |
| auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt()); |
| uint32_t remaining = cmd_size; |
| while (remaining) { |
| buffer->id = id_; |
| buffer->cmd = PIPE_CMD_CODE_WRITE; |
| buffer->status = PIPE_ERROR_INVAL; |
| buffer->rw_params.ptrs[0] = io_buffer_.phys() + cmd_size - remaining; |
| buffer->rw_params.sizes[0] = remaining; |
| buffer->rw_params.buffers_count = 1; |
| buffer->rw_params.consumed_size = 0; |
| pipe_.Exec(id_); |
| |
| if (buffer->rw_params.consumed_size) { |
| remaining -= buffer->rw_params.consumed_size; |
| continue; |
| } |
| |
| // Early out if error is not because of back-pressure. |
| if (buffer->status != PIPE_ERROR_AGAIN) { |
| zxlogf(ERROR, "%s: write to pipe buffer failed: %d", kTag, buffer->status); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| buffer->id = id_; |
| buffer->cmd = PIPE_CMD_CODE_WAKE_ON_WRITE; |
| buffer->status = PIPE_ERROR_INVAL; |
| pipe_.Exec(id_); |
| |
| // Wait for pipe to become writable. |
| zx_status_t status = pipe_event_.wait_one(fuchsia_hardware_goldfish::wire::SIGNAL_HANGUP | |
| fuchsia_hardware_goldfish::wire::SIGNAL_WRITABLE, |
| zx::time::infinite(), nullptr); |
| if (status != ZX_OK) { |
| if (status != ZX_ERR_CANCELED) { |
| zxlogf(ERROR, "%s: zx_object_wait_one failed: %d", kTag, status); |
| } |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Display::ReadResultLocked(uint32_t* result, uint32_t count) { |
| TRACE_DURATION("gfx", "Display::ReadResult"); |
| |
| size_t length = sizeof(*result) * count; |
| size_t remaining = length; |
| while (remaining) { |
| auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt()); |
| buffer->id = id_; |
| buffer->cmd = PIPE_CMD_CODE_READ; |
| buffer->status = PIPE_ERROR_INVAL; |
| buffer->rw_params.ptrs[0] = io_buffer_.phys(); |
| buffer->rw_params.sizes[0] = static_cast<uint32_t>(remaining); |
| buffer->rw_params.buffers_count = 1; |
| buffer->rw_params.consumed_size = 0; |
| pipe_.Exec(id_); |
| |
| // Positive consumed size always indicate a successful transfer. |
| if (buffer->rw_params.consumed_size) { |
| memcpy(reinterpret_cast<char*>(result) + (length - remaining), io_buffer_.virt(), |
| buffer->rw_params.consumed_size); |
| remaining -= buffer->rw_params.consumed_size; |
| continue; |
| } |
| |
| // Early out if error is not because of back-pressure. |
| if (buffer->status != PIPE_ERROR_AGAIN) { |
| zxlogf(ERROR, "%s: reading result failed: %d", kTag, buffer->status); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| buffer->id = id_; |
| buffer->cmd = PIPE_CMD_CODE_WAKE_ON_READ; |
| buffer->status = PIPE_ERROR_INVAL; |
| pipe_.Exec(id_); |
| ZX_DEBUG_ASSERT(!buffer->status); |
| |
| // Wait for pipe to become readable. |
| zx_status_t status = pipe_event_.wait_one(fuchsia_hardware_goldfish::wire::SIGNAL_HANGUP | |
| fuchsia_hardware_goldfish::wire::SIGNAL_READABLE, |
| zx::time::infinite(), nullptr); |
| if (status != ZX_OK) { |
| if (status != ZX_ERR_CANCELED) { |
| zxlogf(ERROR, "%s: zx_object_wait_one failed: %d", kTag, status); |
| } |
| return status; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Display::ExecuteCommandLocked(uint32_t cmd_size, uint32_t* result) { |
| TRACE_DURATION("gfx", "Display::ExecuteCommand", "cmd_size", cmd_size); |
| |
| zx_status_t status = WriteLocked(cmd_size); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return ReadResultLocked(result, 1); |
| } |
| |
| int32_t Display::GetFbParamLocked(uint32_t param, int32_t default_value) { |
| TRACE_DURATION("gfx", "Display::GetFbParam", "param", param); |
| |
| auto cmd = static_cast<GetFbParamCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcGetFbParam; |
| cmd->size = kSize_rcGetFbParam; |
| cmd->param = param; |
| |
| uint32_t result; |
| zx_status_t status = ExecuteCommandLocked(kSize_rcGetFbParam, &result); |
| return status == ZX_OK ? result : default_value; |
| } |
| |
| zx_status_t Display::CreateColorBufferLocked(uint32_t width, uint32_t height, uint32_t format, |
| uint32_t* id) { |
| TRACE_DURATION("gfx", "Display::CreateColorBuffer", "width", width, "height", height, "format", |
| format); |
| |
| auto cmd = static_cast<CreateColorBufferCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcCreateColorBuffer; |
| cmd->size = kSize_rcCreateColorBuffer; |
| cmd->width = width; |
| cmd->height = height; |
| cmd->internalformat = format; |
| |
| return ExecuteCommandLocked(kSize_rcCreateColorBuffer, id); |
| } |
| |
| zx_status_t Display::OpenColorBufferLocked(uint32_t id) { |
| TRACE_DURATION("gfx", "Display::OpenColorBuffer", "id", id); |
| |
| auto cmd = static_cast<OpenColorBufferCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcOpenColorBuffer; |
| cmd->size = kSize_rcOpenColorBuffer; |
| cmd->id = id; |
| |
| return WriteLocked(kSize_rcOpenColorBuffer); |
| } |
| |
| zx_status_t Display::CloseColorBufferLocked(uint32_t id) { |
| TRACE_DURATION("gfx", "Display::CloseColorBuffer", "id", id); |
| |
| auto cmd = static_cast<CloseColorBufferCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcCloseColorBuffer; |
| cmd->size = kSize_rcCloseColorBuffer; |
| cmd->id = id; |
| |
| return WriteLocked(kSize_rcCloseColorBuffer); |
| } |
| |
| zx_status_t Display::SetColorBufferVulkanModeLocked(uint32_t id, uint32_t mode, uint32_t* result) { |
| TRACE_DURATION("gfx", "Display::SetColorBufferVulkanMode", "id", id, "mode", mode); |
| |
| auto cmd = static_cast<SetColorBufferVulkanModeCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcSetColorBufferVulkanMode; |
| cmd->size = kSize_rcSetColorBufferVulkanMode; |
| cmd->id = id; |
| cmd->mode = mode; |
| |
| return ExecuteCommandLocked(kSize_rcSetColorBufferVulkanMode, result); |
| } |
| |
| zx_status_t Display::UpdateColorBufferLocked(uint32_t id, zx_paddr_t paddr, uint32_t width, |
| uint32_t height, uint32_t format, size_t size, |
| uint32_t* result) { |
| TRACE_DURATION("gfx", "Display::UpdateColorBuffer", "size", size); |
| |
| auto cmd = static_cast<UpdateColorBufferCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcUpdateColorBuffer; |
| cmd->size = kSize_rcUpdateColorBuffer + static_cast<uint32_t>(size); |
| cmd->id = id; |
| cmd->x = 0; |
| cmd->y = 0; |
| cmd->width = width; |
| cmd->height = height; |
| cmd->format = format; |
| cmd->type = GL_UNSIGNED_BYTE; |
| cmd->size_pixels = static_cast<uint32_t>(size); |
| |
| auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt()); |
| buffer->id = id_; |
| buffer->cmd = PIPE_CMD_CODE_WRITE; |
| buffer->status = PIPE_ERROR_INVAL; |
| buffer->rw_params.ptrs[0] = io_buffer_.phys(); |
| buffer->rw_params.ptrs[1] = paddr; |
| buffer->rw_params.sizes[0] = kSize_rcUpdateColorBuffer; |
| buffer->rw_params.sizes[1] = static_cast<uint32_t>(size); |
| buffer->rw_params.buffers_count = 2; |
| buffer->rw_params.consumed_size = 0; |
| |
| pipe_.Exec(id_); |
| ZX_DEBUG_ASSERT(buffer->rw_params.consumed_size == |
| static_cast<int32_t>(kSize_rcUpdateColorBuffer + size)); |
| |
| return ReadResultLocked(result, 1); |
| } |
| |
| zx_status_t Display::FbPostLocked(uint32_t id) { |
| TRACE_DURATION("gfx", "Display::FbPost", "id", id); |
| |
| auto cmd = static_cast<FbPostCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcFbPost; |
| cmd->size = kSize_rcFbPost; |
| cmd->id = id; |
| |
| return WriteLocked(kSize_rcFbPost); |
| } |
| |
| zx_status_t Display::CreateDisplayLocked(uint32_t* result) { |
| TRACE_DURATION("gfx", "Display::CreateDisplay"); |
| |
| auto cmd = static_cast<CreateDisplayCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcCreateDisplay; |
| cmd->size = kSize_rcCreateDisplay; |
| cmd->size_display_id = sizeof(uint32_t); |
| |
| zx_status_t status = WriteLocked(kSize_rcCreateDisplay); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return ReadResultLocked(result, 2); |
| } |
| |
| zx_status_t Display::DestroyDisplayLocked(uint32_t display_id, uint32_t* result) { |
| TRACE_DURATION("gfx", "Display::DestroyDisplay", "display_id", display_id); |
| |
| auto cmd = static_cast<DestroyDisplayCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcDestroyDisplay; |
| cmd->size = kSize_rcDestroyDisplay; |
| cmd->display_id = display_id; |
| |
| return ExecuteCommandLocked(kSize_rcDestroyDisplay, result); |
| } |
| |
| zx_status_t Display::SetDisplayColorBufferLocked(uint32_t display_id, uint32_t id, |
| uint32_t* result) { |
| TRACE_DURATION("gfx", "Display::SetDisplayColorBuffer", "display_id", display_id, "id", id); |
| |
| auto cmd = static_cast<SetDisplayColorBufferCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcSetDisplayColorBuffer; |
| cmd->size = kSize_rcSetDisplayColorBuffer; |
| cmd->display_id = display_id; |
| cmd->id = id; |
| |
| return ExecuteCommandLocked(kSize_rcSetDisplayColorBuffer, result); |
| } |
| |
| zx_status_t Display::SetDisplayPoseLocked(uint32_t display_id, int32_t x, int32_t y, uint32_t w, |
| uint32_t h, uint32_t* result) { |
| TRACE_DURATION("gfx", "Display::SetDisplayPose", "display_id", display_id); |
| |
| auto cmd = static_cast<SetDisplayPoseCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcSetDisplayPose; |
| cmd->size = kSize_rcSetDisplayPose; |
| cmd->display_id = display_id; |
| cmd->x = x; |
| cmd->y = y; |
| cmd->w = w; |
| cmd->h = h; |
| |
| return ExecuteCommandLocked(kSize_rcSetDisplayPose, result); |
| } |
| |
| zx_status_t Display::SetupDisplayLocked(uint64_t display_id) { |
| Device& device = devices_[display_id]; |
| |
| // Create secondary displays. |
| if (display_id != kPrimaryDisplayId) { |
| uint32_t result[2] = {0, 1}; |
| zx_status_t status = CreateDisplayLocked(result); |
| if (status != ZX_OK || result[1]) { |
| zxlogf(ERROR, "%s: failed to create display: %d %d", kTag, status, result[1]); |
| return status != ZX_OK ? status : ZX_ERR_INTERNAL; |
| } |
| device.host_display_id = result[0]; |
| } |
| 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); |
| uint32_t result = 1; |
| zx_status_t status = |
| SetDisplayPoseLocked(device.host_display_id, device.x, device.y, width, height, &result); |
| if (status != ZX_OK || result) { |
| zxlogf(ERROR, "%s: failed to set display pose: %d %d", kTag, status, result); |
| return status != ZX_OK ? status : ZX_ERR_INTERNAL; |
| } |
| device.expected_next_flush = async::Now(loop_.dispatcher()); |
| |
| return ZX_OK; |
| } |
| |
| void Display::TeardownDisplay(uint64_t display_id) { |
| Device& device = devices_[display_id]; |
| |
| if (device.host_display_id) { |
| fbl::AutoLock lock(&lock_); |
| uint32_t result; |
| zx_status_t status = DestroyDisplayLocked(device.host_display_id, &result); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| ZX_DEBUG_ASSERT(!result); |
| } |
| } |
| |
| void Display::FlushDisplay(async_dispatcher_t* dispatcher, uint64_t 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; |
| |
| ColorBuffer* pending_cb = nullptr; |
| if (pending_cb_.find(display_id) != pending_cb_.end()) { |
| pending_cb = pending_cb_[display_id]; |
| } |
| |
| if (pending_cb) { |
| zx_status_t status = PresentColorBuffer(display_id, pending_cb); |
| ZX_DEBUG_ASSERT(status == ZX_OK || status == ZX_ERR_SHOULD_WAIT); |
| } |
| |
| { |
| fbl::AutoLock lock(&flush_lock_); |
| |
| if (dc_intf_.is_valid()) { |
| ColorBuffer* current_cb = nullptr; |
| if (current_cb_.find(display_id) != current_cb_.end()) { |
| current_cb = current_cb_[display_id]; |
| } |
| uint64_t handles[] = {reinterpret_cast<uint64_t>(current_cb)}; |
| zx::time now = async::Now(dispatcher); |
| dc_intf_.OnDisplayVsync(display_id, now.get(), handles, current_cb ? 1 : 0); |
| } |
| } |
| |
| // 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"); |