| // 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 <dirent.h> |
| #include <errno.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/array.h> |
| #include <fbl/string.h> |
| #include <fbl/string_printf.h> |
| #include <fbl/unique_ptr.h> |
| #include <fbl/vector.h> |
| #include <fcntl.h> |
| #include <fuchsia/hardware/input/c/fidl.h> |
| #include <gfx/gfx.h> |
| #include <hid/paradise.h> |
| #include <hid/usages.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/fidl/coding.h> |
| #include <lib/fit/defer.h> |
| #include <lib/fzl/fdio.h> |
| #include <limits.h> |
| #include <math.h> |
| #include <poll.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <trace-provider/provider.h> |
| #include <trace/event.h> |
| #include <unistd.h> |
| #include <zircon/device/display-controller.h> |
| #include <zircon/process.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| #include <zircon/status.h> |
| |
| #include <utility> |
| |
| #include "fuchsia/hardware/display/c/fidl.h" |
| |
| #define DEV_INPUT "/dev/class/input" |
| #define NUM_FINGERS 5u |
| #define STYLUS_PEN 0 |
| #define TOUCH_PEN 1 |
| #define NUM_PENCILS 2 |
| #define NUM_BUFFERS 2u |
| #define SPRITE_DIM 256u |
| #define SPRITE_RAD (SPRITE_DIM / 2) |
| #define SPRITE_FORMAT ZX_PIXEL_FORMAT_ARGB_8888 |
| #define PEN_VELOCITY_MAX 12.5f |
| // Interpolation factor used to compute responsive velocity. Valid range |
| // is 0.0 to 1.0, where 1.0 takes only current velocity into account. |
| #define RESPONSIVE_VELOCITY_FACTOR 0.75 |
| // Interpolation factor used to compute smooth velocity. Valid range |
| // is 0.0 to 1.0, where 1.0 takes only current velocity into account. |
| #define SMOOTH_VELOCITY_FACTOR 0.25 |
| // Interpolation factor used to compute pen movement. Valid range |
| // is 0.0 to 1.0, where 1.0 takes only smooth velocity into account. |
| #define PEN_MOVEMENT_FACTOR 0.25 |
| #define MAX_BLUR_RADIUS 90.0 |
| #define MIN_MOVEMENT_FOR_CURSOR_MOTION_BLUR 2.0 |
| #define CURSOR_MOVEMENT_PREDICTION_MS (1000.0f / 60.0f) |
| #define CURSOR_HOTSPOT_X 15 |
| #define CURSOR_HOTSPOT_Y 14 |
| #define ORIGIN_VELOCITY_MAX 10.0f |
| #define ORIGIN_MOVEMENT_FACTOR 0.9f |
| // Input prediction models depend on velocity which need to be sampled |
| // at a fixed interval. For example, the lack of input over the interval |
| // affects the model. Note: This is currently set to cause an update |
| // for each frame when VSync is enabled. |
| #define INPUT_PREDICTION_UPDATE_INTERVAL_MS 16 |
| |
| #define HID_REPORT_TRACE_ID(trace_id, report_id) \ |
| (((uint64_t)(report_id) << 32) | (trace_id)) |
| |
| enum class VSync { |
| ON, |
| OFF, |
| ADAPTIVE, |
| }; |
| |
| enum class TouchDevice { |
| PARADISE_V2, |
| PARADISE_V3, |
| }; |
| |
| typedef struct point { |
| uint32_t x; |
| uint32_t y; |
| } point_t; |
| |
| typedef struct line { |
| point_t p1; |
| point_t p2; |
| } line_t; |
| |
| typedef struct vector { |
| int32_t x; |
| int32_t y; |
| } vector_t; |
| |
| typedef struct pointf { |
| float x; |
| float y; |
| } pointf_t; |
| |
| typedef struct vectorf { |
| float x; |
| float y; |
| } vectorf_t; |
| |
| typedef struct rect { |
| uint32_t x1; |
| uint32_t y1; |
| uint32_t x2; |
| uint32_t y2; |
| } rect_t; |
| |
| typedef struct buffer { |
| zx_handle_t vmo; |
| uintptr_t data; |
| uint64_t image_id; |
| zx_handle_t wait_event; |
| uint64_t wait_event_id; |
| rect_t damage; |
| } buffer_t; |
| |
| static zx_handle_t dc_handle = ZX_HANDLE_INVALID; |
| static int32_t txid = 0; |
| |
| #include "background.inc" |
| #include "cursor.inc" |
| |
| static double scale(double z, uint32_t screen_dim, uint32_t rpt_dim) { |
| return (z * screen_dim) / rpt_dim; |
| } |
| |
| static void vector_interpolate(vectorf_t* result, const vectorf_t* start, |
| const vectorf_t* end, float f) { |
| result->x = start->x + (end->x - start->x) * f; |
| result->y = start->y + (end->y - start->y) * f; |
| } |
| |
| static void copy_rect(uint32_t* dst, const uint32_t* src, uint32_t dst_stride, |
| uint32_t src_stride, uint32_t x1, uint32_t y1, |
| uint32_t x2, uint32_t y2) { |
| size_t bytes_per_line = (x2 - x1) * sizeof(uint32_t); |
| size_t lines = y2 - y1; |
| |
| dst += y1 * dst_stride + x1; |
| src += y1 * src_stride + x1; |
| |
| while (lines--) { |
| memcpy(dst, src, bytes_per_line); |
| dst += dst_stride; |
| src += src_stride; |
| } |
| } |
| |
| /* source dimensions must be a power of two. */ |
| static void rotate_rect(uint32_t* dst, const uint32_t* src, uint32_t dst_width, |
| uint32_t dst_height, uint32_t dst_stride, |
| uint32_t src_width, uint32_t src_height, |
| uint32_t src_stride, double dst_cx, double dst_cy, |
| double src_cx, double src_cy, double angle) { |
| ZX_ASSERT(fbl::is_pow2(src_width)); |
| ZX_ASSERT(fbl::is_pow2(src_height)); |
| |
| double du_y = sin(-angle); |
| double dv_y = cos(-angle); |
| double du_x = dv_y; |
| double dv_x = -du_y; |
| double startu = src_cx - (dst_cx * dv_y + dst_cy * du_y); |
| double startv = src_cy - (dst_cx * dv_x + dst_cy * du_x); |
| double rowu = startu; |
| double rowv = startv; |
| int width_mask = src_width - 1; |
| int height_mask = src_height - 1; |
| |
| for (uint32_t y = 0; y < dst_height; y++) { |
| uint32_t* d = dst + y * dst_stride; |
| double u = rowu; |
| double v = rowv; |
| |
| for (uint32_t x = 0; x < dst_width; x++) { |
| const uint32_t* s = src + ((((int)v) & height_mask) * src_stride) + |
| (((int)u) & width_mask); |
| |
| *d++ = *s++; |
| |
| u += du_x; |
| v += dv_x; |
| } |
| |
| rowu += du_y; |
| rowv += dv_y; |
| } |
| } |
| |
| static inline uint8_t mul_div_255_round(uint16_t a, uint16_t b) { |
| unsigned prod = a * b + 128; |
| return (uint8_t)((prod + (prod >> 8)) >> 8); |
| } |
| |
| static inline void argb_8888_unpack_mul(uint32_t p, uint8_t* a, uint8_t* r, |
| uint8_t* g, uint8_t* b) { |
| *a = (uint8_t)((p & 0xff000000) >> 24); |
| *r = (uint8_t)((p & 0x00ff0000) >> 16); |
| *g = (uint8_t)((p & 0x0000ff00) >> 8); |
| *b = (uint8_t)(p & 0x000000ff); |
| if (*a != 255) { |
| *r = mul_div_255_round(*r, *a); |
| *g = mul_div_255_round(*g, *a); |
| *b = mul_div_255_round(*b, *a); |
| } |
| } |
| |
| /* width must be a power of two. */ |
| static void blur_rect(uint32_t* dst, const uint32_t* src, uint32_t width, |
| uint32_t height, uint32_t stride, int radius) { |
| ZX_ASSERT(fbl::is_pow2(width)); |
| ZX_ASSERT(radius > 0); |
| |
| int width_mask = width - 1; |
| int radius0 = -radius; |
| int radius1 = radius + 1; |
| int size = radius + radius + 1; |
| |
| for (uint32_t y = 0; y < height; y++) { |
| uint8_t a, r, g, b; |
| uint32_t a32 = 0; |
| uint32_t r32 = 0; |
| uint32_t g32 = 0; |
| uint32_t b32 = 0; |
| |
| for (int x = radius0; x < radius1; ++x) { |
| argb_8888_unpack_mul(src[x & width_mask], &a, &r, &g, &b); |
| a32 += a; |
| r32 += r; |
| g32 += g; |
| b32 += b; |
| } |
| |
| for (int x = 0; x <= width_mask; ++x) { |
| dst[x] = a32 ? ((a32 / size) << 24) | (((255 * r32) / a32) << 16) | |
| (((255 * g32) / a32) << 8) | (((255 * b32) / a32)) |
| : 0; |
| |
| argb_8888_unpack_mul(src[(x + radius0) & width_mask], &a, &r, &g, &b); |
| a32 -= a; |
| r32 -= r; |
| g32 -= g; |
| b32 -= b; |
| |
| argb_8888_unpack_mul(src[(x + radius1) & width_mask], &a, &r, &g, &b); |
| a32 += a; |
| r32 += r; |
| g32 += g; |
| b32 += b; |
| } |
| |
| src += stride; |
| dst += stride; |
| } |
| } |
| |
| static bool is_rect_empty(const rect_t* rect) { |
| return rect->x1 >= rect->x2 && rect->y1 >= rect->y2; |
| } |
| |
| static void union_rects(rect_t* dst, const rect_t* a, const rect_t* b) { |
| if (is_rect_empty(b)) { |
| *dst = *a; |
| } else if (is_rect_empty(a)) { |
| *dst = *b; |
| } else { |
| dst->x1 = fbl::min(a->x1, b->x1); |
| dst->y1 = fbl::min(a->y1, b->y1); |
| dst->x2 = fbl::max(a->x2, b->x2); |
| dst->y2 = fbl::max(a->y2, b->y2); |
| } |
| } |
| |
| static fbl::String rect_as_string(const rect_t* rect) { |
| return fbl::StringPrintf("%d,%d %dx%d", rect->x1, rect->y1, |
| rect->x2 - rect->x1, rect->y2 - rect->y1); |
| } |
| |
| static void prepare_poll(int touchfd, int touchpadfd, int* startfd, int* endfd, |
| struct pollfd* fds) { |
| *startfd = 1; |
| *endfd = 1; |
| if (touchfd >= 0) { |
| fds[0].fd = touchfd; |
| fds[0].events = POLLIN; |
| fds[0].revents = 0; |
| *startfd = 0; |
| } |
| if (touchpadfd >= 0) { |
| fds[1].fd = touchpadfd; |
| fds[1].events = POLLIN; |
| fds[1].revents = 0; |
| *endfd = 2; |
| } |
| } |
| |
| template <typename T> |
| void parse_paradise_touch_report(uint8_t* r, uint32_t width, uint32_t height, |
| pointf_t touch[NUM_FINGERS]) { |
| const auto report = reinterpret_cast<T*>(r); |
| |
| for (uint8_t c = 0; c < NUM_FINGERS; c++) { |
| touch[c].x = NAN; |
| touch[c].y = NAN; |
| if (paradise_finger_flags_tswitch(report->fingers[c].flags)) { |
| touch[c].x = (float)scale(report->fingers[c].x, width, PARADISE_X_MAX); |
| touch[c].y = (float)scale(report->fingers[c].y, height, PARADISE_Y_MAX); |
| } |
| } |
| } |
| |
| void parse_paradise_touchpad_report(uint8_t* r, uint32_t width, uint32_t height, |
| pointf_t touch[NUM_FINGERS], |
| uint8_t* button) { |
| const auto report = reinterpret_cast<paradise_touchpad_t*>(r); |
| |
| for (uint8_t c = 0; c < NUM_FINGERS; c++) { |
| touch[c].x = NAN; |
| touch[c].y = NAN; |
| if (report->fingers[c].tip_switch) { |
| touch[c].x = (float)scale(report->fingers[c].x, width, PARADISE_X_MAX); |
| touch[c].y = (float)scale(report->fingers[c].y, height, PARADISE_Y_MAX); |
| } |
| } |
| *button = report->button; |
| } |
| |
| void parse_paradise_stylus_report(uint8_t* r, uint32_t width, uint32_t height, |
| pointf_t* pen) { |
| const auto report = reinterpret_cast<paradise_stylus_t*>(r); |
| |
| if (paradise_stylus_status_tswitch(report->status)) { |
| pen->x = (float)scale(report->x, width, PARADISE_STYLUS_X_MAX); |
| pen->y = (float)scale(report->y, height, PARADISE_STYLUS_Y_MAX); |
| } else { |
| pen->x = NAN; |
| pen->y = NAN; |
| } |
| } |
| |
| static zx_status_t compute_linear_image_stride(uint32_t width, |
| zx_pixel_format_t format, |
| uint32_t* stride_out) { |
| zx_status_t status; |
| |
| fuchsia_hardware_display_ControllerComputeLinearImageStrideRequest stride_msg; |
| stride_msg.hdr.ordinal = |
| fuchsia_hardware_display_ControllerComputeLinearImageStrideOrdinal; |
| stride_msg.hdr.txid = txid++; |
| stride_msg.width = width; |
| stride_msg.pixel_format = format; |
| |
| fuchsia_hardware_display_ControllerComputeLinearImageStrideResponse |
| stride_rsp; |
| zx_channel_call_args_t stride_call = {}; |
| stride_call.wr_bytes = &stride_msg; |
| stride_call.rd_bytes = &stride_rsp; |
| stride_call.wr_num_bytes = sizeof(stride_msg); |
| stride_call.rd_num_bytes = sizeof(stride_rsp); |
| uint32_t actual_bytes, actual_handles; |
| if ((status = zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &stride_call, |
| &actual_bytes, &actual_handles)) != ZX_OK) { |
| return status; |
| } |
| |
| *stride_out = stride_rsp.stride; |
| return ZX_OK; |
| } |
| |
| static zx_status_t import_image(zx_handle_t handle, uint32_t width, |
| uint32_t height, zx_pixel_format_t format, |
| uint64_t* id_out) { |
| zx_status_t status; |
| zx_handle_t dup; |
| status = zx_handle_duplicate(handle, ZX_RIGHT_SAME_RIGHTS, &dup); |
| ZX_ASSERT(status == ZX_OK); |
| |
| fuchsia_hardware_display_ControllerImportVmoImageRequest import_msg = {}; |
| import_msg.hdr.ordinal = |
| fuchsia_hardware_display_ControllerImportVmoImageOrdinal; |
| import_msg.hdr.txid = txid++; |
| import_msg.image_config.height = height; |
| import_msg.image_config.width = width; |
| import_msg.image_config.pixel_format = format; |
| import_msg.image_config.type = IMAGE_TYPE_SIMPLE; |
| import_msg.vmo = FIDL_HANDLE_PRESENT; |
| import_msg.offset = 0; |
| |
| fuchsia_hardware_display_ControllerImportVmoImageResponse import_rsp; |
| zx_channel_call_args_t import_call = {}; |
| import_call.wr_bytes = &import_msg; |
| import_call.wr_handles = &dup; |
| import_call.rd_bytes = &import_rsp; |
| import_call.wr_num_bytes = sizeof(import_msg); |
| import_call.wr_num_handles = 1; |
| import_call.rd_num_bytes = sizeof(import_rsp); |
| uint32_t actual_bytes, actual_handles; |
| if ((status = zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &import_call, |
| &actual_bytes, &actual_handles)) != ZX_OK) { |
| return status; |
| } |
| |
| if (import_rsp.res != ZX_OK) { |
| return import_rsp.res; |
| } |
| |
| *id_out = import_rsp.image_id; |
| return ZX_OK; |
| } |
| |
| static void release_image(uint64_t image_id) { |
| fuchsia_hardware_display_ControllerReleaseEventRequest release_img_msg; |
| release_img_msg.hdr.ordinal = |
| fuchsia_hardware_display_ControllerReleaseEventOrdinal; |
| release_img_msg.hdr.txid = txid++; |
| release_img_msg.id = image_id; |
| zx_channel_write(dc_handle, 0, &release_img_msg, sizeof(release_img_msg), |
| NULL, 0); |
| } |
| |
| static zx_status_t import_event(zx_handle_t handle, uint64_t id) { |
| zx_status_t status; |
| zx_handle_t dup; |
| status = zx_handle_duplicate(handle, ZX_RIGHT_SAME_RIGHTS, &dup); |
| ZX_ASSERT(status == ZX_OK); |
| |
| fuchsia_hardware_display_ControllerImportEventRequest import_evt_msg; |
| import_evt_msg.hdr.ordinal = |
| fuchsia_hardware_display_ControllerImportEventOrdinal; |
| import_evt_msg.hdr.txid = txid++; |
| import_evt_msg.id = id; |
| import_evt_msg.event = FIDL_HANDLE_PRESENT; |
| return zx_channel_write(dc_handle, 0, &import_evt_msg, sizeof(import_evt_msg), |
| &dup, 1); |
| } |
| |
| static void release_event(uint64_t id) { |
| fuchsia_hardware_display_ControllerReleaseEventRequest release_evt_msg; |
| release_evt_msg.hdr.ordinal = |
| fuchsia_hardware_display_ControllerReleaseEventOrdinal; |
| release_evt_msg.hdr.txid = txid++; |
| release_evt_msg.id = id; |
| zx_channel_write(dc_handle, 0, &release_evt_msg, sizeof(release_evt_msg), |
| NULL, 0); |
| } |
| |
| static zx_status_t create_layer(uint64_t* layer_id_out) { |
| zx_status_t status; |
| |
| fuchsia_hardware_display_ControllerCreateLayerRequest create_layer_msg; |
| create_layer_msg.hdr.ordinal = |
| fuchsia_hardware_display_ControllerCreateLayerOrdinal; |
| |
| fuchsia_hardware_display_ControllerCreateLayerResponse create_layer_rsp; |
| zx_channel_call_args_t call_args = {}; |
| call_args.wr_bytes = &create_layer_msg; |
| call_args.rd_bytes = &create_layer_rsp; |
| call_args.wr_num_bytes = sizeof(create_layer_msg); |
| call_args.rd_num_bytes = sizeof(create_layer_rsp); |
| uint32_t actual_bytes, actual_handles; |
| if ((status = zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &call_args, |
| &actual_bytes, &actual_handles)) != ZX_OK) { |
| return status; |
| } |
| if (create_layer_rsp.res != ZX_OK) { |
| return create_layer_rsp.res; |
| } |
| |
| *layer_id_out = create_layer_rsp.layer_id; |
| return ZX_OK; |
| } |
| |
| static zx_status_t set_display_layers(uint64_t display_id, uint64_t layer_id, |
| uint64_t sprite_layer_id) { |
| uint8_t fidl_bytes |
| [sizeof(fuchsia_hardware_display_ControllerSetDisplayLayersRequest) + |
| FIDL_ALIGN(sizeof(uint64_t) * 2)]; |
| fuchsia_hardware_display_ControllerSetDisplayLayersRequest* |
| display_layers_msg = |
| (fuchsia_hardware_display_ControllerSetDisplayLayersRequest*) |
| fidl_bytes; |
| display_layers_msg->hdr.ordinal = |
| fuchsia_hardware_display_ControllerSetDisplayLayersOrdinal; |
| display_layers_msg->display_id = display_id; |
| display_layers_msg->layer_ids.count = 2; |
| display_layers_msg->layer_ids.data = (void*)FIDL_ALLOC_PRESENT; |
| uint64_t* layer_list = (uint64_t*)(display_layers_msg + 1); |
| layer_list[0] = layer_id; |
| layer_list[1] = sprite_layer_id; |
| return zx_channel_write(dc_handle, 0, fidl_bytes, sizeof(fidl_bytes), NULL, |
| 0); |
| } |
| |
| static zx_status_t set_layer_config(uint64_t layer_id, uint32_t width, |
| uint32_t height, zx_pixel_format_t format) { |
| fuchsia_hardware_display_ControllerSetLayerPrimaryConfigRequest |
| layer_cfg_msg = {}; |
| layer_cfg_msg.hdr.ordinal = |
| fuchsia_hardware_display_ControllerSetLayerPrimaryConfigOrdinal; |
| layer_cfg_msg.layer_id = layer_id; |
| layer_cfg_msg.image_config.width = width; |
| layer_cfg_msg.image_config.height = height; |
| layer_cfg_msg.image_config.pixel_format = format; |
| layer_cfg_msg.image_config.type = IMAGE_TYPE_SIMPLE; |
| return zx_channel_write(dc_handle, 0, &layer_cfg_msg, sizeof(layer_cfg_msg), |
| NULL, 0); |
| } |
| |
| static zx_status_t set_layer_alpha(uint64_t layer_id, bool alpha) { |
| fuchsia_hardware_display_ControllerSetLayerPrimaryAlphaRequest alpha_msg; |
| alpha_msg.hdr.ordinal = |
| fuchsia_hardware_display_ControllerSetLayerPrimaryAlphaOrdinal; |
| alpha_msg.layer_id = layer_id; |
| alpha_msg.mode = alpha ? fuchsia_hardware_display_AlphaMode_HW_MULTIPLY |
| : fuchsia_hardware_display_AlphaMode_DISABLE; |
| alpha_msg.val = 1.0; |
| return zx_channel_write(dc_handle, 0, &alpha_msg, sizeof(alpha_msg), NULL, 0); |
| } |
| |
| static zx_status_t set_layer_position(uint64_t layer_id, uint32_t src_x, |
| uint32_t src_y, uint32_t dest_x, |
| uint32_t dest_y, uint32_t width, |
| uint32_t height) { |
| fuchsia_hardware_display_ControllerSetLayerPrimaryPositionRequest pos_msg; |
| pos_msg.hdr.ordinal = |
| fuchsia_hardware_display_ControllerSetLayerPrimaryPositionOrdinal; |
| pos_msg.layer_id = layer_id; |
| pos_msg.transform = fuchsia_hardware_display_Transform_IDENTITY; |
| pos_msg.src_frame.width = width; |
| pos_msg.src_frame.height = height; |
| pos_msg.src_frame.x_pos = src_x; |
| pos_msg.src_frame.y_pos = src_y; |
| pos_msg.dest_frame.width = width; |
| pos_msg.dest_frame.height = height; |
| pos_msg.dest_frame.x_pos = dest_x; |
| pos_msg.dest_frame.y_pos = dest_y; |
| return zx_channel_write(dc_handle, 0, &pos_msg, sizeof(pos_msg), NULL, 0); |
| } |
| |
| static zx_status_t set_layer_image(uint64_t layer_id, uint64_t image_id, |
| uint64_t wait_event_id) { |
| fuchsia_hardware_display_ControllerSetLayerImageRequest set_msg; |
| set_msg.hdr.ordinal = fuchsia_hardware_display_ControllerSetLayerImageOrdinal; |
| set_msg.hdr.txid = txid++; |
| set_msg.layer_id = layer_id; |
| set_msg.image_id = image_id; |
| set_msg.wait_event_id = wait_event_id; |
| set_msg.signal_event_id = INVALID_ID; |
| return zx_channel_write(dc_handle, 0, &set_msg, sizeof(set_msg), NULL, 0); |
| } |
| |
| static zx_status_t check_config() { |
| fuchsia_hardware_display_ControllerCheckConfigRequest check_msg; |
| uint8_t check_resp_bytes[ZX_CHANNEL_MAX_MSG_BYTES]; |
| check_msg.discard = false; |
| check_msg.hdr.ordinal = fuchsia_hardware_display_ControllerCheckConfigOrdinal; |
| zx_channel_call_args_t check_call = {}; |
| check_call.wr_bytes = &check_msg; |
| check_call.rd_bytes = check_resp_bytes; |
| check_call.wr_num_bytes = sizeof(check_msg); |
| check_call.rd_num_bytes = sizeof(check_resp_bytes); |
| uint32_t actual_bytes, actual_handles; |
| zx_status_t status; |
| if ((status = zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &check_call, |
| &actual_bytes, &actual_handles)) != ZX_OK) { |
| return status; |
| } |
| |
| const char* err_msg; |
| if ((status = fidl_decode( |
| &fuchsia_hardware_display_ControllerCheckConfigResponseTable, |
| check_resp_bytes, actual_bytes, NULL, 0, &err_msg)) != ZX_OK) { |
| return ZX_ERR_STOP; |
| } |
| |
| fuchsia_hardware_display_ControllerCheckConfigResponse* check_rsp = |
| (fuchsia_hardware_display_ControllerCheckConfigResponse*)check_resp_bytes; |
| if (check_rsp->res != fuchsia_hardware_display_ConfigResult_OK) { |
| fprintf(stderr, "config not valid (%d)\n", check_rsp->res); |
| auto* arr = static_cast<fuchsia_hardware_display_ClientCompositionOp*>( |
| check_rsp->ops.data); |
| for (unsigned i = 0; i < check_rsp->ops.count; i++) { |
| fprintf(stderr, "client composition op (display %ld, layer %ld): %d\n", |
| arr[i].display_id, arr[i].layer_id, arr[i].opcode); |
| } |
| return ZX_ERR_STOP; |
| } |
| return ZX_OK; |
| } |
| |
| static zx_status_t apply_config() { |
| fuchsia_hardware_display_ControllerApplyConfigRequest apply_msg; |
| apply_msg.hdr.txid = txid++; |
| apply_msg.hdr.ordinal = fuchsia_hardware_display_ControllerApplyConfigOrdinal; |
| return zx_channel_write(dc_handle, 0, &apply_msg, sizeof(apply_msg), NULL, 0); |
| } |
| |
| static zx_status_t alloc_image_buffer(uint32_t size, zx_handle_t* vmo_out) { |
| fuchsia_hardware_display_ControllerAllocateVmoRequest alloc_msg; |
| alloc_msg.hdr.ordinal = fuchsia_hardware_display_ControllerAllocateVmoOrdinal; |
| alloc_msg.hdr.txid = txid++; |
| alloc_msg.size = size; |
| |
| fuchsia_hardware_display_ControllerAllocateVmoResponse alloc_rsp; |
| zx_channel_call_args_t call_args = {}; |
| call_args.wr_bytes = &alloc_msg; |
| call_args.rd_bytes = &alloc_rsp; |
| call_args.rd_handles = vmo_out; |
| call_args.wr_num_bytes = sizeof(alloc_msg); |
| call_args.rd_num_bytes = sizeof(alloc_rsp); |
| call_args.rd_num_handles = 1; |
| uint32_t actual_bytes, actual_handles; |
| zx_status_t status; |
| if ((status = zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &call_args, |
| &actual_bytes, &actual_handles)) != ZX_OK) { |
| if (alloc_rsp.res != ZX_OK) { |
| status = alloc_rsp.res; |
| } |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t enable_vsync(bool enable) { |
| fuchsia_hardware_display_ControllerEnableVsyncRequest enable_vsync; |
| enable_vsync.hdr.ordinal = |
| fuchsia_hardware_display_ControllerEnableVsyncOrdinal; |
| enable_vsync.enable = enable; |
| return zx_channel_write(dc_handle, 0, &enable_vsync, sizeof(enable_vsync), |
| NULL, 0); |
| } |
| |
| zx_status_t wait_for_vsync(zx_time_t* timestamp, uint64_t image_ids[2]) { |
| zx_status_t status; |
| zx_handle_t observed; |
| uint32_t signals = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED; |
| if ((status = zx_object_wait_one(dc_handle, signals, ZX_TIME_INFINITE, |
| &observed)) != ZX_OK) { |
| return status; |
| } |
| if (observed & ZX_CHANNEL_PEER_CLOSED) { |
| return ZX_ERR_PEER_CLOSED; |
| } |
| |
| uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES]; |
| uint32_t actual_bytes, actual_handles; |
| if ((status = |
| zx_channel_read(dc_handle, 0, bytes, NULL, ZX_CHANNEL_MAX_MSG_BYTES, |
| 0, &actual_bytes, &actual_handles)) != ZX_OK) { |
| return ZX_ERR_STOP; |
| } |
| |
| if (actual_bytes < sizeof(fidl_message_header_t)) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| fidl_message_header_t* header = (fidl_message_header_t*)bytes; |
| |
| switch (header->ordinal) { |
| case fuchsia_hardware_display_ControllerDisplaysChangedOrdinal: |
| return ZX_ERR_STOP; |
| case fuchsia_hardware_display_ControllerClientOwnershipChangeOrdinal: |
| return ZX_ERR_NEXT; |
| case fuchsia_hardware_display_ControllerVsyncOrdinal: |
| break; |
| default: |
| return ZX_ERR_STOP; |
| } |
| |
| const char* err_msg; |
| if ((status = fidl_decode(&fuchsia_hardware_display_ControllerVsyncEventTable, |
| bytes, actual_bytes, NULL, 0, &err_msg)) != ZX_OK) { |
| return ZX_ERR_STOP; |
| } |
| |
| fuchsia_hardware_display_ControllerVsyncEvent* vsync = |
| (fuchsia_hardware_display_ControllerVsyncEvent*)bytes; |
| *timestamp = vsync->timestamp; |
| image_ids[0] = |
| vsync->images.count > 0 ? ((uint64_t*)vsync->images.data)[0] : INVALID_ID; |
| image_ids[1] = |
| vsync->images.count > 1 ? ((uint64_t*)vsync->images.data)[1] : INVALID_ID; |
| return ZX_OK; |
| } |
| |
| static void print_usage(FILE* stream) { |
| fprintf(stream, |
| "usage: gfxlatency [options]\n\n" |
| "options:\n" |
| " -h, --help\t\t\tPrint this help\n" |
| " --vsync=on|off|adaptive\tVSync mode (default=adaptive)\n" |
| " --offset=MS\t\t\tVSync offset (default=15)\n" |
| " --pen-prediction=MS\t\tPen prediction (default=15)\n" |
| " --scroll-prediction=MS\tScroll prediction (default=15)\n" |
| " --prediction-color=COLOR\tPrediction color (default=0x7f000000)\n" |
| " --slow-down-scale-factor=NUM\tUpdate each line multiple times " |
| "(default=1)\n"); |
| } |
| |
| class BufferArray { |
| public: |
| BufferArray(uint32_t buffer_size, size_t count, uint32_t width, |
| uint32_t height, zx_pixel_format_t format) |
| : size_(buffer_size), array_(new buffer_t[NUM_BUFFERS], count) { |
| for (auto& buffer : array_) { |
| zx_status_t status = alloc_image_buffer(size_, &buffer.vmo); |
| ZX_ASSERT(status == ZX_OK); |
| |
| zx_vmo_set_cache_policy(buffer.vmo, ZX_CACHE_POLICY_WRITE_COMBINING); |
| |
| status = |
| import_image(buffer.vmo, width, height, format, &buffer.image_id); |
| ZX_ASSERT(status == ZX_OK); |
| |
| status = |
| zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, |
| 0, buffer.vmo, 0, size_, &buffer.data); |
| ZX_ASSERT(status == ZX_OK); |
| } |
| } |
| |
| ~BufferArray() { |
| for (auto& buffer : array_) { |
| release_image(buffer.image_id); |
| if (buffer.wait_event_id != INVALID_ID) { |
| release_event(buffer.wait_event_id); |
| } |
| if (buffer.wait_event != ZX_HANDLE_INVALID) { |
| zx_handle_close(buffer.wait_event); |
| } |
| zx_vmar_unmap(zx_vmar_root_self(), buffer.data, size_); |
| zx_handle_close(buffer.vmo); |
| } |
| } |
| |
| size_t size() const { return array_.size(); } |
| |
| buffer_t& operator[](size_t i) const { return array_[i]; } |
| |
| buffer_t* begin() const { return array_.begin(); } |
| |
| buffer_t* end() const { return array_.end(); } |
| |
| private: |
| uint32_t size_; |
| fbl::Array<buffer_t> array_; |
| }; |
| |
| int main(int argc, char* argv[]) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToThread); |
| trace::TraceProvider provider(loop.dispatcher()); |
| |
| VSync vsync = VSync::ADAPTIVE; |
| zx_time_t vsync_offset = ZX_MSEC(15); |
| int slow_down_scale_factor = 1; |
| uint32_t pen_prediction_ms = 15; |
| uint32_t scroll_prediction_ms = 15; |
| uint32_t prediction_color = 0x7f000000; |
| |
| for (int i = 1; i < argc; ++i) { |
| const char* arg = argv[i]; |
| if (strstr(arg, "--vsync") == arg) { |
| const char* s = strchr(arg, '='); |
| ++s; |
| if (!strcmp(s, "on")) { |
| vsync = VSync::ON; |
| } else if (!strcmp(s, "off")) { |
| vsync = VSync::OFF; |
| } else if (!strcmp(s, "adaptive")) { |
| vsync = VSync::ADAPTIVE; |
| } else { |
| fprintf(stderr, "invalid vsync mode: %s\n", s); |
| print_usage(stderr); |
| return -1; |
| } |
| } else if (strstr(arg, "--offset") == arg) { |
| const char* s = strchr(arg, '='); |
| ++s; |
| vsync_offset = ZX_MSEC(atoi(s)); |
| } else if (strstr(arg, "--pen-prediction") == arg) { |
| const char* s = strchr(arg, '='); |
| ++s; |
| pen_prediction_ms = atoi(s); |
| } else if (strstr(arg, "--prediction-color") == arg) { |
| const char* s = strchr(arg, '='); |
| ++s; |
| prediction_color = (uint32_t)strtol(s, NULL, 16); |
| } else if (strstr(arg, "--scroll-prediction") == arg) { |
| const char* s = strchr(arg, '='); |
| ++s; |
| scroll_prediction_ms = atoi(s); |
| } else if (strstr(arg, "--slow-down-scale-factor") == arg) { |
| const char* s = strchr(arg, '='); |
| ++s; |
| slow_down_scale_factor = fbl::max(1, atoi(s)); |
| } else if (strstr(arg, "-h") == arg) { |
| print_usage(stdout); |
| return 0; |
| } else { |
| fprintf(stderr, "invalid argument: %s\n", arg); |
| print_usage(stderr); |
| return -1; |
| } |
| } |
| |
| fbl::unique_fd fd(open("/dev/class/display-controller/000", O_RDWR)); |
| if (!fd) { |
| fprintf(stderr, "failed to open display controller\n"); |
| return -1; |
| } |
| |
| zx::channel device_server, device_client; |
| zx_status_t status = zx::channel::create(0, &device_server, &device_client); |
| if (status != ZX_OK) { |
| fprintf(stderr, "failed to create device channel %d (%s)\n", status, zx_status_get_string(status)); |
| return -1; |
| } |
| zx::channel dc_server, dc_client; |
| status = zx::channel::create(0, &dc_server, &dc_client); |
| if (status != ZX_OK) { |
| fprintf(stderr, "failed to create controller channel %d (%s)\n", status, zx_status_get_string(status)); |
| return -1; |
| } |
| |
| fzl::FdioCaller caller(std::move(fd)); |
| zx_status_t fidl_status = fuchsia_hardware_display_ProviderOpenController( |
| caller.borrow_channel(), device_server.release(), dc_server.release(), &status); |
| if (fidl_status != ZX_OK) { |
| fprintf(stderr, "failed to call service handle %d (%s)\n", fidl_status, zx_status_get_string(fidl_status)); |
| return -1; |
| } |
| if (status != ZX_OK) { |
| fprintf(stderr, "failed to open controller %d (%s)\n", status, zx_status_get_string(status)); |
| return -1; |
| } |
| dc_handle = dc_client.release(); |
| |
| uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES]; |
| uint32_t actual_bytes, actual_handles; |
| bool has_display = false; |
| while (!has_display) { |
| zx_handle_t observed; |
| uint32_t signals = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED; |
| if ((status = zx_object_wait_one(dc_handle, signals, ZX_TIME_INFINITE, |
| &observed)) != ZX_OK) { |
| fprintf(stderr, "failed waiting for display\n"); |
| return -1; |
| } |
| if (observed & ZX_CHANNEL_PEER_CLOSED) { |
| fprintf(stderr, "display controller connection closed\n"); |
| return -1; |
| } |
| |
| if ((status = zx_channel_read(dc_handle, 0, bytes, NULL, |
| ZX_CHANNEL_MAX_MSG_BYTES, 0, &actual_bytes, |
| &actual_handles)) != ZX_OK || |
| actual_bytes < sizeof(fidl_message_header_t)) { |
| fprintf(stderr, "reading display addded callback failed\n"); |
| return -1; |
| } |
| |
| fidl_message_header_t* hdr = (fidl_message_header_t*)bytes; |
| if (hdr->ordinal != |
| fuchsia_hardware_display_ControllerDisplaysChangedOrdinal) { |
| continue; |
| } |
| const char* err_msg; |
| if ((status = fidl_decode( |
| &fuchsia_hardware_display_ControllerDisplaysChangedEventTable, |
| bytes, actual_bytes, NULL, 0, &err_msg)) != ZX_OK) { |
| fprintf(stderr, "%s\n", err_msg); |
| return -1; |
| } |
| has_display = true; |
| } |
| |
| // We're guaranteed that added contains at least one display, since we |
| // haven't been notified of any displays to remove. |
| fuchsia_hardware_display_ControllerDisplaysChangedEvent* changes = |
| (fuchsia_hardware_display_ControllerDisplaysChangedEvent*)bytes; |
| fuchsia_hardware_display_Info* display = |
| (fuchsia_hardware_display_Info*)changes->added.data; |
| fuchsia_hardware_display_Mode* mode = |
| (fuchsia_hardware_display_Mode*)display->modes.data; |
| |
| uint32_t width = mode->horizontal_resolution; |
| uint32_t height = mode->vertical_resolution; |
| zx_pixel_format_t format = ((int32_t*)(display->pixel_format.data))[0]; |
| |
| uint32_t stride; |
| if (compute_linear_image_stride(width, format, &stride) != ZX_OK) { |
| fprintf(stderr, "failed to get linear stride\n"); |
| return -1; |
| } |
| |
| uint64_t layer_id; |
| if (create_layer(&layer_id) != ZX_OK) { |
| fprintf(stderr, "failed to create layer\n"); |
| return -1; |
| } |
| |
| uint32_t sprite_stride; |
| if (compute_linear_image_stride(SPRITE_DIM, SPRITE_FORMAT, &sprite_stride) != |
| ZX_OK) { |
| fprintf(stderr, "failed to get linear stride\n"); |
| return -1; |
| } |
| |
| uint64_t sprite_layer_id; |
| if (create_layer(&sprite_layer_id) != ZX_OK) { |
| fprintf(stderr, "failed to create sprite layer\n"); |
| return -1; |
| } |
| |
| if (set_display_layers(display->id, layer_id, sprite_layer_id) != ZX_OK) { |
| fprintf(stderr, "failed to set display layers\n"); |
| return -1; |
| } |
| |
| if ((status = set_layer_config(layer_id, width, height, format)) != ZX_OK) { |
| fprintf(stderr, "failed to set layer config\n"); |
| return -1; |
| } |
| |
| if ((status = set_layer_config(sprite_layer_id, SPRITE_DIM, SPRITE_DIM, |
| format)) != ZX_OK) { |
| fprintf(stderr, "failed to set sprite layer config\n"); |
| return -1; |
| } |
| |
| uint32_t buffer_size = ZX_PIXEL_FORMAT_BYTES(format) * height * stride; |
| uint32_t canvas_width = width * 2; |
| uint32_t canvas_height = height * 2; |
| fbl::unique_ptr<uint8_t[]> surface_data( |
| new uint8_t[ZX_PIXEL_FORMAT_BYTES(format) * canvas_width * |
| canvas_height]); |
| gfx_surface* surface = |
| gfx_create_surface((void*)surface_data.get(), canvas_width, canvas_height, |
| canvas_width, format, 0); |
| auto cleanup_surface = |
| fit::defer([surface]() { gfx_surface_destroy(surface); }); |
| ZX_ASSERT(surface); |
| { |
| TRACE_DURATION("app", "Initialize Canvas"); |
| |
| // Initialize using background image if format allows. |
| if (ZX_PIXEL_FORMAT_BYTES(format) == 4) { |
| copy_rect((uint32_t*)surface->ptr, |
| (const uint32_t*)background_image.pixel_data, canvas_width, |
| background_image.width, 0, 0, background_image.width, |
| background_image.height); |
| for (uint32_t y = 0; y < canvas_height; y += background_image.height) { |
| for (uint32_t x = 0; x < canvas_width; x += background_image.width) { |
| gfx_copyrect(surface, 0, 0, background_image.width, |
| background_image.height, x, y); |
| } |
| } |
| } else { |
| gfx_clear(surface, 0xffffffff); |
| } |
| } |
| |
| fbl::unique_ptr<uint8_t[]> sprite_surface_data( |
| new uint8_t[ZX_PIXEL_FORMAT_BYTES(SPRITE_FORMAT) * SPRITE_DIM * |
| SPRITE_DIM]); |
| gfx_surface* sprite_surface = |
| gfx_create_surface((void*)sprite_surface_data.get(), SPRITE_DIM, |
| SPRITE_DIM, SPRITE_DIM, SPRITE_FORMAT, 0); |
| ZX_ASSERT(sprite_surface); |
| auto cleanup_sprite_surface = |
| fit::defer([sprite_surface]() { gfx_surface_destroy(sprite_surface); }); |
| gfx_clear(sprite_surface, 0); |
| |
| // Scratch buffer for sprite updates. 2 times the size of the sprite. |
| fbl::unique_ptr<uint8_t[]> sprite_scratch( |
| new uint8_t[ZX_PIXEL_FORMAT_BYTES(SPRITE_FORMAT) * SPRITE_DIM * |
| SPRITE_DIM * 2]); |
| memset(sprite_scratch.get(), 0, |
| ZX_PIXEL_FORMAT_BYTES(SPRITE_FORMAT) * SPRITE_DIM * SPRITE_DIM * 2); |
| |
| pointf_t pen[NUM_PENCILS] = {{.x = NAN, .y = NAN}, {.x = NAN, .y = NAN}}; |
| point_t sprite_location = {.x = width / 2, .y = height / 2}; |
| vector_t sprite_hotspot = {.x = SPRITE_RAD, .y = SPRITE_RAD}; |
| pointf_t cursor = {.x = (float)sprite_location.x, |
| .y = (float)sprite_location.y}; |
| pointf_t touch[NUM_FINGERS] = {{.x = NAN, .y = NAN}, |
| {.x = NAN, .y = NAN}, |
| {.x = NAN, .y = NAN}, |
| {.x = NAN, .y = NAN}, |
| {.x = NAN, .y = NAN}}; |
| point_t origin = {.x = canvas_width / 2 - width / 2, |
| .y = canvas_height / 2 - height / 2}; |
| point_t predicted_origin = origin; |
| vectorf_t origin_delta = {.x = 0, .y = 0}; |
| |
| uint64_t next_event_id = INVALID_ID + 1; |
| |
| BufferArray buffers(buffer_size, vsync == VSync::OFF ? 1 : NUM_BUFFERS, width, |
| height, format); |
| for (auto& buffer : buffers) { |
| status = zx_event_create(0, &buffer.wait_event); |
| ZX_ASSERT(status == ZX_OK); |
| buffer.wait_event_id = INVALID_ID; |
| if (vsync == VSync::ON) { |
| buffer.wait_event_id = next_event_id++; |
| status = import_event(buffer.wait_event, buffer.wait_event_id); |
| ZX_ASSERT(status == ZX_OK); |
| } |
| zx_object_signal(buffer.wait_event, 0, ZX_EVENT_SIGNALED); |
| |
| copy_rect( |
| (uint32_t*)buffer.data, |
| (const uint32_t*)surface->ptr + origin.y * canvas_width + origin.x, |
| stride, canvas_width, 0, 0, width, height); |
| memset(&buffer.damage, 0, sizeof(buffer.damage)); |
| } |
| |
| uint32_t sprite_size = |
| ZX_PIXEL_FORMAT_BYTES(format) * SPRITE_DIM * sprite_stride; |
| BufferArray sprites(sprite_size, vsync == VSync::OFF ? 1 : NUM_BUFFERS, |
| SPRITE_DIM, SPRITE_DIM, SPRITE_FORMAT); |
| for (auto& sprite : sprites) { |
| status = zx_event_create(0, &sprite.wait_event); |
| ZX_ASSERT(status == ZX_OK); |
| sprite.wait_event_id = INVALID_ID; |
| if (vsync == VSync::ON) { |
| sprite.wait_event_id = next_event_id++; |
| status = import_event(sprite.wait_event, sprite.wait_event_id); |
| ZX_ASSERT(status == ZX_OK); |
| } |
| zx_object_signal(sprite.wait_event, 0, ZX_EVENT_SIGNALED); |
| |
| memset((void*)sprite.data, 0, sprite_size); |
| memset(&sprite.damage, 0, sizeof(sprite.damage)); |
| } |
| |
| // Enable vsync if needed. |
| if (vsync != VSync::OFF) { |
| if ((status = enable_vsync(true)) != ZX_OK) { |
| fprintf(stderr, "failed to enable vsync\n"); |
| return -1; |
| } |
| TRACE_ASYNC_BEGIN("app", "Buffer Scheduled", (uintptr_t)&buffers[0], |
| "image", buffers[0].image_id); |
| TRACE_ASYNC_BEGIN("app", "Sprite Scheduled", (uintptr_t)&sprites[0], |
| "image", sprites[0].image_id); |
| } |
| |
| // Set initial image for root layer. |
| if ((status = set_layer_image(layer_id, buffers[0].image_id, INVALID_ID)) != |
| ZX_OK) { |
| fprintf(stderr, "failed to set layer image\n"); |
| return -1; |
| } |
| // Set initial image and position for sprite layer. |
| if ((status = set_layer_image(sprite_layer_id, sprites[0].image_id, |
| INVALID_ID)) != ZX_OK) { |
| fprintf(stderr, "failed to set sprite layer image\n"); |
| return -1; |
| } |
| if ((status = set_layer_position( |
| sprite_layer_id, 0, 0, sprite_location.x - sprite_hotspot.x, |
| sprite_location.y - sprite_hotspot.y, SPRITE_DIM, SPRITE_DIM)) != |
| ZX_OK) { |
| fprintf(stderr, "failed to set sprite layer position\n"); |
| return -1; |
| } |
| if ((status = set_layer_alpha(sprite_layer_id, true)) != ZX_OK) { |
| fprintf(stderr, "failed to set sprite layer alpha\n"); |
| return -1; |
| } |
| |
| // Check initial layer config. We assume that movement to the sprite layer |
| // doesn't require another check. |
| if ((status = check_config()) != ZX_OK) { |
| fprintf(stderr, "layer config failed\n"); |
| return -1; |
| } |
| |
| // Present initial buffers. |
| if ((status = apply_config()) != ZX_OK) { |
| fprintf(stderr, "failed to present layers\n"); |
| return -1; |
| } |
| |
| DIR* dir = opendir(DEV_INPUT); |
| if (!dir) { |
| fprintf(stderr, "failed to open %s: %d\n", DEV_INPUT, errno); |
| return -1; |
| } |
| |
| TouchDevice touch_device; |
| int touchfd = -1; |
| uint32_t touchtraceid = 0; |
| int touchpadfd = -1; |
| uint32_t touchpadtraceid = 0; |
| struct dirent* de; |
| while ((de = readdir(dir))) { |
| char devname[128]; |
| |
| if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) |
| continue; |
| |
| snprintf(devname, sizeof(devname), "%s/%s", DEV_INPUT, de->d_name); |
| int fd = open(devname, O_RDONLY); |
| if (fd < 0) { |
| fprintf(stderr, "failed to open %s: %d\n", devname, errno); |
| continue; |
| } |
| |
| fzl::FdioCaller caller{fbl::unique_fd(fd)}; |
| |
| uint16_t rpt_desc_len; |
| status = fuchsia_hardware_input_DeviceGetReportDescSize( |
| caller.borrow_channel(), &rpt_desc_len); |
| if (status != ZX_OK) { |
| fprintf(stderr, "failed to get report descriptor length for %s: %d\n", |
| devname, status); |
| continue; |
| } |
| |
| uint8_t rpt_desc[rpt_desc_len]; |
| size_t actual_rpt_desc_len; |
| status = fuchsia_hardware_input_DeviceGetReportDesc( |
| caller.borrow_channel(), rpt_desc, sizeof(rpt_desc), |
| &actual_rpt_desc_len); |
| if (status != ZX_OK) { |
| fprintf(stderr, "failed to get report descriptor for %s: %d\n", devname, |
| status); |
| continue; |
| } |
| |
| // Use lower 32 bits of channel koid as trace ID. |
| zx_info_handle_basic_t info; |
| zx_object_get_info(caller.borrow_channel(), ZX_INFO_HANDLE_BASIC, &info, |
| sizeof(info), nullptr, nullptr); |
| uint32_t traceid = info.koid & 0xffffffff; |
| |
| status = fuchsia_hardware_input_DeviceSetTraceId(caller.borrow_channel(), |
| traceid); |
| if (status != ZX_OK) { |
| fprintf(stderr, "failed to set trace ID for %s: %d\n", devname, status); |
| continue; |
| } |
| |
| if (is_paradise_touch_v2_report_desc(rpt_desc, actual_rpt_desc_len)) { |
| touch_device = TouchDevice::PARADISE_V2; |
| touchfd = caller.release().release(); |
| touchtraceid = traceid; |
| continue; |
| } |
| |
| if (is_paradise_touch_v3_report_desc(rpt_desc, actual_rpt_desc_len)) { |
| touch_device = TouchDevice::PARADISE_V3; |
| touchfd = caller.release().release(); |
| touchtraceid = traceid; |
| continue; |
| } |
| |
| if (is_paradise_touchpad_v2_report_desc(rpt_desc, actual_rpt_desc_len)) { |
| touchpadfd = caller.release().release(); |
| touchpadtraceid = traceid; |
| continue; |
| } |
| } |
| closedir(dir); |
| |
| if (touchfd < 0 && touchpadfd < 0) { |
| fprintf(stderr, "could not find a touch device\n"); |
| return -1; |
| } |
| |
| uint16_t max_touch_rpt_sz = 0; |
| uint16_t max_touchpad_rpt_sz = 0; |
| if (touchfd >= 0) { |
| fzl::FdioCaller caller{fbl::unique_fd(touchfd)}; |
| status = fuchsia_hardware_input_DeviceGetMaxInputReportSize( |
| caller.borrow_channel(), &max_touch_rpt_sz); |
| touchfd = caller.release().release(); |
| ZX_ASSERT(status == ZX_OK); |
| } |
| if (touchpadfd >= 0) { |
| fzl::FdioCaller caller{fbl::unique_fd(touchpadfd)}; |
| status = fuchsia_hardware_input_DeviceGetMaxInputReportSize( |
| caller.borrow_channel(), &max_touchpad_rpt_sz); |
| touchpadfd = caller.release().release(); |
| ZX_ASSERT(status == ZX_OK); |
| } |
| |
| async::Loop update_loop(&kAsyncLoopConfigNoAttachToThread); |
| update_loop.StartThread(); |
| async::Loop sprite_update_loop(&kAsyncLoopConfigNoAttachToThread); |
| sprite_update_loop.StartThread(); |
| |
| size_t buffer_frame = 0; |
| size_t sprite_frame = 0; |
| bool buffer_frame_scheduled = vsync != VSync::OFF; |
| bool sprite_frame_scheduled = vsync != VSync::OFF; |
| bool buffer_update_pending = false; |
| bool sprite_update_pending = false; |
| bool show_cursor = false; |
| fbl::Vector<pointf_t> points[NUM_PENCILS]; |
| fbl::Vector<line_t> lines; |
| |
| // Input prediction state. |
| zx_time_t last_input_prediction_update = zx_clock_get_monotonic(); |
| pointf_t touch0[NUM_FINGERS]; |
| memcpy(touch0, touch, sizeof(touch)); |
| pointf_t pen0[NUM_PENCILS]; |
| memcpy(pen0, pen, sizeof(pen)); |
| pointf_t origin0 = {.x = (float)origin.x, .y = (float)origin.y}; |
| pointf_t cursor0 = cursor; |
| double cursor_blur_radius = 0; |
| double cursor_movement_angle = 0; |
| vector_t cursor_blur_offset = {.x = 0, .y = 0}; |
| vectorf_t origin_responsive_velocity = {.x = 0, .y = 0}; |
| vectorf_t origin_smooth_velocity = {.x = 0, .y = 0}; |
| vectorf_t predicted_origin_movement = {.x = 0, .y = 0}; |
| vectorf_t pen_responsive_velocity[NUM_PENCILS] = { |
| {.x = 0, .y = 0}, |
| {.x = 0, .y = 0}, |
| }; |
| vectorf_t pen_smooth_velocity[NUM_PENCILS] = { |
| {.x = 0, .y = 0}, |
| {.x = 0, .y = 0}, |
| }; |
| vectorf_t predicted_stylus_movement = {.x = 0, .y = 0}; |
| uint32_t touchreports_read = 0; |
| uint32_t touchpadreports_read = 0; |
| |
| async::TaskClosure frame_task([&] { |
| if (vsync == VSync::OFF) { |
| int startfd, endfd; |
| struct pollfd fds[2]; |
| |
| // Wait for input until it is time to update the input prediction |
| // model. |
| int timeout = fbl::max((int)(ZX_MSEC(last_input_prediction_update) + |
| INPUT_PREDICTION_UPDATE_INTERVAL_MS - |
| ZX_MSEC(zx_clock_get_monotonic())), |
| 0); |
| prepare_poll(touchfd, touchpadfd, &startfd, &endfd, fds); |
| poll(&fds[startfd], endfd - startfd, timeout); |
| } else { |
| zx_time_t vsync_time; |
| zx_status_t status; |
| |
| // Wait for VSync. |
| uint64_t image_ids[2] = {INVALID_ID, INVALID_ID}; |
| while ((status = wait_for_vsync(&vsync_time, image_ids)) != ZX_OK) { |
| if (status == ZX_ERR_STOP) { |
| loop.Quit(); |
| return; |
| } |
| } |
| |
| // Detect when image from current frame is being scanned out. |
| auto& buffer = buffers[buffer_frame % buffers.size()]; |
| if (buffer_frame_scheduled && (image_ids[0] == buffer.image_id || |
| image_ids[1] == buffer.image_id)) { |
| TRACE_ASYNC_END("app", "Buffer Scheduled", (uintptr_t)&buffer, "image", |
| buffer.image_id); |
| if (buffer_frame > 0) { |
| auto& last_buffer = buffers[(buffer_frame - 1) % buffers.size()]; |
| TRACE_ASYNC_END("app", "Buffer Displayed", (uintptr_t)&last_buffer, |
| "image", last_buffer.image_id); |
| } |
| TRACE_ASYNC_BEGIN("app", "Buffer Displayed", (uintptr_t)&buffer, |
| "image", buffer.image_id); |
| buffer_frame_scheduled = false; |
| } |
| auto& sprite = sprites[sprite_frame % sprites.size()]; |
| if (sprite_frame_scheduled && (image_ids[0] == sprite.image_id || |
| image_ids[1] == sprite.image_id)) { |
| TRACE_ASYNC_END("app", "Sprite Scheduled", (uintptr_t)&sprite, "image", |
| sprite.image_id); |
| if (sprite_frame > 0) { |
| auto& last_sprite = sprites[(sprite_frame - 1) % sprites.size()]; |
| TRACE_ASYNC_END("app", "Sprite Displayed", (uintptr_t)&last_sprite, |
| "image", last_sprite.image_id); |
| } |
| TRACE_ASYNC_BEGIN("app", "Sprite Displayed", (uintptr_t)&sprite, |
| "image", sprite.image_id); |
| sprite_frame_scheduled = false; |
| } |
| |
| { |
| TRACE_DURATION("app", "Waiting For VSync Offset"); |
| |
| // Wait until vsync + offset. |
| zx_nanosleep(vsync_time + vsync_offset); |
| } |
| } |
| |
| // Save current state. |
| point_t old_origin = origin; |
| pointf_t old_pen[NUM_PENCILS]; |
| memcpy(old_pen, pen, sizeof(pen)); |
| point_t old_sprite_location = sprite_location; |
| bool old_show_cursor = show_cursor; |
| double old_cursor_blur_radius = cursor_blur_radius; |
| vector_t old_cursor_blur_offset = cursor_blur_offset; |
| |
| // Process all pending input events. |
| int ready = 0; |
| while (true) { |
| int startfd, endfd; |
| struct pollfd fds[2]; |
| |
| prepare_poll(touchfd, touchpadfd, &startfd, &endfd, fds); |
| ready = poll(&fds[startfd], endfd - startfd, 0); |
| if (!ready) |
| break; |
| |
| TRACE_DURATION("app", "Process Input Event"); |
| |
| if (touchfd >= 0 && fds[0].revents) { |
| uint8_t rpt_buf[max_touch_rpt_sz]; |
| ssize_t bytes = read(touchfd, rpt_buf, max_touch_rpt_sz); |
| ZX_ASSERT(bytes > 0); |
| |
| TRACE_FLOW_END("input", "hid_report", |
| HID_REPORT_TRACE_ID(touchtraceid, touchreports_read)); |
| ++touchreports_read; |
| |
| uint8_t id = *(uint8_t*)rpt_buf; |
| if (id == PARADISE_RPT_ID_TOUCH) { |
| if (touch_device == TouchDevice::PARADISE_V2) { |
| parse_paradise_touch_report<paradise_touch_v2_t>(rpt_buf, width, |
| height, touch); |
| } else { |
| parse_paradise_touch_report<paradise_touch_t>(rpt_buf, width, |
| height, touch); |
| } |
| for (uint8_t c = 0; c < NUM_FINGERS; c++) { |
| if (!isnan(touch[c].x) && !isnan(touch[c].y)) { |
| show_cursor = false; |
| } |
| } |
| } else if (id == PARADISE_RPT_ID_STYLUS) { |
| parse_paradise_stylus_report(rpt_buf, width, height, |
| &pen[STYLUS_PEN]); |
| |
| if (!isnan(pen[STYLUS_PEN].x) && !isnan(pen[STYLUS_PEN].y)) { |
| points[STYLUS_PEN].push_back(pen[STYLUS_PEN]); |
| show_cursor = false; |
| } |
| } |
| } |
| |
| if (touchpadfd >= 0 && fds[1].revents) { |
| uint8_t rpt_buf[max_touchpad_rpt_sz]; |
| ssize_t bytes = read(touchpadfd, rpt_buf, max_touchpad_rpt_sz); |
| ZX_ASSERT(bytes > 0); |
| |
| TRACE_FLOW_END( |
| "input", "hid_report", |
| HID_REPORT_TRACE_ID(touchpadtraceid, touchpadreports_read)); |
| ++touchpadreports_read; |
| |
| uint8_t button = 0; |
| parse_paradise_touchpad_report(rpt_buf, width, height, touch, &button); |
| |
| uint32_t contact_count = 0; |
| for (uint8_t c = 0; c < NUM_FINGERS; c++) { |
| if (!isnan(touch[c].x) && !isnan(touch[c].y)) { |
| ++contact_count; |
| } |
| } |
| |
| pen[TOUCH_PEN].x = NAN; |
| pen[TOUCH_PEN].y = NAN; |
| // Show cursor if we only have one contact point. |
| if (contact_count == 1 && (!isnan(touch[0].x) && !isnan(touch[0].y))) { |
| show_cursor = true; |
| if (button) { |
| pen[TOUCH_PEN].x = cursor.x; |
| pen[TOUCH_PEN].y = cursor.y; |
| points[TOUCH_PEN].push_back(pen[TOUCH_PEN]); |
| } |
| } |
| } |
| } |
| |
| // Calculate origin delta from the average touch delta. |
| vectorf_t touch_delta = {.x = 0, .y = 0}; |
| int32_t contact_count = 0; |
| for (uint8_t c = 0; c < NUM_FINGERS; c++) { |
| if (isnan(touch0[c].x) || isnan(touch[c].x) || isnan(touch0[c].y) || |
| isnan(touch[c].y)) { |
| continue; |
| } |
| |
| // Ignore cursor. |
| if (show_cursor && c == 0) { |
| continue; |
| } |
| |
| touch_delta.x += touch[c].x - touch0[c].x; |
| touch_delta.y += touch[c].y - touch0[c].y; |
| ++contact_count; |
| } |
| if (contact_count) { |
| origin_delta.x = -touch_delta.x / (float)contact_count; |
| origin_delta.y = -touch_delta.y / (float)contact_count; |
| } |
| |
| // Calculate cursor delta. Cursor should only move when no other |
| // touch points are active. |
| vectorf_t cursor_delta = {.x = 0, .y = 0}; |
| if (show_cursor && !contact_count && !isnan(touch0[0].x) && |
| !isnan(touch[0].x) && !isnan(touch0[0].y) && !isnan(touch[0].y)) { |
| cursor_delta.x = touch[0].x - touch0[0].x; |
| cursor_delta.y = touch[0].y - touch0[0].y; |
| } |
| |
| // Update input prodiction model if enough time has passed. The input |
| // prediction model is effected by velocity. Velocity needs to be |
| // sampled at an interval to provide a meaningful value. |
| zx_time_t current_time = zx_clock_get_monotonic(); |
| if (ZX_MSEC(current_time - last_input_prediction_update) >= |
| INPUT_PREDICTION_UPDATE_INTERVAL_MS) { |
| zx_time_t elapsed = current_time - last_input_prediction_update; |
| float elapsed_ms = (float)elapsed / ZX_MSEC(1); |
| last_input_prediction_update = current_time; |
| |
| TRACE_DURATION("app", "Update Input Prediction", "elapsed", elapsed_ms); |
| |
| // Update origin prediction. |
| pointf_t new_origin = { |
| .x = fbl::clamp(origin0.x + origin_delta.x, 0.0f, |
| (float)(surface->width - width)), |
| .y = fbl::clamp(origin0.y + origin_delta.y, 0.0f, |
| (float)(surface->height - height))}; |
| vectorf_t velocity = { |
| .x = fbl::clamp((new_origin.x - origin0.x) / elapsed_ms, |
| -ORIGIN_VELOCITY_MAX, ORIGIN_VELOCITY_MAX), |
| .y = fbl::clamp((new_origin.y - origin0.y) / elapsed_ms, |
| -ORIGIN_VELOCITY_MAX, ORIGIN_VELOCITY_MAX)}; |
| // Slowly reduce velocity when we don't have any active touch |
| // points. |
| if (!contact_count) { |
| velocity.x *= 0.95f; |
| velocity.y *= 0.95f; |
| } |
| vector_interpolate(&origin_responsive_velocity, |
| &origin_responsive_velocity, &velocity, |
| RESPONSIVE_VELOCITY_FACTOR); |
| vector_interpolate(&origin_smooth_velocity, &origin_smooth_velocity, |
| &velocity, SMOOTH_VELOCITY_FACTOR); |
| |
| origin0 = new_origin; |
| // Update origin delta to match current touch points when we have |
| // active contact points. |
| if (contact_count) { |
| origin_delta.x = 0; |
| origin_delta.y = 0; |
| vector_interpolate(&predicted_origin_movement, |
| &origin_responsive_velocity, &origin_smooth_velocity, |
| ORIGIN_MOVEMENT_FACTOR); |
| predicted_origin_movement.x *= (float)scroll_prediction_ms; |
| predicted_origin_movement.y *= (float)scroll_prediction_ms; |
| TRACE_INSTANT("app", "Scroll Prediction", TRACE_SCOPE_THREAD, "dx", |
| predicted_origin_movement.x, "dy", |
| predicted_origin_movement.y); |
| } else { |
| // Compute a new delta based on velocity when we don't have |
| // any active touch points. This results in some motion |
| // being maintained after active touch points are gone. |
| vector_interpolate(&origin_delta, &origin_responsive_velocity, |
| &origin_smooth_velocity, ORIGIN_MOVEMENT_FACTOR); |
| origin_delta.x *= elapsed_ms; |
| origin_delta.y *= elapsed_ms; |
| origin_delta.x += predicted_origin_movement.x; |
| origin_delta.y += predicted_origin_movement.y; |
| predicted_origin_movement.x = 0; |
| predicted_origin_movement.y = 0; |
| } |
| |
| // Update cursor prediction. |
| pointf_t new_cursor = { |
| .x = fbl::clamp(cursor0.x + cursor_delta.x, 0.0f, (float)(width - 1)), |
| .y = fbl::clamp(cursor0.y + cursor_delta.y, 0.0f, |
| (float)(height - 1))}; |
| vectorf_t pen_velocity = { |
| .x = fbl::clamp((new_cursor.x - cursor0.x) / elapsed_ms, |
| -PEN_VELOCITY_MAX, PEN_VELOCITY_MAX), |
| .y = fbl::clamp((new_cursor.y - cursor0.y) / elapsed_ms, |
| -PEN_VELOCITY_MAX, PEN_VELOCITY_MAX)}; |
| vector_interpolate(&pen_responsive_velocity[TOUCH_PEN], |
| &pen_responsive_velocity[TOUCH_PEN], &pen_velocity, |
| RESPONSIVE_VELOCITY_FACTOR); |
| vector_interpolate(&pen_smooth_velocity[TOUCH_PEN], |
| &pen_smooth_velocity[TOUCH_PEN], &pen_velocity, |
| SMOOTH_VELOCITY_FACTOR); |
| vectorf_t movement; |
| vector_interpolate(&movement, &pen_responsive_velocity[TOUCH_PEN], |
| &pen_smooth_velocity[TOUCH_PEN], PEN_MOVEMENT_FACTOR); |
| movement.x *= CURSOR_MOVEMENT_PREDICTION_MS; |
| movement.y *= CURSOR_MOVEMENT_PREDICTION_MS; |
| TRACE_INSTANT("app", "Cursor Prediction", TRACE_SCOPE_THREAD, "dx", |
| movement.x, "dy", movement.y); |
| |
| double distance = sqrt(movement.x * movement.x + movement.y * movement.y); |
| if (distance >= MIN_MOVEMENT_FOR_CURSOR_MOTION_BLUR) { |
| cursor_movement_angle = atan2(movement.y, movement.x); |
| cursor_blur_radius = fbl::min(round(distance / 2), MAX_BLUR_RADIUS); |
| cursor_blur_offset.x = |
| (int32_t)round(movement.x * cursor_blur_radius / distance); |
| cursor_blur_offset.y = |
| (int32_t)round(movement.y * cursor_blur_radius / distance); |
| } else { |
| cursor_movement_angle = 0; |
| cursor_blur_radius = 0; |
| cursor_blur_offset.x = 0; |
| cursor_blur_offset.y = 0; |
| } |
| |
| cursor0 = new_cursor; |
| cursor_delta.x = 0; |
| cursor_delta.y = 0; |
| |
| memcpy(touch0, touch, sizeof(touch)); |
| |
| // Update pen prediction. |
| pen_velocity = {.x = 0, .y = 0}; |
| if (!isnan(pen0[STYLUS_PEN].x) && !isnan(pen[STYLUS_PEN].x) && |
| !isnan(pen0[STYLUS_PEN].y) && !isnan(pen[STYLUS_PEN].y)) { |
| pen_velocity = { |
| .x = fbl::clamp( |
| (pen[STYLUS_PEN].x - pen0[STYLUS_PEN].x) / elapsed_ms, |
| -PEN_VELOCITY_MAX, PEN_VELOCITY_MAX), |
| .y = fbl::clamp( |
| (pen[STYLUS_PEN].y - pen0[STYLUS_PEN].y) / elapsed_ms, |
| -PEN_VELOCITY_MAX, PEN_VELOCITY_MAX)}; |
| } |
| vector_interpolate(&pen_responsive_velocity[STYLUS_PEN], |
| &pen_responsive_velocity[STYLUS_PEN], &pen_velocity, |
| RESPONSIVE_VELOCITY_FACTOR); |
| vector_interpolate(&pen_smooth_velocity[STYLUS_PEN], |
| &pen_smooth_velocity[STYLUS_PEN], &pen_velocity, |
| SMOOTH_VELOCITY_FACTOR); |
| vector_interpolate(&predicted_stylus_movement, |
| &pen_responsive_velocity[STYLUS_PEN], |
| &pen_smooth_velocity[STYLUS_PEN], PEN_MOVEMENT_FACTOR); |
| predicted_stylus_movement.x *= (float)pen_prediction_ms; |
| predicted_stylus_movement.y *= (float)pen_prediction_ms; |
| TRACE_INSTANT("app", "Pen Prediction", TRACE_SCOPE_THREAD, "dx", |
| predicted_stylus_movement.x, "dy", |
| predicted_stylus_movement.y); |
| |
| pen0[STYLUS_PEN] = pen[STYLUS_PEN]; |
| } |
| |
| // Determine new origin. This might add lines if pencils are active. |
| origin.x = fbl::clamp((int32_t)round(origin0.x + origin_delta.x), 0, |
| (int32_t)(surface->width - width)); |
| origin.y = fbl::clamp((int32_t)round(origin0.y + origin_delta.y), 0, |
| (int32_t)(surface->height - height)); |
| if (origin.x != old_origin.x || origin.y != old_origin.y) { |
| rect_t damage = {.x1 = 0, .y1 = 0, .x2 = width, .y2 = height}; |
| for (auto& buffer : buffers) { |
| union_rects(&buffer.damage, &buffer.damage, &damage); |
| } |
| |
| // Update lines if penciles were active during change to origin. |
| for (size_t i = 0; i < NUM_PENCILS; ++i) { |
| if (!isnan(pen[i].x) && !isnan(old_pen[i].x) && !isnan(pen[i].y) && |
| !isnan(old_pen[i].y)) { |
| lines.push_back({{(uint32_t)round(old_pen[i].x) + old_origin.x, |
| (uint32_t)round(old_pen[i].y) + old_origin.y}, |
| {(uint32_t)round(pen[i].x) + origin.x, |
| (uint32_t)round(pen[i].y) + origin.y}}); |
| points[i].reset(); |
| } |
| } |
| } |
| |
| // Determine new predicted origin. |
| predicted_origin.x = fbl::clamp((int32_t)round(origin0.x + origin_delta.x + |
| predicted_origin_movement.x), |
| 0, (int32_t)(surface->width - width)); |
| predicted_origin.y = fbl::clamp((int32_t)round(origin0.y + origin_delta.y + |
| predicted_origin_movement.y), |
| 0, (int32_t)(surface->height - height)); |
| if (predicted_origin.x != origin.x || predicted_origin.y != origin.y) { |
| rect_t damage = {.x1 = 0, .y1 = 0, .x2 = width, .y2 = height}; |
| for (auto& buffer : buffers) { |
| union_rects(&buffer.damage, &buffer.damage, &damage); |
| } |
| } |
| |
| // Full sprite damage if cursor or stylus pen state changed. |
| if (old_show_cursor != show_cursor || |
| !isnan(pen[STYLUS_PEN].x) != !isnan(old_pen[STYLUS_PEN].x) || |
| !isnan(pen[STYLUS_PEN].y) != !isnan(old_pen[STYLUS_PEN].y)) { |
| rect_t damage = {.x1 = 0, .y1 = 0, .x2 = SPRITE_DIM, .y2 = SPRITE_DIM}; |
| for (auto& sprite : sprites) { |
| union_rects(&sprite.damage, &sprite.damage, &damage); |
| } |
| } |
| |
| // Determine new cursor position. |
| if (show_cursor) { |
| cursor.x = cursor0.x + cursor_delta.x; |
| cursor.y = cursor0.y + cursor_delta.y; |
| |
| sprite_location.x = |
| (uint32_t)round(cursor.x - (float)cursor_blur_offset.x); |
| sprite_location.y = |
| (uint32_t)round(cursor.y - (float)cursor_blur_offset.y); |
| sprite_hotspot.x = SPRITE_RAD - cursor_image.width / 2 + CURSOR_HOTSPOT_X; |
| sprite_hotspot.y = |
| SPRITE_RAD - cursor_image.height / 2 + CURSOR_HOTSPOT_Y; |
| |
| if (cursor_blur_radius != old_cursor_blur_radius || |
| cursor_blur_offset.x != old_cursor_blur_offset.y || |
| cursor_blur_offset.x != old_cursor_blur_offset.y) { |
| // TODO(reveman): Limit damage to area of sprite that changed. |
| rect_t damage = {.x1 = 0, .y1 = 0, .x2 = SPRITE_DIM, .y2 = SPRITE_DIM}; |
| for (auto& sprite : sprites) { |
| union_rects(&sprite.damage, &sprite.damage, &damage); |
| } |
| } |
| } |
| |
| // Handle stylus prediction. |
| if (pen_prediction_ms && !isnan(pen[STYLUS_PEN].x) && |
| !isnan(pen[STYLUS_PEN].y)) { |
| sprite_location.x = |
| (uint32_t)round(pen[STYLUS_PEN].x + predicted_stylus_movement.x); |
| sprite_location.y = |
| (uint32_t)round(pen[STYLUS_PEN].y + predicted_stylus_movement.y); |
| sprite_hotspot.x = SPRITE_RAD; |
| sprite_hotspot.y = SPRITE_RAD; |
| |
| // New prediction point. |
| point_t pp = { |
| (uint32_t)round(pen[STYLUS_PEN].x) + SPRITE_RAD - sprite_location.x, |
| (uint32_t)round(pen[STYLUS_PEN].y) + SPRITE_RAD - sprite_location.y}; |
| rect_t damage = {.x1 = fbl::min(pp.x, SPRITE_RAD), |
| .y1 = fbl::min(pp.y, SPRITE_RAD), |
| .x2 = fbl::max(pp.x, SPRITE_RAD) + 1, |
| .y2 = fbl::max(pp.y, SPRITE_RAD) + 1}; |
| |
| // Old prediction point. |
| if (!isnan(old_pen[STYLUS_PEN].x) && !isnan(old_pen[STYLUS_PEN].y)) { |
| point_t pp = {(uint32_t)round(old_pen[STYLUS_PEN].x) + SPRITE_RAD - |
| old_sprite_location.x, |
| (uint32_t)round(old_pen[STYLUS_PEN].y) + SPRITE_RAD - |
| old_sprite_location.y}; |
| rect_t r = {.x1 = fbl::min(pp.x, SPRITE_RAD), |
| .y1 = fbl::min(pp.y, SPRITE_RAD), |
| .x2 = fbl::max(pp.x, SPRITE_RAD) + 1, |
| .y2 = fbl::max(pp.y, SPRITE_RAD) + 1}; |
| union_rects(&damage, &damage, &r); |
| } |
| for (auto& sprite : sprites) { |
| union_rects(&sprite.damage, &sprite.damage, &damage); |
| } |
| } |
| |
| // Update lines if we have new points from the pencils. |
| for (size_t i = 0; i < NUM_PENCILS; ++i) { |
| if (points[i].is_empty()) |
| continue; |
| |
| pointf_t p0 = old_pen[i]; |
| |
| // Convert point to surface coordinate by adding origin. |
| if (!isnan(p0.x)) |
| p0.x += (float)origin.x; |
| if (!isnan(p0.y)) |
| p0.y += (float)origin.y; |
| |
| for (auto& p : points[i]) { |
| pointf_t p1 = {.x = p.x + (float)origin.x, .y = p.y + (float)origin.y}; |
| |
| if (!isnan(p0.x) && !isnan(p0.y)) { |
| uint32_t x1 = (uint32_t)round(p0.x); |
| uint32_t y1 = (uint32_t)round(p0.y); |
| uint32_t x2 = (uint32_t)round(p1.x); |
| uint32_t y2 = (uint32_t)round(p1.y); |
| |
| lines.push_back({{x1, y1}, {x2, y2}}); |
| |
| rect_t damage = {.x1 = fbl::min(x1, x2) - origin.x, |
| .y1 = fbl::min(y1, y2) - origin.y, |
| .x2 = fbl::max(x1, x2) - origin.x + 1, |
| .y2 = fbl::max(y1, y2) - origin.y + 1}; |
| for (auto& buffer : buffers) { |
| union_rects(&buffer.damage, &buffer.damage, &damage); |
| } |
| } |
| p0 = p1; |
| } |
| |
| points[i].reset(); |
| } |
| |
| // Update pending and frame scheduled are the same when VSync is on. |
| if (vsync == VSync::ON) { |
| buffer_update_pending = buffer_frame_scheduled; |
| sprite_update_pending = sprite_frame_scheduled; |
| } else { |
| // Check if updates have completed. This provides back-pressure when |
| // not using VSync. |
| if (buffer_update_pending) { |
| zx_handle_t observed; |
| auto& buffer = buffers[buffer_frame % buffers.size()]; |
| status = zx_object_wait_one(buffer.wait_event, ZX_EVENT_SIGNALED, 0, |
| &observed); |
| buffer_update_pending = (status == ZX_ERR_TIMED_OUT); |
| } |
| if (sprite_update_pending) { |
| zx_handle_t observed; |
| auto& sprite = sprites[sprite_frame % sprites.size()]; |
| status = zx_object_wait_one(sprite.wait_event, ZX_EVENT_SIGNALED, 0, |
| &observed); |
| sprite_update_pending = (status == ZX_ERR_TIMED_OUT); |
| } |
| } |
| |
| bool update_buffer = false; |
| bool update_sprite = false; |
| |
| // Delay update if frame is scheduled or update is pending. |
| if (!buffer_frame_scheduled && !buffer_update_pending) { |
| if ((update_buffer = !is_rect_empty( |
| &buffers[buffer_frame % buffers.size()].damage))) { |
| auto& buffer = buffers[++buffer_frame % buffers.size()]; |
| |
| // Reset wait event. |
| zx_object_signal(buffer.wait_event, ZX_EVENT_SIGNALED, 0); |
| |
| if (vsync != VSync::OFF) { |
| // Present buffer. wait_event_id is invalid when using |
| // adaptive sync as that allows scanout to start event if we |
| // haven't finished producing the new frame. |
| status = |
| set_layer_image(layer_id, buffer.image_id, buffer.wait_event_id); |
| ZX_ASSERT(status == ZX_OK); |
| |
| buffer_frame_scheduled = true; |
| TRACE_ASYNC_BEGIN("app", "Buffer Scheduled", (uintptr_t)&buffer, |
| "image", buffer.image_id); |
| } |
| } |
| } |
| if (!sprite_frame_scheduled && !sprite_update_pending) { |
| if ((update_sprite = !is_rect_empty( |
| &sprites[sprite_frame % sprites.size()].damage))) { |
| auto& sprite = sprites[++sprite_frame % sprites.size()]; |
| |
| // Reset wait event. |
| zx_object_signal(sprite.wait_event, ZX_EVENT_SIGNALED, 0); |
| |
| if (vsync != VSync::OFF) { |
| // Present sprite. wait_event_id is invalid when using |
| // adaptive sync as that allows scanout to start event if we |
| // haven't finished producing the new frame. |
| status = set_layer_image(sprite_layer_id, sprite.image_id, |
| sprite.wait_event_id); |
| ZX_ASSERT(status == ZX_OK); |
| |
| sprite_frame_scheduled = true; |
| TRACE_ASYNC_BEGIN("app", "Sprite Scheduled", (uintptr_t)&sprite, |
| "image", sprite.image_id); |
| } |
| } |
| } |
| |
| if (update_sprite) { |
| auto& sprite = sprites[sprite_frame % sprites.size()]; |
| rect_t damage = sprite.damage; |
| memset(&sprite.damage, 0, sizeof(sprite.damage)); |
| sprite_update_pending = true; |
| |
| // Schedule update on sprite update thread. |
| async::PostTask( |
| sprite_update_loop.dispatcher(), |
| [&sprite_stride, &sprite_surface, &sprite_scratch, &prediction_color, |
| &sprites, sprite_frame, damage, sprite_location, pen, show_cursor, |
| cursor_blur_radius, cursor_movement_angle] { |
| auto& sprite = sprites[sprite_frame % sprites.size()]; |
| |
| TRACE_DURATION("app", "Update Sprite", "image", sprite.image_id, |
| "damage", rect_as_string(&sprite.damage)); |
| |
| ZX_ASSERT(!is_rect_empty(&damage)); |
| ZX_ASSERT(sprite_surface->pixelsize == sizeof(uint32_t)); |
| |
| if (show_cursor) { |
| ZX_ASSERT(cursor_image.width <= SPRITE_DIM); |
| ZX_ASSERT(cursor_image.height <= SPRITE_DIM); |
| |
| if (cursor_blur_radius > 0.0) { |
| uint32_t* sprite_scratch1 = (uint32_t*)sprite_scratch.get(); |
| uint32_t* sprite_scratch2 = |
| sprite_scratch1 + SPRITE_DIM * sprite_stride; |
| uint32_t blur_offset = |
| (SPRITE_RAD - cursor_image.height / 2) * sprite_stride; |
| |
| { |
| TRACE_DURATION("app", "Rotate Cursor", "angle", |
| cursor_movement_angle); |
| rotate_rect(sprite_scratch1 + |
| (SPRITE_RAD - cursor_image.height / 2) * |
| sprite_stride + |
| SPRITE_RAD - cursor_image.width / 2, |
| (const uint32_t*)cursor_image.pixel_data, |
| cursor_image.width, cursor_image.height, |
| sprite_stride, cursor_image.width, |
| cursor_image.height, cursor_image.width, |
| cursor_image.width / 2, cursor_image.height / 2, |
| cursor_image.width / 2, cursor_image.height / 2, |
| cursor_movement_angle); |
| } |
| |
| { |
| TRACE_DURATION("app", "Blur Cursor", "radius", |
| cursor_blur_radius); |
| blur_rect(sprite_scratch2 + blur_offset, |
| sprite_scratch1 + blur_offset, SPRITE_DIM, |
| cursor_image.height, sprite_stride, |
| (int)cursor_blur_radius); |
| } |
| |
| { |
| TRACE_DURATION("app", "Rotate Sprite", "angle", |
| -cursor_movement_angle); |
| rotate_rect((uint32_t*)sprite.data, sprite_scratch2, |
| SPRITE_DIM, SPRITE_DIM, sprite_stride, SPRITE_DIM, |
| SPRITE_DIM, sprite_stride, SPRITE_RAD, SPRITE_RAD, |
| SPRITE_RAD, SPRITE_RAD, -cursor_movement_angle); |
| } |
| } else { |
| { |
| TRACE_DURATION("app", "Clear Sprite"); |
| gfx_fillrect(sprite_surface, 0, 0, SPRITE_DIM, SPRITE_DIM, 0); |
| } |
| |
| { |
| TRACE_DURATION("app", "Copy Cursor To Sprite"); |
| copy_rect(((uint32_t*)sprite_surface->ptr) + |
| (SPRITE_RAD - cursor_image.height / 2) * |
| sprite_stride + |
| SPRITE_RAD - cursor_image.width / 2, |
| (const uint32_t*)cursor_image.pixel_data, |
| sprite_surface->stride, cursor_image.width, 0, 0, |
| cursor_image.width, cursor_image.height); |
| } |
| |
| { |
| TRACE_DURATION("app", "Copy Sprite To Buffer"); |
| copy_rect((uint32_t*)sprite.data, |
| (const uint32_t*)sprite_surface->ptr, sprite_stride, |
| sprite_surface->stride, 0, 0, SPRITE_DIM, |
| SPRITE_DIM); |
| } |
| } |
| } else { |
| uint32_t x1 = damage.x1; |
| uint32_t y1 = damage.y1; |
| uint32_t x2 = damage.x2; |
| uint32_t y2 = damage.y2; |
| |
| if (x2 > SPRITE_DIM) |
| x2 = SPRITE_DIM; |
| if (y2 > SPRITE_DIM) |
| y2 = SPRITE_DIM; |
| |
| if (x1 < x2 && y1 < y2) { |
| { |
| TRACE_DURATION("app", "Clear Sprite"); |
| gfx_fillrect(sprite_surface, x1, y1, x2 - x1, y2 - y1, 0); |
| } |
| |
| if (!isnan(pen[STYLUS_PEN].x) && !isnan(pen[STYLUS_PEN].y)) { |
| TRACE_DURATION( |
| "app", "Draw Stylus Prediction Line", "dx", |
| (uint32_t)round(pen[STYLUS_PEN].x) - sprite_location.x, |
| "dy", |
| (uint32_t)round(pen[STYLUS_PEN].y) - sprite_location.y); |
| gfx_line(sprite_surface, |
| (uint32_t)round(pen[STYLUS_PEN].x) + SPRITE_RAD - |
| sprite_location.x, |
| (uint32_t)round(pen[STYLUS_PEN].y) + SPRITE_RAD - |
| sprite_location.y, |
| SPRITE_RAD, SPRITE_RAD, prediction_color); |
| } |
| |
| { |
| TRACE_DURATION("app", "Copy Sprite To Buffer"); |
| copy_rect((uint32_t*)sprite.data, |
| (const uint32_t*)sprite_surface->ptr, sprite_stride, |
| sprite_surface->stride, x1, y1, x2, y2); |
| } |
| } |
| } |
| |
| // Signal wait event to communicate that update has |
| // completed. |
| zx_object_signal(sprite.wait_event, 0, ZX_EVENT_SIGNALED); |
| }); |
| } |
| |
| if (update_buffer) { |
| auto& buffer = buffers[buffer_frame % buffers.size()]; |
| rect_t damage = buffer.damage; |
| memset(&buffer.damage, 0, sizeof(buffer.damage)); |
| buffer_update_pending = true; |
| |
| // Schedule each line on update thread. |
| for (auto& line : lines) { |
| async::PostTask(update_loop.dispatcher(), [&surface, line] { |
| TRACE_DURATION("app", "Draw Line"); |
| gfx_line(surface, line.p1.x, line.p1.y, line.p2.x, line.p2.y, |
| /*color=*/0); |
| }); |
| } |
| lines.reset(); |
| |
| // Schedule buffer update on update thread. |
| async::PostTask( |
| update_loop.dispatcher(), |
| [&stride, &surface, &slow_down_scale_factor, &width, &height, |
| &buffers, buffer_frame, damage, predicted_origin] { |
| auto& buffer = buffers[buffer_frame % buffers.size()]; |
| |
| TRACE_DURATION("app", "Update Buffer", "image", buffer.image_id, |
| "damage", rect_as_string(&damage)); |
| |
| ZX_ASSERT(!is_rect_empty(&damage)); |
| |
| uint32_t x1 = damage.x1; |
| uint32_t y1 = damage.y1; |
| uint32_t x2 = damage.x2; |
| uint32_t y2 = damage.y2; |
| |
| if (x2 > width) |
| x2 = width; |
| if (y2 > height) |
| y2 = height; |
| |
| if (x1 < x2 && y1 < y2) { |
| uint32_t pixelsize = surface->pixelsize; |
| uint32_t lines = y2 - y1; |
| uint32_t bytes_per_line = (x2 - x1) * pixelsize; |
| uint32_t dst_pitch = stride * pixelsize; |
| uint32_t src_pitch = surface->stride * pixelsize; |
| uint8_t* dst = |
| ((uint8_t*)buffer.data) + (y1 * stride + x1) * pixelsize; |
| const uint8_t* src = |
| ((uint8_t*)surface->ptr) + |
| ((y1 + predicted_origin.y) * surface->stride + x1 + |
| predicted_origin.x) * |
| pixelsize; |
| |
| TRACE_DURATION("app", "Copy Contents To Buffer"); |
| |
| while (lines--) { |
| int n = slow_down_scale_factor; |
| while (n--) |
| memcpy(dst, src, bytes_per_line); |
| dst += dst_pitch; |
| src += src_pitch; |
| } |
| } |
| |
| // Signal wait event to communicate that update has |
| // completed. |
| zx_object_signal(buffer.wait_event, 0, ZX_EVENT_SIGNALED); |
| }); |
| } |
| |
| // Set sprite position. |
| int32_t sprite_x1 = sprite_location.x - sprite_hotspot.x; |
| int32_t sprite_y1 = sprite_location.y - sprite_hotspot.y; |
| int32_t sprite_x2 = sprite_x1 + SPRITE_DIM; |
| int32_t sprite_y2 = sprite_y1 + SPRITE_DIM; |
| int32_t clipped_sprite_x1 = fbl::max(sprite_x1, 0); |
| int32_t clipped_sprite_y1 = fbl::max(sprite_y1, 0); |
| int32_t clipped_sprite_x2 = fbl::min(sprite_x2, (int32_t)width); |
| int32_t clipped_sprite_y2 = fbl::min(sprite_y2, (int32_t)height); |
| ZX_ASSERT(sprite_x1 <= clipped_sprite_x1); |
| ZX_ASSERT(sprite_y1 <= clipped_sprite_y1); |
| status = set_layer_position(sprite_layer_id, clipped_sprite_x1 - sprite_x1, |
| clipped_sprite_y1 - sprite_y1, |
| clipped_sprite_x1, clipped_sprite_y1, |
| clipped_sprite_x2 - clipped_sprite_x1, |
| clipped_sprite_y2 - clipped_sprite_y1); |
| ZX_ASSERT(status == ZX_OK); |
| |
| status = apply_config(); |
| ZX_ASSERT(status == ZX_OK); |
| |
| frame_task.Post(loop.dispatcher()); |
| }); |
| frame_task.Post(loop.dispatcher()); |
| |
| loop.Run(); |
| |
| if (touchfd >= 0) |
| close(touchfd); |
| if (touchpadfd >= 0) |
| close(touchpadfd); |
| zx_handle_close(dc_handle); |
| return 0; |
| } |