From c79cc74684e091e3301fa3e4407da96a99e25d4e Mon Sep 17 00:00:00 2001 From: ardera <2488440+ardera@users.noreply.github.com> Date: Wed, 4 Sep 2019 12:29:07 +0200 Subject: [PATCH 1/4] Small Improvements, Touchscreen Support --- src/flutter-pi.c | 259 +++++++++++++++++++++++++++++++++++--------- src/flutter-pi.h | 7 ++ src/methodchannel.c | 128 +++++++++++----------- src/methodchannel.h | 6 +- 4 files changed, 288 insertions(+), 112 deletions(-) diff --git a/src/flutter-pi.c b/src/flutter-pi.c index bfb432cc..96f052c2 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -25,14 +25,21 @@ #include "methodchannel.h" -char* usage = "Flutter Raspberry Pi\n\nUsage:\n flutter-pi \n"; - -int argc; -const char* const *argv; -char asset_bundle_path[1024]; -char kernel_blob_path[1024]; -char executable_path[1024]; -char icu_data_path[1024]; +char* usage ="\ +Flutter for Raspberry Pi\n\n\ +Usage:\n\ + flutter-pi [options] \n\n\ +Options:\n\ + -m Path to the mouse device file. Typically /dev/input/mouseX or /dev/input/eventX\n\ + -t Path to the touchscreen device file. Typically /dev/input/touchscreenX or /dev/input/eventX\n\ +"; + +int engine_argc; +const char* const *engine_argv; +char asset_bundle_path[1024] = {0}; +char kernel_blob_path[1024] = {0}; +char executable_path[1024] = {0}; +char icu_data_path[1024] = {0}; uint32_t width; uint32_t height; EGLDisplay display; @@ -44,10 +51,15 @@ DISPMANX_ELEMENT_HANDLE_T dispman_element; EGL_DISPMANX_WINDOW_T native_window; FlutterRendererConfig renderer_config; FlutterProjectArgs project_args; -int mouse_filehandle; + +bool input_is_mouse = false; +char input_device_path[1024]; +int input_filehandle; double mouse_x = 0; double mouse_y = 0; uint8_t button = 0; +struct TouchscreenSlot ts_slots[10]; +struct TouchscreenSlot* ts_slot = &(ts_slots[0]); pthread_t io_thread_id; pthread_t platform_thread_id; @@ -77,11 +89,16 @@ bool clear_current(void* userdata) { return true; } bool present(void* userdata) { + printf("*** PRESENT ***\n"); + eglWaitClient(); + if (eglSwapBuffers(display, surface) != EGL_TRUE) { fprintf(stderr, "Could not swap buffers to present the screen.\n"); return false; } + eglWaitNative(EGL_CORE_NATIVE_ENGINE); + GLenum error = glGetError(); if (error != GL_NO_ERROR) { printf("got gl error: %d\n", error); @@ -94,8 +111,6 @@ uint32_t fbo_callback(void* userdata) { } void* proc_resolver(void* userdata, const char* name) { if (name == NULL) return NULL; - - printf("calling proc_resolver with %s\n", name); void* address; if ((address = dlsym(RTLD_DEFAULT, name))) { @@ -113,7 +128,7 @@ void on_platform_message(const FlutterPlatformMessage* message, void* userda printf("MethodCall: method name: %s argument type: %d\n", methodcall.method, methodcall.argument.type); if (strcmp(methodcall.method, "counter") == 0) { - printf("method \"counter\" was called with argument %d\n", methodcall.argument.value.int_value); + printf("method \"counter\" was called with argument %d\n", methodcall.argument.int_value); } MethodChannel_freeMethodCall(&methodcall); @@ -276,6 +291,9 @@ bool init_display(void) { EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 8, + EGL_STENCIL_SIZE, 1, + EGL_RENDERABLE_TYPE,EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE }; @@ -288,8 +306,7 @@ bool init_display(void) { // create the EGL context EGLint context_attributes[] = { - EGL_CONTEXT_CLIENT_VERSION, - 2, + EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; @@ -383,13 +400,14 @@ bool init_application(void) { renderer_config.open_gl.present = present; renderer_config.open_gl.fbo_callback = fbo_callback; renderer_config.open_gl.gl_proc_resolver= proc_resolver; + //renderer_config.open_gl.fbo_reset_after_present = true; // configure flutter project_args.struct_size = sizeof(FlutterProjectArgs); project_args.assets_path = asset_bundle_path; project_args.icu_data_path = icu_data_path; - project_args.command_line_argc = argc; - project_args.command_line_argv = argv; + project_args.command_line_argc = engine_argc; + project_args.command_line_argv = engine_argv; project_args.platform_message_callback = on_platform_message; project_args.custom_task_runners = &(FlutterCustomTaskRunners) { .struct_size = sizeof(FlutterCustomTaskRunners), @@ -436,9 +454,9 @@ void destroy_application(void) { * Input-Output * ****************/ bool init_io(void) { - mouse_filehandle = open("/dev/input/event0", O_RDONLY); - if (mouse_filehandle < 0) { - perror("error opening the mouse file"); + input_filehandle = open(input_device_path, O_RDONLY); + if (input_filehandle < 0) { + perror("error opening the input device file"); return false; } @@ -447,29 +465,36 @@ bool init_io(void) { void* io_loop(void* userdata) { FlutterPointerPhase phase; struct input_event event[64]; + struct input_event* ev; + FlutterPointerEvent pointer_event[64]; + int n_pointerevents = 0; bool ok; // first, tell flutter that the mouse is inside the engine window - ok = FlutterEngineSendPointerEvent( - engine, - & (FlutterPointerEvent) { - .struct_size = sizeof(FlutterPointerEvent), - .phase = kAdd, - .timestamp = (size_t) (FlutterEngineGetCurrentTime()*1000), - .x = mouse_x, - .y = mouse_y, - .signal_kind = kFlutterPointerSignalKindNone + if (input_is_mouse) { + ok = FlutterEngineSendPointerEvent( + engine, + & (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .phase = kAdd, + .timestamp = (size_t) (FlutterEngineGetCurrentTime()*1000), + .x = mouse_x, + .y = mouse_y, + .signal_kind = kFlutterPointerSignalKindNone + }, }, - 1 - ) == kSuccess; - if (!ok) return false; - + }, + 1 + ) == kSuccess; + if (!ok) return false; + } - while (1) { + // mouse + while (input_is_mouse) { // read up to 64 input events - int rd = read(mouse_filehandle, &event, sizeof(struct input_event)*64); + int rd = read(input_filehandle, &event, sizeof(struct input_event)*64); if (rd < (int) sizeof(struct input_event)) { - perror("error reading from mouse"); + perror("error reading from input device"); return false; } @@ -478,23 +503,32 @@ void* io_loop(void* userdata) { // process all input events, and send all resulting pointer events at once. for (int i = 0; i < rd / sizeof(struct input_event); i++) { phase = kCancel; + ev = &(event[i]); - if (event[i].type == EV_REL) { - if (event[i].code == REL_X) { // mouse moved in the x-direction - mouse_x += event[i].value; + if (ev->type == EV_REL) { + if (ev->code == REL_X) { // mouse moved in the x-direction + mouse_x += ev->value; phase = button ? kMove : kHover; - } else if (event[i].code == REL_Y) { // mouse moved in the y-direction - mouse_y += event[i].value; + } else if (ev->code == REL_Y) { // mouse moved in the y-direction + mouse_y += ev->value; phase = button ? kMove : kHover; } - } else if ((event[i].type == EV_KEY) && ((event[i].code == BTN_LEFT) || (event[i].code == BTN_RIGHT))) { + } else if (ev->type == EV_ABS) { + if (ev->code == ABS_X) { + mouse_x = ev->value; + phase = button ? kMove : kHover; + } else if (ev->code == ABS_Y) { + mouse_y = ev->value; + phase = button ? kMove : kHover; + } + } else if ((ev->type == EV_KEY) && ((ev->code == BTN_LEFT) || (ev->code == BTN_RIGHT))) { // either the left or the right mouse button was pressed // the 1st bit in "button" is set when BTN_LEFT is down. the 2nd bit when BTN_RIGHT is down. - uint8_t mask = event[i].code == BTN_LEFT ? 1 : 2; - if (event[i].value == 1) button |= mask; + uint8_t mask = ev->code == BTN_LEFT ? 1 : 2; + if (ev->value == 1) button |= mask; else button &= ~mask; - phase = event[i].value == 1 ? kDown : kUp; + phase = ev->value == 1 ? kDown : kUp; } if (phase != kCancel) { @@ -516,6 +550,90 @@ void* io_loop(void* userdata) { printf("mouse position: %f, %f\n", mouse_x, mouse_y); } + if (!input_is_mouse) { + for (int j = 0; j<10; j++) { + printf("Sending kAdd %d to Flutter Engine\n", j); + ts_slots[j].id = -1; + ok = FlutterEngineSendPointerEvent( + engine, + & (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .phase = kAdd, + .timestamp = (size_t) (FlutterEngineGetCurrentTime()*1000), + .device = j, + .x = 0, + .y = 0, + .signal_kind = kFlutterPointerSignalKindNone + }, + 1 + ) == kSuccess; + if (!ok) { + fprintf(stderr, "Error sending Pointer message to flutter engine\n"); + return false; + } + } + } + + // touchscreen + while (!input_is_mouse) { + int rd = read(input_filehandle, &event, sizeof(struct input_event)*64); + if (rd < (int) sizeof(struct input_event)) { + perror("error reading from input device"); + return false; + } + + n_pointerevents = 0; + for (int i = 0; i < rd / sizeof(struct input_event); i++) { + ev = &(event[i]); + + if (ev->type == EV_ABS) { + if (ev->code == ABS_MT_SLOT) { + ts_slot = &(ts_slots[ev->value]); + } else if (ev->code == ABS_MT_TRACKING_ID) { + if (ts_slot->id == -1) { + ts_slot->id = ev->value; + ts_slot->phase = kDown; + } else if (ev->value == -1) { + ts_slot->id = ev->value; + ts_slot->phase = kUp; + } + } else if (ev->code == ABS_MT_POSITION_X) { + ts_slot->x = ev->value; + if (ts_slot->phase == kCancel) ts_slot->phase = kMove; + } else if (ev->code == ABS_MT_POSITION_Y) { + ts_slot->y = ev->value; + if (ts_slot->phase == kCancel) ts_slot->phase = kMove; + } + } else if ((ev->type == EV_SYN) && (ev->code == SYN_REPORT)) { + for (int j = 0; j < 10; j++) { + if (ts_slots[j].phase != kCancel) { + pointer_event[n_pointerevents++] = (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .phase = ts_slots[j].phase, + .timestamp = (size_t) (FlutterEngineGetCurrentTime()*1000), + .device = j, + .x = ts_slots[j].x, + .y = ts_slots[j].y, + .signal_kind = kFlutterPointerSignalKindNone + }; + ts_slots[j].phase = kCancel; + } + } + } + } + + ok = FlutterEngineSendPointerEvent( + engine, + pointer_event, + n_pointerevents + ) == kSuccess; + + if (!ok) { + fprintf(stderr, "Error sending pointer events to flutter\n"); + return false; + } + } + return NULL; } bool run_io_thread(void) { @@ -534,17 +652,58 @@ bool run_io_thread(void) { return true; } +bool parse_cmd_args(int argc, const char *const * argv) { + int opt; + int index = 0; + + while ((opt = getopt(argc, (char *const *) argv, "m:t:")) != -1) { + index++; + switch(opt) { + case 'm': + printf("Using mouse input from mouse %s\n", optarg); + snprintf(input_device_path, 1023, "%s", optarg); + input_is_mouse = true; + + index++; + break; + case 't': + printf("Using touchscreen input from %s\n", optarg); + snprintf(input_device_path, 1023, "%s", optarg); + input_is_mouse = false; + + index++; + break; + default: + fprintf(stderr, "Unknown Option: %c\n%s", (char) optopt, usage); + return false; + } + } + + if (strlen(input_device_path) == 0) { + fprintf(stderr, "At least one of -t or -r has to be given\n%s", usage); + return false; + } + + if (optind >= argc) { + fprintf(stderr, "Expected Asset bundle path argument after options\n%s", usage); + return false; + } + + snprintf(asset_bundle_path, 1023, "%s", argv[optind]); + printf("Asset bundle path: %s\n", asset_bundle_path); + + engine_argc = argc-optind-1; + engine_argv = &(argv[optind+1]); + + return true; +} int main(int argc, const char *const * argv) { - if (argc <= 1) { - fprintf(stderr, "Invalid Arguments\n"); - fprintf(stdout, "%s", usage); + if (!parse_cmd_args(argc, argv)) { return EXIT_FAILURE; } - + // check if asset bundle path is valid - printf("asset_bundle_path: %s\n", argv[1]); - snprintf(asset_bundle_path, 1024, "%s", argv[1]); if (!setup_paths()) { return EXIT_FAILURE; } diff --git a/src/flutter-pi.h b/src/flutter-pi.h index bb56babc..f4442c11 100644 --- a/src/flutter-pi.h +++ b/src/flutter-pi.h @@ -10,6 +10,13 @@ struct LinkedTaskListElement { uint64_t target_time; }; +struct TouchscreenSlot { + int id; + int x; + int y; + FlutterPointerPhase phase; +}; + FlutterEngine engine; #endif \ No newline at end of file diff --git a/src/methodchannel.c b/src/methodchannel.c index 0f21497e..af9dd044 100644 --- a/src/methodchannel.c +++ b/src/methodchannel.c @@ -48,21 +48,21 @@ bool MethodChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value, break; case kTypeString: - size = strlen(value->value.string_value); + size = strlen(value->string_value); *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size *p_buffer_size += size; break; case kTypeByteArray: - size = value->value.bytearray_value.size; + size = value->bytearray_value.size; *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size *p_buffer_size += size; break; case kTypeIntArray: - size = value->value.intarray_value.size; + size = value->intarray_value.size; *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size *p_buffer_size = (((*p_buffer_size) + 3) | 3) - 3; // 4-byte aligned @@ -70,7 +70,7 @@ bool MethodChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value, break; case kTypeLongArray: - size = value->value.longarray_value.size; + size = value->longarray_value.size; *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size *p_buffer_size = (((*p_buffer_size) + 7) | 7) - 7; // 8-byte aligned @@ -78,7 +78,7 @@ bool MethodChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value, break; case kTypeDoubleArray: - size = value->value.doublearray_value.size; + size = value->doublearray_value.size; *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size *p_buffer_size = (((*p_buffer_size) + 7) | 7) - 7; // 8-byte aligned @@ -86,20 +86,20 @@ bool MethodChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value, break; case kTypeList: - size = value->value.list_value.size; + size = value->list_value.size; *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write list size for (int i = 0; ivalue.list_value.list[i]), p_buffer_size)) return false; + if (!MethodChannel_calculateValueSizeInBuffer(&(value->list_value.list[i]), p_buffer_size)) return false; break; case kTypeMap: - size = value->value.map_value.size; + size = value->map_value.size; *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write map size for (int i = 0; ivalue.list_value.list[i*2 ]), p_buffer_size)) return false; - if (!MethodChannel_calculateValueSizeInBuffer(&(value->value.list_value.list[i*2+1]), p_buffer_size)) return false; + if (!MethodChannel_calculateValueSizeInBuffer(&(value->list_value.list[i*2 ]), p_buffer_size)) return false; + if (!MethodChannel_calculateValueSizeInBuffer(&(value->list_value.list[i*2+1]), p_buffer_size)) return false; } break; @@ -145,31 +145,31 @@ bool MethodChannel_writeValueToBuffer(struct MethodChannelValue* value, uint8_t* case kFalse: break; case kTypeInt: - *(int32_t*) *p_buffer = value->value.int_value; + *(int32_t*) *p_buffer = value->int_value; NEXTN(*p_buffer, 4) break; case kTypeLong: - *(int64_t*) *p_buffer = value->value.long_value; + *(int64_t*) *p_buffer = value->long_value; NEXTN(*p_buffer, 8) break; case kTypeDouble: MethodChannel_alignBuffer(8, p_buffer); - *(double*) *p_buffer = value->value.double_value; + *(double*) *p_buffer = value->double_value; NEXTN(*p_buffer, 8) break; case kTypeBigInt: case kTypeString: case kTypeByteArray: if (value->type == kTypeBigInt) { - size = strlen(value->value.bigint_value); - byteArray = (uint8_t*) value->value.bigint_value; + size = strlen(value->bigint_value); + byteArray = (uint8_t*) value->bigint_value; } else if (value->type == kTypeString) { - size = strlen(value->value.string_value); - byteArray = (uint8_t*) value->value.string_value; + size = strlen(value->string_value); + byteArray = (uint8_t*) value->string_value; } else if (value->type == kTypeByteArray) { - size = value->value.bytearray_value.size; - byteArray = (uint8_t*) value->value.bytearray_value.array; + size = value->bytearray_value.size; + byteArray = (uint8_t*) value->bytearray_value.array; } MethodChannel_writeSizeValueToBuffer(size, p_buffer); @@ -179,55 +179,55 @@ bool MethodChannel_writeValueToBuffer(struct MethodChannelValue* value, uint8_t* } break; case kTypeIntArray: - size = value->value.intarray_value.size; + size = value->intarray_value.size; MethodChannel_writeSizeValueToBuffer(size, p_buffer); MethodChannel_alignBuffer(4, p_buffer); for (int i=0; ivalue.intarray_value.array[i]; + *(int32_t*) *p_buffer = value->intarray_value.array[i]; NEXTN(*p_buffer, 4) } break; case kTypeLongArray: - size = value->value.longarray_value.size; + size = value->longarray_value.size; MethodChannel_writeSizeValueToBuffer(size, p_buffer); MethodChannel_alignBuffer(8, p_buffer); for (int i=0; ivalue.longarray_value.array[i]; + *(int64_t*) *p_buffer = value->longarray_value.array[i]; NEXTN(*p_buffer, 8) } break; case kTypeDoubleArray: - size = value->value.doublearray_value.size; + size = value->doublearray_value.size; MethodChannel_writeSizeValueToBuffer(size, p_buffer); MethodChannel_alignBuffer(8, p_buffer); for (int i=0; ivalue.doublearray_value.array[i]; + *(double*) *p_buffer = value->doublearray_value.array[i]; NEXTN(*p_buffer, 8) } break; case kTypeList: - size = value->value.list_value.size; + size = value->list_value.size; MethodChannel_writeSizeValueToBuffer(size, p_buffer); for (int i=0; ivalue.list_value.list[i]), p_buffer)) return false; + if (!MethodChannel_writeValueToBuffer(&(value->list_value.list[i]), p_buffer)) return false; break; case kTypeMap: - size = value->value.map_value.size; + size = value->map_value.size; MethodChannel_writeSizeValueToBuffer(size, p_buffer); for (int i=0; ivalue.map_value.map[i*2 ]), p_buffer)) return false; - if (!MethodChannel_writeValueToBuffer(&(value->value.map_value.map[i*2+1]), p_buffer)) return false; + if (!MethodChannel_writeValueToBuffer(&(value->map_value.map[i*2 ]), p_buffer)) return false; + if (!MethodChannel_writeValueToBuffer(&(value->map_value.map[i*2+1]), p_buffer)) return false; } break; default: @@ -246,9 +246,7 @@ bool MethodChannel_call(char* channel, char* method, struct MethodChannelValue* // the method name is encoded as a String value and is the first value written to the buffer. struct MethodChannelValue method_name_value = { .type = kTypeString, - .value = { - .string_value = method - } + .string_value = method }; // calculate buffer size @@ -331,14 +329,14 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str case kTypeInt: ASSERT_RETURN_BOOL(*buffer_remaining >= 4, "Error decoding platform message: while decoding kTypeInt: message ended to soon") - value->value.int_value = *(int32_t*) *p_buffer; + value->int_value = *(int32_t*) *p_buffer; NEXTN(*p_buffer, *buffer_remaining, 4) break; case kTypeLong: ASSERT_RETURN_BOOL(*buffer_remaining >= 8, "Error decoding platform message: while decoding kTypeLong: message ended too soon") - value->value.long_value = *(int64_t*) *p_buffer; + value->long_value = *(int64_t*) *p_buffer; NEXTN(*p_buffer, *buffer_remaining, 8) break; @@ -346,7 +344,7 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str ASSERT_RETURN_BOOL(*buffer_remaining >= 8 + ALIGNMENT_DIFF(*p_buffer, 8), "Error decoding platform message: while decoding kTypeDouble: message ended too soon") ALIGN(*p_buffer, *buffer_remaining, 8) - value->value.double_value = *(double*) *p_buffer; + value->double_value = *(double*) *p_buffer; NEXTN(*p_buffer, *buffer_remaining, 8) break; @@ -360,15 +358,15 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str c_string[i] = **p_buffer; NEXT(*p_buffer, *buffer_remaining) } - value->value.string_value = c_string; + value->string_value = c_string; break; case kTypeByteArray: if (!MethodChannel_decodeSize(p_buffer, buffer_remaining, &size)) return false; ASSERT_RETURN_BOOL(*buffer_remaining >= size, "Error decoding platform message: while decoding kTypeByteArray: message ended too soon") - value->value.bytearray_value.size = size; - value->value.bytearray_value.array = *p_buffer; + value->bytearray_value.size = size; + value->bytearray_value.array = *p_buffer; NEXTN(*p_buffer, *buffer_remaining, size); @@ -379,8 +377,8 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str ASSERT_RETURN_BOOL(*buffer_remaining >= size*4 + ALIGNMENT_DIFF(*p_buffer, 4), "Error decoding platform message: while decoding kTypeIntArray: message ended too soon") ALIGN(*p_buffer, *buffer_remaining, 4) - value->value.intarray_value.size = size; - value->value.intarray_value.array = (int32_t*) *p_buffer; + value->intarray_value.size = size; + value->intarray_value.array = (int32_t*) *p_buffer; NEXTN(*p_buffer, *buffer_remaining, size*4) @@ -391,8 +389,8 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str ASSERT_RETURN_BOOL(*buffer_remaining >= size*8 + ALIGNMENT_DIFF(*p_buffer, 8), "Error decoding platform message: while decoding kTypeLongArray: message ended too soon") ALIGN(*p_buffer, *buffer_remaining, 8) - value->value.longarray_value.size = size; - value->value.longarray_value.array = (int64_t*) *p_buffer; + value->longarray_value.size = size; + value->longarray_value.array = (int64_t*) *p_buffer; NEXTN(*p_buffer, *buffer_remaining, size*8) @@ -403,8 +401,8 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str ASSERT_RETURN_BOOL(*buffer_remaining >= size*8 + ALIGNMENT_DIFF(*p_buffer, 8), "Error decoding platform message: while decoding kTypeIntArray: message ended too soon") ALIGN(*p_buffer, *buffer_remaining, 8) - value->value.doublearray_value.size = size; - value->value.doublearray_value.array = (double*) *p_buffer; + value->doublearray_value.size = size; + value->doublearray_value.array = (double*) *p_buffer; NEXTN(*p_buffer, *buffer_remaining, size*8) @@ -412,23 +410,23 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str case kTypeList: if (!MethodChannel_decodeSize(p_buffer, buffer_remaining, &size)) return false; - value->value.list_value.size = size; - value->value.list_value.list = calloc(size, sizeof(struct MethodChannelValue)); + value->list_value.size = size; + value->list_value.list = calloc(size, sizeof(struct MethodChannelValue)); for (int i = 0; i < size; i++) { - if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->value.list_value.list[i]))) return false; + if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->list_value.list[i]))) return false; } break; case kTypeMap: if (!MethodChannel_decodeSize(p_buffer, buffer_remaining, &size)) return false; - value->value.map_value.size = size; - value->value.map_value.map = calloc(size*2, sizeof(struct MethodChannelValue)); + value->map_value.size = size; + value->map_value.map = calloc(size*2, sizeof(struct MethodChannelValue)); for (int i = 0; i < size; i++) { - if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->value.list_value.list[i*2 ]))) return false; - if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->value.list_value.list[i*2+1]))) return false; + if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->list_value.list[i*2 ]))) return false; + if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->list_value.list[i*2+1]))) return false; } break; @@ -443,13 +441,21 @@ bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall uint8_t* buffer_cursor = buffer; size_t buffer_remaining = buffer_size; + if (*buffer == (char) 123) { + result->protocol = kJSONProtocol; + fprintf(stderr, "Error decoding Method Call: JSON Protocol not supported yet.\n"); + return false; + } else { + result->protocol = kStandardProtocol; + } + struct MethodChannelValue method_name; if (!MethodChannel_decodeValue(&buffer_cursor, &buffer_remaining, &method_name)) return false; if (method_name.type != kTypeString) { fprintf(stderr, "Error decoding Method Call: expected type of first value in buffer to be string (i.e. method name), got %d\n", method_name.type); return false; } - result->method = method_name.value.string_value; + result->method = method_name.string_value; if (!MethodChannel_decodeValue(&buffer_cursor, &buffer_remaining, &(result->argument))) return false; @@ -460,21 +466,21 @@ bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall bool MethodChannel_freeValue(struct MethodChannelValue* p_value) { switch (p_value->type) { case kTypeString: - free(p_value->value.string_value); + free(p_value->string_value); break; case kTypeList: - for (int i=0; i < p_value->value.list_value.size; i++) - if (!MethodChannel_freeValue(&(p_value->value.list_value.list[i]))) return false; + for (int i=0; i < p_value->list_value.size; i++) + if (!MethodChannel_freeValue(&(p_value->list_value.list[i]))) return false; - free(p_value->value.list_value.list); + free(p_value->list_value.list); break; case kTypeMap: - for (int i=0; i< p_value->value.map_value.size; i++) { - if (!MethodChannel_freeValue(&(p_value->value.map_value.map[i*2 ]))) return false; - if (!MethodChannel_freeValue(&(p_value->value.map_value.map[i*2+1]))) return false; + for (int i=0; i< p_value->map_value.size; i++) { + if (!MethodChannel_freeValue(&(p_value->map_value.map[i*2 ]))) return false; + if (!MethodChannel_freeValue(&(p_value->map_value.map[i*2+1]))) return false; } - free(p_value->value.map_value.map); + free(p_value->map_value.map); default: break; } diff --git a/src/methodchannel.h b/src/methodchannel.h index 6534023c..c7183db5 100644 --- a/src/methodchannel.h +++ b/src/methodchannel.h @@ -55,10 +55,14 @@ struct MethodChannelValue { size_t size; struct MethodChannelValue* map; } map_value; - } value; + }; }; struct MethodCall { + enum { + kStandardProtocol, + kJSONProtocol + } protocol; char* method; struct MethodChannelValue argument; }; From 072536dc15d7012bd5550fc9396c8b940613daa3 Mon Sep 17 00:00:00 2001 From: ardera <2488440+ardera@users.noreply.github.com> Date: Tue, 10 Sep 2019 21:39:28 +0200 Subject: [PATCH 2/4] v3d support, fixed texture glitches --- src/flutter-pi.c | 964 ++++++++++++++++++++++++++++++++------------ src/flutter-pi.h | 16 +- src/methodchannel.c | 12 +- src/methodchannel.h | 4 +- 4 files changed, 733 insertions(+), 263 deletions(-) diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 96f052c2..d8b944d7 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -6,25 +6,30 @@ #include #include #include -#include -#include -#include -#include -#include #include #include #include - +#include #include #include #include +#include + +//#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include "flutter-pi.h" #include "methodchannel.h" - char* usage ="\ Flutter for Raspberry Pi\n\n\ Usage:\n\ @@ -34,110 +39,374 @@ Options:\n\ -t Path to the touchscreen device file. Typically /dev/input/touchscreenX or /dev/input/eventX\n\ "; -int engine_argc; -const char* const *engine_argv; -char asset_bundle_path[1024] = {0}; -char kernel_blob_path[1024] = {0}; -char executable_path[1024] = {0}; -char icu_data_path[1024] = {0}; -uint32_t width; -uint32_t height; -EGLDisplay display; -EGLConfig config; -EGLContext context; -EGLSurface surface; -DISPMANX_DISPLAY_HANDLE_T dispman_display; -DISPMANX_ELEMENT_HANDLE_T dispman_element; -EGL_DISPMANX_WINDOW_T native_window; -FlutterRendererConfig renderer_config; -FlutterProjectArgs project_args; - -bool input_is_mouse = false; -char input_device_path[1024]; -int input_filehandle; -double mouse_x = 0; -double mouse_y = 0; -uint8_t button = 0; -struct TouchscreenSlot ts_slots[10]; -struct TouchscreenSlot* ts_slot = &(ts_slots[0]); +// width & height of the display in pixels +uint32_t width, height; + +// physical width & height of the display in millimeters +uint32_t width_mm, height_mm; +uint32_t refresh_rate; + +// this is the pixel ratio (used by flutter) for the Official Raspberry Pi 7inch display. +// if a HDMI screen is connected and being used by this application, the pixel ratio will be +// computed inside init_display. +// for DSI the pixel ratio can not be calculated, because there's no way to query the physical +// size for DSI displays. +double pixel_ratio = 1.3671; + +struct { + char device[128]; + int fd; + uint32_t connector_id; + drmModeModeInfo *mode; + uint32_t crtc_id; + size_t crtc_index; + int waiting_for_flip; + struct gbm_bo *previous_bo; + drmEventContext evctx; +} drm = {0}; + +struct { + struct gbm_device *device; + struct gbm_surface *surface; + uint32_t format; + uint64_t modifier; +} gbm = {0}; + +struct { + EGLDisplay display; + EGLConfig config; + EGLContext context; + EGLSurface surface; + + bool modifiers_supported; + + EGLDisplay (*eglGetPlatformDisplayEXT)(EGLenum platform, void *native_display, const EGLint *attrib_list); + EGLSurface (*eglCreatePlatformWindowSurfaceEXT)(EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list); + EGLSurface (*eglCreatePlatformPixmapSurfaceEXT)(EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLint *attrib_list); +} egl = {0}; + +struct { + char asset_bundle_path[256]; + char kernel_blob_path[256]; + char executable_path[256]; + char icu_data_path[256]; + FlutterRendererConfig renderer_config; + FlutterProjectArgs args; + int engine_argc; + const char* const *engine_argv; + intptr_t next_vblank_baton; +} flutter = {0}; + +struct { + bool is_mouse; + char device_path[128]; + int fd; + double x, y; + uint8_t button; + struct TouchscreenSlot ts_slots[10]; + struct TouchscreenSlot* ts_slot; +} input = {0}; pthread_t io_thread_id; pthread_t platform_thread_id; -struct LinkedTaskListElement task_list_head_sentinel - = {.next = NULL, .target_time = 0, .task = {.runner = NULL, .task = 0}}; +struct LinkedTaskListElement task_list_head_sentinel = { + .next = NULL, + .is_vblank_event = false, + .target_time = 0, + .task = {.runner = NULL, .task = 0} +}; pthread_mutex_t task_list_lock; bool should_notify_platform_thread = false; sigset_t sigusr1_set; + /********************* * FLUTTER CALLBACKS * *********************/ -bool make_current(void* userdata) { - if (eglMakeCurrent(display, surface, surface, context) != EGL_TRUE) { +bool make_current(void* userdata) { + if (eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.context) != EGL_TRUE) { fprintf(stderr, "Could not make the context current.\n"); return false; } return true; } -bool clear_current(void* userdata) { - if (eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) { +bool clear_current(void* userdata) { + if (eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) { fprintf(stderr, "Could not clear the current context.\n"); return false; } return true; } -bool present(void* userdata) { - printf("*** PRESENT ***\n"); - eglWaitClient(); +void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { + int *waiting_for_flip = data; + *waiting_for_flip = 0; +} +void drm_fb_destroy_callback(struct gbm_bo *bo, void *data) { + struct drm_fb *fb = data; + + if (fb->fb_id) + drmModeRmFB(drm.fd, fb->fb_id); + + free(fb); +} +struct drm_fb* drm_fb_get_from_bo(struct gbm_bo *bo) { + uint32_t width, height, format, strides[4] = {0}, handles[4] = {0}, offsets[4] = {0}, flags = 0; + int ok = -1; + + struct drm_fb *fb = gbm_bo_get_user_data(bo); + + if (fb) return fb; + + fb = calloc(1, sizeof(struct drm_fb)); + fb->bo = bo; + + width = gbm_bo_get_width(bo); + height = gbm_bo_get_height(bo); + format = gbm_bo_get_format(bo); + + if (gbm_bo_get_modifier && gbm_bo_get_plane_count && gbm_bo_get_stride_for_plane && gbm_bo_get_offset) { + uint64_t modifiers[4] = {0}; + modifiers[0] = gbm_bo_get_modifier(bo); + const int num_planes = gbm_bo_get_plane_count(bo); + + for (int i = 0; i < num_planes; i++) { + strides[i] = gbm_bo_get_stride_for_plane(bo, i); + handles[i] = gbm_bo_get_handle(bo).u32; + offsets[i] = gbm_bo_get_offset(bo, i); + modifiers[i] = modifiers[0]; + } + + if (modifiers[0]) { + flags = DRM_MODE_FB_MODIFIERS; + } + + ok = drmModeAddFB2WithModifiers(drm.fd, width, height, format, handles, strides, offsets, modifiers, &fb->fb_id, flags); + } - if (eglSwapBuffers(display, surface) != EGL_TRUE) { - fprintf(stderr, "Could not swap buffers to present the screen.\n"); + if (ok) { + if (flags) + fprintf(stderr, "Modifiers failed!\n"); + + memcpy(handles, (uint32_t [4]){gbm_bo_get_handle(bo).u32,0,0,0}, 16); + memcpy(strides, (uint32_t [4]){gbm_bo_get_stride(bo),0,0,0}, 16); + memset(offsets, 0, 16); + + ok = drmModeAddFB2(drm.fd, width, height, format, handles, strides, offsets, &fb->fb_id, 0); + } + + if (ok) { + fprintf(stderr, "failed to create fb: %s\n", strerror(errno)); + free(fb); + return NULL; + } + + gbm_bo_set_user_data(bo, fb, drm_fb_destroy_callback); + + return fb; +} +bool present(void* userdata) { + fd_set fds; + struct gbm_bo *next_bo; + struct drm_fb *fb; + int ok; + + eglSwapBuffers(egl.display, egl.surface); + next_bo = gbm_surface_lock_front_buffer(gbm.surface); + fb = drm_fb_get_from_bo(next_bo); + + /* + ok = drmModePageFlip(drm.fd, drm.crtc_id, fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT, &drm.waiting_for_flip); + if (ok) { + fprintf(stderr, "failed to queue page flip: %s\n", strerror(errno)); return false; } - eglWaitNative(EGL_CORE_NATIVE_ENGINE); + uint64_t t1 = FlutterEngineGetCurrentTime(); + + while (drm.waiting_for_flip) { + FD_ZERO(&fds); + FD_SET(0, &fds); + FD_SET(drm.fd, &fds); - GLenum error = glGetError(); - if (error != GL_NO_ERROR) { - printf("got gl error: %d\n", error); + ok = select(drm.fd+1, &fds, NULL, NULL, NULL); + if (ok < 0) { + fprintf(stderr, "select err: %s\n", strerror(errno)); + return false; + } else if (ok == 0) { + fprintf(stderr, "select timeout!\n"); + return false; + } else if (FD_ISSET(0, &fds)) { + fprintf(stderr, "user interrupted!\n"); + return false; + } + + drmHandleEvent(drm.fd, &drm.evctx); } + + uint64_t t2 = FlutterEngineGetCurrentTime(); + printf("waited %" PRIu64 " nanoseconds for page flip\n", t2-t1); + */ + + gbm_surface_release_buffer(gbm.surface, drm.previous_bo); + drm.previous_bo = next_bo; + + drm.waiting_for_flip = 1; return true; } -uint32_t fbo_callback(void* userdata) { +uint32_t fbo_callback(void* userdata) { return 0; } -void* proc_resolver(void* userdata, const char* name) { +void cut_word_from_string(char* string, char* word) { + size_t word_length = strlen(word); + char* word_in_str = strstr(string, word); + + // check if the given word is surrounded by spaces in the string + if (word_in_str + && ((word_in_str == string) || (word_in_str[-1] == ' ')) + && ((word_in_str[word_length] == 0) || (word_in_str[word_length] == ' ')) + ) { + if (word_in_str[word_length] == ' ') word_length++; + + int i = 0; + do { + word_in_str[i] = word_in_str[i+word_length]; + } while (word_in_str[i++ + word_length] != 0); + } +} +const GLubyte* hacked_glGetString(GLenum name) { + if (name == GL_EXTENSIONS) { + static GLubyte* extensions; + + if (extensions == NULL) { + GLubyte* orig_extensions = glGetString(GL_EXTENSIONS); + size_t len_orig_extensions = strlen(orig_extensions); + + extensions = malloc(len_orig_extensions+1); + strcpy(extensions, orig_extensions); + + /* + * working (apparently) + */ + //cut_word_from_string(extensions, "GL_EXT_blend_minmax"); + //cut_word_from_string(extensions, "GL_EXT_multi_draw_arrays"); + //cut_word_from_string(extensions, "GL_EXT_texture_format_BGRA8888"); + //cut_word_from_string(extensions, "GL_OES_compressed_ETC1_RGB8_texture"); + //cut_word_from_string(extensions, "GL_OES_depth24"); + //cut_word_from_string(extensions, "GL_OES_texture_npot"); + //cut_word_from_string(extensions, "GL_OES_vertex_half_float"); + //cut_word_from_string(extensions, "GL_OES_EGL_image"); + //cut_word_from_string(extensions, "GL_OES_depth_texture"); + //cut_word_from_string(extensions, "GL_AMD_performance_monitor"); + //cut_word_from_string(extensions, "GL_OES_EGL_image_external"); + //cut_word_from_string(extensions, "GL_EXT_occlusion_query_boolean"); + //cut_word_from_string(extensions, "GL_KHR_texture_compression_astc_ldr"); + //cut_word_from_string(extensions, "GL_EXT_compressed_ETC1_RGB8_sub_texture"); + //cut_word_from_string(extensions, "GL_EXT_draw_elements_base_vertex"); + //cut_word_from_string(extensions, "GL_EXT_texture_border_clamp"); + //cut_word_from_string(extensions, "GL_OES_draw_elements_base_vertex"); + //cut_word_from_string(extensions, "GL_OES_texture_border_clamp"); + //cut_word_from_string(extensions, "GL_KHR_texture_compression_astc_sliced_3d"); + //cut_word_from_string(extensions, "GL_MESA_tile_raster_order"); + + /* + * should be working, but isn't + */ + cut_word_from_string(extensions, "GL_EXT_map_buffer_range"); + + /* + * definitely broken + */ + cut_word_from_string(extensions, "GL_OES_element_index_uint"); + cut_word_from_string(extensions, "GL_OES_fbo_render_mipmap"); + cut_word_from_string(extensions, "GL_OES_mapbuffer"); + cut_word_from_string(extensions, "GL_OES_rgb8_rgba8"); + cut_word_from_string(extensions, "GL_OES_stencil8"); + cut_word_from_string(extensions, "GL_OES_texture_3D"); + cut_word_from_string(extensions, "GL_OES_packed_depth_stencil"); + cut_word_from_string(extensions, "GL_OES_get_program_binary"); + cut_word_from_string(extensions, "GL_APPLE_texture_max_level"); + cut_word_from_string(extensions, "GL_EXT_discard_framebuffer"); + cut_word_from_string(extensions, "GL_EXT_read_format_bgra"); + cut_word_from_string(extensions, "GL_EXT_frag_depth"); + cut_word_from_string(extensions, "GL_NV_fbo_color_attachments"); + cut_word_from_string(extensions, "GL_OES_EGL_sync"); + cut_word_from_string(extensions, "GL_OES_vertex_array_object"); + cut_word_from_string(extensions, "GL_EXT_unpack_subimage"); + cut_word_from_string(extensions, "GL_NV_draw_buffers"); + cut_word_from_string(extensions, "GL_NV_read_buffer"); + cut_word_from_string(extensions, "GL_NV_read_depth"); + cut_word_from_string(extensions, "GL_NV_read_depth_stencil"); + cut_word_from_string(extensions, "GL_NV_read_stencil"); + cut_word_from_string(extensions, "GL_EXT_draw_buffers"); + cut_word_from_string(extensions, "GL_KHR_debug"); + cut_word_from_string(extensions, "GL_OES_required_internalformat"); + cut_word_from_string(extensions, "GL_OES_surfaceless_context"); + cut_word_from_string(extensions, "GL_EXT_separate_shader_objects"); + cut_word_from_string(extensions, "GL_KHR_context_flush_control"); + cut_word_from_string(extensions, "GL_KHR_no_error"); + cut_word_from_string(extensions, "GL_KHR_parallel_shader_compile"); + } + + return extensions; + } else { + return glGetString(name); + } +} +void* proc_resolver(void* userdata, const char* name) { if (name == NULL) return NULL; + /* + * The mesa v3d driver reports some OpenGL ES extensions as supported and working + * even though they aren't. hacked_glGetString is a workaround for this, which will + * cut out the non-working extensions from the list of supported extensions. + */ + if (strcmp(name, "glGetString") == 0) { + return hacked_glGetString; + } + void* address; - if ((address = dlsym(RTLD_DEFAULT, name))) { + if ((address = dlsym(RTLD_DEFAULT, name)) || (address = eglGetProcAddress(name))) { return address; } + printf("could not resolve symbol %s\n", name); + return NULL; } -void on_platform_message(const FlutterPlatformMessage* message, void* userdata) { - struct MethodCall methodcall; +void on_platform_message(const FlutterPlatformMessage* message, void* userdata) { + struct MethodCall* methodcall; + if (!MethodChannel_decode(message->message_size, (uint8_t*) (message->message), &methodcall)) { fprintf(stderr, "Decoding method call failed\n"); return; } - printf("MethodCall: method name: %s argument type: %d\n", methodcall.method, methodcall.argument.type); - if (strcmp(methodcall.method, "counter") == 0) { - printf("method \"counter\" was called with argument %d\n", methodcall.argument.int_value); + printf("MethodCall: method name: %s argument type: %d\n", methodcall->method, methodcall->argument.type); + + if (strcmp(methodcall->method, "counter") == 0) { + printf("method \"counter\" was called with argument %d\n", methodcall->argument.int_value); } MethodChannel_freeMethodCall(&methodcall); } +void vsync_callback(void* userdata, intptr_t baton) { + flutter.next_vblank_baton = baton; + + drmVBlank vbl; + vbl.request.type = DRM_VBLANK_EVENT | DRM_VBLANK_RELATIVE; + vbl.request.sequence = 1; + vbl.request.signal = SIGUSR2; + + drmWaitVBlank(drm.fd, &vbl); +} /************************ * PLATFORM TASK-RUNNER * ************************/ -void handle_signal(int _) {} +void handle_sigusr1(int _) {} bool init_message_loop() { platform_thread_id = pthread_self(); @@ -149,8 +418,7 @@ bool init_message_loop() { sigemptyset(&sigusr1_set); sigaddset(&sigusr1_set, SIGUSR1); - - sigaction(SIGUSR1, &(struct sigaction) {.sa_handler = &handle_signal}, NULL); + sigaction(SIGUSR1, &(struct sigaction) {.sa_handler = &handle_sigusr1}, NULL); pthread_sigmask(SIG_UNBLOCK, &sigusr1_set, NULL); return true; @@ -162,8 +430,8 @@ bool message_loop(void) { pthread_mutex_lock(&task_list_lock); if (task_list_head_sentinel.next == NULL) { pthread_mutex_unlock(&task_list_lock); - sigwaitinfo(&sigusr1_set, NULL); + continue; } else { uint64_t target_time = task_list_head_sentinel.next->target_time; uint64_t current_time = FlutterEngineGetCurrentTime(); @@ -172,28 +440,28 @@ bool message_loop(void) { uint64_t diff = target_time - current_time; struct timespec target_timespec = { - .tv_sec = (uint64_t) (diff / 1000000000l), - .tv_nsec = (uint64_t) (diff % 1000000000l) + .tv_sec = (uint64_t) (diff / 1000000000ull), + .tv_nsec = (uint64_t) (diff % 1000000000ull) }; pthread_mutex_unlock(&task_list_lock); - - int result = sigtimedwait(&sigusr1_set, NULL, &target_timespec); - if (result == EINTR) continue; - } else { - pthread_mutex_unlock(&task_list_lock); + sigtimedwait(&sigusr1_set, NULL, &target_timespec); + continue; } } - pthread_mutex_lock(&task_list_lock); FlutterTask task = task_list_head_sentinel.next->task; + bool is_vblank_event = task_list_head_sentinel.next->is_vblank_event; + drmVBlankReply vbl = task_list_head_sentinel.next->vbl; struct LinkedTaskListElement* new_first = task_list_head_sentinel.next->next; free(task_list_head_sentinel.next); task_list_head_sentinel.next = new_first; pthread_mutex_unlock(&task_list_lock); - - if (FlutterEngineRunTask(engine, &task) != kSuccess) { + + if (is_vblank_event) { + + } else if (FlutterEngineRunTask(engine, &task) != kSuccess) { fprintf(stderr, "Error running platform task\n"); return false; }; @@ -204,6 +472,7 @@ bool message_loop(void) { void post_platform_task(FlutterTask task, uint64_t target_time, void* userdata) { struct LinkedTaskListElement* to_insert = malloc(sizeof(struct LinkedTaskListElement)); to_insert->next = NULL; + to_insert->is_vblank_event = false; to_insert->task = task; to_insert->target_time = target_time; @@ -231,185 +500,356 @@ bool runs_platform_tasks_on_current_thread(void* userdata) { bool setup_paths(void) { #define PATH_EXISTS(path) (access((path),R_OK)==0) - if (!PATH_EXISTS(asset_bundle_path)) { - fprintf(stderr, "Asset Bundle Directory \"%s\" does not exist\n", asset_bundle_path); + if (!PATH_EXISTS(flutter.asset_bundle_path)) { + fprintf(stderr, "Asset Bundle Directory \"%s\" does not exist\n", flutter.asset_bundle_path); return false; } - snprintf(kernel_blob_path, 1024, "%s/kernel_blob.bin", asset_bundle_path); - if (!PATH_EXISTS(kernel_blob_path)) { + snprintf(flutter.kernel_blob_path, sizeof(flutter.kernel_blob_path), "%s/kernel_blob.bin", flutter.asset_bundle_path); + if (!PATH_EXISTS(flutter.kernel_blob_path)) { fprintf(stderr, "Kernel blob does not exist inside Asset Bundle Directory.\n"); return false; } - #ifdef ICUDTL_IN_EXECUTABLE_DIR - char _link_path[256]; - snprintf(_link_path, 256, "/proc/%d/exe", getpid()); - size_t size = readlink(_link_path, executable_path, 1023); - if (size <= 0) sprintf(executable_path, ""); - - char* lastSlash = strrchr(executable_path, ("/")[0]); - if (lastSlash == NULL) sprintf(icu_data_path, "/icudtl.dat"); - else snprintf(icu_data_path, 1024, "%.*s/icudtl.dat", (int) (lastSlash - executable_path), executable_path); - #else - snprintf(icu_data_path, 1024, "/usr/lib/icudtl.dat"); - #endif + snprintf(flutter.icu_data_path, sizeof(flutter.icu_data_path), "/usr/lib/icudtl.dat"); - if (!PATH_EXISTS(icu_data_path)) { - fprintf(stderr, "ICU Data file not find at %s.\n", icu_data_path); + if (!PATH_EXISTS(flutter.icu_data_path)) { + fprintf(stderr, "ICU Data file not find at %s.\n", flutter.icu_data_path); return false; } + snprintf(drm.device, sizeof(drm.device), "/dev/dri/card0"); + return true; #undef PATH_EXISTS } bool init_display(void) { - printf("Initializing bcm_host...\n"); - bcm_host_init(); + /********************** + * DRM INITIALIZATION * + **********************/ + + drmModeRes *resources; + drmModeConnector *connector; + drmModeEncoder *encoder; + int i, ok, area; - // setup the EGL Display - printf("Getting the EGL display...\n"); - display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (display == EGL_NO_DISPLAY) { - fprintf(stderr, "Could not get the EGL display.\n"); + + printf("Opening DRM device...\n"); + drm.fd = open(drm.device, O_RDWR); + if (drm.fd < 0) { + fprintf(stderr, "Could not open DRM device\n"); return false; } - - printf("Initializing EGL...\n"); - if (eglInitialize(display, NULL, NULL) != EGL_TRUE) { - fprintf(stderr, "Could not initialize the EGL display.\n"); + + + printf("Getting DRM resources...\n"); + resources = drmModeGetResources(drm.fd); + if (resources == NULL) { + if (errno == EOPNOTSUPP) fprintf(stderr, "%s doesn't look like a modeset device\n", drm.device); + else fprintf(stderr, "drmModeGetResources failed: %s\n", strerror(errno)); + return false; } - - // choose an EGL config - EGLConfig config = {0}; - EGLint num_config = 0; - EGLint attribute_list[] = { - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_DEPTH_SIZE, 8, - EGL_STENCIL_SIZE, 1, - EGL_RENDERABLE_TYPE,EGL_OPENGL_ES2_BIT, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_NONE - }; - - printf("Choosing an EGL config...\n"); - if (eglChooseConfig(display, attribute_list, &config, 1, &num_config) != EGL_TRUE) { - fprintf(stderr, "Could not choose an EGL config.\n"); + + + printf("Finding a connected connector...\n"); + for (i = 0; i < resources->count_connectors; i++) { + connector = drmModeGetConnector(drm.fd, resources->connectors[i]); + if (connector->connection == DRM_MODE_CONNECTED) { + width_mm = connector->mmWidth; + height_mm = connector->mmHeight; + break; + } + drmModeFreeConnector(connector); + connector = NULL; + } + if (!connector) { + fprintf(stderr, "could not find a connected connector!\n"); return false; } + + printf("Choosing DRM mode...\n"); + for (i = 0, area = 0; i < connector->count_modes; i++) { + drmModeModeInfo *current_mode = &connector->modes[i]; + + if (current_mode->type & DRM_MODE_TYPE_PREFERRED) { + drm.mode = current_mode; + width = drm.mode->hdisplay; + height = drm.mode->vdisplay; + refresh_rate = drm.mode->vrefresh; + + if (width_mm) pixel_ratio = (10.0 * width) / (width_mm * 38.0); + + break; + } + + int current_area = current_mode->hdisplay * current_mode->vdisplay; + if (current_area > area) { + drm.mode = current_mode; + area = current_area; + } + } + if (!drm.mode) { + fprintf(stderr, "could not find a suitable DRM mode!\n"); + return false; + } + + printf("Display properties:\n %u x %u, %uHz\n %umm x %umm\n pixel_ratio = %f\n", width, height, refresh_rate, width_mm, height_mm, pixel_ratio); + + printf("Finding DRM encoder...\n"); + for (i = 0; i < resources->count_encoders; i++) { + encoder = drmModeGetEncoder(drm.fd, resources->encoders[i]); + if (encoder->encoder_id == connector->encoder_id) + break; + drmModeFreeEncoder(encoder); + encoder = NULL; + } - // create the EGL context - EGLint context_attributes[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - - printf("Creating the EGL context...\n"); - context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attributes); - if (context == EGL_NO_CONTEXT) { - fprintf(stderr, "Could not create the EGL context.\n"); + if (encoder) { + drm.crtc_id = encoder->crtc_id; + } else { + fprintf(stderr, "could not find a suitable crtc!\n"); return false; } - // query current display size - printf("Querying the display size...\n"); - if (graphics_get_display_size(0, &width, &height) < 0) { - fprintf(stderr, "Could not query the display size.\n"); + for (i = 0; i < resources->count_crtcs; i++) { + if (resources->crtcs[i] == drm.crtc_id) { + drm.crtc_index = i; + break; + } + } + + drmModeFreeResources(resources); + + drm.connector_id = connector->connector_id; + + + + /********************** + * GBM INITIALIZATION * + **********************/ + printf("Creating GBM device\n"); + gbm.device = gbm_create_device(drm.fd); + gbm.format = DRM_FORMAT_XRGB8888; + gbm.surface = NULL; + + if (gbm_surface_create_with_modifiers) { + gbm.surface = gbm_surface_create_with_modifiers(gbm.device, width, height, gbm.format, &gbm.modifier, 1); + } + + if (!gbm.surface) { + if (gbm.modifier != 0) { + fprintf(stderr, "GBM Surface creation modifiers requested but not supported by GBM\n"); + return false; + } + gbm.surface = gbm_surface_create(gbm.device, width, height, gbm.format, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + } + + if (!gbm.surface) { + fprintf(stderr, "failed to create GBM surface\n"); return false; } + - // setup dispman display - printf("Opening the dispmanx display...\n"); - dispman_display = vc_dispmanx_display_open(0); - - printf("Setting up the dispmanx display...\n"); - DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0); - const VC_RECT_T dest_rect = { - .x = 0, .y = 0, - .width = width, .height = height, + + /********************** + * EGL INITIALIZATION * + **********************/ + EGLint major, minor; + + static const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE }; - const VC_RECT_T src_rect = { - .x = 0, .y = 0, - .width = width << 16, .height = height << 16, + + const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE }; + + const char *egl_exts_client, *egl_exts_dpy, *gl_exts; + + printf("Querying EGL client extensions...\n"); + egl_exts_client = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + + egl.eglGetPlatformDisplayEXT = (void*) eglGetProcAddress("eglGetPlatformDisplayEXT"); + printf("Getting EGL display for GBM device...\n"); + if (egl.eglGetPlatformDisplayEXT) egl.display = egl.eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm.device, NULL); + else egl.display = eglGetDisplay((void*) gbm.device); - dispman_element = vc_dispmanx_element_add( - update, - dispman_display, - 0, - &dest_rect, - 0, - &src_rect, - DISPMANX_PROTECTION_NONE, - 0, - 0, - DISPMANX_NO_ROTATE - ); - - vc_dispmanx_update_submit_sync(update); - - native_window.element = dispman_element; - native_window.width = width; - native_window.height = height; - - - // Create EGL window surface - printf("Creating the EGL window surface...\n"); - surface = eglCreateWindowSurface(display, config, &native_window, NULL); - if (surface == EGL_NO_SURFACE) { - fprintf(stderr, "Could not create the EGL Surface.\n"); + if (!egl.display) { + fprintf(stderr, "Couldn't get EGL display\n"); return false; } - - return true; -} -void destroy_display(void) { - if (surface != EGL_NO_SURFACE) { - eglDestroySurface(display, surface); - surface = EGL_NO_SURFACE; + + printf("Initializing EGL...\n"); + if (!eglInitialize(egl.display, &major, &minor)) { + fprintf(stderr, "failed to initialize EGL\n"); + return false; } - - vc_dispmanx_display_close(dispman_display); - - if (context != EGL_NO_CONTEXT) { - eglDestroyContext(display, context); - context = EGL_NO_CONTEXT; + + printf("Querying EGL display extensions...\n"); + egl_exts_dpy = eglQueryString(egl.display, EGL_EXTENSIONS); + egl.modifiers_supported = strstr(egl_exts_dpy, "EGL_EXT_image_dma_buf_import_modifiers") != NULL; + + + printf("Using display %d with EGL version %d.%d\n", egl.display, major, minor); + printf("===================================\n"); + printf("EGL information:\n"); + printf(" version: %s\n", eglQueryString(egl.display, EGL_VERSION)); + printf(" vendor: \"%s\"\n", eglQueryString(egl.display, EGL_VENDOR)); + printf(" client extensions: \"%s\"\n", egl_exts_client); + printf(" display extensions: \"%s\"\n", egl_exts_dpy); + printf("===================================\n"); + + + printf("Binding OpenGL ES API...\n"); + if (!eglBindAPI(EGL_OPENGL_ES_API)) { + fprintf(stderr, "failed to bind OpenGL ES API\n"); + return false; } + + + printf("Choosing EGL config...\n"); + EGLint count = 0, matched = 0; + EGLConfig *configs; + bool _found_matching_config = false; - if (display != EGL_NO_DISPLAY) { - eglTerminate(display); - display = EGL_NO_DISPLAY; + if (!eglGetConfigs(egl.display, NULL, 0, &count) || count < 1) { + fprintf(stderr, "No EGL configs to choose from.\n"); + return false; } - - bcm_host_deinit(); + + configs = malloc(count * sizeof(EGLConfig)); + if (!configs) return false; + + printf("Finding EGL configs with appropriate attributes...\n"); + if (!eglChooseConfig(egl.display, config_attribs, configs, count, &matched) || !matched) { + fprintf(stderr, "No EGL configs with appropriate attributes.\n"); + free(configs); + return false; + } + + if (!gbm.format) { + _found_matching_config = true; + } else { + for (int i = 0; i < count; i++) { + EGLint id; + if (!eglGetConfigAttrib(egl.display, configs[i], EGL_NATIVE_VISUAL_ID, &id)) continue; + + if (id == gbm.format) { + egl.config = configs[i]; + _found_matching_config = true; + break; + } + } + } + free(configs); + + if (!_found_matching_config) { + fprintf(stderr, "Could not find context with appropriate attributes and matching native visual ID.\n"); + return false; + } + + + printf("Creating EGL context...\n"); + egl.context = eglCreateContext(egl.display, egl.config, EGL_NO_CONTEXT, context_attribs); + if (egl.context == NULL) { + fprintf(stderr, "failed to create EGL context\n"); + return false; + } + + + printf("Creating EGL window surface...\n"); + egl.surface = eglCreateWindowSurface(egl.display, egl.config, (EGLNativeWindowType) gbm.surface, NULL); + if (egl.surface == EGL_NO_SURFACE) { + fprintf(stderr, "failed to create EGL window surface\n"); + return false; + } + + if (!eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.context)) { + fprintf(stderr, "Could not make EGL context current to get OpenGL information\n"); + return false; + } + + gl_exts = (char*) glGetString(GL_EXTENSIONS); + printf("===================================\n"); + printf("OpenGL ES 2.x information:\n"); + printf(" version: \"%s\"\n", glGetString(GL_VERSION)); + printf(" shading language version: \"%s\"\n", glGetString(GL_SHADING_LANGUAGE_VERSION)); + printf(" vendor: \"%s\"\n", glGetString(GL_VENDOR)); + printf(" renderer: \"%s\"\n", glGetString(GL_RENDERER)); + printf(" extensions: \"%s\"\n", gl_exts); + printf("===================================\n"); + + + drm.evctx.version = 2; + drm.evctx.page_flip_handler = page_flip_handler; + //drm.evctx.vblank_handler = vblank_handler; + + printf("Swapping buffers...\n"); + eglSwapBuffers(egl.display, egl.surface); + + printf("Locking front buffer...\n"); + drm.previous_bo = gbm_surface_lock_front_buffer(gbm.surface); + + + printf("getting new framebuffer for BO...\n"); + struct drm_fb *fb = drm_fb_get_from_bo(drm.previous_bo); + if (!fb) { + fprintf(stderr, "failed to get a new framebuffer BO\n"); + return false; + } + + + printf("Setting CRTC...\n"); + ok = drmModeSetCrtc(drm.fd, drm.crtc_id, fb->fb_id, 0, 0, &drm.connector_id, 1, drm.mode); + if (ok) { + fprintf(stderr, "failed to set mode: %s\n", strerror(errno)); + return false; + } + + drm.waiting_for_flip = 1; + + printf("Clearing current context...\n"); + if (!eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + fprintf(stderr, "Could not clear EGL context\n"); + return false; + } + + printf("finished display setup!\n"); + + return true; +} +void destroy_display(void) { + } bool init_application(void) { // configure flutter rendering - renderer_config.type = kOpenGL; - renderer_config.open_gl.struct_size = sizeof(renderer_config.open_gl); - renderer_config.open_gl.make_current = make_current; - renderer_config.open_gl.clear_current = clear_current; - renderer_config.open_gl.present = present; - renderer_config.open_gl.fbo_callback = fbo_callback; - renderer_config.open_gl.gl_proc_resolver= proc_resolver; - //renderer_config.open_gl.fbo_reset_after_present = true; + flutter.renderer_config.type = kOpenGL; + flutter.renderer_config.open_gl.struct_size = sizeof(flutter.renderer_config.open_gl); + flutter.renderer_config.open_gl.make_current = make_current; + flutter.renderer_config.open_gl.clear_current = clear_current; + flutter.renderer_config.open_gl.present = present; + flutter.renderer_config.open_gl.fbo_callback = fbo_callback; + flutter.renderer_config.open_gl.gl_proc_resolver= proc_resolver; + + for (int i=0; itype == EV_REL) { if (ev->code == REL_X) { // mouse moved in the x-direction - mouse_x += ev->value; - phase = button ? kMove : kHover; + input.x += ev->value; + phase = input.button ? kMove : kHover; } else if (ev->code == REL_Y) { // mouse moved in the y-direction - mouse_y += ev->value; - phase = button ? kMove : kHover; + input.y += ev->value; + phase = input.button ? kMove : kHover; } } else if (ev->type == EV_ABS) { if (ev->code == ABS_X) { - mouse_x = ev->value; - phase = button ? kMove : kHover; + input.x = ev->value; + phase = input.button ? kMove : kHover; } else if (ev->code == ABS_Y) { - mouse_y = ev->value; - phase = button ? kMove : kHover; + input.y = ev->value; + phase = input.button ? kMove : kHover; } } else if ((ev->type == EV_KEY) && ((ev->code == BTN_LEFT) || (ev->code == BTN_RIGHT))) { // either the left or the right mouse button was pressed // the 1st bit in "button" is set when BTN_LEFT is down. the 2nd bit when BTN_RIGHT is down. uint8_t mask = ev->code == BTN_LEFT ? 1 : 2; - if (ev->value == 1) button |= mask; - else button &= ~mask; + if (ev->value == 1) input.button |= mask; + else input.button &= ~mask; phase = ev->value == 1 ? kDown : kUp; } @@ -538,7 +980,7 @@ void* io_loop(void* userdata) { & (FlutterPointerEvent) { .struct_size = sizeof(FlutterPointerEvent), .timestamp = (size_t) (FlutterEngineGetCurrentTime()*1000), - .phase=phase, .x=mouse_x, .y=mouse_y, + .phase=phase, .x=input.x, .y=input.y, .signal_kind = kFlutterPointerSignalKindNone }, 1 @@ -547,13 +989,13 @@ void* io_loop(void* userdata) { } } - printf("mouse position: %f, %f\n", mouse_x, mouse_y); + printf("mouse position: %f, %f\n", input.x, input.y); } - if (!input_is_mouse) { + if (!input.is_mouse) { for (int j = 0; j<10; j++) { printf("Sending kAdd %d to Flutter Engine\n", j); - ts_slots[j].id = -1; + input.ts_slots[j].id = -1; ok = FlutterEngineSendPointerEvent( engine, & (FlutterPointerEvent) { @@ -572,11 +1014,13 @@ void* io_loop(void* userdata) { return false; } } + + input.ts_slot = &(input.ts_slots[0]); } // touchscreen - while (!input_is_mouse) { - int rd = read(input_filehandle, &event, sizeof(struct input_event)*64); + while (!input.is_mouse) { + int rd = read(input.fd, &event, sizeof(struct input_event)*64); if (rd < (int) sizeof(struct input_event)) { perror("error reading from input device"); return false; @@ -588,35 +1032,35 @@ void* io_loop(void* userdata) { if (ev->type == EV_ABS) { if (ev->code == ABS_MT_SLOT) { - ts_slot = &(ts_slots[ev->value]); + input.ts_slot = &(input.ts_slots[ev->value]); } else if (ev->code == ABS_MT_TRACKING_ID) { - if (ts_slot->id == -1) { - ts_slot->id = ev->value; - ts_slot->phase = kDown; + if (input.ts_slot->id == -1) { + input.ts_slot->id = ev->value; + input.ts_slot->phase = kDown; } else if (ev->value == -1) { - ts_slot->id = ev->value; - ts_slot->phase = kUp; + input.ts_slot->id = ev->value; + input.ts_slot->phase = kUp; } } else if (ev->code == ABS_MT_POSITION_X) { - ts_slot->x = ev->value; - if (ts_slot->phase == kCancel) ts_slot->phase = kMove; + input.ts_slot->x = ev->value; + if (input.ts_slot->phase == kCancel) input.ts_slot->phase = kMove; } else if (ev->code == ABS_MT_POSITION_Y) { - ts_slot->y = ev->value; - if (ts_slot->phase == kCancel) ts_slot->phase = kMove; + input.ts_slot->y = ev->value; + if (input.ts_slot->phase == kCancel) input.ts_slot->phase = kMove; } } else if ((ev->type == EV_SYN) && (ev->code == SYN_REPORT)) { for (int j = 0; j < 10; j++) { - if (ts_slots[j].phase != kCancel) { + if (input.ts_slots[j].phase != kCancel) { pointer_event[n_pointerevents++] = (FlutterPointerEvent) { .struct_size = sizeof(FlutterPointerEvent), - .phase = ts_slots[j].phase, + .phase = input.ts_slots[j].phase, .timestamp = (size_t) (FlutterEngineGetCurrentTime()*1000), .device = j, - .x = ts_slots[j].x, - .y = ts_slots[j].y, + .x = input.ts_slots[j].x, + .y = input.ts_slots[j].y, .signal_kind = kFlutterPointerSignalKindNone }; - ts_slots[j].phase = kCancel; + input.ts_slots[j].phase = kCancel; } } } @@ -652,24 +1096,25 @@ bool run_io_thread(void) { return true; } -bool parse_cmd_args(int argc, const char *const * argv) { + +bool parse_cmd_args(int argc, char **argv) { int opt; int index = 0; - while ((opt = getopt(argc, (char *const *) argv, "m:t:")) != -1) { + while ((opt = getopt(argc, (char *const *) argv, "+m:t:")) != -1) { index++; switch(opt) { case 'm': printf("Using mouse input from mouse %s\n", optarg); - snprintf(input_device_path, 1023, "%s", optarg); - input_is_mouse = true; + snprintf(input.device_path, sizeof(input.device_path), "%s", optarg); + input.is_mouse = true; index++; break; case 't': printf("Using touchscreen input from %s\n", optarg); - snprintf(input_device_path, 1023, "%s", optarg); - input_is_mouse = false; + snprintf(input.device_path, sizeof(input.device_path), "%s", optarg); + input.is_mouse = false; index++; break; @@ -679,7 +1124,7 @@ bool parse_cmd_args(int argc, const char *const * argv) { } } - if (strlen(input_device_path) == 0) { + if (strlen(input.device_path) == 0) { fprintf(stderr, "At least one of -t or -r has to be given\n%s", usage); return false; } @@ -689,16 +1134,19 @@ bool parse_cmd_args(int argc, const char *const * argv) { return false; } - snprintf(asset_bundle_path, 1023, "%s", argv[optind]); - printf("Asset bundle path: %s\n", asset_bundle_path); + snprintf(flutter.asset_bundle_path, sizeof(flutter.asset_bundle_path), "%s", argv[optind]); + printf("Asset bundle path: %s\n", flutter.asset_bundle_path); - engine_argc = argc-optind-1; - engine_argv = &(argv[optind+1]); + argv[optind] = argv[0]; + flutter.engine_argc = argc-optind; + flutter.engine_argv = &(argv[optind]); + for (int i=0; i +#include +#include #include #include +#define EGL_PLATFORM_GBM_KHR 0x31D7 + struct LinkedTaskListElement { struct LinkedTaskListElement* next; - FlutterTask task; + bool is_vblank_event; + union { + FlutterTask task; + drmVBlankReply vbl; + }; uint64_t target_time; }; @@ -17,6 +26,11 @@ struct TouchscreenSlot { FlutterPointerPhase phase; }; +struct drm_fb { + struct gbm_bo *bo; + uint32_t fb_id; +}; + FlutterEngine engine; #endif \ No newline at end of file diff --git a/src/methodchannel.c b/src/methodchannel.c index af9dd044..4073b073 100644 --- a/src/methodchannel.c +++ b/src/methodchannel.c @@ -437,7 +437,10 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str return true; } -bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall* result) { +bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall** presult) { + *presult = malloc(sizeof(struct MethodCall)); + struct MethodCall* result = *presult; + uint8_t* buffer_cursor = buffer; size_t buffer_remaining = buffer_size; @@ -487,9 +490,14 @@ bool MethodChannel_freeValue(struct MethodChannelValue* p_value) { return true; } -bool MethodChannel_freeMethodCall(struct MethodCall* methodcall) { +bool MethodChannel_freeMethodCall(struct MethodCall **pmethodcall) { + struct MethodCall* methodcall = *pmethodcall; + free(methodcall->method); if (!MethodChannel_freeValue(&(methodcall->argument))) return false; + free(methodcall); + + *pmethodcall = NULL; return true; } diff --git a/src/methodchannel.h b/src/methodchannel.h index c7183db5..2e84f84d 100644 --- a/src/methodchannel.h +++ b/src/methodchannel.h @@ -69,7 +69,7 @@ struct MethodCall { bool MethodChannel_call(char* channel, char* method, struct MethodChannelValue* argument); bool MethodChannel_respond(FlutterPlatformMessageResponseHandle* response_handle, struct MethodChannelValue* response_value); -bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall* result); -bool MethodChannel_freeMethodCall(struct MethodCall* methodcall); +bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall** presult); +bool MethodChannel_freeMethodCall(struct MethodCall** pmethodcall); #endif \ No newline at end of file From e3e357d1aa48d5967dd3515a5d17590b8e33b240 Mon Sep 17 00:00:00 2001 From: ardera <2488440+ardera@users.noreply.github.com> Date: Wed, 11 Sep 2019 13:12:11 +0200 Subject: [PATCH 3/4] update readme, formatting, delete bcm_host dep --- README.md | 58 +++++++++++++++++++++++------------------------- src/flutter-pi.c | 10 ++++++++- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 38f812e0..b686c9b0 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,49 @@ # flutter-pi -A light-weight Flutter Engine Embedder for Raspberry Pi that's using the broadcom APIs. Inspired by https://github.com/chinmaygarde/flutter_from_scratch. +A light-weight Flutter Engine Embedder for Raspberry Pi. Inspired by https://github.com/chinmaygarde/flutter_from_scratch. +Flutter-pi also runs without X11, so you don't need to boot into Raspbian Desktop & have X11 and LXDE load up; just boot into the command-line. -Currently supported are basic, pure-dart Apps & mouse input (no mouse cursor yet). -Not yet supported are Method & Platform-channels, touchscreen input; and probably a lot more. +Currently supported are basic, pure-dart Apps (not using any plugins), mouse input (no mouse cursor yet), touchscreen input, and the StandardMethodCodec method-channels. +Not yet supported are JSON method-channels. Generally, flutter-pi is not yet ready to be used as a base for your project. ## Running +This branch (feature-v3d-anholt) doesn't support the legacy GL driver anymore. You need to activate the anholt v3d driver in raspi-config. Go to raspi-config -> Advanced -> GL Driver -> and select fake-KMS. Full-KMS is a bit buggy and doesn't work with the Raspberry Pi 7" display (or generally, any DSI display). + +Also, you need to tell flutter-pi which input device to use and whether it's a touchscreen or mouse. Input devices are typically located at `/dev/input/...`. Just run `evtest` (`sudo apt install evtest`) to find out which exact path you should use. Currently only one input device is supported by flutter-pi. + Run using ```bash -./flutter-pi /path/without/trailing/slash [flutter arguments...] +./flutter-pi [flutter-pi options...] /path/without/trailing/slash [flutter engine arguments...] ``` -where `/path/without/trailing/slash` is the path of the flutter asset bundle directory (i.e. the directory containing the kernel_blob.bin) + +`[flutter-pi options...]` are: +- `-t /path/to/device` where `/path/to/device` is a path to a touchscreen input device (typically `/dev/input/event0` or similiar) +- `-m /path/to/device` where `/path/to/device` is a path to a mouse input device (typically `/dev/input/mouse0` or `/dev/input/event0` or similiar) + +`/path/without/trailing/slash` is the path of the flutter asset bundle directory (i.e. the directory containing the kernel_blob.bin) of the flutter app you're trying to run. -The `[flutter arguments...]` will be passed as commandline arguments to the flutter engine. +`[flutter engine arguments...]` will be passed as commandline arguments to the flutter engine. You can find a list of commandline options for the flutter engine [Here](https://github.com/flutter/engine/blob/master/shell/common/switches.h); ## Building the asset bundle +You need a correctly installed flutter SDK. (i.e. the `flutter` tool must be in your PATH) + Example for flutter_gallery: (note that the flutter_gallery example doesn't work with flutter-pi, since it requires plugins) ```bash -cd ./flutter/examples/flutter_gallery -../../bin/flutter build bundle +cd flutter/examples/flutter_gallery +flutter build bundle ``` -After that `./flutter/examples/flutter_gallery/build/flutter_assets` would be a valid path to pass as an argument to flutter-pi. +After that `flutter/examples/flutter_gallery/build/flutter_assets` would be a valid path to pass as an argument to flutter-pi. + +## Compiling (on the Raspberry Pi) +You first need a `libflutter_engine.so` and `flutter_embedder.h`. [Here](https://medium.com/flutter/flutter-on-raspberry-pi-mostly-from-scratch-2824c5e7dcb1) +are some rough guidelines on how to build it. (Note: the icudtl.dat that is generated during the engine compilation needs to be on the RPi too, but it's not needed for compilation of flutter-pi) -## Compiling -You first need a valid `libflutter_engine.so`. [Here](https://medium.com/flutter/flutter-on-raspberry-pi-mostly-from-scratch-2824c5e7dcb1) -are some rough guidelines on how to build it. +You also need some dependencies; run `sudo apt install libgl1-mesa-dev libgles2-mesa-dev libegl-meso0 libdrm-dev libgbm-dev`. Compiling the embedder: ```bash mkdir out cc -D_GNU_SOURCE \ - -lrt -lbrcmGLESv2 -lflutter_engine -lpthread -ldl -lbcm_host -lvcos -lvchiq_arm -lm \ + `pkg-config --cflags --libs dri gbm libdrm glesv2 egl` -lrt -lflutter_engine -lpthread -ldl \ ./src/flutter-pi.c ./src/methodchannel.c -o ./out/flutter-pi -``` - -## Cross-Compiling -You need a valid `libflutter_engine.so`, `flutter_embedder.h`, a valid raspberry pi sysroot including the /opt directory, and a valid toolchain targeting -arm-linux-gnueabihf. Then execute: -```bash -mkdir out -/path/to/cross_c_compiler \ - -D_GNU_SOURCE \ - --sysroot /path/to/sysroot \ - -I/path/to/sysroot/opt/vc/include \ - -I/directory/containing/flutter_embedder.h/ \ - -L/path/to/sysroot/opt/vc/lib \ - -L/directory/containing/libflutter_engine.so/ \ - -lrt -lbrcmEGL -lbrcmGLESv2 -lflutter_engine -lpthread -ldl -lbcm_host -lvcos -lvchiq_arm -lm \ - ./src/flutter-pi.c ./src/methodchannel.c -o ./out/flutter-rpi -``` +``` \ No newline at end of file diff --git a/src/flutter-pi.c b/src/flutter-pi.c index d8b944d7..6a490427 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -15,7 +16,6 @@ #include #include -//#include #include #include #include @@ -30,6 +30,7 @@ #include "flutter-pi.h" #include "methodchannel.h" + char* usage ="\ Flutter for Raspberry Pi\n\n\ Usage:\n\ @@ -120,6 +121,7 @@ bool should_notify_platform_thread = false; sigset_t sigusr1_set; + /********************* * FLUTTER CALLBACKS * *********************/ @@ -403,6 +405,8 @@ void vsync_callback(void* userdata, intptr_t baton) { drmWaitVBlank(drm.fd, &vbl); } + + /************************ * PLATFORM TASK-RUNNER * ************************/ @@ -494,6 +498,7 @@ bool runs_platform_tasks_on_current_thread(void* userdata) { } + /****************** * INITIALIZATION * ******************/ @@ -894,6 +899,8 @@ void destroy_application(void) { } } + + /**************** * Input-Output * ****************/ @@ -1097,6 +1104,7 @@ bool run_io_thread(void) { } + bool parse_cmd_args(int argc, char **argv) { int opt; int index = 0; From add39c3f0f94b39e6a1436a4141c98138d3d8583 Mon Sep 17 00:00:00 2001 From: ardera <2488440+ardera@users.noreply.github.com> Date: Wed, 18 Sep 2019 11:34:16 +0200 Subject: [PATCH 4/4] update readme, reformat flutter-pi.c --- README.md | 15 ++- src/flutter-pi.c | 247 +++++++++++++++++++++++------------------------ 2 files changed, 136 insertions(+), 126 deletions(-) diff --git a/README.md b/README.md index b686c9b0..b699d508 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,14 @@ A light-weight Flutter Engine Embedder for Raspberry Pi. Inspired by https://github.com/chinmaygarde/flutter_from_scratch. Flutter-pi also runs without X11, so you don't need to boot into Raspbian Desktop & have X11 and LXDE load up; just boot into the command-line. -Currently supported are basic, pure-dart Apps (not using any plugins), mouse input (no mouse cursor yet), touchscreen input, and the StandardMethodCodec method-channels. +Currently supported are basic, pure-dart Apps (not using any plugins), mouse input (no mouse cursor yet), touchscreen input, and the StandardMethodCodec method-channels (currently needs fixing). Not yet supported are JSON method-channels. Generally, flutter-pi is not yet ready to be used as a base for your project. ## Running This branch (feature-v3d-anholt) doesn't support the legacy GL driver anymore. You need to activate the anholt v3d driver in raspi-config. Go to raspi-config -> Advanced -> GL Driver -> and select fake-KMS. Full-KMS is a bit buggy and doesn't work with the Raspberry Pi 7" display (or generally, any DSI display). +For some reason performance is much better when I gave the GPU only 16M RAM in fake-kms. I don't know why. + Also, you need to tell flutter-pi which input device to use and whether it's a touchscreen or mouse. Input devices are typically located at `/dev/input/...`. Just run `evtest` (`sudo apt install evtest`) to find out which exact path you should use. Currently only one input device is supported by flutter-pi. Run using @@ -46,4 +48,13 @@ mkdir out cc -D_GNU_SOURCE \ `pkg-config --cflags --libs dri gbm libdrm glesv2 egl` -lrt -lflutter_engine -lpthread -ldl \ ./src/flutter-pi.c ./src/methodchannel.c -o ./out/flutter-pi -``` \ No newline at end of file +``` + +## Performance +Performance is actually better than I expected. With most of the apps inside the `flutter SDK -> examples -> catalog` directory I get smooth 50-60fps. + +## Touchscreen Bug +~~If you use the official 7 inch touchscreen, performance will feel much worse while dragging something. This seems to be some bug in the touchscreen driver. The embedder / userspace only gets around 25 touch events a second, meaning that while dragging something (like in tabbed_app_bar.dart), the position of the object being dragged is only updated 25 times a second. This results in the app looking like it runs at 25fps. The touchscreen could do up to 100 touch updates a second though.~~ + +[This has been fixed.](https://github.com/raspberrypi/linux/issues/3227) If you want to get the fix, you can run (rpi-update)[https://github.com/hexxeh/rpi-update], which will update your system to the newest version. + diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 6a490427..cb013796 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -50,7 +50,7 @@ uint32_t refresh_rate; // this is the pixel ratio (used by flutter) for the Official Raspberry Pi 7inch display. // if a HDMI screen is connected and being used by this application, the pixel ratio will be // computed inside init_display. -// for DSI the pixel ratio can not be calculated, because there's no way to query the physical +// for DSI the pixel ratio can not be calculated, because there's no (general) way to query the physical // size for DSI displays. double pixel_ratio = 1.3671; @@ -99,13 +99,14 @@ struct { } flutter = {0}; struct { - bool is_mouse; char device_path[128]; int fd; double x, y; uint8_t button; struct TouchscreenSlot ts_slots[10]; struct TouchscreenSlot* ts_slot; + bool is_mouse; + bool is_touchscreen; } input = {0}; pthread_t io_thread_id; @@ -218,7 +219,7 @@ bool present(void* userdata) { next_bo = gbm_surface_lock_front_buffer(gbm.surface); fb = drm_fb_get_from_bo(next_bo); - /* + /* wait for vsync, ok = drmModePageFlip(drm.fd, drm.crtc_id, fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT, &drm.waiting_for_flip); if (ok) { fprintf(stderr, "failed to queue page flip: %s\n", strerror(errno)); @@ -395,14 +396,8 @@ void on_platform_message(const FlutterPlatformMessage* message, void* user MethodChannel_freeMethodCall(&methodcall); } void vsync_callback(void* userdata, intptr_t baton) { - flutter.next_vblank_baton = baton; - - drmVBlank vbl; - vbl.request.type = DRM_VBLANK_EVENT | DRM_VBLANK_RELATIVE; - vbl.request.sequence = 1; - vbl.request.signal = SIGUSR2; - - drmWaitVBlank(drm.fd, &vbl); + // not yet implemented + fprintf(stderr, "flutter vsync callback not yet implemented\n"); } @@ -657,8 +652,6 @@ bool init_display(void) { return false; } - - /********************** * EGL INITIALIZATION * **********************/ @@ -831,7 +824,7 @@ bool init_display(void) { return true; } void destroy_display(void) { - + fprintf(stderr, "Deinitializing display not yet implemented\n"); } bool init_application(void) { @@ -851,6 +844,14 @@ bool init_application(void) { flutter.args.struct_size = sizeof(FlutterProjectArgs); flutter.args.assets_path = flutter.asset_bundle_path; flutter.args.icu_data_path = flutter.icu_data_path; + flutter.args.isolate_snapshot_data_size = 0; + flutter.args.isolate_snapshot_data = NULL; + flutter.args.isolate_snapshot_instructions_size = 0; + flutter.args.isolate_snapshot_instructions = NULL; + flutter.args.vm_snapshot_data_size = 0; + flutter.args.vm_snapshot_data = NULL; + flutter.args.vm_snapshot_instructions_size = 0; + flutter.args.vm_snapshot_instructions = NULL; flutter.args.command_line_argc = flutter.engine_argc; flutter.args.command_line_argv = flutter.engine_argv; flutter.args.platform_message_callback = on_platform_message; @@ -936,70 +937,68 @@ void* io_loop(void* userdata) { 1 ) == kSuccess; if (!ok) return false; - } - // mouse - while (input.is_mouse) { - // read up to 64 input events - int rd = read(input.fd, &event, sizeof(struct input_event)*64); - if (rd < (int) sizeof(struct input_event)) { - fprintf(stderr, "Read %d bytes from input device, should have been %d; error msg: %s\n", rd, sizeof(struct input_event), strerror(errno)); - return false; - } + // mouse + while (1) { + // read up to 64 input events + int rd = read(input.fd, &event, sizeof(struct input_event)*64); + if (rd < (int) sizeof(struct input_event)) { + fprintf(stderr, "Read %d bytes from input device, should have been %d; error msg: %s\n", rd, sizeof(struct input_event), strerror(errno)); + return false; + } - // process the input events - // TODO: instead of processing an input event, and then send the single resulting Pointer Event (i.e., one at a time) to the Flutter Engine, - // process all input events, and send all resulting pointer events at once. - for (int i = 0; i < rd / sizeof(struct input_event); i++) { - phase = kCancel; - ev = &(event[i]); - - if (ev->type == EV_REL) { - if (ev->code == REL_X) { // mouse moved in the x-direction - input.x += ev->value; - phase = input.button ? kMove : kHover; - } else if (ev->code == REL_Y) { // mouse moved in the y-direction - input.y += ev->value; - phase = input.button ? kMove : kHover; - } - } else if (ev->type == EV_ABS) { - if (ev->code == ABS_X) { - input.x = ev->value; - phase = input.button ? kMove : kHover; - } else if (ev->code == ABS_Y) { - input.y = ev->value; - phase = input.button ? kMove : kHover; + // process the input events + // TODO: instead of processing an input event, and then send the single resulting Pointer Event (i.e., one at a time) to the Flutter Engine, + // process all input events, and send all resulting pointer events at once. + for (int i = 0; i < rd / sizeof(struct input_event); i++) { + phase = kCancel; + ev = &(event[i]); + + if (ev->type == EV_REL) { + if (ev->code == REL_X) { // mouse moved in the x-direction + input.x += ev->value; + phase = input.button ? kMove : kHover; + } else if (ev->code == REL_Y) { // mouse moved in the y-direction + input.y += ev->value; + phase = input.button ? kMove : kHover; + } + } else if (ev->type == EV_ABS) { + if (ev->code == ABS_X) { + input.x = ev->value; + phase = input.button ? kMove : kHover; + } else if (ev->code == ABS_Y) { + input.y = ev->value; + phase = input.button ? kMove : kHover; + } + } else if ((ev->type == EV_KEY) && ((ev->code == BTN_LEFT) || (ev->code == BTN_RIGHT))) { + // either the left or the right mouse button was pressed + // the 1st bit in "button" is set when BTN_LEFT is down. the 2nd bit when BTN_RIGHT is down. + uint8_t mask = ev->code == BTN_LEFT ? 1 : 2; + if (ev->value == 1) input.button |= mask; + else input.button &= ~mask; + + phase = ev->value == 1 ? kDown : kUp; } - } else if ((ev->type == EV_KEY) && ((ev->code == BTN_LEFT) || (ev->code == BTN_RIGHT))) { - // either the left or the right mouse button was pressed - // the 1st bit in "button" is set when BTN_LEFT is down. the 2nd bit when BTN_RIGHT is down. - uint8_t mask = ev->code == BTN_LEFT ? 1 : 2; - if (ev->value == 1) input.button |= mask; - else input.button &= ~mask; - phase = ev->value == 1 ? kDown : kUp; - } - - if (phase != kCancel) { - // if something changed, send the pointer event to flutter - ok = FlutterEngineSendPointerEvent( - engine, - & (FlutterPointerEvent) { - .struct_size = sizeof(FlutterPointerEvent), - .timestamp = (size_t) (FlutterEngineGetCurrentTime()*1000), - .phase=phase, .x=input.x, .y=input.y, - .signal_kind = kFlutterPointerSignalKindNone - }, - 1 - ) == kSuccess; - if (!ok) return false; + if (phase != kCancel) { + // if something changed, send the pointer event to flutter + ok = FlutterEngineSendPointerEvent( + engine, + & (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .timestamp = (size_t) (ev->time.tv_sec * 1000000ul) + ev->time.tv_usec, + .phase=phase, .x=input.x, .y=input.y, + .signal_kind = kFlutterPointerSignalKindNone + }, + 1 + ) == kSuccess; + if (!ok) return false; + } } - } - - printf("mouse position: %f, %f\n", input.x, input.y); - } - if (!input.is_mouse) { + printf("mouse position: %f, %f\n", input.x, input.y); + } + } else if (input.is_touchscreen) { for (int j = 0; j<10; j++) { printf("Sending kAdd %d to Flutter Engine\n", j); input.ts_slots[j].id = -1; @@ -1023,65 +1022,63 @@ void* io_loop(void* userdata) { } input.ts_slot = &(input.ts_slots[0]); - } - - // touchscreen - while (!input.is_mouse) { - int rd = read(input.fd, &event, sizeof(struct input_event)*64); - if (rd < (int) sizeof(struct input_event)) { - perror("error reading from input device"); - return false; - } + while (1) { + int rd = read(input.fd, &event, sizeof(struct input_event)*64); + if (rd < (int) sizeof(struct input_event)) { + perror("error reading from input device"); + return false; + } - n_pointerevents = 0; - for (int i = 0; i < rd / sizeof(struct input_event); i++) { - ev = &(event[i]); - - if (ev->type == EV_ABS) { - if (ev->code == ABS_MT_SLOT) { - input.ts_slot = &(input.ts_slots[ev->value]); - } else if (ev->code == ABS_MT_TRACKING_ID) { - if (input.ts_slot->id == -1) { - input.ts_slot->id = ev->value; - input.ts_slot->phase = kDown; - } else if (ev->value == -1) { - input.ts_slot->id = ev->value; - input.ts_slot->phase = kUp; + n_pointerevents = 0; + for (int i = 0; i < rd / sizeof(struct input_event); i++) { + ev = &(event[i]); + + if (ev->type == EV_ABS) { + if (ev->code == ABS_MT_SLOT) { + input.ts_slot = &(input.ts_slots[ev->value]); + } else if (ev->code == ABS_MT_TRACKING_ID) { + if (input.ts_slot->id == -1) { + input.ts_slot->id = ev->value; + input.ts_slot->phase = kDown; + } else if (ev->value == -1) { + input.ts_slot->id = ev->value; + input.ts_slot->phase = kUp; + } + } else if (ev->code == ABS_MT_POSITION_X) { + input.ts_slot->x = ev->value; + if (input.ts_slot->phase == kCancel) input.ts_slot->phase = kMove; + } else if (ev->code == ABS_MT_POSITION_Y) { + input.ts_slot->y = ev->value; + if (input.ts_slot->phase == kCancel) input.ts_slot->phase = kMove; } - } else if (ev->code == ABS_MT_POSITION_X) { - input.ts_slot->x = ev->value; - if (input.ts_slot->phase == kCancel) input.ts_slot->phase = kMove; - } else if (ev->code == ABS_MT_POSITION_Y) { - input.ts_slot->y = ev->value; - if (input.ts_slot->phase == kCancel) input.ts_slot->phase = kMove; - } - } else if ((ev->type == EV_SYN) && (ev->code == SYN_REPORT)) { - for (int j = 0; j < 10; j++) { - if (input.ts_slots[j].phase != kCancel) { - pointer_event[n_pointerevents++] = (FlutterPointerEvent) { - .struct_size = sizeof(FlutterPointerEvent), - .phase = input.ts_slots[j].phase, - .timestamp = (size_t) (FlutterEngineGetCurrentTime()*1000), - .device = j, - .x = input.ts_slots[j].x, - .y = input.ts_slots[j].y, - .signal_kind = kFlutterPointerSignalKindNone - }; - input.ts_slots[j].phase = kCancel; + } else if ((ev->type == EV_SYN) && (ev->code == SYN_REPORT)) { + for (int j = 0; j < 10; j++) { + if (input.ts_slots[j].phase != kCancel) { + pointer_event[n_pointerevents++] = (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .phase = input.ts_slots[j].phase, + .timestamp = (size_t) (ev->time.tv_sec * 1000000ul) + ev->time.tv_usec, + .device = j, + .x = input.ts_slots[j].x, + .y = input.ts_slots[j].y, + .signal_kind = kFlutterPointerSignalKindNone + }; + input.ts_slots[j].phase = kCancel; + } } } } - } - ok = FlutterEngineSendPointerEvent( - engine, - pointer_event, - n_pointerevents - ) == kSuccess; + ok = FlutterEngineSendPointerEvent( + engine, + pointer_event, + n_pointerevents + ) == kSuccess; - if (!ok) { - fprintf(stderr, "Error sending pointer events to flutter\n"); - return false; + if (!ok) { + fprintf(stderr, "Error sending pointer events to flutter\n"); + return false; + } } } @@ -1116,6 +1113,7 @@ bool parse_cmd_args(int argc, char **argv) { printf("Using mouse input from mouse %s\n", optarg); snprintf(input.device_path, sizeof(input.device_path), "%s", optarg); input.is_mouse = true; + input.is_touchscreen = false; index++; break; @@ -1123,6 +1121,7 @@ bool parse_cmd_args(int argc, char **argv) { printf("Using touchscreen input from %s\n", optarg); snprintf(input.device_path, sizeof(input.device_path), "%s", optarg); input.is_mouse = false; + input.is_touchscreen = true; index++; break;