From 9a191216df17250d7588d13bf9f4e3d040d918e2 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 23 Jan 2023 23:15:06 +0000 Subject: [PATCH 01/55] updates for gstreamer video player - add new platform message interface for gstreamer video player - add a way to create gstreamer video player from raw gstreamer pipeline - add new API for using standard message codec values without decoding --- include/collection.h | 24 + include/platformchannel.h | 94 ++- include/plugins/gstreamer_video_player.h | 9 + include/plugins/omxplayer_video_player.h | 2 +- src/platformchannel.c | 705 +++++++++++++++++++- src/plugins/gstreamer_video_player/player.c | 115 +++- src/plugins/gstreamer_video_player/plugin.c | 696 ++++++++++++++++++- src/plugins/omxplayer_video_player.c | 4 +- 8 files changed, 1614 insertions(+), 35 deletions(-) diff --git a/include/collection.h b/include/collection.h index 3035f010..7f9486e3 100644 --- a/include/collection.h +++ b/include/collection.h @@ -455,6 +455,30 @@ static const char *__file_logging_name = _logging_name; #define UNIMPLEMENTED() assert(0 && "Unimplemented") +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + +#if defined(__GNUC__) || __has_builtin(__builtin_unreachable) +#define UNREACHABLE() __builtin_unreachable() +#else +#define UNREACHABLE() assert(0 && "Unreachable") +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define MAYBE_UNUSED __attribute__((unused)) +#define ATTR_MALLOC __attribute__((malloc)) +#define NONNULL(...) __attribute__((nonnull(__VA_ARGS__))) +#define ATTR_PURE __attribute__((pure)) +#define ATTR_CONST __attribute__((const)) +#else +#define MAYBE_UNUSED +#define ATTR_MALLOC +#define NONNULL(...) +#define ATTR_PURE +#define ATTR_CONST +#endif + static inline int refcount_inc_n(refcount_t *refcount, int n) { return atomic_fetch_add_explicit(refcount, n, memory_order_relaxed); } diff --git a/include/platformchannel.h b/include/platformchannel.h index acbe3805..2105e346 100644 --- a/include/platformchannel.h +++ b/include/platformchannel.h @@ -64,8 +64,10 @@ enum std_value_type { kStdInt64Array, kStdFloat64Array, kStdList, - kStdMap + kStdMap, + kStdFloat32Array }; + struct std_value { enum std_value_type type; union { @@ -773,6 +775,12 @@ int platch_respond_error_std(FlutterPlatformMessageResponseHandle *handle, int platch_respond_illegal_arg_std(FlutterPlatformMessageResponseHandle *handle, char *error_msg); +int platch_respond_illegal_arg_ext_std( + FlutterPlatformMessageResponseHandle *handle, + char *error_msg, + struct std_value *error_details +); + int platch_respond_native_error_std(FlutterPlatformMessageResponseHandle *handle, int _errno); @@ -868,4 +876,88 @@ struct std_value *stdmap_get(struct std_value *map, struct std_value *key); struct std_value *stdmap_get_str(struct std_value *map, char *key); +struct raw_std_value; + +ATTR_PURE bool raw_std_value_is_null(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_true(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_false(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_int32(const struct raw_std_value *value); +ATTR_PURE int32_t raw_std_value_as_int32(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_int64(const struct raw_std_value *value); +ATTR_PURE int64_t raw_std_value_as_int64(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_float64(const struct raw_std_value *value); +ATTR_PURE double raw_std_value_as_float64(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_string(const struct raw_std_value *value); +ATTR_PURE char *raw_std_string_dup(const struct raw_std_value *value); +ATTR_PURE bool raw_std_string_equals(const struct raw_std_value *value, const char *str); +ATTR_PURE bool raw_std_value_is_uint8array(const struct raw_std_value *value); +ATTR_PURE const uint8_t *raw_std_value_as_uint8array(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_int32array(const struct raw_std_value *value); +ATTR_PURE const int32_t *raw_std_value_as_int32array(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_int64array(const struct raw_std_value *value); +ATTR_PURE const int64_t *raw_std_value_as_int64array(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_float64array(const struct raw_std_value *value); +ATTR_PURE const double *raw_std_value_as_float64array(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_list(const struct raw_std_value *value); +ATTR_PURE size_t raw_std_list_get_size(const struct raw_std_value *list); +ATTR_PURE bool raw_std_value_is_map(const struct raw_std_value *value); +ATTR_PURE size_t raw_std_map_get_size(const struct raw_std_value *map); +ATTR_PURE bool raw_std_value_is_float32array(const struct raw_std_value *value); +ATTR_PURE const float *raw_std_value_as_float32array(const struct raw_std_value *value); + +ATTR_PURE bool raw_std_value_equals(const struct raw_std_value *a, const struct raw_std_value *b); +ATTR_PURE bool raw_std_value_is_bool(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_as_bool(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_int(const struct raw_std_value *value); +ATTR_PURE int64_t raw_std_value_as_int(const struct raw_std_value *value); +ATTR_PURE size_t raw_std_value_get_size(const struct raw_std_value *value); +ATTR_PURE const struct raw_std_value *raw_std_value_after(const struct raw_std_value *value); +ATTR_PURE const struct raw_std_value *raw_std_list_get_first_element(const struct raw_std_value *list); +ATTR_PURE const struct raw_std_value *raw_std_list_get_nth_element(const struct raw_std_value *list, size_t index); +ATTR_PURE const struct raw_std_value *raw_std_map_get_first_key(const struct raw_std_value *map); +ATTR_PURE const struct raw_std_value *raw_std_map_find(const struct raw_std_value *map, const struct raw_std_value *key); +ATTR_PURE const struct raw_std_value *raw_std_map_find_str(const struct raw_std_value *map, const char *str); + +ATTR_PURE bool raw_std_value_check(const struct raw_std_value *value, size_t buffer_size); + + +#define CONCAT(a, b) CONCAT_INNER(a, b) +#define CONCAT_INNER(a, b) a ## b + +#define UNIQUE_NAME(base) CONCAT(base, __COUNTER__) + +#define for_each_entry_in_raw_std_map_indexed(index, key, value, map) \ + for ( \ + const struct raw_std_value *key = raw_std_map_get_first_key(map), *value = raw_std_value_after(key), *guard = NULL; \ + guard == NULL; \ + guard = (void*) 1 \ + ) \ + for ( \ + size_t index = 0; \ + index < raw_std_map_get_size(map); \ + index++, \ + key = raw_std_value_after(value), \ + value = raw_std_value_after(key) \ + ) + +#define for_each_entry_in_raw_std_map(key, value, map) \ + for_each_entry_in_raw_std_map_indexed(UNIQUE_NAME(__raw_std_map_entry_index), key, value, map) + +#define for_each_element_in_raw_std_list_indexed(index, element, list) \ + for ( \ + const struct raw_std_value *element = raw_std_list_get_first_element(list), *guard = NULL; \ + guard == NULL; \ + guard = (void*) 1 \ + ) \ + for ( \ + size_t index = 0; \ + index < raw_std_list_get_size(list); \ + index++, \ + element = raw_std_value_after(element) \ + ) + +#define for_each_element_in_raw_std_list(value, list) \ + for_each_element_in_raw_std_list_indexed(UNIQUE_NAME(__raw_std_list_element_index), value, list) + + #endif \ No newline at end of file diff --git a/include/plugins/gstreamer_video_player.h b/include/plugins/gstreamer_video_player.h index ee1ac375..131542d1 100644 --- a/include/plugins/gstreamer_video_player.h +++ b/include/plugins/gstreamer_video_player.h @@ -97,6 +97,15 @@ struct gstplayer *gstplayer_new_from_file( void *userdata ); +/// Create a gstreamer video player with a custom gstreamer pipeline. +/// @arg pipeline The description of the custom pipeline that should be used. Should contain an appsink called "sink". +/// @arg userdata The userdata associated with this player. +struct gstplayer *gstplayer_new_from_pipeline( + struct flutterpi *flutterpi, + const char *pipeline, + void *userdata +); + /// Destroy this gstreamer player instance and the resources /// associated with it. (texture, gstreamer pipeline, etc) /// diff --git a/include/plugins/omxplayer_video_player.h b/include/plugins/omxplayer_video_player.h index 95ad07a0..dc2fdf2f 100644 --- a/include/plugins/omxplayer_video_player.h +++ b/include/plugins/omxplayer_video_player.h @@ -24,7 +24,7 @@ struct omxplayer_mgr; struct omxplayer_video_player { int64_t player_id; char event_channel_name[256]; - char video_uri[256]; + char *video_uri; bool has_view; int64_t view_id; diff --git a/src/platformchannel.c b/src/platformchannel.c index 3fe3a27c..a5fe8da6 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -12,7 +12,6 @@ #include #include - struct platch_msg_resp_handler_data { enum platch_codec codec; platch_msg_resp_callback on_response; @@ -1314,6 +1313,16 @@ int platch_respond_illegal_arg_std(FlutterPlatformMessageResponseHandle *handle, return platch_respond_error_std(handle, "illegalargument", error_msg, NULL); } +/// Sends a platform message to `handle` with error code "illegalargument" +/// and error message `errmsg`. +int platch_respond_illegal_arg_ext_std( + FlutterPlatformMessageResponseHandle *handle, + char *error_msg, + struct std_value *error_details +) { + return platch_respond_error_std(handle, "illegalargument", error_msg, error_details); +} + /// Sends a platform message to `handle` with error code "nativeerror" /// and error messsage `strerror(_errno)` int platch_respond_native_error_std(FlutterPlatformMessageResponseHandle *handle, @@ -1719,8 +1728,700 @@ struct std_value *stdmap_get(struct std_value *map, struct std_value *key) { return NULL; } + struct std_value *stdmap_get_str(struct std_value *map, char *key) { DEBUG_ASSERT_NOT_NULL(map); DEBUG_ASSERT_NOT_NULL(key); return stdmap_get(map, &STDSTRING(key)); -} \ No newline at end of file +} + +/** + * BEGIN Raw Standard Message Codec Value API. + * + * New API, for using standard-message-codec encoded buffers directly, without parsing them first. + * + */ + +ATTR_PURE enum std_value_type raw_std_value_get_type(const struct raw_std_value *value) { + return (enum std_value_type) *((const uint8_t*) value); +} + +ATTR_CONST static const void *get_value_ptr(const struct raw_std_value *value, int alignment) { + uintptr_t addr = (uintptr_t) value; + + // skip type byte + addr++; + + // make sure we're aligned + if (alignment == 4) { + if (addr & 3) { + addr = (addr | 3) + 1; + } + } else if (alignment == 8) { + if (addr & 7) { + addr = (addr | 7) + 1; + } + } + + return (const void*) addr; +} + +ATTR_CONST MAYBE_UNUSED static const void *get_after_ptr(const struct raw_std_value *value, int alignment, int value_size) { + return ((const uint8_t*) get_value_ptr(value, alignment)) + value_size; +} + +ATTR_CONST static const void *get_array_value_ptr(const struct raw_std_value *value, int alignment, size_t size) { + uintptr_t addr = (uintptr_t) value; + + // skip type byte + addr++; + + // skip initial size byte + addr++; + if (size == 254) { + // skip two additional size bytes + addr += 2; + } else if (size == 255) { + // skip four additional size bytes + addr += 4; + } + + // make sure we're aligned + if (alignment == 4) { + if (addr & 3) { + addr = (addr | 3) + 1; + } + } else if (alignment == 8) { + if (addr & 7) { + addr = (addr | 7) + 1; + } + } + + return (const void*) addr; +} + +ATTR_CONST static const void *get_array_after_ptr(const struct raw_std_value *value, int alignment, size_t size, int element_size) { + uintptr_t addr = (uintptr_t) get_array_value_ptr(value, alignment, size); + + // skip all the array elements + addr += size * element_size; + + return (const void*) addr; +} + + +ATTR_PURE bool raw_std_value_is_null(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdNull; +} + +ATTR_PURE bool raw_std_value_is_true(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdTrue; +} + +ATTR_PURE bool raw_std_value_is_false(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdFalse; +} + +ATTR_PURE bool raw_std_value_is_int32(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdInt32; +} + +ATTR_PURE int32_t raw_std_value_as_int32(const struct raw_std_value *value) { + DEBUG_ASSERT(raw_std_value_is_int32(value)); + int32_t result; + memcpy(&result, get_value_ptr(value, 0), sizeof(result)); + + return result; +} + +ATTR_PURE bool raw_std_value_is_int64(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdInt64; +} + +ATTR_PURE int64_t raw_std_value_as_int64(const struct raw_std_value *value) { + DEBUG_ASSERT(raw_std_value_is_int64(value)); + + int64_t result; + memcpy(&result, get_value_ptr(value, 0), sizeof(result)); + + return result; +} + +ATTR_PURE bool raw_std_value_is_float64(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdFloat64; +} + +ATTR_PURE double raw_std_value_as_float64(const struct raw_std_value *value) { + DEBUG_ASSERT(raw_std_value_is_float64(value)); + return *(double*) get_value_ptr(value, 8); +} + +ATTR_PURE bool raw_std_value_is_string(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdString; +} + +ATTR_PURE char *raw_std_string_dup(const struct raw_std_value *value) { + DEBUG_ASSERT(raw_std_value_is_string(value)); + + size_t size = raw_std_value_get_size(value); + + char *str = malloc(size + 1); + if (str == NULL) { + return NULL; + } + + memcpy(str, get_array_value_ptr(value, 0, size), size); + str[size] = '\0'; + + return str; +} + +ATTR_PURE bool raw_std_string_equals(const struct raw_std_value *value, const char *str) { + size_t length = raw_std_value_get_size(value); + const char *as_unterminated_string = get_array_value_ptr(value, 0, length); + return strncmp(as_unterminated_string, str, length) == 0 && str[length] == '\0'; +} + +ATTR_PURE bool raw_std_value_is_uint8array(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdUInt8Array; +} + +ATTR_PURE const uint8_t *raw_std_value_as_uint8array(const struct raw_std_value *value) { + DEBUG_ASSERT(raw_std_value_is_uint8array(value)); + return get_array_value_ptr(value, 0, raw_std_value_get_size(value)); +} + +ATTR_PURE bool raw_std_value_is_int32array(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdInt32Array; +} + +ATTR_PURE const int32_t *raw_std_value_as_int32array(const struct raw_std_value *value) { + DEBUG_ASSERT(raw_std_value_is_int32array(value)); + return get_array_value_ptr(value, 4, raw_std_value_get_size(value)); +} + +ATTR_PURE bool raw_std_value_is_int64array(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdInt64Array; +} + +ATTR_PURE const int64_t *raw_std_value_as_int64array(const struct raw_std_value *value) { + DEBUG_ASSERT(raw_std_value_is_int64array(value)); + return get_array_value_ptr(value, 8, raw_std_value_get_size(value)); +} + +ATTR_PURE bool raw_std_value_is_float64array(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdFloat64Array; +} + +ATTR_PURE const double *raw_std_value_as_float64array(const struct raw_std_value *value) { + DEBUG_ASSERT(raw_std_value_is_float64array(value)); + return get_array_value_ptr(value, 8, raw_std_value_get_size(value)); +} + +ATTR_PURE bool raw_std_value_is_list(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdList; +} + +ATTR_PURE size_t raw_std_list_get_size(const struct raw_std_value *list) { + DEBUG_ASSERT(raw_std_value_is_list(list)); + return raw_std_value_get_size(list); +} + +ATTR_PURE bool raw_std_value_is_map(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdMap; +} + +ATTR_PURE size_t raw_std_map_get_size(const struct raw_std_value *map) { + DEBUG_ASSERT(raw_std_value_is_map(map)); + return raw_std_value_get_size(map); +} + +ATTR_PURE bool raw_std_value_is_float32array(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdFloat32Array; +} + +ATTR_PURE const float *raw_std_value_as_float32array(const struct raw_std_value *value) { + DEBUG_ASSERT(raw_std_value_is_float32array(value)); + return get_array_value_ptr(value, 4, raw_std_value_get_size(value)); +} + + +ATTR_PURE bool raw_std_value_equals(const struct raw_std_value *a, const struct raw_std_value *b) { + size_t length; + int alignment, element_size; + + DEBUG_ASSERT_NOT_NULL(a); + DEBUG_ASSERT_NOT_NULL(b); + + if (a == b) { + return true; + } + + if (raw_std_value_get_type(a) != raw_std_value_get_type(b)) { + return false; + } + + switch (raw_std_value_get_type(a)) { + case kStdNull: + case kStdTrue: + case kStdFalse: + return true; + case kStdInt32: + return raw_std_value_as_int32(a) == raw_std_value_as_int32(b); + case kStdInt64: + return raw_std_value_as_int64(a) == raw_std_value_as_int64(b); + case kStdFloat64: + return raw_std_value_as_float64(a) == raw_std_value_as_float64(b); + case kStdString: + alignment = 0; + element_size = 1; + goto memcmp_arrays; + case kStdUInt8Array: + alignment = 0; + element_size = 1; + goto memcmp_arrays; + case kStdInt32Array: + alignment = 4; + element_size = 4; + goto memcmp_arrays; + case kStdInt64Array: + alignment = 8; + element_size = 8; + + memcmp_arrays: + if (raw_std_value_get_size(a) != raw_std_value_get_size(b)) { + return false; + } + + length = raw_std_value_get_size(a); + const void *values_a = get_array_value_ptr(a, alignment, length); + const void *values_b = get_array_value_ptr(b, alignment, length); + return memcmp(values_a, values_b, length * element_size) == 0; + case kStdFloat64Array: + if (raw_std_value_get_size(a) != raw_std_value_get_size(b)) { + return false; + } + + length = raw_std_value_get_size(a); + const double *a_doubles = raw_std_value_as_float64array(a); + const double *b_doubles = raw_std_value_as_float64array(b); + for (int i = 0; i < length; i++) { + if (a_doubles[i] != b_doubles[i]) { + return false; + } + } + + return true; + case kStdList: + if (raw_std_value_get_size(a) != raw_std_value_get_size(b)) { + return false; + } + + const struct raw_std_value *cursor_a = raw_std_list_get_first_element(a); + const struct raw_std_value *cursor_b = raw_std_list_get_first_element(b); + for (int i = 0; i < raw_std_value_get_size(a); i++, cursor_a = raw_std_value_after(cursor_a), cursor_b = raw_std_value_after(cursor_b)) { + if (!raw_std_value_equals(cursor_a, cursor_b)) { + return false; + } + } + + return true; + case kStdMap: + if (raw_std_value_get_size(a) != raw_std_value_get_size(b)) { + return false; + } + + size_t size = raw_std_value_get_size(a); + + // key_from_b_also_in_a[i] == true means that there's a key in a that matches + // the i-th key in b. So if we're searching for a key from a in b, we can safely ignore / don't need to compare + // keys in b that have they're key_from_b_also_in_a set to true. + bool *key_from_b_found_in_a = calloc(size, sizeof(bool)); + + // for each key in a, look for a matching key in b. + // once it's found, mark it as used, so we don't try matching it again. + // then, check if the values associated with both keys are equal. + for_each_entry_in_raw_std_map(key_a, value_a, a) { + for_each_entry_in_raw_std_map_indexed(index_b, key_b, value_b, b) { + // we've already linked this entry from b to an entry from a. + // skip to the next entry. + if (key_from_b_found_in_a[index_b]) { + continue; + } + + // Keys don't match. Skip to the next entry. + if (!raw_std_value_equals(key_a, key_b)) { + continue; + } + + key_from_b_found_in_a[index_b] = true; + + // the values associated with both keys must be equal. + if (raw_std_value_equals(value_a, value_b) == false) { + free(key_from_b_found_in_a); + return false; + } + + goto found; + } + + // we didn't find key_a in b. + free(key_from_b_found_in_a); + return false; + + found: + continue; + } + + // each key, value pair in a was found in b. + // because a and b have the same number of entries, + // this must mean that every key, value pair in b must also be present in a. + // (a subset of b) and (b subset of a) ==> a equals b. + + free(key_from_b_found_in_a); + return true; + case kStdFloat32Array: + if (raw_std_value_get_size(a) != raw_std_value_get_size(b)) { + return false; + } + + length = raw_std_value_get_size(a); + const float *a_floats = raw_std_value_as_float32array(a); + const float *b_floats = raw_std_value_as_float32array(b); + for (int i = 0; i < length; i++) { + if (a_floats[i] != b_floats[i]) { + return false; + } + } + + return true; + default: + return false; + } +} + + +ATTR_PURE bool raw_std_value_is_bool(const struct raw_std_value *value) { + return raw_std_value_is_true(value) || raw_std_value_is_false(value); +} + +ATTR_PURE bool raw_std_value_as_bool(const struct raw_std_value *value) { + DEBUG_ASSERT(raw_std_value_is_bool(value)); + return raw_std_value_is_true(value); +} + +ATTR_PURE bool raw_std_value_is_int(const struct raw_std_value *value) { + return raw_std_value_is_int32(value) || raw_std_value_is_int64(value); +} + +ATTR_PURE int64_t raw_std_value_as_int(const struct raw_std_value *value) { + DEBUG_ASSERT(raw_std_value_is_int(value)); + if (raw_std_value_is_int32(value)) { + return raw_std_value_as_int32(value); + } else { + return raw_std_value_as_int64(value); + } +} + +ATTR_PURE size_t raw_std_value_get_size(const struct raw_std_value *value) { + const uint8_t *byteptr; + size_t size; + + DEBUG_ASSERT( + raw_std_value_is_uint8array(value) || + raw_std_value_is_int32array(value) || + raw_std_value_is_int64array(value) || + raw_std_value_is_float64array(value) || + raw_std_value_is_string(value) || + raw_std_value_is_list(value) || + raw_std_value_is_map(value) + ); + + byteptr = (const uint8_t*) value; + + // skip type byte + byteptr++; + + size = *byteptr; + if (size <= 253) { + return size; + } else if (size == 254) { + size = 0; + memcpy(&size, byteptr + 1, 2); + return size; + } else if (size == 255) { + size = 0; + memcpy(&size, byteptr + 1, 4); + return size; + } + + UNREACHABLE(); +} + + + +ATTR_PURE const struct raw_std_value *raw_std_value_after(const struct raw_std_value *value) { + size_t size; + + switch (raw_std_value_get_type(value)) { + case kStdNull: + return get_after_ptr(value, 0, 0); + case kStdTrue: + return get_after_ptr(value, 0, 0); + case kStdFalse: + return get_after_ptr(value, 0, 0); + case kStdInt32: + return get_after_ptr(value, 0, 4); + case kStdInt64: + return get_after_ptr(value, 8, 8); + case kStdLargeInt: + case kStdString: + return get_array_after_ptr(value, 0, raw_std_value_get_size(value), 1); + case kStdFloat64: + return get_array_after_ptr(value, 8, raw_std_value_get_size(value), 8); + case kStdUInt8Array: + return get_array_after_ptr(value, 0, raw_std_value_get_size(value), 1); + case kStdInt32Array: + return get_array_after_ptr(value, 4, raw_std_value_get_size(value), 4); + case kStdInt64Array: + return get_array_after_ptr(value, 8, raw_std_value_get_size(value), 8); + case kStdFloat64Array: + return get_array_after_ptr(value, 8, raw_std_value_get_size(value), 8); + case kStdList: ; + size = raw_std_value_get_size(value); + + value = get_array_value_ptr(value, 0, size); + for (; size > 0; size--) { + value = raw_std_value_after(value); + } + + return value; + case kStdMap: + size = raw_std_value_get_size(value); + + value = get_array_value_ptr(value, 0, size); + for (; size > 0; size--) { + value = raw_std_value_after(value); + value = raw_std_value_after(value); + } + + return value; + default: + return value; + } +} + +ATTR_PURE const struct raw_std_value *raw_std_list_get_first_element(const struct raw_std_value *list) { + DEBUG_ASSERT(raw_std_value_is_list(list)); + DEBUG_ASSERT(raw_std_value_get_size(list) > 0); + return get_array_value_ptr(list, 0, raw_std_value_get_size(list)); +} + +ATTR_PURE const struct raw_std_value *raw_std_list_get_nth_element(const struct raw_std_value *list, size_t index) { + const struct raw_std_value *element; + + DEBUG_ASSERT(raw_std_value_is_list(list)); + DEBUG_ASSERT(raw_std_value_get_size(list) > index); + + element = raw_std_list_get_first_element(list); + while (index != 0) { + element = raw_std_value_after(element); + index--; + } + + return element; +} + +ATTR_PURE const struct raw_std_value *raw_std_map_get_first_key(const struct raw_std_value *map) { + DEBUG_ASSERT(raw_std_value_is_map(map)); + + return get_array_value_ptr(map, 0, raw_std_value_get_size(map)); +} + +ATTR_PURE const struct raw_std_value *raw_std_map_find(const struct raw_std_value *map, const struct raw_std_value *key) { + DEBUG_ASSERT(raw_std_value_is_map(map)); + + for_each_entry_in_raw_std_map(key_iter, value_iter, map) { + if (raw_std_value_equals(key_iter, key)) { + return value_iter; + } + } + + return NULL; +} + +ATTR_PURE const struct raw_std_value *raw_std_map_find_str(const struct raw_std_value *map, const char *str) { + DEBUG_ASSERT(raw_std_value_is_map(map)); + + for_each_entry_in_raw_std_map(key_iter, value_iter, map) { + if (raw_std_value_is_string(key_iter) && raw_std_string_equals(key_iter, str)) { + return value_iter; + } + } + + return NULL; +} + + +ATTR_PURE static bool check_size(const struct raw_std_value *value, size_t buffer_size) { + size_t size; + + if (buffer_size < 1) { + return false; + } + + const uint8_t *byteptr = (const uint8_t*) value; + + // skip type byte + byteptr++; + + size = *byteptr; + buffer_size--; + + if (size == 254) { + if (buffer_size < 2) { + return false; + } + } else if (size == 255) { + if (buffer_size < 4) { + return false; + } + } + + return true; +} + +ATTR_PURE bool raw_std_value_check(const struct raw_std_value *value, size_t buffer_size) { + size_t size; + int alignment, element_size; + + if (buffer_size < 1) { + return false; + } + + switch (raw_std_value_get_type(value)) { + case kStdNull: + case kStdTrue: + case kStdFalse: + return true; + case kStdInt32: + return buffer_size >= 5; + case kStdInt64: + return buffer_size >= 9; + case kStdFloat64: + return buffer_size >= 9; + case kStdString: + alignment = 0; + element_size = 1; + goto check_arrays; + case kStdUInt8Array: + alignment = 0; + element_size = 1; + goto check_arrays; + case kStdInt32Array: + alignment = 4; + element_size = 4; + goto check_arrays; + case kStdInt64Array: + alignment = 8; + element_size = 8; + goto check_arrays; + case kStdFloat64Array: + alignment = 8; + element_size = 8; + goto check_arrays; + case kStdFloat32Array: + alignment = 4; + element_size = 4; + + // common code for checking if the buffer is large enough to contain + // a fixed size array. + check_arrays: + + // check if buffer is large enough to contain the value size. + if (!check_size(value, buffer_size)) { + return false; + } + + // get the value size. + size = raw_std_value_get_size(value); + + // get the offset of the actual array values. + int diff = (intptr_t) get_array_value_ptr(value, alignment, size) - (intptr_t) value; + DEBUG_ASSERT(diff >= 0); + + if (buffer_size < diff) { + return false; + } + buffer_size -= diff; + + // check if the buffer is large enough to contain all the the array values. + if (buffer_size < size * element_size) { + return false; + } + + return true; + case kStdList: + // check if buffer is large enough to contain the value size. + if (!check_size(value, buffer_size)) { + return false; + } + + // get the value size. + size = raw_std_value_get_size(value); + + for_each_element_in_raw_std_list(element, value) { + int diff = (intptr_t) element - (intptr_t) value; + if (buffer_size < diff) { + return false; + } + + if (!raw_std_value_check(value, buffer_size - diff)) { + return false; + } + } + + return true; + case kStdMap: + // check if buffer is large enough to contain the value size. + if (!check_size(value, buffer_size)) { + return false; + } + + // get the value size. + size = raw_std_value_get_size(value); + + const struct raw_std_value *key = NULL, *map_value; + for (int diff, i = 0; i < size; i++) { + if (key == NULL) { + key = raw_std_map_get_first_key(value); + } else { + value = raw_std_value_after(map_value); + } + + diff = (intptr_t) key - (intptr_t) value; + if (buffer_size < diff) { + return false; + } + + if (!raw_std_value_check(key, buffer_size - diff)) { + return false; + } + + map_value = raw_std_value_after(key); + + diff = (intptr_t) map_value - (intptr_t) value; + if (buffer_size < diff) { + return false; + } + + if (!raw_std_value_check(map_value, buffer_size - diff)) { + return false; + } + } + + return true; + default: + return false; + } +} diff --git a/src/plugins/gstreamer_video_player/player.c b/src/plugins/gstreamer_video_player/player.c index 54c46bd0..57c36efa 100644 --- a/src/plugins/gstreamer_video_player/player.c +++ b/src/plugins/gstreamer_video_player/player.c @@ -66,7 +66,10 @@ struct gstplayer { struct flutterpi *flutterpi; void *userdata; + char *video_uri; + char *pipeline_description; + GstStructure *headers; /** @@ -594,14 +597,18 @@ static GstPadProbeReturn on_query_appsink(GstPad *pad, GstPadProbeInfo *info, vo (void) pad; (void) userdata; - query = info->data; + query = gst_pad_probe_info_get_query(info); + if (query == NULL) { + LOG_DEBUG("Couldn't get query from pad probe info.\n"); + return GST_PAD_PROBE_OK; + } if (GST_QUERY_TYPE(query) != GST_QUERY_ALLOCATION) { return GST_PAD_PROBE_OK; } gst_query_add_allocation_meta(query, GST_VIDEO_META_API_TYPE, NULL); - + return GST_PAD_PROBE_HANDLED; } @@ -827,6 +834,16 @@ static void on_appsink_cbs_destroy(void *userdata) { (void) player; } +void on_source_setup(GstElement *bin, GstElement *source, gpointer userdata) { + (void) bin; + + if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "extra-headers") != NULL) { + g_object_set(source, "extra-headers", (GstStructure*) userdata, NULL); + } else { + LOG_ERROR("Failed to set custom HTTP headers because gstreamer source element has no 'extra-headers' property.\n"); + } +} + static int init(struct gstplayer *player, bool force_sw_decoders) { sd_event_source *busfd_event_source; GstElement *pipeline, *sink, *src; @@ -836,9 +853,16 @@ static int init(struct gstplayer *player, bool force_sw_decoders) { GError *error = NULL; int ok; - static const char *pipeline_descr = "uridecodebin name=\"src\" ! video/x-raw ! appsink sync=true name=\"sink\""; + static const char *default_pipeline_descr = "uridecodebin name=\"src\" ! video/x-raw ! appsink sync=true name=\"sink\""; + + const char *pipeline_descr; + if (player->pipeline_description != NULL) { + pipeline_descr = player->pipeline_description; + } else { + pipeline_descr = default_pipeline_descr; + } - pipeline = gst_parse_launch(pipeline_descr, &error); + pipeline = gst_parse_launch(default_pipeline_descr, &error); if (pipeline == NULL) { LOG_ERROR("Could create GStreamer pipeline from description: %s (pipeline: `%s`)\n", error->message, pipeline_descr); return error->code; @@ -867,16 +891,34 @@ static int init(struct gstplayer *player, bool force_sw_decoders) { ); src = gst_bin_get_by_name(GST_BIN(pipeline), "src"); - if (src == NULL) { - LOG_ERROR("Couldn't find uridecodebin in pipeline bin.\n"); - ok = EINVAL; - goto fail_unref_sink; + + if (player->video_uri != NULL) { + if (src != NULL) { + g_object_set(G_OBJECT(src), "uri", player->video_uri, NULL); + } else { + LOG_ERROR("Couldn't find \"src\" element to configure Video URI.\n"); + } + } + + if (force_sw_decoders) { + if (src != NULL) { + g_object_set(G_OBJECT(src), "force-sw-decoders", force_sw_decoders, NULL); + } else { + LOG_ERROR("Couldn't find \"src\" element to force sw decoding.\n"); + } } - g_object_set(G_OBJECT(src), "uri", player->video_uri, "force-sw-decoders", force_sw_decoders, NULL); + if (player->headers != NULL) { + if (src != NULL) { + g_signal_connect(G_OBJECT(src), "source-setup", G_CALLBACK(on_source_setup), player->headers); + } else { + LOG_ERROR("Couldn't find \"src\" element to configure additional HTTP headers.\n"); + } + } gst_base_sink_set_max_lateness(GST_BASE_SINK(sink), 20 * GST_MSECOND); gst_base_sink_set_qos_enabled(GST_BASE_SINK(sink), TRUE); + gst_base_sink_set_sync(GST_BASE_SINK(sink), TRUE); gst_app_sink_set_max_buffers(GST_APP_SINK(sink), 2); gst_app_sink_set_emit_signals(GST_APP_SINK(sink), TRUE); gst_app_sink_set_drop(GST_APP_SINK(sink), FALSE); @@ -900,8 +942,18 @@ static int init(struct gstplayer *player, bool force_sw_decoders) { player, NULL ); + + /// FIXME: Make this work for custom pipelines as well. + if (src != NULL) { + g_signal_connect(src, "element-added", G_CALLBACK(on_element_added), player); + } else { + LOG_DEBUG("Couldn't find \"src\" element to setup v4l2 'capture-io-mode' to 'dmabuf'.\n"); + } - g_signal_connect(src, "element-added", G_CALLBACK(on_element_added), player); + if (src != NULL) { + gst_object_unref(src); + src = NULL; + } bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); @@ -926,7 +978,6 @@ static int init(struct gstplayer *player, bool force_sw_decoders) { player->busfd_events = busfd_event_source; player->is_forcing_sw_decoding = force_sw_decoders; - gst_object_unref(src); gst_object_unref(pad); return 0; @@ -982,15 +1033,18 @@ static void maybe_deinit(struct gstplayer *player) { DEFINE_LOCK_OPS(gstplayer, lock) -static struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char *uri, void *userdata) { +static struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char *uri, const char *pipeline_descr, void *userdata) { struct frame_interface *frame_interface; struct gstplayer *player; struct texture *texture; GstStructure *gst_headers; int64_t texture_id; - char *uri_owned; + char *uri_owned, *pipeline_descr_owned; int ok; + DEBUG_ASSERT_NOT_NULL(flutterpi); + DEBUG_ASSERT((uri != NULL) != (pipeline_descr != NULL)); + player = malloc(sizeof *player); if (player == NULL) return NULL; @@ -1002,8 +1056,19 @@ static struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char * texture_id = texture_get_id(texture); - uri_owned = strdup(uri); - if (uri_owned == NULL) goto fail_destroy_frame_interface; + if (uri != NULL) { + uri_owned = strdup(uri); + if (uri_owned == NULL) goto fail_destroy_frame_interface; + } else { + uri_owned = NULL; + } + + if (pipeline_descr != NULL) { + pipeline_descr_owned = strdup(pipeline_descr); + if (pipeline_descr_owned == NULL) goto fail_destroy_frame_interface; + } else { + pipeline_descr_owned = NULL; + } gst_headers = gst_structure_new_empty("http-headers"); @@ -1022,6 +1087,7 @@ static struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char * player->flutterpi = flutterpi; player->userdata = userdata; player->video_uri = uri_owned; + player->pipeline_description = pipeline_descr_owned; player->headers = gst_headers; player->playback_rate_forward = 1.0; player->playback_rate_backward = 1.0; @@ -1095,7 +1161,7 @@ struct gstplayer *gstplayer_new_from_asset( return NULL; } - player = gstplayer_new(flutterpi, uri, userdata); + player = gstplayer_new(flutterpi, uri, NULL, userdata); free(uri); @@ -1109,7 +1175,7 @@ struct gstplayer *gstplayer_new_from_network( void *userdata ) { (void) format_hint; - return gstplayer_new(flutterpi, uri, userdata); + return gstplayer_new(flutterpi, uri, NULL, userdata); } struct gstplayer *gstplayer_new_from_file( @@ -1117,7 +1183,7 @@ struct gstplayer *gstplayer_new_from_file( const char *uri, void *userdata ) { - return gstplayer_new(flutterpi, uri, userdata); + return gstplayer_new(flutterpi, uri, NULL, userdata); } struct gstplayer *gstplayer_new_from_content_uri( @@ -1125,9 +1191,17 @@ struct gstplayer *gstplayer_new_from_content_uri( const char *uri, void *userdata ) { - return gstplayer_new(flutterpi, uri, userdata); + return gstplayer_new(flutterpi, uri, NULL, userdata); } +struct gstplayer *gstplayer_new_from_pipeline( + struct flutterpi *flutterpi, + const char *pipeline, + void *userdata +) { + return gstplayer_new(flutterpi, NULL, pipeline, userdata); +} + void gstplayer_destroy(struct gstplayer *player) { LOG_DEBUG("gstplayer_destroy(%p)\n", player); notifier_deinit(&player->video_info_notifier); @@ -1136,7 +1210,8 @@ void gstplayer_destroy(struct gstplayer *player) { maybe_deinit(player); pthread_mutex_destroy(&player->lock); if (player->headers != NULL) gst_structure_free(player->headers); - free(player->video_uri); + if (player->video_uri != NULL) free(player->video_uri); + if (player->pipeline_description != NULL) free(player->pipeline_description); frame_interface_unref(player->frame_interface); texture_destroy(player->texture); free(player); diff --git a/src/plugins/gstreamer_video_player/plugin.c b/src/plugins/gstreamer_video_player/plugin.c index b2830d98..ffa3ae81 100644 --- a/src/plugins/gstreamer_video_player/plugin.c +++ b/src/plugins/gstreamer_video_player/plugin.c @@ -245,6 +245,14 @@ static int respond_init_failed(FlutterPlatformMessageResponseHandle *handle) { ); } +static int respond_init_failed_v2(FlutterPlatformMessageResponseHandle *handle) { + return platch_respond_error_std( + handle, + "couldnotinit", + "gstreamer video player plugin failed to initialize gstreamer. See flutter-pi log for details.", + NULL + ); +} static int send_initialized_event(struct gstplayer_meta *meta, bool is_stream, int width, int height, int64_t duration_ms) { return platch_send_success_event_std( @@ -386,8 +394,6 @@ static int on_receive_evch( method = object->method; - LOG_DEBUG("on_receive_evch\n"); - player = get_player_by_evch(channel); if (player == NULL) { return platch_respond_not_implemented(responsehandle); @@ -439,11 +445,9 @@ static int on_initialize( ok = ensure_initialized(); if (ok != 0) { - return respond_init_failed(responsehandle); + return respond_init_failed_v2(responsehandle); } - LOG_DEBUG("on_initialize\n"); - // what do we even do here? return platch_respond_success_pigeon(responsehandle, NULL); @@ -702,8 +706,6 @@ static int on_create( goto fail_remove_receiver; } - LOG_DEBUG("respond success on_create\n"); - return platch_respond_success_pigeon( responsehandle, &STDMAP1( @@ -982,7 +984,6 @@ static int on_set_mix_with_others( (void) arg; /// TODO: Should we do anything other here than just returning? - LOG_DEBUG("on_set_mix_with_others\n"); return platch_respond_success_std(responsehandle, &STDNULL); } @@ -1089,6 +1090,674 @@ static int on_receive_method_channel( } } + +static struct gstplayer *get_player_from_texture_id_with_custom_errmsg(int64_t texture_id, FlutterPlatformMessageResponseHandle *responsehandle, char *error_message) { + struct gstplayer *player; + + player = get_player_by_texture_id(texture_id); + if (player == NULL) { + cpset_lock(&plugin.players); + + int n_texture_ids = cpset_get_count_pointers_locked(&plugin.players); + int64_t *texture_ids = alloca(sizeof(int64_t) * n_texture_ids); + int64_t *texture_ids_cursor = texture_ids; + + for_each_pointer_in_cpset(&plugin.players, player) { + *texture_ids_cursor++ = gstplayer_get_texture_id(player); + } + + cpset_unlock(&plugin.players); + + platch_respond_illegal_arg_ext_std( + responsehandle, + error_message, + &STDMAP2( + STDSTRING("receivedTextureId"), STDINT64(texture_id), + STDSTRING("registeredTextureIds"), ((struct std_value) { + .type = kStdInt64Array, + .size = n_texture_ids, + .int64array = texture_ids + }) + ) + ); + + return NULL; + } + + return player; +} + +static struct gstplayer *get_player_from_v2_root_arg(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + int64_t texture_id; + + if (!raw_std_value_is_int(arg)) { + platch_respond_illegal_arg_std(responsehandle, "Expected `arg` to be an integer."); + return NULL; + } + + texture_id = raw_std_value_as_int(arg); + + return get_player_from_texture_id_with_custom_errmsg( + texture_id, + responsehandle, + "Expected `arg` to be a valid texture id." + ); +} + +static struct gstplayer *get_player_from_v2_list_arg(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + int64_t texture_id; + + if (!(raw_std_value_is_list(arg) && raw_std_list_get_size(arg) >= 1)) { + platch_respond_illegal_arg_std(responsehandle, "Expected `arg` to be a list with at least one element."); + return NULL; + } + + const struct raw_std_value *first_element = raw_std_list_get_first_element(arg); + if (!raw_std_value_is_int(first_element)) { + platch_respond_illegal_arg_std(responsehandle, "Expected `arg[0]` to be an integer."); + return NULL; + } + + texture_id = raw_std_value_as_int(first_element); + + return get_player_from_texture_id_with_custom_errmsg( + texture_id, + responsehandle, + "Expected `arg[0]` to be a valid texture id." + ); +} + +static int check_arg_is_minimum_sized_list( + const struct raw_std_value *arg, + size_t minimum_size, + FlutterPlatformMessageResponseHandle *responsehandle +) { + if (!(raw_std_value_is_list(arg) && raw_std_list_get_size(arg) >= minimum_size)) { + char *error_message = NULL; + + asprintf(&error_message, "Expected `arg` to be a list with at least %zu element(s).", minimum_size); + + platch_respond_illegal_arg_std(responsehandle, error_message); + + free(error_message); + + return EINVAL; + } + + return 0; +} + +static int on_initialize_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + int ok; + + (void) arg; + + ok = ensure_initialized(); + if (ok != 0) { + return respond_init_failed(responsehandle); + } + + return platch_respond_success_std(responsehandle, &STDNULL); +} + +static int on_create_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + const struct raw_std_value *headers; + struct gstplayer_meta *meta; + struct gstplayer *player; + enum format_hint format_hint; + char *asset, *uri, *package_name, *pipeline; + size_t size; + int ok; + + ok = ensure_initialized(); + if (ok != 0) { + return respond_init_failed_v2(responsehandle); + } + + if (!raw_std_value_is_list(arg)) { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg` to be a List."); + } + + size = raw_std_list_get_size(arg); + + // arg[0]: Asset Path + if (size >= 1) { + arg = raw_std_list_get_first_element(arg); + + if (raw_std_value_is_null(arg)) { + asset = NULL; + } else if (raw_std_value_is_string(arg)) { + asset = raw_std_string_dup(arg); + if (asset == NULL) { + ok = ENOMEM; + goto fail_respond_error; + } + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[0]` to be a String or null."); + } + } else { + asset = NULL; + } + + + // arg[1]: Package Name + if (size >= 2) { + arg = raw_std_value_after(arg); + + if (raw_std_value_is_null(arg)) { + package_name = NULL; + } else if (raw_std_value_is_string(arg)) { + package_name = raw_std_string_dup(arg); + if (package_name == NULL) { + ok = ENOMEM; + goto fail_respond_error; + } + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be a String or null."); + } + } else { + package_name = NULL; + } + + // arg[1]: URI + if (size >= 3) { + arg = raw_std_value_after(arg); + + if (raw_std_value_is_null(arg)) { + uri = NULL; + } else if (raw_std_value_is_string(arg)) { + uri = raw_std_string_dup(arg); + if (uri == NULL) { + ok = ENOMEM; + goto fail_respond_error; + } + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[2]` to be a String or null."); + } + } else { + uri = NULL; + } + + // arg[3]: Format Hint + if (size >= 4) { + arg = raw_std_value_after(arg); + + if (raw_std_value_is_null(arg)) { + format_hint = kNoFormatHint; + } else if (raw_std_value_is_string(arg)) { + if (raw_std_string_equals(arg, "ss")) { + format_hint = kSS_FormatHint; + } else if (raw_std_string_equals(arg, "hls")) { + format_hint = kHLS_FormatHint; + } else if (raw_std_string_equals(arg, "dash")) { + format_hint = kMpegDash_FormatHint; + } else if (raw_std_string_equals(arg, "other")) { + format_hint = kOther_FormatHint; + } else { + goto invalid_format_hint; + } + } else { + invalid_format_hint: + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[3]` to be one of 'ss', 'hls', 'dash', 'other' or null."); + } + } else { + format_hint = kNoFormatHint; + } + + // arg[4]: HTTP Headers + if (size >= 5) { + arg = raw_std_value_after(arg); + + if (raw_std_value_is_null(arg)) { + headers = NULL; + } else if (raw_std_value_is_map(arg)) { + for_each_entry_in_raw_std_map(key, value, arg) { + if (!raw_std_value_is_string(key) || !raw_std_value_is_string(value)) { + goto invalid_headers; + } + } + headers = arg; + } else { + invalid_headers: + return platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg[4]` to be a map of strings or null." + ); + } + } else { + headers = NULL; + } + + // arg[5]: Gstreamer Pipeline + if (size >= 6) { + arg = raw_std_value_after(arg); + + if (raw_std_value_is_null(arg)) { + pipeline = NULL; + } else if (raw_std_value_is_string(arg)) { + pipeline = raw_std_string_dup(arg); + } else { + return platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg[5]` to be a string or null." + ); + } + } else { + pipeline = NULL; + } + + if ((asset ? 1 : 0) + (uri ? 1 : 0) + (pipeline ? 1 : 0) != 1) { + return platch_respond_illegal_arg_std( + responsehandle, + "Expected exactly one of `arg[0]`, `arg[2]` or `arg[5]` to be non-null." + ); + } + + // Create our actual player (this doesn't initialize it) + if (asset != NULL) { + player = gstplayer_new_from_asset(&flutterpi, asset, package_name, NULL); + } else if (uri != NULL) { + player = gstplayer_new_from_network(&flutterpi, uri, format_hint, NULL); + } else if (pipeline != NULL) { + player = gstplayer_new_from_pipeline(&flutterpi, pipeline, NULL); + } else { + UNREACHABLE(); + } + + if (player == NULL) { + LOG_ERROR("Couldn't create gstreamer video player.\n"); + ok = EIO; + goto fail_respond_error; + } + + // create a meta object so we can store the event channel name + // of a player with it + meta = create_meta(gstplayer_get_texture_id(player)); + if (meta == NULL) { + ok = ENOMEM; + goto fail_destroy_player; + } + + gstplayer_set_userdata_locked(player, meta); + + // Add all the HTTP headers to gstplayer using gstplayer_put_http_header + if (headers != NULL) { + for_each_entry_in_raw_std_map(header_name, header_value, headers) { + char *header_name_duped = raw_std_string_dup(header_name); + char *header_value_duped = raw_std_string_dup(header_value); + + gstplayer_put_http_header(player, header_name_duped, header_value_duped); + + free(header_value_duped); + free(header_name_duped); + } + } + + // Add it to our player collection + ok = add_player(player); + if (ok != 0) { + goto fail_destroy_meta; + } + + // Set a receiver on the videoEvents event channel + ok = plugin_registry_set_receiver( + meta->event_channel_name, + kStandardMethodCall, + on_receive_evch + ); + if (ok != 0) { + goto fail_remove_player; + } + + // Finally, start initializing + ok = gstplayer_initialize(player); + if (ok != 0) { + goto fail_remove_receiver; + } + + return platch_respond_success_std( + responsehandle, + &STDINT64(gstplayer_get_texture_id(player)) + ); + + + fail_remove_receiver: + plugin_registry_remove_receiver(meta->event_channel_name); + + fail_remove_player: + remove_player(player); + + fail_destroy_meta: + destroy_meta(meta); + + fail_destroy_player: + gstplayer_destroy(player); + + fail_respond_error: + return platch_respond_native_error_std(responsehandle, ok); +} + +static int on_dispose_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + struct gstplayer *player; + + player = get_player_from_v2_root_arg(arg, responsehandle); + if (player == NULL) { + return EINVAL; + } + + return 0; +} + +static int on_set_looping_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + const struct raw_std_value *second; + struct gstplayer *player; + bool looping; + int ok; + + ok = check_arg_is_minimum_sized_list(arg, 2, responsehandle); + if (ok != 0) { + return 0; + } + + player = get_player_from_v2_list_arg(arg, responsehandle); + if (player == NULL) { + return 0; + } + + second = raw_std_list_get_nth_element(arg, 1); + if (raw_std_value_is_bool(second)) { + looping = raw_std_value_as_bool(second); + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be a bool."); + } + + ok = gstplayer_set_looping(player, looping); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return 0; +} + +static int on_set_volume_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + const struct raw_std_value *second; + struct gstplayer *player; + double volume; + int ok; + + ok = check_arg_is_minimum_sized_list(arg, 2, responsehandle); + if (ok != 0) { + return 0; + } + + player = get_player_from_v2_list_arg(arg, responsehandle); + if (player == NULL) { + return 0; + } + + second = raw_std_list_get_nth_element(arg, 1); + if (raw_std_value_is_float64(second)) { + volume = raw_std_value_as_float64(second); + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be a double."); + } + + ok = gstplayer_set_volume(player, volume); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return 0; +} + +static int on_set_playback_speed_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + const struct raw_std_value *second; + struct gstplayer *player; + double speed; + int ok; + + ok = check_arg_is_minimum_sized_list(arg, 2, responsehandle); + if (ok != 0) { + return 0; + } + + player = get_player_from_v2_list_arg(arg, responsehandle); + if (player == NULL) { + return 0; + } + + second = raw_std_list_get_nth_element(arg, 1); + if (raw_std_value_is_float64(second)) { + speed = raw_std_value_as_float64(second); + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be a double."); + } + + ok = gstplayer_set_playback_speed(player, speed); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return 0; +} + +static int on_play_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + struct gstplayer *player; + int ok; + + player = get_player_from_v2_root_arg(arg, responsehandle); + if (player == NULL) { + return EINVAL; + } + + ok = gstplayer_play(player); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return 0; +} + +static int on_get_position_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + struct gstplayer *player; + int64_t position; + + player = get_player_from_v2_root_arg(arg, responsehandle); + if (player == NULL) { + return EINVAL; + } + + position = gstplayer_get_position(player); + if (position < 0) { + return platch_respond_native_error_std(responsehandle, EIO); + } + + return platch_respond_success_std( + responsehandle, + &STDINT64(position) + ); +} + +static int on_seek_to_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + const struct raw_std_value *second; + struct gstplayer *player; + int64_t position; + int ok; + + ok = check_arg_is_minimum_sized_list(arg, 2, responsehandle); + if (ok != 0) { + return 0; + } + + player = get_player_from_v2_list_arg(arg, responsehandle); + if (player == NULL) { + return 0; + } + + second = raw_std_list_get_nth_element(arg, 1); + if (raw_std_value_is_int(second)) { + position = raw_std_value_as_int(second); + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be an integer."); + } + + ok = gstplayer_seek_to(player, position, false); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return 0; +} + +static int on_pause_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + struct gstplayer *player; + int ok; + + player = get_player_from_v2_root_arg(arg, responsehandle); + if (player == NULL) { + return EINVAL; + } + + ok = gstplayer_pause(player); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return 0; +} + +static int on_set_mix_with_others_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + (void) arg; + (void) responsehandle; + + return platch_respond_success_std(responsehandle, &STDNULL); +} + +static int on_fast_seek_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + const struct raw_std_value *second; + struct gstplayer *player; + int64_t position; + int ok; + + ok = check_arg_is_minimum_sized_list(arg, 2, responsehandle); + if (ok != 0) { + return 0; + } + + player = get_player_from_v2_list_arg(arg, responsehandle); + if (player == NULL) { + return 0; + } + + second = raw_std_list_get_nth_element(arg, 1); + if (raw_std_value_is_int(second)) { + position = raw_std_value_as_int(second); + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be an integer."); + } + + ok = gstplayer_seek_to(player, position, true); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return 0; +} + +static int on_step_forward_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + struct gstplayer *player; + int ok; + + player = get_player_from_v2_root_arg(arg, responsehandle); + if (player == NULL) { + return EINVAL; + } + + ok = gstplayer_step_forward(player); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return 0; +} + +static int on_step_backward_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + struct gstplayer *player; + int ok; + + player = get_player_from_v2_root_arg(arg, responsehandle); + if (player == NULL) { + return EINVAL; + } + + ok = gstplayer_step_backward(player); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return 0; +} + +static int on_receive_method_channel_v2( + char *channel, + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { + const struct raw_std_value *method, *arg; + + DEBUG_ASSERT_NOT_NULL(channel); + DEBUG_ASSERT_NOT_NULL(object); + DEBUG_ASSERT_NOT_NULL(responsehandle); + DEBUG_ASSERT_EQUALS(object->codec, kBinaryCodec); + DEBUG_ASSERT(object->binarydata_size != 0); + (void) channel; + + if (!raw_std_value_check((const struct raw_std_value*) (object->binarydata), object->binarydata_size)) { + return platch_respond_error_std(responsehandle, "malformed-message", "", &STDNULL); + } + + method = (const struct raw_std_value*) (object->binarydata); + if (!raw_std_value_is_string(method)) { + return platch_respond_error_std(responsehandle, "malformed-message", "", &STDNULL); + } + + arg = raw_std_value_after(method); + DEBUG_ASSERT_NOT_NULL(arg); + + if (raw_std_string_equals(method, "initialize")) { + return on_initialize_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "create")) { + return on_create_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "dispose")) { + return on_dispose_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "setLooping")) { + return on_set_looping_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "setVolume")) { + return on_set_volume_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "setPlaybackSpeed")) { + return on_set_playback_speed_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "play")) { + return on_play_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "getPosition")) { + return on_get_position_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "seekTo")) { + return on_seek_to_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "pause")) { + return on_pause_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "setMixWithOthers")) { + return on_set_mix_with_others_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "stepForward")) { + return on_step_forward_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "stepBackward")) { + return on_step_backward_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "fastSeek")) { + return on_fast_seek_v2(arg, responsehandle); + } else { + return platch_respond_not_implemented(responsehandle); + } +} + + enum plugin_init_result gstplayer_plugin_init(struct flutterpi *flutterpi, void **userdata_out) { int ok; @@ -1162,9 +1831,17 @@ enum plugin_init_result gstplayer_plugin_init(struct flutterpi *flutterpi, void goto fail_remove_setMixWithOthers_receiver; } + ok = plugin_registry_set_receiver("flutter-pi/gstreamerVideoPlayer", kBinaryCodec, on_receive_method_channel_v2); + if (ok != 0) { + goto fail_remove_advancedControls_receiver; + } + return kInitialized_PluginInitResult; + fail_remove_advancedControls_receiver: + plugin_registry_remove_receiver("flutter.io/videoPlayer/gstreamerVideoPlayer/advancedControls"); + fail_remove_setMixWithOthers_receiver: plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers"); @@ -1207,6 +1884,7 @@ void gstplayer_plugin_deinit(struct flutterpi *flutterpi, void *userdata) { (void) flutterpi; (void) userdata; + plugin_registry_remove_receiver("flutter-pi/gstreamerVideoPlayer"); plugin_registry_remove_receiver("flutter.io/videoPlayer/gstreamerVideoPlayer/advancedControls"); plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers"); plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.pause"); @@ -1227,4 +1905,4 @@ FLUTTERPI_PLUGIN( gstplayer, gstplayer_plugin_init, gstplayer_plugin_deinit -) \ No newline at end of file +) diff --git a/src/plugins/omxplayer_video_player.c b/src/plugins/omxplayer_video_player.c index d6050c18..18224e90 100644 --- a/src/plugins/omxplayer_video_player.c +++ b/src/plugins/omxplayer_video_player.c @@ -1148,9 +1148,9 @@ static int on_create( player->player_id = omxpvidpp.next_unused_player_id++; player->mgr = mgr; if (asset != NULL) { - snprintf(player->video_uri, sizeof(player->video_uri), "%s/%s", flutterpi.flutter.paths->asset_bundle_path, asset); + asprintf(&player->video_uri, "%s/%s", flutterpi.flutter.paths->asset_bundle_path, asset); } else { - strncpy(player->video_uri, uri, sizeof(player->video_uri)); + player->video_uri = strdup(uri); } mgr->player = player; From d08518b1f5dc22bcad8c699b04b74369543e7698 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 24 Jan 2023 13:25:16 +0000 Subject: [PATCH 02/55] empty commit to retrigger CI From 03d6e3ce8960bc8f37df1fccc167e98d0ad5f0bd Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 24 Jan 2023 15:48:23 +0000 Subject: [PATCH 03/55] More `raw_std_...` API - add some `raw_std_method_call_...` API for checking, handling method calls - make gstreamer plugin use that API --- include/platformchannel.h | 8 +- src/platformchannel.c | 90 ++++++++++++++++++++- src/plugins/gstreamer_video_player/plugin.c | 14 ++-- 3 files changed, 101 insertions(+), 11 deletions(-) diff --git a/include/platformchannel.h b/include/platformchannel.h index 2105e346..080bc229 100644 --- a/include/platformchannel.h +++ b/include/platformchannel.h @@ -888,7 +888,7 @@ ATTR_PURE int64_t raw_std_value_as_int64(const struct raw_std_value *value); ATTR_PURE bool raw_std_value_is_float64(const struct raw_std_value *value); ATTR_PURE double raw_std_value_as_float64(const struct raw_std_value *value); ATTR_PURE bool raw_std_value_is_string(const struct raw_std_value *value); -ATTR_PURE char *raw_std_string_dup(const struct raw_std_value *value); +ATTR_PURE ATTR_MALLOC char *raw_std_string_dup(const struct raw_std_value *value); ATTR_PURE bool raw_std_string_equals(const struct raw_std_value *value, const char *str); ATTR_PURE bool raw_std_value_is_uint8array(const struct raw_std_value *value); ATTR_PURE const uint8_t *raw_std_value_as_uint8array(const struct raw_std_value *value); @@ -919,7 +919,13 @@ ATTR_PURE const struct raw_std_value *raw_std_map_find(const struct raw_std_valu ATTR_PURE const struct raw_std_value *raw_std_map_find_str(const struct raw_std_value *map, const char *str); ATTR_PURE bool raw_std_value_check(const struct raw_std_value *value, size_t buffer_size); +ATTR_PURE bool raw_std_method_call_check(const struct raw_std_value *value, size_t buffer_size); +ATTR_PURE bool raw_std_method_call_response_check(const struct raw_std_value *value, size_t buffer_size); +ATTR_PURE bool raw_std_event_check(const struct raw_std_value *value, size_t buffer_size); +ATTR_PURE const struct raw_std_value *raw_std_method_call_get_method(const struct raw_std_value *value); +ATTR_PURE ATTR_MALLOC char *raw_std_method_call_get_method_dup(const struct raw_std_value *value); +ATTR_PURE const struct raw_std_value *raw_std_method_call_get_arg(const struct raw_std_value *value); #define CONCAT(a, b) CONCAT_INNER(a, b) #define CONCAT_INNER(a, b) a ## b diff --git a/src/platformchannel.c b/src/platformchannel.c index a5fe8da6..85890709 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -1860,7 +1860,7 @@ ATTR_PURE bool raw_std_value_is_string(const struct raw_std_value *value) { return raw_std_value_get_type(value) == kStdString; } -ATTR_PURE char *raw_std_string_dup(const struct raw_std_value *value) { +ATTR_PURE ATTR_MALLOC char *raw_std_string_dup(const struct raw_std_value *value) { DEBUG_ASSERT(raw_std_value_is_string(value)); size_t size = raw_std_value_get_size(value); @@ -2425,3 +2425,91 @@ ATTR_PURE bool raw_std_value_check(const struct raw_std_value *value, size_t buf return false; } } + +ATTR_PURE bool raw_std_method_call_check(const struct raw_std_value *value, size_t buffer_size) { + if (!raw_std_value_check(value, buffer_size)) { + return false; + } + + // first value must be a string. (method name) + if (!raw_std_value_is_string(value)) { + return false; + } + + const struct raw_std_value *after = raw_std_value_after(value); + int diff = (intptr_t) after - (intptr_t) value; + DEBUG_ASSERT(diff <= buffer_size); + + if (!raw_std_value_check(after, buffer_size - diff)) { + return false; + } + + return true; +} + +ATTR_PURE bool raw_std_method_call_response_check(const struct raw_std_value *value, size_t buffer_size) { + // method call responses have non-standard encoding for the first byte. + // first byte is zero for failure, non-zero for success. + if (buffer_size < 1) { + return false; + } + + bool successful = (*(const uint8_t*) (value)) != 0; + + value = (intptr_t) value + 1; + buffer_size -= 1; + + if (successful) { + if (!raw_std_value_check(value, buffer_size)) { + return false; + } + } else { + // first value should be the error code (string). + if (!raw_std_value_check(value, buffer_size) || !raw_std_value_is_string(value)) { + return false; + } + + const struct raw_std_value *second = raw_std_value_after(value); + int diff = (intptr_t) second - (intptr_t) value; + DEBUG_ASSERT(diff <= buffer_size); + + // second value should be the error message. (string or null) + if (!raw_std_value_check(second, buffer_size - diff)) { + return false; + } + + if (!raw_std_value_is_string(second) && !raw_std_value_is_null(second)) { + return false; + } + + const struct raw_std_value *third = raw_std_value_after(value); + diff = (intptr_t) third - (intptr_t) value; + DEBUG_ASSERT(diff <= buffer_size); + + // third value is the error detail. Can be anything. + if (!raw_std_value_check(third, buffer_size - diff)) { + return false; + } + } + + return true; +} + +ATTR_PURE bool raw_std_event_check(const struct raw_std_value *value, size_t buffer_size) { + return raw_std_method_call_response_check(value, buffer_size); +} + + +ATTR_PURE const struct raw_std_value *raw_std_method_call_get_method(const struct raw_std_value *value) { + DEBUG_ASSERT(raw_std_value_is_string(value)); + return value; +} + +ATTR_MALLOC ATTR_PURE char *raw_std_method_call_get_method_dup(const struct raw_std_value *value) { + DEBUG_ASSERT(raw_std_value_is_string(value)); + return raw_std_string_dup(value); +} + +ATTR_PURE const struct raw_std_value *raw_std_method_call_get_arg(const struct raw_std_value *value) { + return raw_std_value_after(value); +} diff --git a/src/plugins/gstreamer_video_player/plugin.c b/src/plugins/gstreamer_video_player/plugin.c index ffa3ae81..84e07808 100644 --- a/src/plugins/gstreamer_video_player/plugin.c +++ b/src/plugins/gstreamer_video_player/plugin.c @@ -1703,7 +1703,7 @@ static int on_receive_method_channel_v2( struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle ) { - const struct raw_std_value *method, *arg; + const struct raw_std_value *envelope, *method, *arg; DEBUG_ASSERT_NOT_NULL(channel); DEBUG_ASSERT_NOT_NULL(object); @@ -1712,17 +1712,14 @@ static int on_receive_method_channel_v2( DEBUG_ASSERT(object->binarydata_size != 0); (void) channel; - if (!raw_std_value_check((const struct raw_std_value*) (object->binarydata), object->binarydata_size)) { - return platch_respond_error_std(responsehandle, "malformed-message", "", &STDNULL); - } + envelope = (const struct raw_std_value*) (object->binarydata); - method = (const struct raw_std_value*) (object->binarydata); - if (!raw_std_value_is_string(method)) { + if (!raw_std_method_call_check(envelope, object->binarydata_size)) { return platch_respond_error_std(responsehandle, "malformed-message", "", &STDNULL); } - arg = raw_std_value_after(method); - DEBUG_ASSERT_NOT_NULL(arg); + method = raw_std_method_call_get_method(envelope); + arg = raw_std_method_call_get_arg(envelope); if (raw_std_string_equals(method, "initialize")) { return on_initialize_v2(arg, responsehandle); @@ -1757,7 +1754,6 @@ static int on_receive_method_channel_v2( } } - enum plugin_init_result gstplayer_plugin_init(struct flutterpi *flutterpi, void **userdata_out) { int ok; From 5f67c223271490406eeaff6670a7f61f51348dc6 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 24 Jan 2023 15:50:54 +0000 Subject: [PATCH 04/55] remove `ATTR_PURE` from `ATTR_MALLOC` method calls - a function returning new memory can't be pure --- include/platformchannel.h | 4 ++-- src/platformchannel.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/platformchannel.h b/include/platformchannel.h index 080bc229..c785abdc 100644 --- a/include/platformchannel.h +++ b/include/platformchannel.h @@ -888,7 +888,7 @@ ATTR_PURE int64_t raw_std_value_as_int64(const struct raw_std_value *value); ATTR_PURE bool raw_std_value_is_float64(const struct raw_std_value *value); ATTR_PURE double raw_std_value_as_float64(const struct raw_std_value *value); ATTR_PURE bool raw_std_value_is_string(const struct raw_std_value *value); -ATTR_PURE ATTR_MALLOC char *raw_std_string_dup(const struct raw_std_value *value); +ATTR_MALLOC char *raw_std_string_dup(const struct raw_std_value *value); ATTR_PURE bool raw_std_string_equals(const struct raw_std_value *value, const char *str); ATTR_PURE bool raw_std_value_is_uint8array(const struct raw_std_value *value); ATTR_PURE const uint8_t *raw_std_value_as_uint8array(const struct raw_std_value *value); @@ -924,7 +924,7 @@ ATTR_PURE bool raw_std_method_call_response_check(const struct raw_std_value *va ATTR_PURE bool raw_std_event_check(const struct raw_std_value *value, size_t buffer_size); ATTR_PURE const struct raw_std_value *raw_std_method_call_get_method(const struct raw_std_value *value); -ATTR_PURE ATTR_MALLOC char *raw_std_method_call_get_method_dup(const struct raw_std_value *value); +ATTR_MALLOC char *raw_std_method_call_get_method_dup(const struct raw_std_value *value); ATTR_PURE const struct raw_std_value *raw_std_method_call_get_arg(const struct raw_std_value *value); #define CONCAT(a, b) CONCAT_INNER(a, b) diff --git a/src/platformchannel.c b/src/platformchannel.c index 85890709..b37f6bb9 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -1860,7 +1860,7 @@ ATTR_PURE bool raw_std_value_is_string(const struct raw_std_value *value) { return raw_std_value_get_type(value) == kStdString; } -ATTR_PURE ATTR_MALLOC char *raw_std_string_dup(const struct raw_std_value *value) { +ATTR_MALLOC char *raw_std_string_dup(const struct raw_std_value *value) { DEBUG_ASSERT(raw_std_value_is_string(value)); size_t size = raw_std_value_get_size(value); @@ -2456,7 +2456,7 @@ ATTR_PURE bool raw_std_method_call_response_check(const struct raw_std_value *va bool successful = (*(const uint8_t*) (value)) != 0; - value = (intptr_t) value + 1; + value = (void*) ((intptr_t) value + (intptr_t) 1); buffer_size -= 1; if (successful) { @@ -2505,7 +2505,7 @@ ATTR_PURE const struct raw_std_value *raw_std_method_call_get_method(const struc return value; } -ATTR_MALLOC ATTR_PURE char *raw_std_method_call_get_method_dup(const struct raw_std_value *value) { +ATTR_MALLOC char *raw_std_method_call_get_method_dup(const struct raw_std_value *value) { DEBUG_ASSERT(raw_std_value_is_string(value)); return raw_std_string_dup(value); } From c494b1f66859a40860f1560bf29f56f4e3c422fb Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 24 Jan 2023 17:13:10 +0000 Subject: [PATCH 05/55] add platform channel unittests --- .github/workflows/cmake.yml | 21 ++- .gitmodules | 6 +- CMakeLists.txt | 91 +++++----- src/flutter-pi.c | 3 +- src/main.c | 5 + test/CMakeLists.txt | 10 ++ test/platformchannel_test.c | 321 ++++++++++++++++++++++++++++++++++++ test/texture_registry.c | 0 third_party/CMakeLists.txt | 12 ++ third_party/Unity | 1 + 10 files changed, 423 insertions(+), 47 deletions(-) create mode 100644 src/main.c create mode 100644 test/CMakeLists.txt create mode 100644 test/platformchannel_test.c delete mode 100644 test/texture_registry.c create mode 100644 third_party/CMakeLists.txt create mode 160000 third_party/Unity diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 3c598377..d4b2a51e 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -22,7 +22,9 @@ jobs: runs-on: [linux, bullseye] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + submodules: 'recursive' - name: Install dependencies run: | @@ -34,14 +36,21 @@ jobs: - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN=On -DBUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN=On -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -GNinja + run: | + cmake \ + -B ${{github.workspace}}/build \ + -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN=On \ + -DBUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN=On \ + -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On \ + -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ + -DENABLE_TESTS=On \ + -GNinja - name: Build # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Test - if: false working-directory: ${{github.workspace}}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail @@ -55,7 +64,9 @@ jobs: env: DEBIAN_FRONTEND: noninteractive steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + submodules: 'recursive' - name: Install dependencies run: | @@ -74,6 +85,7 @@ jobs: -DBUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN=On \ -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On \ -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ + -DENABLE_TESTS=On \ -GNinja - name: Build @@ -81,7 +93,6 @@ jobs: run: cmake --build ${{ github.workspace }}/build --config ${{ env.BUILD_TYPE }} - name: Test - if: false working-directory: ${{github.workspace}}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail diff --git a/.gitmodules b/.gitmodules index fd6e8e06..4bb04965 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "third_party/backward-cpp"] - path = third_party/backward-cpp - url = https://github.com/bombela/backward-cpp.git +[submodule "third_party/Unity"] + path = third_party/Unity + url = https://github.com/ThrowTheSwitch/Unity diff --git a/CMakeLists.txt b/CMakeLists.txt index aa420a83..6c8d831c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ option(ENABLE_TSAN "True to build & link with -fsanitize=thread" OFF) option(ENABLE_ASAN "True to build & link with -fsanitize=address" OFF) option(ENABLE_UBSAN "True to build & link with -fsanitize=undefined" OFF) option(ENABLE_MTRACE "True if flutter-pi should call GNU mtrace() on startup." OFF) +option(ENABLE_TESTS "True if tests should be built. Requires Unity to be checked out at third_party/Unity." OFF) set(FILESYSTEM_LAYOUTS default meta-flutter) set(FILESYSTEM_LAYOUT "default" CACHE STRING "Where to look for the icudtl.dat, app.so/libapp.so, flutter asset bundle.") @@ -115,7 +116,8 @@ pkg_check_modules(LIBINPUT REQUIRED libinput) pkg_check_modules(LIBXKBCOMMON REQUIRED xkbcommon) pkg_check_modules(LIBUDEV REQUIRED libudev) -add_executable(flutter-pi +add_library( + flutterpi_module STATIC src/flutter-pi.c src/platformchannel.c src/pluginregistry.c @@ -133,7 +135,7 @@ add_executable(flutter-pi src/plugins/services.c ) -target_link_libraries(flutter-pi +target_link_libraries(flutterpi_module ${DRM_LDFLAGS} ${GBM_LDFLAGS} ${EGL_LDFLAGS} @@ -146,7 +148,7 @@ target_link_libraries(flutter-pi pthread dl rt m atomic ) -target_include_directories(flutter-pi PRIVATE +target_include_directories(flutterpi_module PUBLIC ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/include/plugins @@ -160,7 +162,7 @@ target_include_directories(flutter-pi PRIVATE ${LIBXKBCOMMON_INCLUDE_DIRS} ) -target_compile_options(flutter-pi PRIVATE +target_compile_options(flutterpi_module PRIVATE ${DRM_CFLAGS} ${GBM_CFLAGS} ${EGL_CFLAGS} @@ -181,14 +183,14 @@ list(GET LIBINPUT_VERSION_AS_LIST 0 LIBINPUT_VERSION_MAJOR) list(GET LIBINPUT_VERSION_AS_LIST 1 LIBINPUT_VERSION_MINOR) list(GET LIBINPUT_VERSION_AS_LIST 2 LIBINPUT_VERSION_PATCH) -target_compile_definitions(flutter-pi PRIVATE +target_compile_definitions(flutterpi_module PRIVATE LIBINPUT_VERSION_MAJOR=${LIBINPUT_VERSION_MAJOR} LIBINPUT_VERSION_MINOR=${LIBINPUT_VERSION_MINOR} LIBINPUT_VERSION_PATCH=${LIBINPUT_VERSION_PATCH} ) # TODO: Just unconditionally define those, make them optional later -target_compile_definitions(flutter-pi PRIVATE HAS_KMS HAS_EGL HAS_GBM HAS_FBDEV) +target_compile_definitions(flutterpi_module PRIVATE HAS_KMS HAS_EGL HAS_GBM HAS_FBDEV) if(NOT FILESYSTEM_LAYOUT IN_LIST FILESYSTEM_LAYOUTS) message(FATAL_ERROR "FILESYSTEM_LAYOUT must be one of ${FILESYSTEM_LAYOUTS}") @@ -197,32 +199,32 @@ endif() message(STATUS "Filesystem Layout ...... ${FILESYSTEM_LAYOUT}") if(FILESYSTEM_LAYOUT STREQUAL default) - target_compile_definitions(flutter-pi PRIVATE "FILESYSTEM_LAYOUT_DEFAULT") + target_compile_definitions(flutterpi_module PRIVATE "FILESYSTEM_LAYOUT_DEFAULT") elseif(FILESYSTEM_LAYOUT STREQUAL meta-flutter) - target_compile_definitions(flutter-pi PRIVATE "FILESYSTEM_LAYOUT_METAFLUTTER") + target_compile_definitions(flutterpi_module PRIVATE "FILESYSTEM_LAYOUT_METAFLUTTER") endif() # TODO: We actually don't need the compile definitions anymore, except for # text input and raw keyboard plugin (because those have special treatment # in flutter-pi.c) if (BUILD_TEXT_INPUT_PLUGIN) - target_sources(flutter-pi PRIVATE src/plugins/text_input.c) - target_compile_definitions(flutter-pi PRIVATE "BUILD_TEXT_INPUT_PLUGIN") + target_sources(flutterpi_module PRIVATE src/plugins/text_input.c) + target_compile_definitions(flutterpi_module PRIVATE "BUILD_TEXT_INPUT_PLUGIN") endif() if (BUILD_RAW_KEYBOARD_PLUGIN) - target_sources(flutter-pi PRIVATE src/plugins/raw_keyboard.c) - target_compile_definitions(flutter-pi PRIVATE "BUILD_RAW_KEYBOARD_PLUGIN") + target_sources(flutterpi_module PRIVATE src/plugins/raw_keyboard.c) + target_compile_definitions(flutterpi_module PRIVATE "BUILD_RAW_KEYBOARD_PLUGIN") endif() if (BUILD_TEST_PLUGIN) - target_sources(flutter-pi PRIVATE src/plugins/testplugin.c) - target_compile_definitions(flutter-pi PRIVATE "BUILD_TEST_PLUGIN") + target_sources(flutterpi_module PRIVATE src/plugins/testplugin.c) + target_compile_definitions(flutterpi_module PRIVATE "BUILD_TEST_PLUGIN") endif() if (BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN) - target_sources(flutter-pi PRIVATE src/plugins/omxplayer_video_player.c) - target_compile_definitions(flutter-pi PRIVATE "BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN") + target_sources(flutterpi_module PRIVATE src/plugins/omxplayer_video_player.c) + target_compile_definitions(flutterpi_module PRIVATE "BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN") endif() if (OMXPLAYER_SUPPORTS_RUNTIME_ROTATION) - target_compile_definitions(flutter-pi PRIVATE "OMXPLAYER_SUPPORTS_RUNTIME_ROTATION") + target_compile_definitions(flutterpi_module PRIVATE "OMXPLAYER_SUPPORTS_RUNTIME_ROTATION") endif() if (BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) if (TRY_BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) @@ -240,13 +242,13 @@ if (BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) endif() if (LIBGSTREAMER_FOUND AND LIBGSTREAMER_PLUGINS_BASE_FOUND AND LIBGSTREAMER_APP_FOUND AND LIBGSTREAMER_ALLOCATORS_FOUND AND LIBGSTREAMER_VIDEO_FOUND) - target_sources(flutter-pi PRIVATE + target_sources(flutterpi_module PRIVATE src/plugins/gstreamer_video_player/plugin.c src/plugins/gstreamer_video_player/player.c src/plugins/gstreamer_video_player/frame.c ) - target_compile_definitions(flutter-pi PRIVATE "BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN") - target_link_libraries(flutter-pi + target_compile_definitions(flutterpi_module PRIVATE "BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN") + target_link_libraries(flutterpi_module ${LIBGSTREAMER_LDFLAGS} ${LIBGSTREAMER_PLUGINS_BASE_LDFLAGS} ${LIBGSTREAMER_APP_LDFLAGS} @@ -254,7 +256,7 @@ if (BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) ${LIBGSTREAMER_VIDEO_LDFLAGS} ${LIBGSTREAMER_AUDIO_LIBRARY_DIRS} ) - target_include_directories(flutter-pi PRIVATE + target_include_directories(flutterpi_module PRIVATE ${LIBGSTREAMER_INCLUDE_DIRS} ${LIBGSTREAMER_PLUGINS_BASE_INCLUDE_DIRS} ${LIBGSTREAMER_APP_INCLUDE_DIRS} @@ -262,7 +264,7 @@ if (BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) ${LIBGSTREAMER_VIDEO_INCLUDE_DIRS} ${LIBGSTREAMER_AUDIO_INCLUDE_DIRS} ) - target_compile_options(flutter-pi PRIVATE + target_compile_options(flutterpi_module PRIVATE ${LIBGSTREAMER_CFLAGS} ${LIBGSTREAMER_PLUGINS_BASE_CFLAGS} ${LIBGSTREAMER_APP_CFLAGS} @@ -287,22 +289,22 @@ if (BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN) endif() if (LIBGSTREAMER_FOUND AND LIBGSTREAMER_APP_FOUND AND LIBGSTREAMER_AUDIO_FOUND) - target_sources(flutter-pi PRIVATE + target_sources(flutterpi_module PRIVATE src/plugins/audioplayers/plugin.c src/plugins/audioplayers/player.c ) - target_compile_definitions(flutter-pi PRIVATE "BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN") - target_link_libraries(flutter-pi + target_compile_definitions(flutterpi_module PRIVATE "BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN") + target_link_libraries(flutterpi_module ${LIBGSTREAMER_LDFLAGS} ${LIBGSTREAMER_APP_LDFLAGS} ${LIBGSTREAMER_AUDIO_LIBRARY_DIRS} ) - target_include_directories(flutter-pi PRIVATE + target_include_directories(flutterpi_module PRIVATE ${LIBGSTREAMER_INCLUDE_DIRS} ${LIBGSTREAMER_APP_INCLUDE_DIRS} ${LIBGSTREAMER_AUDIO_INCLUDE_DIRS} ) - target_compile_options(flutter-pi PRIVATE + target_compile_options(flutterpi_module PRIVATE ${LIBGSTREAMER_CFLAGS} ${LIBGSTREAMER_APP_CFLAGS} ${LIBGSTREAMER_AUDIO_CFLAGS} @@ -314,13 +316,13 @@ endif() # Needed so dart VM can actually resolve symbols in the same # executable. -target_link_options(flutter-pi PRIVATE +target_link_options(flutterpi_module PRIVATE -rdynamic ) if (ENABLE_TSAN) - target_link_options(flutter-pi PRIVATE -fsanitize=thread) - target_compile_options(flutter-pi PRIVATE -fsanitize=thread) + target_link_options(flutterpi_module PRIVATE -fsanitize=thread) + target_compile_options(flutterpi_module PRIVATE -fsanitize=thread) endif() if (ENABLE_ASAN) # when we use asan, we need to force linking against the C++ stdlib. @@ -328,21 +330,36 @@ if (ENABLE_ASAN) # and something in the dynamically loaded library triggers asan, it'll throw an error because it hasn't # intercepted stdc++ yet. # Also disable --as-needed so we _actually_ link against c++, even though we don't use any symbols from it. - target_link_libraries(flutter-pi stdc++) - target_link_options(flutter-pi PRIVATE -fsanitize=address -fno-omit-frame-pointer -Wl,--no-as-needed) - target_compile_options(flutter-pi PRIVATE -fsanitize=address) + target_link_libraries(flutterpi_module stdc++) + target_link_options(flutterpi_module PRIVATE -fsanitize=address -fno-omit-frame-pointer -Wl,--no-as-needed) + target_compile_options(flutterpi_module PRIVATE -fsanitize=address) check_c_compiler_flag(-static-libasan HAS_STATIC_LIBASAN) if (HAS_STATIC_LIBASAN) - target_link_options(flutter-pi PRIVATE -static-libasan) + target_link_options(flutterpi_module PRIVATE -static-libasan) endif() endif() if (ENABLE_UBSAN) - target_link_options(flutter-pi PRIVATE -fsanitize=undefined) - target_compile_options(flutter-pi PRIVATE -fsanitize=undefined) + target_link_options(flutterpi_module PRIVATE -fsanitize=undefined) + target_compile_options(flutterpi_module PRIVATE -fsanitize=undefined) endif() if (ENABLE_MTRACE) - target_compile_definitions(flutter-pi PRIVATE "ENABLE_MTRACE") + target_compile_definitions(flutterpi_module PRIVATE "ENABLE_MTRACE") endif() +add_executable( + flutter-pi + src/main.c +) +target_link_libraries( + flutter-pi + flutterpi_module +) install(TARGETS flutter-pi RUNTIME DESTINATION bin) + +if(ENABLE_TESTS) + include(CTest) + + add_subdirectory(third_party) + add_subdirectory(test) +endif() diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 8d3b4d68..5d47cc13 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -2510,8 +2510,7 @@ void deinit() { return; } - -int main(int argc, char **argv) { +int flutterpi_app_main(int argc, char **argv) { int ok; #ifdef ENABLE_MTRACE diff --git a/src/main.c b/src/main.c new file mode 100644 index 00000000..b339272a --- /dev/null +++ b/src/main.c @@ -0,0 +1,5 @@ +int flutterpi_app_main(int argc, char **argv); + +int main(int argc, char **argv) { + return flutterpi_app_main(argc, argv); +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..d564b103 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(testsuite_app + platformchannel_test.c +) + +target_link_libraries(testsuite_app + flutterpi_module + Unity +) + +add_test(testsuite_test testsuite_app) \ No newline at end of file diff --git a/test/platformchannel_test.c b/test/platformchannel_test.c new file mode 100644 index 00000000..951483e1 --- /dev/null +++ b/test/platformchannel_test.c @@ -0,0 +1,321 @@ +#define _GNU_SOURCE +#include +#include +#include + +#include + +#define RAW_STD_BUF(...) (const struct raw_std_value*) ((const uint8_t[]) {__VA_ARGS__}) +#define AS_RAW_STD_VALUE(_value) ((const struct raw_std_value*) (_value)) + +// required by Unity. +void setUp() {} + +void tearDown() {} + +void test_raw_std_value_is_null() { + TEST_ASSERT_TRUE(raw_std_value_is_null(RAW_STD_BUF(kStdNull))); + TEST_ASSERT_FALSE(raw_std_value_is_null(RAW_STD_BUF(kStdTrue))); +} + +void test_raw_std_value_is_true() { + TEST_ASSERT_TRUE(raw_std_value_is_true(RAW_STD_BUF(kStdTrue))); + TEST_ASSERT_FALSE(raw_std_value_is_true(RAW_STD_BUF(kStdFalse))); +} + +void test_raw_std_value_is_false() { + TEST_ASSERT_TRUE(raw_std_value_is_false(RAW_STD_BUF(kStdFalse))); + TEST_ASSERT_FALSE(raw_std_value_is_false(RAW_STD_BUF(kStdTrue))); +} + +void test_raw_std_value_is_int32() { + TEST_ASSERT_TRUE(raw_std_value_is_int32(RAW_STD_BUF(kStdInt32))); + TEST_ASSERT_FALSE(raw_std_value_is_int32(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_value_as_int32() { + uint8_t buffer[5] = { + kStdInt32, + 0, 0, 0, 0 + }; + + + TEST_ASSERT_EQUAL_INT32(0, raw_std_value_as_int32(AS_RAW_STD_VALUE(buffer))); + + int value = -2003205; + memcpy(buffer + 1, &value, sizeof(int)); + + TEST_ASSERT_EQUAL_INT32(-2003205, raw_std_value_as_int32(AS_RAW_STD_VALUE(buffer))); +} + +void test_raw_std_value_is_int64() { + TEST_ASSERT_TRUE(raw_std_value_is_int64(RAW_STD_BUF(kStdInt64))); + TEST_ASSERT_FALSE(raw_std_value_is_int64(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_value_as_int64() { + uint8_t buffer[9] = { + kStdInt64, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + + + TEST_ASSERT_EQUAL_INT64(0, raw_std_value_as_int64(AS_RAW_STD_VALUE(buffer))); + + int64_t value = -7998090352538419200; + memcpy(buffer + 1, &value, sizeof(value)); + + TEST_ASSERT_EQUAL_INT64(-7998090352538419200, raw_std_value_as_int64(AS_RAW_STD_VALUE(buffer))); +} + +void test_raw_std_value_is_float64() { + TEST_ASSERT_TRUE(raw_std_value_is_float64(RAW_STD_BUF(kStdFloat64))); + TEST_ASSERT_FALSE(raw_std_value_is_float64(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_value_as_float64() { + uint8_t buffer[] = { + kStdFloat64, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + + double value = M_PI; + memcpy(buffer + 8, &value, sizeof(value)); + + TEST_ASSERT_EQUAL_DOUBLE(M_PI, raw_std_value_as_float64(AS_RAW_STD_VALUE(buffer))); + + value = INFINITY; + memcpy(buffer + 8, &value, sizeof(value)); + + TEST_ASSERT_EQUAL_DOUBLE(INFINITY, raw_std_value_as_float64(AS_RAW_STD_VALUE(buffer))); +} + +void test_raw_std_value_is_string() { + TEST_ASSERT_TRUE(raw_std_value_is_string(RAW_STD_BUF(kStdString))); + TEST_ASSERT_FALSE(raw_std_value_is_string(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_string_dup() { + const char *str = "The quick brown fox jumps over the lazy dog."; + + uint8_t buffer[1 + 1 + 45] = { + kStdString, 45, 0 + }; + + memcpy(buffer + 2, str, strlen(str)); + + char *str_duped = raw_std_string_dup(AS_RAW_STD_VALUE(buffer)); + TEST_ASSERT_NOT_NULL(str_duped); + TEST_ASSERT_EQUAL_STRING(str, str_duped); + + free(str_duped); + + buffer[1] = 0; + str_duped = raw_std_string_dup(AS_RAW_STD_VALUE(buffer)); + TEST_ASSERT_NOT_NULL(str_duped); + TEST_ASSERT_EQUAL_STRING("", str_duped); + + free(str_duped); +} + +void test_raw_std_string_equals() { + const char *str = "The quick brown fox jumps over the lazy dog."; + + uint8_t buffer[1 + 1 + 45] = { + kStdString, 45, 0 + }; + + memcpy(buffer + 2, str, strlen(str)); + + TEST_ASSERT_TRUE(raw_std_string_equals(AS_RAW_STD_VALUE(buffer), "The quick brown fox jumps over the lazy dog.")); + TEST_ASSERT_FALSE(raw_std_string_equals(AS_RAW_STD_VALUE(buffer), "The quick brown fox jumps over the lazy dog")); + + buffer[1] = 0; + TEST_ASSERT_TRUE(raw_std_string_equals(AS_RAW_STD_VALUE(buffer), "")); + TEST_ASSERT_FALSE(raw_std_string_equals(AS_RAW_STD_VALUE(buffer), "anything")); +} + +void test_raw_std_value_is_uint8array() { + +} + +void test_raw_std_value_as_uint8array() { + +} + +void test_raw_std_value_is_int32array() { + +} + +void test_raw_std_value_as_int32array() { + +} + +void test_raw_std_value_is_int64array() { + +} + +void test_raw_std_value_as_int64array() { + +} + +void test_raw_std_value_is_float64array() { + +} + +void test_raw_std_value_as_float64array() { + +} + +void test_raw_std_value_is_list() { + +} + +void test_raw_std_list_get_size() { + +} + +void test_raw_std_value_is_map() { + +} + +void test_raw_std_map_get_size() { + +} + +void test_raw_std_value_is_float32array() { + +} + +void test_raw_std_value_as_float32array() { + +} + +void test_raw_std_value_equals() { + +} + +void test_raw_std_value_is_bool() { + +} + +void test_raw_std_value_as_bool() { + +} + +void test_raw_std_value_is_int() { + +} + +void test_raw_std_value_as_int() { + +} + +void test_raw_std_value_get_size() { + +} + +void test_raw_std_value_after() { + +} + +void test_raw_std_list_get_first_element() { + +} + +void test_raw_std_list_get_nth_element() { + +} + +void test_raw_std_map_get_first_key() { + +} + +void test_raw_std_map_find() { + +} + +void test_raw_std_map_find_str() { + +} + +void test_raw_std_value_check() { + +} + +void test_raw_std_method_call_check() { + +} + +void test_raw_std_method_call_response_check() { + +} + +void test_raw_std_event_check() { + +} + +void test_raw_std_method_call_get_method() { + +} + +void test_raw_std_method_call_get_method_dup() { + +} + +void test_raw_std_method_call_get_arg() { + +} + +int main(void) { + UNITY_BEGIN(); + + RUN_TEST(test_raw_std_value_is_null); + RUN_TEST(test_raw_std_value_is_true); + RUN_TEST(test_raw_std_value_is_false); + RUN_TEST(test_raw_std_value_is_int32); + RUN_TEST(test_raw_std_value_as_int32); + RUN_TEST(test_raw_std_value_is_int64); + RUN_TEST(test_raw_std_value_as_int64); + RUN_TEST(test_raw_std_value_is_float64); + RUN_TEST(test_raw_std_value_as_float64); + RUN_TEST(test_raw_std_value_is_string); + RUN_TEST(test_raw_std_string_dup); + RUN_TEST(test_raw_std_string_equals); + RUN_TEST(test_raw_std_value_is_uint8array); + RUN_TEST(test_raw_std_value_as_uint8array); + RUN_TEST(test_raw_std_value_is_int32array); + RUN_TEST(test_raw_std_value_as_int32array); + RUN_TEST(test_raw_std_value_is_int64array); + RUN_TEST(test_raw_std_value_as_int64array); + RUN_TEST(test_raw_std_value_is_float64array); + RUN_TEST(test_raw_std_value_as_float64array); + RUN_TEST(test_raw_std_value_is_list); + RUN_TEST(test_raw_std_list_get_size); + RUN_TEST(test_raw_std_value_is_map); + RUN_TEST(test_raw_std_map_get_size); + RUN_TEST(test_raw_std_value_is_float32array); + RUN_TEST(test_raw_std_value_as_float32array); + RUN_TEST(test_raw_std_value_equals); + RUN_TEST(test_raw_std_value_is_bool); + RUN_TEST(test_raw_std_value_as_bool); + RUN_TEST(test_raw_std_value_is_int); + RUN_TEST(test_raw_std_value_as_int); + RUN_TEST(test_raw_std_value_get_size); + RUN_TEST(test_raw_std_value_after); + RUN_TEST(test_raw_std_list_get_first_element); + RUN_TEST(test_raw_std_list_get_nth_element); + RUN_TEST(test_raw_std_map_get_first_key); + RUN_TEST(test_raw_std_map_find); + RUN_TEST(test_raw_std_map_find_str); + RUN_TEST(test_raw_std_value_check); + RUN_TEST(test_raw_std_method_call_check); + RUN_TEST(test_raw_std_method_call_response_check); + RUN_TEST(test_raw_std_event_check); + RUN_TEST(test_raw_std_method_call_get_method); + RUN_TEST(test_raw_std_method_call_get_method_dup); + RUN_TEST(test_raw_std_method_call_get_arg); + + return UNITY_END(); +} \ No newline at end of file diff --git a/test/texture_registry.c b/test/texture_registry.c deleted file mode 100644 index e69de29b..00000000 diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt new file mode 100644 index 00000000..66c03cd9 --- /dev/null +++ b/third_party/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library(Unity STATIC + Unity/src/unity.c +) + +target_include_directories(Unity PUBLIC + Unity/src +) + +target_compile_definitions(Unity PUBLIC + UNITY_SUPPORT_64 + UNITY_INCLUDE_DOUBLE +) diff --git a/third_party/Unity b/third_party/Unity new file mode 160000 index 00000000..5a36b197 --- /dev/null +++ b/third_party/Unity @@ -0,0 +1 @@ +Subproject commit 5a36b197fb34c0a77ac891c355596cb5c25aaf5b From e7a8afe15b5416a97c47bc7750f266ff3e0765a1 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 24 Jan 2023 17:18:51 +0000 Subject: [PATCH 06/55] rename platform channel test --- test/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d564b103..4aa108aa 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,10 +1,10 @@ -add_executable(testsuite_app +add_executable(platformchannel_test platformchannel_test.c ) -target_link_libraries(testsuite_app +target_link_libraries(platformchannel_test flutterpi_module Unity ) -add_test(testsuite_test testsuite_app) \ No newline at end of file +add_test(platformchannel_test platformchannel_test) \ No newline at end of file From e2b97210cb70a629dae8282d0da1203f661e54bb Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 24 Jan 2023 17:19:54 +0000 Subject: [PATCH 07/55] install git in CI jobs --- .github/workflows/cmake.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index d4b2a51e..8e1e2028 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -29,7 +29,7 @@ jobs: - name: Install dependencies run: | sudo apt-get install -y \ - cmake libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev \ + git cmake libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev \ ttf-mscorefonts-installer fontconfig libsystemd-dev libinput-dev libudev-dev \ libxkbcommon-dev ninja-build libgstreamer-plugins-base1.0-dev @@ -71,7 +71,7 @@ jobs: - name: Install dependencies run: | apt-get update && apt-get install -y \ - cmake libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev \ + git cmake libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev \ fonts-liberation fontconfig libsystemd-dev libinput-dev libudev-dev \ libxkbcommon-dev ninja-build libgstreamer-plugins-base1.0-dev From 0757a23a3c7fea38b0c760ae10c7ae701a0f53c8 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 24 Jan 2023 17:21:40 +0000 Subject: [PATCH 08/55] install deps before checking out repo in CI jobs --- .github/workflows/cmake.yml | 68 ++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 8e1e2028..b04134e3 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -22,39 +22,39 @@ jobs: runs-on: [linux, bullseye] steps: - - uses: actions/checkout@v3 - with: - submodules: 'recursive' - - - name: Install dependencies - run: | - sudo apt-get install -y \ - git cmake libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev \ - ttf-mscorefonts-installer fontconfig libsystemd-dev libinput-dev libudev-dev \ - libxkbcommon-dev ninja-build libgstreamer-plugins-base1.0-dev + - name: Install dependencies + run: | + sudo apt-get install -y \ + git cmake libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev \ + ttf-mscorefonts-installer fontconfig libsystemd-dev libinput-dev libudev-dev \ + libxkbcommon-dev ninja-build libgstreamer-plugins-base1.0-dev + + - uses: actions/checkout@v3 + with: + submodules: 'recursive' - - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: | - cmake \ - -B ${{github.workspace}}/build \ - -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN=On \ - -DBUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN=On \ - -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On \ - -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ - -DENABLE_TESTS=On \ - -GNinja + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: | + cmake \ + -B ${{github.workspace}}/build \ + -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN=On \ + -DBUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN=On \ + -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On \ + -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ + -DENABLE_TESTS=On \ + -GNinja - - name: Build - # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - name: Test - working-directory: ${{github.workspace}}/build - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C ${{env.BUILD_TYPE}} + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} build-buster: name: build (debian buster, x64) @@ -64,10 +64,6 @@ jobs: env: DEBIAN_FRONTEND: noninteractive steps: - - uses: actions/checkout@v3 - with: - submodules: 'recursive' - - name: Install dependencies run: | apt-get update && apt-get install -y \ @@ -75,6 +71,10 @@ jobs: fonts-liberation fontconfig libsystemd-dev libinput-dev libudev-dev \ libxkbcommon-dev ninja-build libgstreamer-plugins-base1.0-dev + - uses: actions/checkout@v3 + with: + submodules: 'recursive' + - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From ea9019937ef923a93dc01b12a107ea5bd24adfbc Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 25 Jan 2023 16:05:38 +0000 Subject: [PATCH 09/55] use bash as shell for buster CI job --- .github/workflows/cmake.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index b04134e3..6d64f872 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -48,10 +48,10 @@ jobs: - name: Build # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + run: cmake --build ${{ github.workspace }}/build --config ${{ env.BUILD_TYPE}} - name: Test - working-directory: ${{github.workspace}}/build + working-directory: ${{ github.workspace }}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} @@ -63,6 +63,9 @@ jobs: image: debian:buster env: DEBIAN_FRONTEND: noninteractive + defaults: + run: + shell: bash steps: - name: Install dependencies run: | @@ -93,7 +96,7 @@ jobs: run: cmake --build ${{ github.workspace }}/build --config ${{ env.BUILD_TYPE }} - name: Test - working-directory: ${{github.workspace}}/build + working-directory: ${{ github.workspace }}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C ${{env.BUILD_TYPE}} + run: ctest -C ${{ env.BUILD_TYPE }} From 931ae96252fe4efa5b84309fb131876c156e8fcb Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 25 Jan 2023 16:09:01 +0000 Subject: [PATCH 10/55] use cd for test command instead of `step.working-directory` option --- .github/workflows/cmake.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 6d64f872..aeb9c32f 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -83,7 +83,7 @@ jobs: # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: | cmake \ - -B ${{ github.workspace }}/build \ + -B ./build \ -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN=On \ -DBUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN=On \ -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On \ @@ -93,10 +93,10 @@ jobs: - name: Build # Build your program with the given configuration - run: cmake --build ${{ github.workspace }}/build --config ${{ env.BUILD_TYPE }} + run: cmake --build ./build --config ${{ env.BUILD_TYPE }} - name: Test - working-directory: ${{ github.workspace }}/build + working-directory: build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{ env.BUILD_TYPE }} From ec92df507c79d01ead5f37b4730111a5175d4afb Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 25 Jan 2023 16:18:02 +0000 Subject: [PATCH 11/55] verbose ctest output --- .github/workflows/cmake.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index aeb9c32f..1dbba783 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -54,7 +54,7 @@ jobs: working-directory: ${{ github.workspace }}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C ${{env.BUILD_TYPE}} + run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure build-buster: name: build (debian buster, x64) @@ -99,4 +99,4 @@ jobs: working-directory: build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C ${{ env.BUILD_TYPE }} + run: ctest -C ${{ env.BUILD_TYPE }} --output-on-failure --debug -VV From 488dfa60fe162aba7f2843256725e2b9b8bf94af Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 25 Jan 2023 16:21:35 +0000 Subject: [PATCH 12/55] less verbose ctest output --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 1dbba783..0dd60941 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -99,4 +99,4 @@ jobs: working-directory: build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C ${{ env.BUILD_TYPE }} --output-on-failure --debug -VV + run: ctest -C ${{ env.BUILD_TYPE }} --output-on-failure From ab7b7ec7f34dee5610bddcdeb2d9175c4dfdc3fe Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 25 Jan 2023 16:26:29 +0000 Subject: [PATCH 13/55] use VLA for raw_std_string_equals test --- test/platformchannel_test.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/platformchannel_test.c b/test/platformchannel_test.c index 951483e1..cf4db546 100644 --- a/test/platformchannel_test.c +++ b/test/platformchannel_test.c @@ -122,9 +122,14 @@ void test_raw_std_string_dup() { void test_raw_std_string_equals() { const char *str = "The quick brown fox jumps over the lazy dog."; - uint8_t buffer[1 + 1 + 45] = { - kStdString, 45, 0 - }; + uint8_t buffer[1 + 1 + strlen(str)]; + + buffer[0] = kStdString; + buffer[1] = strlen(str); + + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); memcpy(buffer + 2, str, strlen(str)); From 90ed09c914b9b08e45fbad41a61feabbc3c67484 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 25 Jan 2023 17:50:03 +0000 Subject: [PATCH 14/55] add more platform channel tests --- test/platformchannel_test.c | 532 +++++++++++++++++++++++++++++++++++- 1 file changed, 519 insertions(+), 13 deletions(-) diff --git a/test/platformchannel_test.c b/test/platformchannel_test.c index cf4db546..7da50b65 100644 --- a/test/platformchannel_test.c +++ b/test/platformchannel_test.c @@ -59,7 +59,6 @@ void test_raw_std_value_as_int64() { 0, 0, 0, 0, 0, 0, 0, 0 }; - TEST_ASSERT_EQUAL_INT64(0, raw_std_value_as_int64(AS_RAW_STD_VALUE(buffer))); int64_t value = -7998090352538419200; @@ -142,83 +141,590 @@ void test_raw_std_string_equals() { } void test_raw_std_value_is_uint8array() { - + TEST_ASSERT_TRUE(raw_std_value_is_uint8array(RAW_STD_BUF(kStdUInt8Array))); + TEST_ASSERT_FALSE(raw_std_value_is_uint8array(RAW_STD_BUF(kStdNull))); } void test_raw_std_value_as_uint8array() { + uint8_t buffer[] = { + kStdUInt8Array, + 4, + 1, 2, 3, 4 + }; + + uint8_t expected[] = { + 1, 2, 3, 4 + }; + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, raw_std_value_as_uint8array(AS_RAW_STD_VALUE(buffer)), 4); + + buffer[2] = 0; + expected[0] = 0; + + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, raw_std_value_as_uint8array(AS_RAW_STD_VALUE(buffer)), 4); } void test_raw_std_value_is_int32array() { - + TEST_ASSERT_TRUE(raw_std_value_is_int32array(RAW_STD_BUF(kStdInt32Array))); + TEST_ASSERT_FALSE(raw_std_value_is_int32array(RAW_STD_BUF(kStdNull))); } void test_raw_std_value_as_int32array() { + uint8_t buffer[] = { + // type + kStdInt32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 int32_t's + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + + int32_t expected[] = { + INT_MIN, + 0x12345678, + }; + + memcpy(buffer + 4, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_INT32_ARRAY(expected, raw_std_value_as_int32array(AS_RAW_STD_VALUE(buffer)), 2); + + expected[0] = 0; + memcpy(buffer + 4, expected, sizeof(expected)); + TEST_ASSERT_EQUAL_INT32_ARRAY(expected, raw_std_value_as_int32array(AS_RAW_STD_VALUE(buffer)), 2); } void test_raw_std_value_is_int64array() { - + TEST_ASSERT_TRUE(raw_std_value_is_int64array(RAW_STD_BUF(kStdInt64Array))); + TEST_ASSERT_FALSE(raw_std_value_is_int64array(RAW_STD_BUF(kStdNull))); } void test_raw_std_value_as_int64array() { + uint8_t buffer[] = { + // type + kStdInt64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 int64_t's + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + int64_t expected[] = { + INT64_MIN, + 0x123456789ABCDEF, + }; + memcpy(buffer + 8, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_INT64_ARRAY(expected, raw_std_value_as_int64array(AS_RAW_STD_VALUE(buffer)), 2); + + expected[0] = 0; + memcpy(buffer + 8, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_INT64_ARRAY(expected, raw_std_value_as_int64array(AS_RAW_STD_VALUE(buffer)), 2); } void test_raw_std_value_is_float64array() { - + TEST_ASSERT_TRUE(raw_std_value_is_float64array(RAW_STD_BUF(kStdFloat64Array))); + TEST_ASSERT_FALSE(raw_std_value_is_float64array(RAW_STD_BUF(kStdNull))); } void test_raw_std_value_as_float64array() { + uint8_t buffer[] = { + // type + kStdFloat64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 doubles + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + double expected[] = { + M_PI, + INFINITY, + }; + + memcpy(buffer + 8, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, raw_std_value_as_float64array(AS_RAW_STD_VALUE(buffer)), 2); + + expected[0] = 0.0; + memcpy(buffer + 8, expected, sizeof(expected)); + TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, raw_std_value_as_float64array(AS_RAW_STD_VALUE(buffer)), 2); } void test_raw_std_value_is_list() { - + TEST_ASSERT_TRUE(raw_std_value_is_list(RAW_STD_BUF(kStdList))); + TEST_ASSERT_FALSE(raw_std_value_is_list(RAW_STD_BUF(kStdNull))); } void test_raw_std_list_get_size() { + uint8_t buffer[] = { + // type + kStdList, + // size + 2, + // space for more size bytes + 0, 0, 0, 0, + }; + + TEST_ASSERT_EQUAL_size_t(2, raw_std_list_get_size(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + + TEST_ASSERT_EQUAL_size_t(0, raw_std_list_get_size(AS_RAW_STD_VALUE(buffer))); + + uint32_t size = 0xDEAD; + + buffer[1] = 254; + memcpy(buffer + 2, &size, 2); + + TEST_ASSERT_EQUAL_size_t(0xDEAD, raw_std_list_get_size(AS_RAW_STD_VALUE(buffer))); + size = 0xDEADBEEF; + buffer[1] = 255; + memcpy(buffer + 2, &size, 4); + + TEST_ASSERT_EQUAL_size_t(0xDEADBEEF, raw_std_list_get_size(AS_RAW_STD_VALUE(buffer))); } void test_raw_std_value_is_map() { - + TEST_ASSERT_TRUE(raw_std_value_is_map(RAW_STD_BUF(kStdMap))); + TEST_ASSERT_FALSE(raw_std_value_is_map(RAW_STD_BUF(kStdNull))); } void test_raw_std_map_get_size() { + uint8_t buffer[] = { + // type + kStdMap, + // size + 2, + // space for more size bytes + 0, 0, 0, 0, + }; + + TEST_ASSERT_EQUAL_size_t(2, raw_std_map_get_size(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + + TEST_ASSERT_EQUAL_size_t(0, raw_std_map_get_size(AS_RAW_STD_VALUE(buffer))); + + uint32_t size = 0xDEAD; + + buffer[1] = 254; + memcpy(buffer + 2, &size, 2); + + TEST_ASSERT_EQUAL_size_t(0xDEAD, raw_std_list_get_size(AS_RAW_STD_VALUE(buffer))); + size = 0xDEADBEEF; + buffer[1] = 255; + memcpy(buffer + 2, &size, 4); + + TEST_ASSERT_EQUAL_size_t(0xDEADBEEF, raw_std_map_get_size(AS_RAW_STD_VALUE(buffer))); } void test_raw_std_value_is_float32array() { - + TEST_ASSERT_TRUE(raw_std_value_is_float32array(RAW_STD_BUF(kStdFloat32Array))); + TEST_ASSERT_FALSE(raw_std_value_is_float32array(RAW_STD_BUF(kStdNull))); } void test_raw_std_value_as_float32array() { + uint8_t buffer[] = { + // type + kStdFloat32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 floats + 0, 0, 0, 0, + 0, 0, 0, 0, + }; + + float expected[] = { + M_PI, + INFINITY, + }; + memcpy(buffer + 4, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, raw_std_value_as_float32array(AS_RAW_STD_VALUE(buffer)), 2); + + expected[0] = 0.0; + memcpy(buffer + 4, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, raw_std_value_as_float32array(AS_RAW_STD_VALUE(buffer)), 2); } void test_raw_std_value_equals() { - + TEST_ASSERT_TRUE(raw_std_value_equals(RAW_STD_BUF(kStdNull), RAW_STD_BUF(kStdNull))); + TEST_ASSERT_FALSE(raw_std_value_equals(RAW_STD_BUF(kStdNull), RAW_STD_BUF(kStdTrue))); + TEST_ASSERT_FALSE(raw_std_value_equals(RAW_STD_BUF(kStdTrue), RAW_STD_BUF(kStdFalse))); + + // int32 + { + uint8_t lhs[] = { + kStdInt32, + 1, 2, 3, 4, + }; + + uint8_t rhs[] = { + kStdInt32, + 1, 2, 3, 4, + }; + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[4] = 0; + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // int64 + { + uint8_t lhs[] = { + kStdInt64, + 1, 2, 3, 4, 5, 6, 7, 8 + }; + + uint8_t rhs[] = { + kStdInt64, + 1, 2, 3, 4, 5, 6, 7, 8 + }; + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[8] = 0; + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // float64 + { + uint8_t lhs[] = { + // type byte + kStdFloat64, + // 7 alignment bytes + 0, 0, 0, 0, 0, 0, 0, + // bytes for 1 float64 + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + uint8_t rhs[] = { + // type byte + kStdFloat64, + // 7 alignment bytes + 0, 0, 0, 0, 0, 0, 0, + // bytes for 1 float64 + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + double f = M_PI; + + memcpy(lhs + 8, &f, sizeof(f)); + memcpy(rhs + 8, &f, sizeof(f)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + f = NAN; + memcpy(rhs + 8, &f, sizeof(f)); + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // string + { + const char *str = "The quick brown fox jumps over the lazy dog."; + + uint8_t lhs[1 + 1 + strlen(str)]; + lhs[0] = kStdString; + lhs[1] = strlen(str); + + uint8_t rhs[1 + 1 + strlen(str)]; + rhs[0] = kStdString; + rhs[1] = strlen(str); + + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); + + memcpy(lhs + 2, str, strlen(str)); + memcpy(rhs + 2, str, strlen(str)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = strlen(str) - 1; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + const char *str2 = "The quick brown fox jumps over the lazy DOG "; + TEST_ASSERT_EQUAL_size_t(strlen(str), strlen(str2)); + rhs[1] = strlen(str2); + memcpy(rhs + 2, str2, strlen(str2)); + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // uint8array + { + uint8_t lhs[] = { + kStdUInt8Array, + 4, + 1, 2, 3, 4 + }; + + uint8_t rhs[] = { + kStdUInt8Array, + 4, + 1, 2, 3, 4 + }; + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 3; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 4; + rhs[5] = 0; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // int32array + { + uint8_t lhs[] = { + // type + kStdInt32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 int32_t's + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + + uint8_t rhs[] = { + // type + kStdInt32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 int32_t's + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + + int32_t array[] = { + INT_MIN, + 0x12345678, + }; + + memcpy(lhs + 4, array, sizeof(array)); + memcpy(rhs + 4, array, sizeof(array)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 0; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 2; + int32_t array2[] = { + INT_MAX, + 0x12345678, + }; + memcpy(rhs + 4, array2, sizeof(array2)); + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // int64array + { + uint8_t lhs[] = { + // type + kStdInt64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 int64_t's + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + uint8_t rhs[] = { + // type + kStdInt64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 int64_t's + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + int64_t array[] = { + INT64_MIN, + 0x123456789ABCDEF, + }; + + memcpy(lhs + 8, array, sizeof(array)); + memcpy(rhs + 8, array, sizeof(array)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 0; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 2; + int64_t array2[] = { + INT64_MAX, + 0x123456789ABCDEF, + }; + memcpy(rhs + 8, array2, sizeof(array2)); + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // float64array + { + uint8_t lhs[] = { + // type + kStdFloat64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 doubles + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + uint8_t rhs[] = { + // type + kStdFloat64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 doubles + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + double array[] = { + M_PI, + INFINITY, + }; + + memcpy(lhs + 8, array, sizeof(array)); + memcpy(rhs + 8, array, sizeof(array)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 0; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 2; + double array2[] = { + 0.0, + INFINITY, + }; + memcpy(rhs + 8, array2, sizeof(array2)); + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + /// TODO: Test list + /// TODO: Test map + /// TODO: Test float32array } void test_raw_std_value_is_bool() { - + TEST_ASSERT_FALSE(raw_std_value_is_bool(RAW_STD_BUF(kStdNull))); + TEST_ASSERT_TRUE(raw_std_value_is_bool(RAW_STD_BUF(kStdTrue))); + TEST_ASSERT_TRUE(raw_std_value_is_bool(RAW_STD_BUF(kStdFalse))); } void test_raw_std_value_as_bool() { - + TEST_ASSERT_TRUE(raw_std_value_as_bool(RAW_STD_BUF(kStdTrue))); + TEST_ASSERT_FALSE(raw_std_value_as_bool(RAW_STD_BUF(kStdFalse))); } void test_raw_std_value_is_int() { - + TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdNull))); + TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdTrue))); + TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdFalse))); + TEST_ASSERT_TRUE(raw_std_value_is_int(RAW_STD_BUF(kStdInt32))); + TEST_ASSERT_TRUE(raw_std_value_is_int(RAW_STD_BUF(kStdInt64))); + TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdFloat64))); } void test_raw_std_value_as_int() { + uint8_t buffer[9] = { + kStdInt32, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + int64_t int64 = INT64_MAX; + buffer[0] = kStdInt64; + memcpy(buffer + 1, &int64, sizeof(int64)); + + TEST_ASSERT_EQUAL_INT64(INT64_MAX, raw_std_value_as_int(AS_RAW_STD_VALUE(buffer))); + + buffer[0] = kStdInt32; + TEST_ASSERT_NOT_EQUAL_INT64(INT64_MAX, raw_std_value_as_int(AS_RAW_STD_VALUE(buffer))); + + int32_t int32 = INT32_MIN; + buffer[0] = kStdInt32; + memcpy(buffer + 1, &int32, sizeof(int32)); + + TEST_ASSERT_EQUAL_INT64(INT32_MIN, raw_std_value_as_int(AS_RAW_STD_VALUE(buffer))); } void test_raw_std_value_get_size() { + uint8_t buffer[] = { + // type + kStdList, + // size + 2, + // space for more size bytes + 0, 0, 0, 0, + }; + + TEST_ASSERT_EQUAL_size_t(2, raw_std_value_get_size(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + + TEST_ASSERT_EQUAL_size_t(0, raw_std_value_get_size(AS_RAW_STD_VALUE(buffer))); + uint32_t size = 0xDEAD; + + buffer[1] = 254; + memcpy(buffer + 2, &size, 2); + memcpy(buffer + 4, &size, 2); + + TEST_ASSERT_EQUAL_size_t(0xDEAD, raw_std_value_get_size(AS_RAW_STD_VALUE(buffer))); + + size = 0xDEADBEEF; + buffer[1] = 255; + memcpy(buffer + 2, &size, 4); + + TEST_ASSERT_EQUAL_size_t(0xDEADBEEF, raw_std_value_get_size(AS_RAW_STD_VALUE(buffer))); } void test_raw_std_value_after() { @@ -323,4 +829,4 @@ int main(void) { RUN_TEST(test_raw_std_method_call_get_arg); return UNITY_END(); -} \ No newline at end of file +} From 0c2f366edec56f0e8d65ce18fcbc7fc2825ad9ee Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 26 Jan 2023 01:54:56 +0000 Subject: [PATCH 15/55] fix some tests, allow raw_std_value_get_size for float32 arrays --- src/platformchannel.c | 3 ++- test/platformchannel_test.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/platformchannel.c b/src/platformchannel.c index b37f6bb9..974b0c54 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -2134,7 +2134,8 @@ ATTR_PURE size_t raw_std_value_get_size(const struct raw_std_value *value) { raw_std_value_is_float64array(value) || raw_std_value_is_string(value) || raw_std_value_is_list(value) || - raw_std_value_is_map(value) + raw_std_value_is_map(value) || + raw_std_value_is_float32array(value) ); byteptr = (const uint8_t*) value; diff --git a/test/platformchannel_test.c b/test/platformchannel_test.c index 7da50b65..c1ed132f 100644 --- a/test/platformchannel_test.c +++ b/test/platformchannel_test.c @@ -324,7 +324,7 @@ void test_raw_std_map_get_size() { buffer[1] = 254; memcpy(buffer + 2, &size, 2); - TEST_ASSERT_EQUAL_size_t(0xDEAD, raw_std_list_get_size(AS_RAW_STD_VALUE(buffer))); + TEST_ASSERT_EQUAL_size_t(0xDEAD, raw_std_map_get_size(AS_RAW_STD_VALUE(buffer))); size = 0xDEADBEEF; buffer[1] = 255; From 604b644925147a77e0bbfd089237db9d67e46838 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 29 Jan 2023 18:33:32 +0000 Subject: [PATCH 16/55] add more message codec tests - align test buffers to 16-byte boundaries by default - add float32array to `raw_std_value_after` - fix int64 alignment in `raw_std_value_after` - fix for_each macros in platformchannel.h iterating out of bounds - add more platformchannel unittests for `raw_std_value_equals` --- CMakeLists.txt | 6 +- include/platformchannel.h | 6 +- src/platformchannel.c | 6 +- test/platformchannel_test.c | 211 ++++++++++++++++++++++++++++++------ 4 files changed, 187 insertions(+), 42 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c8d831c..5b179d34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,9 +171,9 @@ target_compile_options(flutterpi_module PRIVATE ${LIBINPUT_CFLAGS} ${LIBUDEV_CFLAGS} ${LIBXKBCOMMON_CFLAGS} - $<$:-O0 -Wall -Wextra -Wno-unused-function -Wno-sign-compare -Wno-missing-field-initializers -Werror -ggdb -U_FORTIFY_SOURCE -DDEBUG> - $<$:-O2 -Wall -Wextra -Wno-unused-function -Wno-sign-compare -Wno-missing-field-initializers -ggdb> - $<$:-O2 -Wall -Wextra -Wno-unused-function -Wno-sign-compare -Wno-missing-field-initializers -ggdb> + $<$:-O0 -Wall -Wextra -Wno-unused-function -Wno-sign-compare -ggdb -U_FORTIFY_SOURCE -DDEBUG> + $<$:-O2 -Wall -Wextra -Wno-sign-compare -Wno-missing-field-initializers -ggdb> + $<$:-O2 -Wall -Wextra -Wno-sign-compare -Wno-missing-field-initializers -ggdb> ) # There's no other way to query the libinput version (in code) somehow. diff --git a/include/platformchannel.h b/include/platformchannel.h index c785abdc..d01c7b07 100644 --- a/include/platformchannel.h +++ b/include/platformchannel.h @@ -942,8 +942,8 @@ ATTR_PURE const struct raw_std_value *raw_std_method_call_get_arg(const struct r size_t index = 0; \ index < raw_std_map_get_size(map); \ index++, \ - key = raw_std_value_after(value), \ - value = raw_std_value_after(key) \ + key = (index) < raw_std_map_get_size(map) ? raw_std_value_after(value) : NULL, \ + value = (index) < raw_std_map_get_size(map) ? raw_std_value_after(key) : NULL \ ) #define for_each_entry_in_raw_std_map(key, value, map) \ @@ -959,7 +959,7 @@ ATTR_PURE const struct raw_std_value *raw_std_method_call_get_arg(const struct r size_t index = 0; \ index < raw_std_list_get_size(list); \ index++, \ - element = raw_std_value_after(element) \ + element = (index) < raw_std_list_get_size(list) ? raw_std_value_after(element) : NULL \ ) #define for_each_element_in_raw_std_list(value, list) \ diff --git a/src/platformchannel.c b/src/platformchannel.c index 974b0c54..f94580b6 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -2096,6 +2096,7 @@ ATTR_PURE bool raw_std_value_equals(const struct raw_std_value *a, const struct return true; default: + DEBUG_ASSERT(false); return false; } } @@ -2174,7 +2175,7 @@ ATTR_PURE const struct raw_std_value *raw_std_value_after(const struct raw_std_v case kStdInt32: return get_after_ptr(value, 0, 4); case kStdInt64: - return get_after_ptr(value, 8, 8); + return get_after_ptr(value, 0, 8); case kStdLargeInt: case kStdString: return get_array_after_ptr(value, 0, raw_std_value_get_size(value), 1); @@ -2207,7 +2208,10 @@ ATTR_PURE const struct raw_std_value *raw_std_value_after(const struct raw_std_v } return value; + case kStdFloat32Array: + return get_array_after_ptr(value, 4, raw_std_value_get_size(value), 4); default: + DEBUG_ASSERT(false); return value; } } diff --git a/test/platformchannel_test.c b/test/platformchannel_test.c index c1ed132f..69b6b511 100644 --- a/test/platformchannel_test.c +++ b/test/platformchannel_test.c @@ -2,6 +2,7 @@ #include #include #include +#include #include @@ -34,7 +35,7 @@ void test_raw_std_value_is_int32() { } void test_raw_std_value_as_int32() { - uint8_t buffer[5] = { + alignas(16) uint8_t buffer[5] = { kStdInt32, 0, 0, 0, 0 }; @@ -54,7 +55,7 @@ void test_raw_std_value_is_int64() { } void test_raw_std_value_as_int64() { - uint8_t buffer[9] = { + alignas(16) uint8_t buffer[9] = { kStdInt64, 0, 0, 0, 0, 0, 0, 0, 0 }; @@ -73,7 +74,7 @@ void test_raw_std_value_is_float64() { } void test_raw_std_value_as_float64() { - uint8_t buffer[] = { + alignas(16) uint8_t buffer[] = { kStdFloat64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 @@ -98,7 +99,7 @@ void test_raw_std_value_is_string() { void test_raw_std_string_dup() { const char *str = "The quick brown fox jumps over the lazy dog."; - uint8_t buffer[1 + 1 + 45] = { + alignas(16) uint8_t buffer[1 + 1 + 45] = { kStdString, 45, 0 }; @@ -121,7 +122,7 @@ void test_raw_std_string_dup() { void test_raw_std_string_equals() { const char *str = "The quick brown fox jumps over the lazy dog."; - uint8_t buffer[1 + 1 + strlen(str)]; + alignas(16) uint8_t buffer[1 + 1 + strlen(str)]; buffer[0] = kStdString; buffer[1] = strlen(str); @@ -146,13 +147,13 @@ void test_raw_std_value_is_uint8array() { } void test_raw_std_value_as_uint8array() { - uint8_t buffer[] = { + alignas(16) uint8_t buffer[] = { kStdUInt8Array, 4, 1, 2, 3, 4 }; - uint8_t expected[] = { + alignas(16) uint8_t expected[] = { 1, 2, 3, 4 }; @@ -170,7 +171,7 @@ void test_raw_std_value_is_int32array() { } void test_raw_std_value_as_int32array() { - uint8_t buffer[] = { + alignas(16) uint8_t buffer[] = { // type kStdInt32Array, // size @@ -203,7 +204,7 @@ void test_raw_std_value_is_int64array() { } void test_raw_std_value_as_int64array() { - uint8_t buffer[] = { + alignas(16) uint8_t buffer[] = { // type kStdInt64Array, // size @@ -236,7 +237,7 @@ void test_raw_std_value_is_float64array() { } void test_raw_std_value_as_float64array() { - uint8_t buffer[] = { + alignas(16) uint8_t buffer[] = { // type kStdFloat64Array, // size @@ -269,7 +270,7 @@ void test_raw_std_value_is_list() { } void test_raw_std_list_get_size() { - uint8_t buffer[] = { + alignas(16) uint8_t buffer[] = { // type kStdList, // size @@ -304,7 +305,7 @@ void test_raw_std_value_is_map() { } void test_raw_std_map_get_size() { - uint8_t buffer[] = { + alignas(16) uint8_t buffer[] = { // type kStdMap, // size @@ -339,7 +340,7 @@ void test_raw_std_value_is_float32array() { } void test_raw_std_value_as_float32array() { - uint8_t buffer[] = { + alignas(16) uint8_t buffer[] = { // type kStdFloat32Array, // size @@ -373,12 +374,12 @@ void test_raw_std_value_equals() { // int32 { - uint8_t lhs[] = { + alignas(16) uint8_t lhs[] = { kStdInt32, 1, 2, 3, 4, }; - uint8_t rhs[] = { + alignas(16) uint8_t rhs[] = { kStdInt32, 1, 2, 3, 4, }; @@ -391,12 +392,12 @@ void test_raw_std_value_equals() { // int64 { - uint8_t lhs[] = { + alignas(16) uint8_t lhs[] = { kStdInt64, 1, 2, 3, 4, 5, 6, 7, 8 }; - uint8_t rhs[] = { + alignas(16) uint8_t rhs[] = { kStdInt64, 1, 2, 3, 4, 5, 6, 7, 8 }; @@ -409,7 +410,7 @@ void test_raw_std_value_equals() { // float64 { - uint8_t lhs[] = { + alignas(16) uint8_t lhs[] = { // type byte kStdFloat64, // 7 alignment bytes @@ -418,7 +419,7 @@ void test_raw_std_value_equals() { 0, 0, 0, 0, 0, 0, 0, 0, }; - uint8_t rhs[] = { + alignas(16) uint8_t rhs[] = { // type byte kStdFloat64, // 7 alignment bytes @@ -444,11 +445,11 @@ void test_raw_std_value_equals() { { const char *str = "The quick brown fox jumps over the lazy dog."; - uint8_t lhs[1 + 1 + strlen(str)]; + alignas(16) uint8_t lhs[1 + 1 + strlen(str)]; lhs[0] = kStdString; lhs[1] = strlen(str); - uint8_t rhs[1 + 1 + strlen(str)]; + alignas(16) uint8_t rhs[1 + 1 + strlen(str)]; rhs[0] = kStdString; rhs[1] = strlen(str); @@ -475,13 +476,13 @@ void test_raw_std_value_equals() { // uint8array { - uint8_t lhs[] = { + alignas(16) uint8_t lhs[] = { kStdUInt8Array, 4, 1, 2, 3, 4 }; - uint8_t rhs[] = { + alignas(16) uint8_t rhs[] = { kStdUInt8Array, 4, 1, 2, 3, 4 @@ -501,7 +502,7 @@ void test_raw_std_value_equals() { // int32array { - uint8_t lhs[] = { + alignas(16) uint8_t lhs[] = { // type kStdInt32Array, // size @@ -513,7 +514,7 @@ void test_raw_std_value_equals() { 0, 0, 0, 0 }; - uint8_t rhs[] = { + alignas(16) uint8_t rhs[] = { // type kStdInt32Array, // size @@ -551,7 +552,7 @@ void test_raw_std_value_equals() { // int64array { - uint8_t lhs[] = { + alignas(16) uint8_t lhs[] = { // type kStdInt64Array, // size @@ -563,7 +564,7 @@ void test_raw_std_value_equals() { 0, 0, 0, 0, 0, 0, 0, 0, }; - uint8_t rhs[] = { + alignas(16) uint8_t rhs[] = { // type kStdInt64Array, // size @@ -601,7 +602,7 @@ void test_raw_std_value_equals() { // float64array { - uint8_t lhs[] = { + alignas(16) uint8_t lhs[] = { // type kStdFloat64Array, // size @@ -613,7 +614,7 @@ void test_raw_std_value_equals() { 0, 0, 0, 0, 0, 0, 0, 0, }; - uint8_t rhs[] = { + alignas(16) uint8_t rhs[] = { // type kStdFloat64Array, // size @@ -649,9 +650,149 @@ void test_raw_std_value_equals() { TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); } - /// TODO: Test list - /// TODO: Test map - /// TODO: Test float32array + // list + { + const char *str = "The quick brown fox jumps over the lazy dog."; + + alignas(16) uint8_t lhs[1 + 1 + 1 + 1 + strlen(str) + 1]; + lhs[0] = kStdList; + lhs[1] = 2; + lhs[2] = kStdString; + lhs[3] = strlen(str); + lhs[4 + strlen(str)] = kStdTrue; + + alignas(16) uint8_t rhs[1 + 1 + 1 + 1 + strlen(str) + 1]; + rhs[0] = kStdList; + rhs[1] = 2; + rhs[2] = kStdString; + rhs[3] = strlen(str); + rhs[4 + strlen(str)] = kStdTrue; + + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); + + memcpy(lhs + 4, str, strlen(str)); + memcpy(rhs + 4, str, strlen(str)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 0; + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 2; + rhs[3] = strlen(str) - 1; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[3] = strlen(str); + rhs[3 + strlen(str)] = kStdFalse; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // map + { + const char *str = "The quick brown fox jumps over the lazy dog."; + + alignas(16) uint8_t lhs[] = { + [0] = kStdMap, + [1] = 2, + [2] = kStdNull, + [3] = kStdInt64, + [4] = 0, 0, 0, 0, 0, 0, 0, 0, + [12] = kStdFloat32Array, + [13] = 2, + [16] = 0, 0, 0, 0, 0, 0, 0, 0, + [24] = kStdTrue, + }; + + alignas(16) uint8_t rhs[] = { + [0] = kStdMap, + [1] = 2, + [2] = kStdFloat32Array, + [3] = 2, + [4] = 0, 0, 0, 0, 0, 0, 0, 0, + [12] = kStdTrue, + [13] = kStdNull, + [14] = kStdInt64, + [15] = 0, 0, 0, 0, 0, 0, 0, 0, + }; + + int64_t int64 = (int64_t) INT64_MIN; + float floats[] = { + M_PI, + INFINITY, + }; + + memcpy(lhs + 4, &int64, sizeof(int64)); + memcpy(rhs + 15, &int64, sizeof(int64)); + memcpy(lhs + 16, floats, sizeof(floats)); + memcpy(rhs + 4, floats, sizeof(floats)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 0; + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 2; + rhs[13] = kStdTrue; + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[13] = kStdNull; + rhs[3] = 1; + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // float32array + { + alignas(16) uint8_t lhs[] = { + // type + kStdFloat32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 floats + 0, 0, 0, 0, + 0, 0, 0, 0, + }; + + alignas(16) uint8_t rhs[] = { + // type + kStdFloat32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 floats + 0, 0, 0, 0, + 0, 0, 0, 0, + }; + + float array[] = { + M_PI, + INFINITY, + }; + + memcpy(lhs + 4, array, sizeof(array)); + memcpy(rhs + 4, array, sizeof(array)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 0; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 2; + float array2[] = { + 0.0, + INFINITY, + }; + memcpy(rhs + 4, array2, sizeof(array2)); + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } } void test_raw_std_value_is_bool() { @@ -675,7 +816,7 @@ void test_raw_std_value_is_int() { } void test_raw_std_value_as_int() { - uint8_t buffer[9] = { + alignas(16) uint8_t buffer[9] = { kStdInt32, 0, 0, 0, 0, 0, 0, 0, 0 }; @@ -697,7 +838,7 @@ void test_raw_std_value_as_int() { } void test_raw_std_value_get_size() { - uint8_t buffer[] = { + alignas(16) uint8_t buffer[] = { // type kStdList, // size @@ -728,7 +869,7 @@ void test_raw_std_value_get_size() { } void test_raw_std_value_after() { - + } void test_raw_std_list_get_first_element() { From 9aacdab0395fff6ef5f43e381722d1197e22e2b0 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 30 Jan 2023 23:40:41 +0000 Subject: [PATCH 17/55] enable `-Wmissing-field-initializers` for gcc after 11.3 --- CMakeLists.txt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b179d34..1f7c68ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,11 +171,18 @@ target_compile_options(flutterpi_module PRIVATE ${LIBINPUT_CFLAGS} ${LIBUDEV_CFLAGS} ${LIBXKBCOMMON_CFLAGS} - $<$:-O0 -Wall -Wextra -Wno-unused-function -Wno-sign-compare -ggdb -U_FORTIFY_SOURCE -DDEBUG> - $<$:-O2 -Wall -Wextra -Wno-sign-compare -Wno-missing-field-initializers -ggdb> - $<$:-O2 -Wall -Wextra -Wno-sign-compare -Wno-missing-field-initializers -ggdb> + $<$:-O0 -Wall -Wextra -Wno-sign-compare -Wno-error=unused-function -ggdb -U_FORTIFY_SOURCE -DDEBUG> + $<$:-O2 -Wall -Wextra -Wno-sign-compare -ggdb> + $<$:-O2 -Wall -Wextra -Wno-sign-compare -ggdb> ) +# GCC prior to 11.3 reports false positives for missing-field-initializers warning. +if (CMAKE_C_COMPILER_ID STREQUAL "GNU") + if (CMAKE_C_COMPILER_VERSION VERSION_LESS "11.3") + target_compile_options(flutterpi_module PRIVATE -Wno-error=missing-field-initializers) + endif() +endif() + # There's no other way to query the libinput version (in code) somehow. # So we need to roll our own libinput version macro string(REPLACE "." ";" LIBINPUT_VERSION_AS_LIST ${LIBINPUT_VERSION}) From b872c846f04acf0a0a8948ecc2711885421b197e Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 7 Feb 2023 03:54:16 +0000 Subject: [PATCH 18/55] fix plugin registration - use cmake object libraries - static libraries don't work well with `__attribute__((constructor))` functions - small fixes for raw_std_value API --- CMakeLists.txt | 10 ++++++---- src/platformchannel.c | 10 +++++----- test/CMakeLists.txt | 3 ++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f7c68ae..4c94c231 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,7 +117,7 @@ pkg_check_modules(LIBXKBCOMMON REQUIRED xkbcommon) pkg_check_modules(LIBUDEV REQUIRED libudev) add_library( - flutterpi_module STATIC + flutterpi_module OBJECT src/flutter-pi.c src/platformchannel.c src/pluginregistry.c @@ -135,7 +135,7 @@ add_library( src/plugins/services.c ) -target_link_libraries(flutterpi_module +target_link_libraries(flutterpi_module PUBLIC ${DRM_LDFLAGS} ${GBM_LDFLAGS} ${EGL_LDFLAGS} @@ -255,7 +255,7 @@ if (BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) src/plugins/gstreamer_video_player/frame.c ) target_compile_definitions(flutterpi_module PRIVATE "BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN") - target_link_libraries(flutterpi_module + target_link_libraries(flutterpi_module PUBLIC ${LIBGSTREAMER_LDFLAGS} ${LIBGSTREAMER_PLUGINS_BASE_LDFLAGS} ${LIBGSTREAMER_APP_LDFLAGS} @@ -301,7 +301,7 @@ if (BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN) src/plugins/audioplayers/player.c ) target_compile_definitions(flutterpi_module PRIVATE "BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN") - target_link_libraries(flutterpi_module + target_link_libraries(flutterpi_module PUBLIC ${LIBGSTREAMER_LDFLAGS} ${LIBGSTREAMER_APP_LDFLAGS} ${LIBGSTREAMER_AUDIO_LIBRARY_DIRS} @@ -358,10 +358,12 @@ add_executable( flutter-pi src/main.c ) + target_link_libraries( flutter-pi flutterpi_module ) + install(TARGETS flutter-pi RUNTIME DESTINATION bin) if(ENABLE_TESTS) diff --git a/src/platformchannel.c b/src/platformchannel.c index f94580b6..853ef3ab 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -2271,17 +2271,17 @@ ATTR_PURE const struct raw_std_value *raw_std_map_find_str(const struct raw_std_ ATTR_PURE static bool check_size(const struct raw_std_value *value, size_t buffer_size) { size_t size; - if (buffer_size < 1) { + const uint8_t *byteptr = (const uint8_t*) value; + + if (buffer_size < 2) { return false; } - const uint8_t *byteptr = (const uint8_t*) value; - // skip type byte byteptr++; size = *byteptr; - buffer_size--; + buffer_size -= 2; if (size == 254) { if (buffer_size < 2) { @@ -2381,7 +2381,7 @@ ATTR_PURE bool raw_std_value_check(const struct raw_std_value *value, size_t buf return false; } - if (!raw_std_value_check(value, buffer_size - diff)) { + if (!raw_std_value_check(element, buffer_size - diff)) { return false; } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4aa108aa..6d56bb89 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,7 +2,8 @@ add_executable(platformchannel_test platformchannel_test.c ) -target_link_libraries(platformchannel_test +target_link_libraries( + platformchannel_test flutterpi_module Unity ) From 526d80a3fc7ab57370571feaa81173c36b0f8d04 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 9 Feb 2023 16:00:38 +0000 Subject: [PATCH 19/55] gstreamer updates - remove old software decoding fallback (for buggy v4l2/gstreamer pi4 hw decoding) - is fixed in kernel & gstreamer now - allow more pixel formats & multi-dmabuf gl upload for video frames --- src/plugins/gstreamer_video_player/frame.c | 68 +++++++--- src/plugins/gstreamer_video_player/player.c | 143 +++++++++----------- 2 files changed, 111 insertions(+), 100 deletions(-) diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index 5c196b6f..3354c36c 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -180,6 +180,7 @@ struct video_frame *frame_new( EGLImage egl_image; GstBuffer *buffer; GstMemory *memory; + gboolean gst_ok; GLenum gl_error; EGLint attributes[2*7 + MAX_N_PLANES*2*5 + 1], *attr_cursor; GLuint texture; @@ -208,7 +209,8 @@ struct video_frame *frame_new( /// TODO: Do we really need to dup() here? if (is_dmabuf_memory) { - dmabuf_fd = dup(gst_dmabuf_memory_get_fd(memory)); + //dmabuf_fd = dup(gst_dmabuf_memory_get_fd(memory)); + dmabuf_fd = -1; } else { dmabuf_fd = dup_gst_buffer_as_dmabuf(interface->gbm_device, buffer); @@ -216,32 +218,60 @@ struct video_frame *frame_new( //goto fail_free_frame; } - if (n_mems > 1) { - LOG_ERROR("Multiple dmabufs for a single frame buffer is not supported right now.\n"); - goto fail_free_frame; - } - width = GST_VIDEO_INFO_WIDTH(info->gst_info); height = GST_VIDEO_INFO_HEIGHT(info->gst_info); n_planes = GST_VIDEO_INFO_N_PLANES(info->gst_info); + size_t plane_sizes[4] = {0}; + meta = gst_buffer_get_video_meta(buffer); if (meta != NULL) { - for (int i = 0; i < n_planes; i++) { - planes[i].fd = dmabuf_fd; - planes[i].offset = meta->offset[i]; - planes[i].pitch = meta->stride[i]; - planes[i].has_modifier = false; - planes[i].modifier = DRM_FORMAT_MOD_LINEAR; - } + gst_ok = gst_video_meta_get_plane_size(meta, plane_sizes); + DEBUG_ASSERT(gst_ok); } else { - for (int i = 0; i < n_planes; i++) { - planes[i].fd = dmabuf_fd; - planes[i].offset = GST_VIDEO_INFO_PLANE_OFFSET(info->gst_info, i); - planes[i].pitch = GST_VIDEO_INFO_PLANE_STRIDE(info->gst_info, i); - planes[i].has_modifier = false; - planes[i].modifier = DRM_FORMAT_MOD_LINEAR; + // Taken from: https://github.com/GStreamer/gstreamer/blob/621604aa3e4caa8db27637f63fa55fac2f7721e5/subprojects/gst-plugins-base/gst-libs/gst/video/video-info.c#L1278-L1301 + for (int i = 0; i < GST_VIDEO_MAX_PLANES; i++) { + if (i < GST_VIDEO_INFO_N_PLANES(info->gst_info)) { + gint comp[GST_VIDEO_MAX_COMPONENTS]; + guint plane_height; + + gst_video_format_info_component(info->gst_info->finfo, i, comp); + plane_height = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT( + info->gst_info->finfo, + comp[0], + GST_VIDEO_INFO_FIELD_HEIGHT(info->gst_info) + ); + plane_sizes[i] = plane_height * GST_VIDEO_INFO_PLANE_STRIDE(info->gst_info, i); + } else { + plane_sizes[i] = 0; + } + } + } + + for (int i = 0; i < n_planes; i++) { + unsigned memory_index = 0, n_memories = 0; + size_t offset_in_memory = 0; + + gst_ok = gst_buffer_find_memory( + buffer, + meta ? meta->offset[i] : GST_VIDEO_INFO_PLANE_OFFSET(info->gst_info, i), + plane_sizes[i], + &memory_index, + &n_memories, + &offset_in_memory + ); + DEBUG_ASSERT(gst_ok); + + if (n_memories != 1) { + LOG_ERROR("Gstreamer Image planes can't span multiple dmabufs.\n"); + goto fail_close_dmabuf_fd; } + + planes[i].fd = dup(gst_dmabuf_memory_get_fd(gst_buffer_peek_memory(buffer, memory_index))); + planes[i].offset = offset_in_memory; + planes[i].pitch = meta ? meta->stride[i] : GST_VIDEO_INFO_PLANE_STRIDE(info->gst_info, i); + planes[i].has_modifier = false; + planes[i].modifier = DRM_FORMAT_MOD_LINEAR; } attr_cursor = attributes; diff --git a/src/plugins/gstreamer_video_player/player.c b/src/plugins/gstreamer_video_player/player.c index 57c36efa..17510839 100644 --- a/src/plugins/gstreamer_video_player/player.c +++ b/src/plugins/gstreamer_video_player/player.c @@ -133,11 +133,9 @@ struct gstplayer { */ int64_t desired_position_ms; - bool is_forcing_sw_decoding; - bool is_currently_falling_back_to_sw_decoding; - struct notifier video_info_notifier, buffering_state_notifier, error_notifier; + bool is_initialized; bool has_sent_info; struct incomplete_video_info info; @@ -156,6 +154,8 @@ struct gstplayer { bool has_drm_modifier; uint64_t drm_modifier; EGLint egl_color_space; + + bool is_live; }; #define MAX_N_PLANES 4 @@ -203,8 +203,14 @@ static void fetch_duration(struct gstplayer *player) { ok = gst_element_query_duration(player->pipeline, GST_FORMAT_TIME, &duration); if (ok == FALSE) { - LOG_ERROR("Could not fetch duration. (gst_element_query_duration)\n"); - return; + if (player->is_live) { + player->info.info.duration_ms = INT64_MAX; + player->info.has_duration = true; + return; + } else { + LOG_ERROR("Could not fetch duration. (gst_element_query_duration)\n"); + return; + } } player->info.info.duration_ms = GST_TIME_AS_MSECONDS(duration); @@ -219,7 +225,16 @@ static void fetch_seeking(struct gstplayer *player) { seeking_query = gst_query_new_seeking(GST_FORMAT_TIME); ok = gst_element_query(player->pipeline, seeking_query); if (ok == FALSE) { - return; + if (player->is_live) { + player->info.info.can_seek = false; + player->info.info.seek_begin_ms = 0; + player->info.info.seek_end_ms = 0; + player->info.has_seeking_info = true; + return; + } else { + LOG_DEBUG("Could not query seeking info. (gst_element_query)\n"); + return; + } } gst_query_parse_seeking(seeking_query, NULL, &seekable, &seek_begin, &seek_end); @@ -287,23 +302,12 @@ static int init(struct gstplayer *player, bool force_sw_decoders); static void maybe_deinit(struct gstplayer *player); -static void fallback_to_sw_decoding(struct gstplayer *player) { - maybe_deinit(player); - player->is_currently_falling_back_to_sw_decoding = true; - init(player, true); -} - static int apply_playback_state(struct gstplayer *player) { GstStateChangeReturn ok; GstState desired_state, current_state, pending_state; double desired_rate; int64_t position; - // if we're currently falling back to software decoding, don't do anything. - if (player->is_currently_falling_back_to_sw_decoding) { - return 0; - } - desired_state = player->playpause_state == kPlaying ? GST_STATE_PLAYING : GST_STATE_PAUSED; /* use GST_STATE_PAUSED if we're stepping */ /// Use 1.0 if we're stepping, otherwise use the stored playback rate for the current direction. @@ -350,18 +354,8 @@ static int apply_playback_state(struct gstplayer *player) { ); if (ok == FALSE) { - if (player->is_forcing_sw_decoding == false) { - LOG_DEBUG("Could not set the new playback speed / playback position (speed: %f, pos: %" GST_TIME_FORMAT ").\n", desired_rate, GST_TIME_ARGS(position)); - LOG_DEBUG("Falling back to software decoding to set the new playback speed / position.\n"); - player->has_desired_position = true; - player->desired_position_ms = GST_TIME_AS_MSECONDS(position); - player->fallback_position_ms = GST_TIME_AS_MSECONDS(position); - fallback_to_sw_decoding(player); - return 0; - } else { - LOG_ERROR("Could not set the new playback speed / playback position (speed: %f, pos: %" GST_TIME_FORMAT ") and player is already using software decoding.\n", desired_rate, GST_TIME_ARGS(position)); - return EIO; - } + LOG_ERROR("Could not set the new playback speed / playback position (speed: %f, pos: %" GST_TIME_FORMAT ").\n", desired_rate, GST_TIME_ARGS(position)); + return EIO; } } @@ -431,12 +425,7 @@ static void on_bus_message(struct gstplayer *player, GstMessage *msg) { case GST_MESSAGE_ERROR: gst_message_parse_error(msg, &error, &debug_info); - fprintf(stderr, "[gstreamer video player] gstreamer error: code: %d, domain: %s, msg: %s (debug info: %s)\n", error->code, g_quark_to_string(error->domain), error->message, debug_info); - if (error->domain == GST_STREAM_ERROR && error->code == GST_STREAM_ERROR_DECODE && strcmp(error->message, "No valid frames decoded before end of stream") == 0) { - LOG_ERROR("Hardware decoder failed. Falling back to software decoding...\n"); - fallback_to_sw_decoding(player); - } - + LOG_ERROR("gstreamer error: code: %d, domain: %s, msg: %s (debug info: %s)\n", error->code, g_quark_to_string(error->domain), error->message, debug_info); g_clear_error(&error); g_free(debug_info); break; @@ -511,10 +500,6 @@ static void on_bus_message(struct gstplayer *player, GstMessage *msg) { break; case GST_MESSAGE_ASYNC_DONE: - if (GST_MESSAGE_SRC(msg) == GST_OBJECT(player->pipeline) && player->is_currently_falling_back_to_sw_decoding) { - player->is_currently_falling_back_to_sw_decoding = false; - apply_playback_state(player); - } break; case GST_MESSAGE_LATENCY: @@ -845,6 +830,7 @@ void on_source_setup(GstElement *bin, GstElement *source, gpointer userdata) { } static int init(struct gstplayer *player, bool force_sw_decoders) { + GstStateChangeReturn state_change_return; sd_event_source *busfd_event_source; GstElement *pipeline, *sink, *src; GstBus *bus; @@ -862,7 +848,7 @@ static int init(struct gstplayer *player, bool force_sw_decoders) { pipeline_descr = default_pipeline_descr; } - pipeline = gst_parse_launch(default_pipeline_descr, &error); + pipeline = gst_parse_launch(pipeline_descr, &error); if (pipeline == NULL) { LOG_ERROR("Could create GStreamer pipeline from description: %s (pipeline: `%s`)\n", error->message, pipeline_descr); return error->code; @@ -968,7 +954,14 @@ static int init(struct gstplayer *player, bool force_sw_decoders) { ); LOG_DEBUG("Setting state to paused...\n"); - gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PAUSED); + state_change_return = gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PAUSED); + if (state_change_return == GST_STATE_CHANGE_NO_PREROLL) { + LOG_DEBUG("Is Live!\n"); + player->is_live = true; + } else { + LOG_DEBUG("Not live!\n"); + player->is_live = false; + } player->sink = sink; /// FIXME: Not sure we need this here. pipeline is floating after gst_parse_launch, which @@ -976,7 +969,6 @@ static int init(struct gstplayer *player, bool force_sw_decoders) { player->pipeline = pipeline; //gst_object_ref(pipeline); player->bus = bus; player->busfd_events = busfd_event_source; - player->is_forcing_sw_decoding = force_sw_decoders; gst_object_unref(pad); return 0; @@ -1098,8 +1090,6 @@ static struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char * player->fallback_position_ms = 0; player->has_desired_position = false; player->desired_position_ms = 0; - player->is_forcing_sw_decoding = false; - player->is_currently_falling_back_to_sw_decoding = false; player->has_sent_info = false; player->info.has_resolution = false; player->info.has_fps = false; @@ -1115,6 +1105,7 @@ static struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char * player->bus = NULL; player->busfd_events = NULL; player->drm_format = 0; + player->is_live = false; return player; //fail_deinit_error_notifier: @@ -1271,13 +1262,7 @@ int64_t gstplayer_get_position(struct gstplayer *player) { GstState current, pending; gboolean ok; int64_t position; - - // If we're currently falling back to software decoding, - // report the position we'll make gstreamer seek to afterwards. - if (player->is_currently_falling_back_to_sw_decoding) { - return player->desired_position_ms; - } - + GstStateChangeReturn statechange = gst_element_get_state(GST_ELEMENT(player->pipeline), ¤t, &pending, 0); if (statechange == GST_STATE_CHANGE_FAILURE) { LOG_GST_GET_STATE_ERROR(player->pipeline); @@ -1332,21 +1317,19 @@ int gstplayer_step_forward(struct gstplayer *player) { return ok; } - if (player->is_currently_falling_back_to_sw_decoding == false) { - gst_ok = gst_element_send_event( - player->pipeline, - gst_event_new_step( - GST_FORMAT_BUFFERS, - 1, - 1, - TRUE, - FALSE - ) - ); - if (gst_ok == FALSE) { - LOG_ERROR("Could not send frame-step event to pipeline. (gst_element_send_event)\n"); - return EIO; - } + gst_ok = gst_element_send_event( + player->pipeline, + gst_event_new_step( + GST_FORMAT_BUFFERS, + 1, + 1, + TRUE, + FALSE + ) + ); + if (gst_ok == FALSE) { + LOG_ERROR("Could not send frame-step event to pipeline. (gst_element_send_event)\n"); + return EIO; } return 0; } @@ -1364,21 +1347,19 @@ int gstplayer_step_backward(struct gstplayer *player) { return ok; } - if (player->is_currently_falling_back_to_sw_decoding == false) { - gst_ok = gst_element_send_event( - player->pipeline, - gst_event_new_step( - GST_FORMAT_BUFFERS, - 1, - 1, - TRUE, - FALSE - ) - ); - if (gst_ok == FALSE) { - LOG_ERROR("Could not send frame-step event to pipeline. (gst_element_send_event)\n"); - return EIO; - } + gst_ok = gst_element_send_event( + player->pipeline, + gst_event_new_step( + GST_FORMAT_BUFFERS, + 1, + 1, + TRUE, + FALSE + ) + ); + if (gst_ok == FALSE) { + LOG_ERROR("Could not send frame-step event to pipeline. (gst_element_send_event)\n"); + return EIO; } return 0; From 81ed4dd3ce2af4e126ba9fe309bcf77e769d11ca Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 9 Feb 2023 16:01:00 +0000 Subject: [PATCH 20/55] update unity --- third_party/Unity | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/Unity b/third_party/Unity index 5a36b197..5204c1ba 160000 --- a/third_party/Unity +++ b/third_party/Unity @@ -1 +1 @@ -Subproject commit 5a36b197fb34c0a77ac891c355596cb5c25aaf5b +Subproject commit 5204c1bacf37e0c38211d2fbb6d9796e410223f8 From a935dcf74a7c5003223d61d3463a74f24d779cd5 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 9 Feb 2023 17:50:10 +0100 Subject: [PATCH 21/55] add `--videomode` option - remove `-i`, `--input` option - refactor cmakelists.txt - use pkgconfig `IMPORTED_TARGET` flag - add -Werror again - add `MAYBE_UNUSED` to functions - remove some unused functions --- CMakeLists.txt | 130 ++++++-------------- include/flutter-pi.h | 2 + src/compositor.c | 4 +- src/flutter-pi.c | 109 ++++++++++------ src/modesetting.c | 2 +- src/platformchannel.c | 4 +- src/pluginregistry.c | 13 -- src/plugins/gstreamer_video_player/frame.c | 15 ++- src/plugins/gstreamer_video_player/plugin.c | 27 +--- src/plugins/text_input.c | 6 +- 10 files changed, 129 insertions(+), 183 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c94c231..83778991 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,14 +107,14 @@ include(ExternalProject) include(CheckCCompilerFlag) include(FindPkgConfig) -pkg_check_modules(DRM REQUIRED libdrm) -pkg_check_modules(GBM REQUIRED gbm) -pkg_check_modules(EGL REQUIRED egl) -pkg_check_modules(GLESV2 REQUIRED glesv2) -pkg_check_modules(LIBSYSTEMD REQUIRED libsystemd) -pkg_check_modules(LIBINPUT REQUIRED libinput) -pkg_check_modules(LIBXKBCOMMON REQUIRED xkbcommon) -pkg_check_modules(LIBUDEV REQUIRED libudev) +pkg_check_modules(DRM REQUIRED IMPORTED_TARGET libdrm) +pkg_check_modules(GBM REQUIRED IMPORTED_TARGET gbm) +pkg_check_modules(EGL REQUIRED IMPORTED_TARGET egl) +pkg_check_modules(GLESV2 REQUIRED IMPORTED_TARGET glesv2) +pkg_check_modules(LIBSYSTEMD REQUIRED IMPORTED_TARGET libsystemd) +pkg_check_modules(LIBINPUT REQUIRED IMPORTED_TARGET libinput) +pkg_check_modules(LIBXKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon) +pkg_check_modules(LIBUDEV REQUIRED IMPORTED_TARGET libudev) add_library( flutterpi_module OBJECT @@ -136,15 +136,14 @@ add_library( ) target_link_libraries(flutterpi_module PUBLIC - ${DRM_LDFLAGS} - ${GBM_LDFLAGS} - ${EGL_LDFLAGS} - ${GLESV2_LDFLAGS} - EGL - systemd #${LIBSYSTEMD_LDFLAGS} - input #${LIBINPUT_LDFLAGS} - xkbcommon #${LIBUDEV_LDFLAGS} - udev #${LIBXKBCOMMON_LDFLAGS} + PkgConfig::DRM + PkgConfig::GBM + PkgConfig::EGL + PkgConfig::GLESV2 + PkgConfig::LIBSYSTEMD + PkgConfig::LIBINPUT + PkgConfig::LIBXKBCOMMON + PkgConfig::LIBUDEV pthread dl rt m atomic ) @@ -152,26 +151,10 @@ target_include_directories(flutterpi_module PUBLIC ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/include/plugins - ${DRM_INCLUDE_DIRS} - ${GBM_INCLUDE_DIRS} - ${EGL_INCLUDE_DIRS} - ${GLESV2_INCLUDE_DIRS} - ${LIBSYSTEMD_INCLUDE_DIRS} - ${LIBINPUT_INCLUDE_DIRS} - ${LIBUDEV_INCLUDE_DIRS} - ${LIBXKBCOMMON_INCLUDE_DIRS} ) target_compile_options(flutterpi_module PRIVATE - ${DRM_CFLAGS} - ${GBM_CFLAGS} - ${EGL_CFLAGS} - ${GLESV2_CFLAGS} - ${LIBSYSTEMD_CFLAGS} - ${LIBINPUT_CFLAGS} - ${LIBUDEV_CFLAGS} - ${LIBXKBCOMMON_CFLAGS} - $<$:-O0 -Wall -Wextra -Wno-sign-compare -Wno-error=unused-function -ggdb -U_FORTIFY_SOURCE -DDEBUG> + $<$:-O0 -Wall -Wextra -Wno-sign-compare -Werror -ggdb -U_FORTIFY_SOURCE -DDEBUG> $<$:-O2 -Wall -Wextra -Wno-sign-compare -ggdb> $<$:-O2 -Wall -Wextra -Wno-sign-compare -ggdb> ) @@ -224,28 +207,26 @@ if (BUILD_RAW_KEYBOARD_PLUGIN) endif() if (BUILD_TEST_PLUGIN) target_sources(flutterpi_module PRIVATE src/plugins/testplugin.c) - target_compile_definitions(flutterpi_module PRIVATE "BUILD_TEST_PLUGIN") endif() if (BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN) target_sources(flutterpi_module PRIVATE src/plugins/omxplayer_video_player.c) - target_compile_definitions(flutterpi_module PRIVATE "BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN") endif() if (OMXPLAYER_SUPPORTS_RUNTIME_ROTATION) target_compile_definitions(flutterpi_module PRIVATE "OMXPLAYER_SUPPORTS_RUNTIME_ROTATION") endif() if (BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) if (TRY_BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) - pkg_check_modules(LIBGSTREAMER gstreamer-1.0) - pkg_check_modules(LIBGSTREAMER_PLUGINS_BASE gstreamer-plugins-base-1.0) - pkg_check_modules(LIBGSTREAMER_APP gstreamer-app-1.0) - pkg_check_modules(LIBGSTREAMER_ALLOCATORS gstreamer-allocators-1.0) - pkg_check_modules(LIBGSTREAMER_VIDEO gstreamer-video-1.0) + pkg_check_modules(LIBGSTREAMER IMPORTED_TARGET gstreamer-1.0) + pkg_check_modules(LIBGSTREAMER_PLUGINS_BASE IMPORTED_TARGET gstreamer-plugins-base-1.0) + pkg_check_modules(LIBGSTREAMER_APP IMPORTED_TARGET gstreamer-app-1.0) + pkg_check_modules(LIBGSTREAMER_ALLOCATORS IMPORTED_TARGET gstreamer-allocators-1.0) + pkg_check_modules(LIBGSTREAMER_VIDEO IMPORTED_TARGET gstreamer-video-1.0) else() - pkg_check_modules(LIBGSTREAMER REQUIRED gstreamer-1.0) - pkg_check_modules(LIBGSTREAMER_PLUGINS_BASE REQUIRED gstreamer-plugins-base-1.0) - pkg_check_modules(LIBGSTREAMER_APP REQUIRED gstreamer-app-1.0) - pkg_check_modules(LIBGSTREAMER_ALLOCATORS REQUIRED gstreamer-allocators-1.0) - pkg_check_modules(LIBGSTREAMER_VIDEO REQUIRED gstreamer-video-1.0) + pkg_check_modules(LIBGSTREAMER REQUIRED IMPORTED_TARGET gstreamer-1.0) + pkg_check_modules(LIBGSTREAMER_PLUGINS_BASE REQUIRED IMPORTED_TARGET gstreamer-plugins-base-1.0) + pkg_check_modules(LIBGSTREAMER_APP REQUIRED IMPORTED_TARGET gstreamer-app-1.0) + pkg_check_modules(LIBGSTREAMER_ALLOCATORS REQUIRED IMPORTED_TARGET gstreamer-allocators-1.0) + pkg_check_modules(LIBGSTREAMER_VIDEO REQUIRED IMPORTED_TARGET gstreamer-video-1.0) endif() if (LIBGSTREAMER_FOUND AND LIBGSTREAMER_PLUGINS_BASE_FOUND AND LIBGSTREAMER_APP_FOUND AND LIBGSTREAMER_ALLOCATORS_FOUND AND LIBGSTREAMER_VIDEO_FOUND) @@ -254,30 +235,12 @@ if (BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) src/plugins/gstreamer_video_player/player.c src/plugins/gstreamer_video_player/frame.c ) - target_compile_definitions(flutterpi_module PRIVATE "BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN") target_link_libraries(flutterpi_module PUBLIC - ${LIBGSTREAMER_LDFLAGS} - ${LIBGSTREAMER_PLUGINS_BASE_LDFLAGS} - ${LIBGSTREAMER_APP_LDFLAGS} - ${LIBGSTREAMER_ALLOCATORS_LDFLAGS} - ${LIBGSTREAMER_VIDEO_LDFLAGS} - ${LIBGSTREAMER_AUDIO_LIBRARY_DIRS} - ) - target_include_directories(flutterpi_module PRIVATE - ${LIBGSTREAMER_INCLUDE_DIRS} - ${LIBGSTREAMER_PLUGINS_BASE_INCLUDE_DIRS} - ${LIBGSTREAMER_APP_INCLUDE_DIRS} - ${LIBGSTREAMER_ALLOCATORS_INCLUDE_DIRS} - ${LIBGSTREAMER_VIDEO_INCLUDE_DIRS} - ${LIBGSTREAMER_AUDIO_INCLUDE_DIRS} - ) - target_compile_options(flutterpi_module PRIVATE - ${LIBGSTREAMER_CFLAGS} - ${LIBGSTREAMER_PLUGINS_BASE_CFLAGS} - ${LIBGSTREAMER_APP_CFLAGS} - ${LIBGSTREAMER_ALLOCATORS_CFLAGS} - ${LIBGSTREAMER_VIDEO_CFLAGS} - ${LIBGSTREAMER_AUDIO_CFLAGS} + PkgConfig::LIBGSTREAMER + PkgConfig::LIBGSTREAMER_PLUGINS_BASE + PkgConfig::LIBGSTREAMER_APP + PkgConfig::LIBGSTREAMER_ALLOCATORS + PkgConfig::LIBGSTREAMER_VIDEO ) else() message(NOTICE "Couldn't find gstreamer libraries. Gstreamer video player plugin won't be build.") @@ -286,13 +249,13 @@ endif() if (BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN) if (TRY_BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN) - pkg_check_modules(LIBGSTREAMER gstreamer-1.0) - pkg_check_modules(LIBGSTREAMER_APP gstreamer-app-1.0) - pkg_check_modules(LIBGSTREAMER_AUDIO gstreamer-audio-1.0) + pkg_check_modules(LIBGSTREAMER IMPORTED_TARGET gstreamer-1.0) + pkg_check_modules(LIBGSTREAMER_APP IMPORTED_TARGET gstreamer-app-1.0) + pkg_check_modules(LIBGSTREAMER_AUDIO IMPORTED_TARGET gstreamer-audio-1.0) else() - pkg_check_modules(LIBGSTREAMER REQUIRED gstreamer-1.0) - pkg_check_modules(LIBGSTREAMER_APP REQUIRED gstreamer-app-1.0) - pkg_check_modules(LIBGSTREAMER_AUDIO REQUIRED gstreamer-audio-1.0) + pkg_check_modules(LIBGSTREAMER REQUIRED IMPORTED_TARGET gstreamer-1.0) + pkg_check_modules(LIBGSTREAMER_APP REQUIRED IMPORTED_TARGET gstreamer-app-1.0) + pkg_check_modules(LIBGSTREAMER_AUDIO REQUIRED IMPORTED_TARGET gstreamer-audio-1.0) endif() if (LIBGSTREAMER_FOUND AND LIBGSTREAMER_APP_FOUND AND LIBGSTREAMER_AUDIO_FOUND) @@ -300,21 +263,10 @@ if (BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN) src/plugins/audioplayers/plugin.c src/plugins/audioplayers/player.c ) - target_compile_definitions(flutterpi_module PRIVATE "BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN") target_link_libraries(flutterpi_module PUBLIC - ${LIBGSTREAMER_LDFLAGS} - ${LIBGSTREAMER_APP_LDFLAGS} - ${LIBGSTREAMER_AUDIO_LIBRARY_DIRS} - ) - target_include_directories(flutterpi_module PRIVATE - ${LIBGSTREAMER_INCLUDE_DIRS} - ${LIBGSTREAMER_APP_INCLUDE_DIRS} - ${LIBGSTREAMER_AUDIO_INCLUDE_DIRS} - ) - target_compile_options(flutterpi_module PRIVATE - ${LIBGSTREAMER_CFLAGS} - ${LIBGSTREAMER_APP_CFLAGS} - ${LIBGSTREAMER_AUDIO_CFLAGS} + PkgConfig::LIBGSTREAMER + PkgConfig::LIBGSTREAMER_APP + PkgConfig::LIBGSTREAMER_AUDIO ) else() message(NOTICE "Couldn't find gstreamer libraries. Gstreamer audio player plugin won't be build.") diff --git a/include/flutter-pi.h b/include/flutter-pi.h index a44348b2..3849e078 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -302,6 +302,8 @@ struct flutterpi { /// This is computed inside init_display using width_mm and height_mm. /// flutter only accepts pixel ratios >= 1.0 double pixel_ratio; + + char *desired_videomode; } display; struct { diff --git a/src/compositor.c b/src/compositor.c index 2ec0585d..274bf107 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -60,7 +60,7 @@ static struct view_cb_data *get_cbs_for_view_id_locked(int64_t view_id) { return NULL; } -static struct view_cb_data *get_cbs_for_view_id(int64_t view_id) { +MAYBE_UNUSED static struct view_cb_data *get_cbs_for_view_id(int64_t view_id) { struct view_cb_data *data; cpset_lock(&compositor.cbs); @@ -73,7 +73,7 @@ static struct view_cb_data *get_cbs_for_view_id(int64_t view_id) { /** * @brief Destroy all the rendertargets in the stale rendertarget cache. */ -static int destroy_stale_rendertargets(void) { +MAYBE_UNUSED static int destroy_stale_rendertargets(void) { struct rendertarget *target; cpset_lock(&compositor.stale_rendertargets); diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 5d47cc13..0d882cf1 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -105,24 +105,10 @@ OPTIONS:\n\ --pixelformat Selects the pixel format to use for the framebuffers.\n\ Available pixel formats:\n\ RGB565, ARGB8888, XRGB8888, BGRA8888, RGBA8888\n\ -\n\ - -i, --input Appends all files matching this glob pattern to the\n\ - list of input (touchscreen, mouse, touchpad, \n\ - keyboard) devices. Brace and tilde expansion is \n\ - enabled.\n\ - Every file that matches this pattern, but is not\n\ - a valid touchscreen / -pad, mouse or keyboard is \n\ - silently ignored.\n\ - If no -i options are given, flutter-pi will try to\n\ - use all input devices assigned to udev seat0.\n\ - If that fails, or udev is not installed, flutter-pi\n\ - will fallback to using all devices matching \n\ - \"/dev/input/event*\" as inputs.\n\ - In most cases, there's no need to specify this\n\ - option.\n\ - Note that you need to properly escape each glob \n\ - pattern you use as a parameter so it isn't \n\ - implicitly expanded by your shell.\n\ + --videomode widthxheight\n\ + --videomode widthxheight@hz Uses an output videomode that satisfies the argument.\n\ + If no hz value is given, the highest possible refreshrate\n\ + will be used.\n\ \n\ -h, --help Show this help and exit.\n\ \n\ @@ -132,8 +118,8 @@ EXAMPLES:\n\ flutter-pi -o portrait_up ./my_app\n\ flutter-pi -r 90 ./my_app\n\ flutter-pi -d \"155, 86\" ./my_app\n\ - flutter-pi -i \"/dev/input/event{0,1}\" -i \"/dev/input/event{2,3}\" /home/pi/helloworld_flutterassets\n\ - flutter-pi -i \"/dev/input/mouse*\" /home/pi/helloworld_flutterassets\n\ + flutter-pi --videomode 1920x1080 ./my_app\n\ + flutter-pi --videomode 1280x720@60 ./my_app\n\ \n\ SEE ALSO:\n\ Author: Hannes Winkler, a.k.a ardera\n\ @@ -449,7 +435,7 @@ static int on_execute_frame_request( /// Called on some flutter internal thread to request a frame, /// and also get the vblank timestamp of the pageflip preceding that frame. -static void on_frame_request( +MAYBE_UNUSED static void on_frame_request( void* userdata, intptr_t baton ) { @@ -1447,24 +1433,60 @@ static int init_display(void) { return EINVAL; } + mode = NULL; + if (flutterpi.display.desired_videomode != NULL) { + for_each_mode_in_connector(connector, mode_iter) { + char *modeline = NULL, *modeline_nohz = NULL; + + ok = asprintf(&modeline, "%"PRIu16"x%"PRIu16"@%"PRIu32, mode_iter->hdisplay, mode_iter->vdisplay, mode_iter->vrefresh); + if (ok < 0) { + return ENOMEM; + } + + ok = asprintf(&modeline_nohz, "%"PRIu16"x%"PRIu16, mode_iter->hdisplay, mode_iter->vdisplay); + if (ok < 0) { + return ENOMEM; + } + + if (strcmp(modeline, flutterpi.display.desired_videomode) == 0) { + // Probably a bit superfluos, but the refresh rate can still vary in the decimal places. + if (mode == NULL || (mode_get_vrefresh(mode_iter) > mode_get_vrefresh(mode))) { + mode = mode_iter; + } + } else if (strcmp(modeline_nohz, flutterpi.display.desired_videomode) == 0) { + if (mode == NULL || (mode_get_vrefresh(mode_iter) > mode_get_vrefresh(mode))) { + mode = mode_iter; + } + } + + free(modeline); + free(modeline_nohz); + } + + if (mode == NULL) { + LOG_ERROR("Didn't find a videomode matching \"%s\"! Falling back to display preferred mode.\n", flutterpi.display.desired_videomode); + } + } + // Find the preferred mode (GPU drivers _should_ always supply a preferred mode, but of course, they don't) // Alternatively, find the mode with the highest width*height. If there are multiple modes with the same w*h, // prefer higher refresh rates. After that, prefer progressive scanout modes. - mode = NULL; - for_each_mode_in_connector(connector, mode_iter) { - if (mode_iter->type & DRM_MODE_TYPE_PREFERRED) { - mode = mode_iter; - break; - } else if (mode == NULL) { - mode = mode_iter; - } else { - int area = mode_iter->hdisplay * mode_iter->vdisplay; - int old_area = mode->hdisplay * mode->vdisplay; - - if ((area > old_area) || - ((area == old_area) && (mode_iter->vrefresh > mode->vrefresh)) || - ((area == old_area) && (mode_iter->vrefresh == mode->vrefresh) && ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0))) { + if (mode == NULL) { + for_each_mode_in_connector(connector, mode_iter) { + if (mode_iter->type & DRM_MODE_TYPE_PREFERRED) { mode = mode_iter; + break; + } else if (mode == NULL) { + mode = mode_iter; + } else { + int area = mode_iter->hdisplay * mode_iter->vdisplay; + int old_area = mode->hdisplay * mode->vdisplay; + + if ((area > old_area) || + ((area == old_area) && (mode_iter->vrefresh > mode->vrefresh)) || + ((area == old_area) && (mode_iter->vrefresh == mode->vrefresh) && ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0))) { + mode = mode_iter; + } } } } @@ -1996,7 +2018,7 @@ static int init_application(void) { .shutdown_dart_vm_when_done = true, .compositor = &flutter_compositor, .dart_old_gen_heap_size = -1, - .compute_platform_resolved_locale_callback = NULL, + .compute_platform_resolved_locale_callback = on_compute_platform_resolved_locales, .dart_entrypoint_argc = 0, .dart_entrypoint_argv = NULL, .log_message_callback = NULL, @@ -2298,10 +2320,6 @@ static int init_user_input(void) { return 0; } -static bool path_exists(const char *path) { - return access(path, R_OK) == 0; -} - static struct flutter_paths *setup_paths(enum flutter_runtime_mode runtime_mode, const char *app_bundle_path) { #if defined(FILESYSTEM_LAYOUT_DEFAULT) return fs_layout_flutterpi_resolve(app_bundle_path, runtime_mode); @@ -2322,12 +2340,12 @@ static bool parse_cmd_args(int argc, char **argv) { struct option long_options[] = { {"release", no_argument, &runtime_mode_int, kRelease}, {"profile", no_argument, &runtime_mode_int, kProfile}, - {"input", required_argument, NULL, 'i'}, {"orientation", required_argument, NULL, 'o'}, {"rotation", required_argument, NULL, 'r'}, {"dimensions", required_argument, NULL, 'd'}, {"help", no_argument, 0, 'h'}, {"pixelformat", required_argument, NULL, 'p'}, + {"videomode", required_argument, NULL, 'v'}, {0, 0, 0, 0} }; @@ -2418,6 +2436,15 @@ static bool parse_cmd_args(int argc, char **argv) { valid_format: break; + case 'v': ; + char *vmode_dup = strdup(optarg); + if (vmode_dup == NULL) { + return false; + } + + flutterpi.display.desired_videomode = vmode_dup; + break; + case 'h': printf("%s", usage); return false; diff --git a/src/modesetting.c b/src/modesetting.c index b22269ba..2d062c23 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -355,7 +355,7 @@ static int fetch_planes(struct drmdev *drmdev, struct drm_plane **planes_out, si return ok; } -static int free_planes(struct drm_plane *planes, size_t n_planes) { +MAYBE_UNUSED static int free_planes(struct drm_plane *planes, size_t n_planes) { for (int i = 0; i < n_planes; i++) { for (int j = 0; j < planes[i].props->count_props; j++) drmModeFreeProperty(planes[i].props_info[j]); diff --git a/src/platformchannel.c b/src/platformchannel.c index 853ef3ab..0575d127 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -92,10 +92,10 @@ static int _advance_size_bytes(uintptr_t *value, size_t size, size_t *remaining) } #define DEFINE_READ_WRITE_FUNC(suffix, value_type) \ -static int _write_##suffix(uint8_t **pbuffer, value_type value, size_t *remaining) { \ +MAYBE_UNUSED static int _write_##suffix(uint8_t **pbuffer, value_type value, size_t *remaining) { \ return _write(pbuffer, &value, sizeof value, remaining); \ } \ -static int _read_##suffix(uint8_t **pbuffer, value_type* value, size_t *remaining) { \ +MAYBE_UNUSED static int _read_##suffix(uint8_t **pbuffer, value_type* value, size_t *remaining) { \ return _read(pbuffer, value, sizeof *value, remaining); \ } diff --git a/src/pluginregistry.c b/src/pluginregistry.c index 73ca0c54..4069f84a 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -93,19 +93,6 @@ static struct platch_obj_cb_data *get_cb_data_by_channel_locked( return data; } -static struct platch_obj_cb_data *get_cb_data_by_channel( - struct plugin_registry *registry, - const char *channel -) { - struct platch_obj_cb_data *data; - - cpset_lock(®istry->callbacks); - data = get_cb_data_by_channel_locked(registry, channel); - cpset_unlock(®istry->callbacks); - - return data; -} - struct plugin_registry *plugin_registry_new(struct flutterpi *flutterpi) { struct plugin_registry *reg; int ok; diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index 3354c36c..163aaf28 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -186,7 +186,7 @@ struct video_frame *frame_new( GLuint texture; EGLint egl_error; bool is_dmabuf_memory; - int dmabuf_fd, n_mems, n_planes, width, height; + int dmabuf_fd, n_planes, width, height; struct { int fd; @@ -205,7 +205,6 @@ struct video_frame *frame_new( memory = gst_buffer_peek_memory(buffer, 0); is_dmabuf_memory = gst_is_dmabuf_memory(memory); - n_mems = gst_buffer_n_memory(buffer); /// TODO: Do we really need to dup() here? if (is_dmabuf_memory) { @@ -227,7 +226,10 @@ struct video_frame *frame_new( meta = gst_buffer_get_video_meta(buffer); if (meta != NULL) { gst_ok = gst_video_meta_get_plane_size(meta, plane_sizes); - DEBUG_ASSERT(gst_ok); + if (gst_ok != TRUE) { + LOG_ERROR("Could not query video frame plane size.\n"); + goto fail_close_dmabuf_fd; + } } else { // Taken from: https://github.com/GStreamer/gstreamer/blob/621604aa3e4caa8db27637f63fa55fac2f7721e5/subprojects/gst-plugins-base/gst-libs/gst/video/video-info.c#L1278-L1301 for (int i = 0; i < GST_VIDEO_MAX_PLANES; i++) { @@ -260,7 +262,10 @@ struct video_frame *frame_new( &n_memories, &offset_in_memory ); - DEBUG_ASSERT(gst_ok); + if (gst_ok != TRUE) { + LOG_ERROR("Could not find video frame memory for plane.\n"); + goto fail_close_dmabuf_fd; + } if (n_memories != 1) { LOG_ERROR("Gstreamer Image planes can't span multiple dmabufs.\n"); @@ -437,8 +442,6 @@ struct video_frame *frame_new( fail_close_dmabuf_fd: close(dmabuf_fd); - - fail_free_frame: free(frame); fail_unref_buffer: diff --git a/src/plugins/gstreamer_video_player/plugin.c b/src/plugins/gstreamer_video_player/plugin.c index 84e07808..c7fec988 100644 --- a/src/plugins/gstreamer_video_player/plugin.c +++ b/src/plugins/gstreamer_video_player/plugin.c @@ -193,31 +193,6 @@ static int get_player_from_map_arg( return 0; } -static int get_player_and_meta_from_map_arg( - struct std_value *arg, - struct gstplayer **player_out, - struct gstplayer_meta **meta_out, - FlutterPlatformMessageResponseHandle *responsehandle -) { - struct gstplayer *player; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) { - return ok; - } - - if (player_out) { - *player_out = player; - } - - if (meta_out) { - *meta_out = (struct gstplayer_meta*) gstplayer_get_userdata_locked(player); - } - - return 0; -} - static int ensure_initialized() { GError *gst_error; gboolean success; @@ -266,7 +241,7 @@ static int send_initialized_event(struct gstplayer_meta *meta, bool is_stream, i ); } -static int send_completed_event(struct gstplayer_meta *meta) { +MAYBE_UNUSED static int send_completed_event(struct gstplayer_meta *meta) { return platch_send_success_event_std( meta->event_channel_name, &STDMAP1( diff --git a/src/plugins/text_input.c b/src/plugins/text_input.c index 65a3ca0f..c4b80f2d 100644 --- a/src/plugins/text_input.c +++ b/src/plugins/text_input.c @@ -558,7 +558,7 @@ static int on_receive( } else if STREQ("TextInput.requestAutofill", object->method) { return on_request_autofill(object, responsehandle); } else if STREQ("TextInput.setEditableSizeAndTransform", object->method) { - return on_set_style(object, responsehandle); + return on_set_editable_size_and_transform(object, responsehandle); } else if STREQ("TextInput.setStyle", object->method) { return on_set_style(object, responsehandle); } else if STREQ("TextInput.finishAutofillContext", object->method) { @@ -824,7 +824,7 @@ static bool model_move_cursor_to_end(void) { return false; } -static bool model_move_cursor_forward(void) { +MAYBE_UNUSED static bool model_move_cursor_forward(void) { if (text_input.selection_base != text_input.selection_extent) { text_input.selection_base = text_input.selection_extent; return true; @@ -839,7 +839,7 @@ static bool model_move_cursor_forward(void) { return false; } -static bool model_move_cursor_back(void) { +MAYBE_UNUSED static bool model_move_cursor_back(void) { if (text_input.selection_base != text_input.selection_extent) { text_input.selection_extent = text_input.selection_base; return true; From ee73e93a2231268d385ac16c34c879af6776e2ed Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 9 Feb 2023 19:20:26 +0100 Subject: [PATCH 22/55] check for gstreamer version - improve video frame texture uploading --- CMakeLists.txt | 13 ++ src/plugins/gstreamer_video_player/frame.c | 225 +++++++++++++++------ 2 files changed, 178 insertions(+), 60 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 83778991..0323f6bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,6 +229,19 @@ if (BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) pkg_check_modules(LIBGSTREAMER_VIDEO REQUIRED IMPORTED_TARGET gstreamer-video-1.0) endif() + # There's no other way to query the libinput version (in code) somehow. + # So we need to roll our own libinput version macro + string(REPLACE "." ";" LIBGSTREAMER_VERSION_AS_LIST ${LIBGSTREAMER_VERSION}) + list(GET LIBGSTREAMER_VERSION_AS_LIST 0 LIBGSTREAMER_VERSION_MAJOR) + list(GET LIBGSTREAMER_VERSION_AS_LIST 1 LIBGSTREAMER_VERSION_MINOR) + list(GET LIBGSTREAMER_VERSION_AS_LIST 2 LIBGSTREAMER_VERSION_PATCH) + + target_compile_definitions(flutterpi_module PRIVATE + LIBGSTREAMER_VERSION_MAJOR=${LIBGSTREAMER_VERSION_MAJOR} + LIBGSTREAMER_VERSION_MINOR=${LIBGSTREAMER_VERSION_MINOR} + LIBGSTREAMER_VERSION_PATCH=${LIBGSTREAMER_VERSION_PATCH} + ) + if (LIBGSTREAMER_FOUND AND LIBGSTREAMER_PLUGINS_BASE_FOUND AND LIBGSTREAMER_APP_FOUND AND LIBGSTREAMER_ALLOCATORS_FOUND AND LIBGSTREAMER_VIDEO_FOUND) target_sources(flutterpi_module PRIVATE src/plugins/gstreamer_video_player/plugin.c diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index 163aaf28..a4e965d6 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -17,6 +17,9 @@ FILE_DESCR("gstreamer video_player") #define MAX_N_PLANES 4 +#define GSTREAMER_VER(major, minor, patch) ((((major) & 0xFF) << 16) | (((minor) & 0xFF) << 8) | ((patch) & 0xFF)) +#define THIS_GSTREAMER_VER GSTREAMER_VER(LIBGSTREAMER_VERSION_MAJOR, LIBGSTREAMER_VERSION_MINOR, LIBGSTREAMER_VERSION_PATCH) + struct video_frame { GstSample *sample; @@ -168,82 +171,106 @@ int dup_gst_buffer_as_dmabuf(struct gbm_device *gbm_device, GstBuffer *buffer) { return -1; } -struct video_frame *frame_new( - struct frame_interface *interface, - const struct frame_info *info, - GstSample *sample -) { -# define PUT_ATTR(_key, _value) do { *attr_cursor++ = _key; *attr_cursor++ = _value; } while (false) - struct video_frame *frame; - GstVideoMeta *meta; - EGLBoolean egl_ok; - EGLImage egl_image; - GstBuffer *buffer; - GstMemory *memory; +/** + * @brief Create a dmabuf fd from the given GstMemory. + * + * Calls gst_memory_map on the memory. + * + */ +int dup_gst_memory_as_dmabuf(struct gbm_device *gbm_device, GstMemory *memory) { + struct gbm_bo *bo; + GstMapInfo map_info; + uint32_t stride; gboolean gst_ok; - GLenum gl_error; - EGLint attributes[2*7 + MAX_N_PLANES*2*5 + 1], *attr_cursor; - GLuint texture; - EGLint egl_error; - bool is_dmabuf_memory; - int dmabuf_fd, n_planes, width, height; + void *map, *map_data; + int fd; - struct { - int fd; - int offset; - int pitch; - bool has_modifier; - uint64_t modifier; - } planes[MAX_N_PLANES]; + gst_ok = gst_memory_map(memory, &map_info, GST_MAP_READ); + if (gst_ok == FALSE) { + LOG_ERROR("Couldn't map gstreamer video frame memory to copy it into a dma buffer.\n"); + return -1; + } - buffer = gst_sample_get_buffer(sample); + bo = gbm_bo_create(gbm_device, map_info.size, 1, GBM_FORMAT_R8, GBM_BO_USE_LINEAR); + if (bo == NULL) { + LOG_ERROR("Couldn't create GBM BO to copy video frame into.\n"); + goto fail_unmap_buffer; + } - frame = malloc(sizeof *frame); - if (frame == NULL) { - goto fail_unref_buffer; + map_data = NULL; + map = gbm_bo_map(bo, 0, 0, map_info.size, 1, GBM_BO_TRANSFER_WRITE, &stride, &map_data); + if (map == NULL) { + LOG_ERROR("Couldn't mmap GBM BO to copy video frame into it.\n"); + goto fail_destroy_bo; } - memory = gst_buffer_peek_memory(buffer, 0); - is_dmabuf_memory = gst_is_dmabuf_memory(memory); + memcpy(map, map_info.data, map_info.size); - /// TODO: Do we really need to dup() here? - if (is_dmabuf_memory) { - //dmabuf_fd = dup(gst_dmabuf_memory_get_fd(memory)); - dmabuf_fd = -1; - } else { - dmabuf_fd = dup_gst_buffer_as_dmabuf(interface->gbm_device, buffer); - - //LOG_ERROR("Only dmabuf memory is supported for video frame buffers right now, but gstreamer didn't provide a dmabuf memory buffer.\n"); - //goto fail_free_frame; + gbm_bo_unmap(bo, map_data); + + fd = gbm_bo_get_fd(bo); + if (fd < 0) { + LOG_ERROR("Couldn't filedescriptor of video frame GBM BO.\n"); + goto fail_destroy_bo; } - width = GST_VIDEO_INFO_WIDTH(info->gst_info); - height = GST_VIDEO_INFO_HEIGHT(info->gst_info); - n_planes = GST_VIDEO_INFO_N_PLANES(info->gst_info); + /// TODO: Should we dup the fd before we destroy the bo? + gbm_bo_destroy(bo); + gst_memory_unmap(memory, &map_info); + return fd; + fail_destroy_bo: + gbm_bo_destroy(bo); + + fail_unmap_buffer: + gst_memory_unmap(memory, &map_info); + return -1; +} + +struct plane_info { + int fd; + uint32_t offset; + uint32_t pitch; + bool has_modifier; + uint64_t modifier; +}; + +#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 18, 0) +int get_plane_infos( + GstBuffer *buffer, + const struct frame_info *frame_info, + struct gbm_device *gbm_device, + struct plane_info plane_infos[MAX_N_PLANES] +) { + GstVideoMeta *meta; + GstMemory *memory; + gboolean gst_ok; size_t plane_sizes[4] = {0}; + int n_planes; + + n_planes = GST_VIDEO_INFO_N_PLANES(frame_info->gst_info); meta = gst_buffer_get_video_meta(buffer); if (meta != NULL) { gst_ok = gst_video_meta_get_plane_size(meta, plane_sizes); if (gst_ok != TRUE) { LOG_ERROR("Could not query video frame plane size.\n"); - goto fail_close_dmabuf_fd; + return EIO; } } else { // Taken from: https://github.com/GStreamer/gstreamer/blob/621604aa3e4caa8db27637f63fa55fac2f7721e5/subprojects/gst-plugins-base/gst-libs/gst/video/video-info.c#L1278-L1301 for (int i = 0; i < GST_VIDEO_MAX_PLANES; i++) { - if (i < GST_VIDEO_INFO_N_PLANES(info->gst_info)) { + if (i < GST_VIDEO_INFO_N_PLANES(frame_info->gst_info)) { gint comp[GST_VIDEO_MAX_COMPONENTS]; guint plane_height; - gst_video_format_info_component(info->gst_info->finfo, i, comp); + gst_video_format_info_component(frame_info->gst_info->finfo, i, comp); plane_height = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT( - info->gst_info->finfo, + frame_info->gst_info->finfo, comp[0], - GST_VIDEO_INFO_FIELD_HEIGHT(info->gst_info) + GST_VIDEO_INFO_FIELD_HEIGHT(frame_info->gst_info) ); - plane_sizes[i] = plane_height * GST_VIDEO_INFO_PLANE_STRIDE(info->gst_info, i); + plane_sizes[i] = plane_height * GST_VIDEO_INFO_PLANE_STRIDE(frame_info->gst_info, i); } else { plane_sizes[i] = 0; } @@ -256,7 +283,7 @@ struct video_frame *frame_new( gst_ok = gst_buffer_find_memory( buffer, - meta ? meta->offset[i] : GST_VIDEO_INFO_PLANE_OFFSET(info->gst_info, i), + meta ? meta->offset[i] : GST_VIDEO_INFO_PLANE_OFFSET(frame_info->gst_info, i), plane_sizes[i], &memory_index, &n_memories, @@ -264,21 +291,93 @@ struct video_frame *frame_new( ); if (gst_ok != TRUE) { LOG_ERROR("Could not find video frame memory for plane.\n"); - goto fail_close_dmabuf_fd; + return EIO; } if (n_memories != 1) { LOG_ERROR("Gstreamer Image planes can't span multiple dmabufs.\n"); - goto fail_close_dmabuf_fd; + return EINVAL; } - planes[i].fd = dup(gst_dmabuf_memory_get_fd(gst_buffer_peek_memory(buffer, memory_index))); - planes[i].offset = offset_in_memory; - planes[i].pitch = meta ? meta->stride[i] : GST_VIDEO_INFO_PLANE_STRIDE(info->gst_info, i); - planes[i].has_modifier = false; - planes[i].modifier = DRM_FORMAT_MOD_LINEAR; + memory = gst_buffer_peek_memory(buffer, memory_index); + if (gst_is_dmabuf_memory(memory)) { + plane_infos[i].fd = dup(gst_dmabuf_memory_get_fd(memory)); + if (plane_infos[i].fd < 0) { + LOG_ERROR("Could not get gstreamer memory as dmabuf.\n"); + return EIO; + } + } else { + plane_infos[i].fd = dup_gst_memory_as_dmabuf(gbm_device, memory); + if (plane_infos[i].fd < 0) { + LOG_ERROR("Could not duplicate gstreamer memory as dmabuf.\n"); + return EIO; + } + } + + plane_infos[i].offset = offset_in_memory; + plane_infos[i].pitch = meta ? meta->stride[i] : GST_VIDEO_INFO_PLANE_STRIDE(frame_info->gst_info, i); + + /// TODO: Detect modifiers here + plane_infos[i].has_modifier = false; + plane_infos[i].modifier = DRM_FORMAT_MOD_LINEAR; + } + + return 0; +} +#elif THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 16, 0) +int get_plane_infos( + GstBuffer *buffer, + const struct frame_info *frame_info, + struct plane_info plane_infos[MAX_N_PLANES] +) { + UNIMPLEMENTED(); +} +#elif THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 14, 0) +int get_plane_infos( + GstBuffer *buffer, + const struct frame_info *frame_info, + struct plane_info plane_infos[MAX_N_PLANES] +) { + UNIMPLEMENTED(); +} +#else +# error "Unsupported gstreamer version." +#endif + +struct video_frame *frame_new( + struct frame_interface *interface, + const struct frame_info *info, + GstSample *sample +) { +# define PUT_ATTR(_key, _value) do { *attr_cursor++ = _key; *attr_cursor++ = _value; } while (false) + struct video_frame *frame; + EGLBoolean egl_ok; + EGLImage egl_image; + GstBuffer *buffer; + GLenum gl_error; + EGLint attributes[2*7 + MAX_N_PLANES*2*5 + 1], *attr_cursor; + GLuint texture; + EGLint egl_error; + int ok, n_planes, width, height; + + struct plane_info planes[MAX_N_PLANES] = {0}; + + buffer = gst_sample_get_buffer(sample); + + frame = malloc(sizeof *frame); + if (frame == NULL) { + goto fail_unref_buffer; } + ok = get_plane_infos(buffer, info, interface->gbm_device, planes); + if (ok != 0) { + goto fail_free_frame; + } + + width = GST_VIDEO_INFO_WIDTH(info->gst_info); + height = GST_VIDEO_INFO_HEIGHT(info->gst_info); + n_planes = GST_VIDEO_INFO_N_PLANES(info->gst_info); + attr_cursor = attributes; // first, put some of our basic attributes like @@ -419,8 +518,11 @@ struct video_frame *frame_new( frame->sample = sample; frame->interface = frame_interface_ref(interface); frame->drm_format = info->drm_format; - frame->n_dmabuf_fds = 1; - frame->dmabuf_fds[0] = dmabuf_fd; + frame->n_dmabuf_fds = n_planes; + frame->dmabuf_fds[0] = planes[0].fd; + frame->dmabuf_fds[1] = planes[1].fd; + frame->dmabuf_fds[2] = planes[2].fd; + frame->dmabuf_fds[3] = planes[3].fd; frame->image = egl_image; frame->gl_frame.target = GL_TEXTURE_EXTERNAL_OES; frame->gl_frame.name = texture; @@ -441,7 +543,10 @@ struct video_frame *frame_new( interface->eglDestroyImageKHR(interface->display, egl_image); fail_close_dmabuf_fd: - close(dmabuf_fd); + for (int i = 0; i < n_planes; i++) + close(planes[i].fd); + + fail_free_frame: free(frame); fail_unref_buffer: From 88da964885e8e0d5db7c551cce79b1d6aca00a30 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 9 Feb 2023 20:38:20 +0100 Subject: [PATCH 23/55] differentiate plane uploading by gstreamer version --- src/plugins/gstreamer_video_player/frame.c | 60 ++++++++++++++++++---- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index a4e965d6..1303df9d 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -121,7 +121,7 @@ DEFINE_REF_OPS(frame_interface, n_refs) * Calls gst_buffer_map on the buffer, so buffer could have changed after the call. * */ -int dup_gst_buffer_as_dmabuf(struct gbm_device *gbm_device, GstBuffer *buffer) { +MAYBE_UNUSED int dup_gst_buffer_as_dmabuf(struct gbm_device *gbm_device, GstBuffer *buffer) { struct gbm_bo *bo; GstMapInfo map_info; uint32_t stride; @@ -177,7 +177,7 @@ int dup_gst_buffer_as_dmabuf(struct gbm_device *gbm_device, GstBuffer *buffer) { * Calls gst_memory_map on the memory. * */ -int dup_gst_memory_as_dmabuf(struct gbm_device *gbm_device, GstMemory *memory) { +MAYBE_UNUSED int dup_gst_memory_as_dmabuf(struct gbm_device *gbm_device, GstMemory *memory) { struct gbm_bo *bo; GstMapInfo map_info; uint32_t stride; @@ -324,21 +324,59 @@ int get_plane_infos( return 0; } -#elif THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 16, 0) -int get_plane_infos( - GstBuffer *buffer, - const struct frame_info *frame_info, - struct plane_info plane_infos[MAX_N_PLANES] -) { - UNIMPLEMENTED(); -} #elif THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 14, 0) int get_plane_infos( GstBuffer *buffer, const struct frame_info *frame_info, + struct gbm_device *gbm_device, struct plane_info plane_infos[MAX_N_PLANES] ) { - UNIMPLEMENTED(); + GstVideoMeta *meta; + GstMemory *memory; + gboolean gst_ok; + unsigned int n_mems; + bool is_dmabuf_memory; + int n_planes, dmabuf_fd; + + memory = gst_buffer_peek_memory(buffer, 0); + is_dmabuf_memory = gst_is_dmabuf_memory(memory); + n_mems = gst_buffer_n_memory(buffer); + + /// TODO: Do we really need to dup() here? + if (is_dmabuf_memory) { + dmabuf_fd = dup(gst_dmabuf_memory_get_fd(memory)); + } else { + dmabuf_fd = dup_gst_buffer_as_dmabuf(gbm_device, buffer); + + //LOG_ERROR("Only dmabuf memory is supported for video frame buffers right now, but gstreamer didn't provide a dmabuf memory buffer.\n"); + //goto fail_free_frame; + } + + if (n_mems > 1) { + LOG_ERROR("Multiple dmabufs for a single frame buffer is not supported right now.\n"); + close(dmabuf_fd); + } + + n_planes = GST_VIDEO_INFO_N_PLANES(frame_info->gst_info); + + meta = gst_buffer_get_video_meta(buffer); + if (meta != NULL) { + for (int i = 0; i < n_planes; i++) { + plane_infos[i].fd = dmabuf_fd; + plane_infos[i].offset = meta->offset[i]; + plane_infos[i].pitch = meta->stride[i]; + plane_infos[i].has_modifier = false; + plane_infos[i].modifier = DRM_FORMAT_MOD_LINEAR; + } + } else { + for (int i = 0; i < n_planes; i++) { + plane_infos[i].fd = dmabuf_fd; + plane_infos[i].offset = GST_VIDEO_INFO_PLANE_OFFSET(frame_info->gst_info, i); + plane_infos[i].pitch = GST_VIDEO_INFO_PLANE_STRIDE(frame_info->gst_info, i); + plane_infos[i].has_modifier = false; + plane_infos[i].modifier = DRM_FORMAT_MOD_LINEAR; + } + } } #else # error "Unsupported gstreamer version." From 2220f82fbcd520ddbd18c1adc7189a98921ad484 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Fri, 10 Feb 2023 21:14:32 +0100 Subject: [PATCH 24/55] add support for tiled formats in frame uploading --- src/plugins/gstreamer_video_player/frame.c | 25 +++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index 1303df9d..e88b57df 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -261,16 +261,21 @@ int get_plane_infos( // Taken from: https://github.com/GStreamer/gstreamer/blob/621604aa3e4caa8db27637f63fa55fac2f7721e5/subprojects/gst-plugins-base/gst-libs/gst/video/video-info.c#L1278-L1301 for (int i = 0; i < GST_VIDEO_MAX_PLANES; i++) { if (i < GST_VIDEO_INFO_N_PLANES(frame_info->gst_info)) { - gint comp[GST_VIDEO_MAX_COMPONENTS]; - guint plane_height; - - gst_video_format_info_component(frame_info->gst_info->finfo, i, comp); - plane_height = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT( - frame_info->gst_info->finfo, - comp[0], - GST_VIDEO_INFO_FIELD_HEIGHT(frame_info->gst_info) - ); - plane_sizes[i] = plane_height * GST_VIDEO_INFO_PLANE_STRIDE(frame_info->gst_info, i); + if (GST_VIDEO_FORMAT_INFO_IS_TILED (frame_info->gst_info->finfo)) { + guint x_tiles = GST_VIDEO_TILE_X_TILES (frame_info->gst_info->stride[i]); + guint y_tiles = GST_VIDEO_TILE_Y_TILES (frame_info->gst_info->stride[i]); + plane_sizes[i] = x_tiles * y_tiles * GST_VIDEO_FORMAT_INFO_TILE_SIZE(frame_info->gst_info->finfo, i); + } else { + gint comp[GST_VIDEO_MAX_COMPONENTS]; + guint plane_height; + + /* Convert plane index to component index */ + gst_video_format_info_component (frame_info->gst_info->finfo, i, comp); + plane_height = + GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT(frame_info->gst_info->finfo, comp[0], + GST_VIDEO_INFO_FIELD_HEIGHT(frame_info->gst_info)); + plane_sizes[i] = plane_height * GST_VIDEO_INFO_PLANE_STRIDE(frame_info->gst_info, i); + } } else { plane_sizes[i] = 0; } From ee33a1235eafbb5ddcf2df6960f31b652c851375 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sat, 18 Feb 2023 16:38:04 +0100 Subject: [PATCH 25/55] use new modesetting.c from feature/compositor-ng branch --- CMakeLists.txt | 8 - include/collection.h | 347 ++- include/compositor.h | 155 +- include/flutter-pi.h | 4 + include/modesetting.h | 745 ++++-- include/notifier_listener.h | 2 - include/pixel_format.h | 79 +- src/compositor.c | 1233 ++------- src/flutter-pi.c | 52 +- src/modesetting.c | 2763 ++++++++++++++------ src/pixel_format.c | 19 +- src/plugins/gstreamer_video_player/frame.c | 6 + src/plugins/omxplayer_video_player.c | 1554 ----------- 13 files changed, 3111 insertions(+), 3856 deletions(-) delete mode 100644 src/plugins/omxplayer_video_player.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 0323f6bf..89f1017c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,11 +35,9 @@ project(flutter-pi LANGUAGES C VERSION "1.0.0") message(STATUS "Generator .............. ${CMAKE_GENERATOR}") message(STATUS "Build Type ............. ${CMAKE_BUILD_TYPE}") -option(OMXPLAYER_SUPPORTS_RUNTIME_ROTATION "Whether omxplayer supports runtime rotation." OFF) option(BUILD_TEXT_INPUT_PLUGIN "Include the text input plugin in the finished binary. Enables text input (to flutter text fields, for example) via attached keyboards." ON) option(BUILD_RAW_KEYBOARD_PLUGIN "Include the raw keyboard plugin in the finished binary. Enables raw keycode listening in flutter via the flutter RawKeyboard interface." ON) option(BUILD_TEST_PLUGIN "Include the test plugin in the finished binary. Allows testing platform channel communication." OFF) -option(BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN "Include the omxplayer_video_player plugin in the finished binary. Allows for hardware accelerated video playback in flutter using omxplayer." ON) option(BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN "Include the gstreamer based video plugins in the finished binary. Allows for more stable, hardware accelerated video playback in flutter using gstreamer." ON) option(BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN "Include the gstreamer based audio plugins in the finished binary." ON) option(TRY_BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN "Don't throw an error if the gstreamer libs aren't found, instead just don't build the gstreamer video player plugin in that case." ON) @@ -208,12 +206,6 @@ endif() if (BUILD_TEST_PLUGIN) target_sources(flutterpi_module PRIVATE src/plugins/testplugin.c) endif() -if (BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN) - target_sources(flutterpi_module PRIVATE src/plugins/omxplayer_video_player.c) -endif() -if (OMXPLAYER_SUPPORTS_RUNTIME_ROTATION) - target_compile_definitions(flutterpi_module PRIVATE "OMXPLAYER_SUPPORTS_RUNTIME_ROTATION") -endif() if (BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) if (TRY_BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) pkg_check_modules(LIBGSTREAMER IMPORTED_TARGET gstreamer-1.0) diff --git a/include/collection.h b/include/collection.h index 7f9486e3..de069ad8 100644 --- a/include/collection.h +++ b/include/collection.h @@ -404,11 +404,15 @@ static inline void *memdup(const void *restrict src, const size_t n) { return memcpy(dest, src, n); } -#define BMAP_DECLARATION(name, n_bits) uint8_t name[(((n_bits) - 1) / 8) + 1] -#define BMAP_IS_SET(p_bmap, i_bit) ((p_bmap)[(i_bit) / sizeof(*(p_bmap))] & (1 << ((i_bit) & (sizeof(*(p_bmap)) - 1)))) -#define BMAP_SET(p_bmap, i_bit) ((p_bmap)[(i_bit) / sizeof(*(p_bmap))] |= (1 << ((i_bit) & (sizeof(*(p_bmap)) - 1)))) -#define BMAP_CLEAR(p_bmap, i_bit) ((p_bmap)[(i_bit) / sizeof(*(p_bmap))] &= ~(1 << ((i_bit) & (sizeof(*(p_bmap)) - 1)))) -#define BMAP_ZERO(p_bmap, n_bits) (memset((p_bmap), 0, (((n_bits) - 1) / 8) + 1)) +#define BMAP_ELEMENT_TYPE uint8_t +#define BMAP_ELEMENT_SIZE (sizeof(BMAP_ELEMENT_TYPE)) +#define BMAP_ELEMENT_BITS (BMAP_ELEMENT_SIZE * 8) +#define BMAP_DECLARATION(name, n_bits) BMAP_ELEMENT_TYPE name[(((n_bits) - 1) / BMAP_ELEMENT_BITS) + 1] +#define BMAP_IS_SET(p_bmap, i_bit) ((p_bmap)[(i_bit) / BMAP_ELEMENT_BITS] & (1 << ((i_bit) & (BMAP_ELEMENT_BITS - 1)))) +#define BMAP_SET(p_bmap, i_bit) ((p_bmap)[(i_bit) / BMAP_ELEMENT_BITS] |= (1 << ((i_bit) & (BMAP_ELEMENT_BITS - 1)))) +#define BMAP_CLEAR(p_bmap, i_bit) ((p_bmap)[(i_bit) / BMAP_ELEMENT_BITS] &= ~(1 << ((i_bit) & (BMAP_ELEMENT_BITS - 1)))) +#define BMAP_ZERO(p_bmap) memset((p_bmap), 0, sizeof(p_bmap) / sizeof(*(p_bmap))) +#define BMAP_SIZE(p_bmap) (ARRAY_SIZE(p_bmap) * BMAP_ELEMENT_BITS) #define min(a, b) ((a) < (b) ? (a) : (b)) #define max(a, b) ((a) > (b) ? (a) : (b)) @@ -424,7 +428,7 @@ static inline uint64_t get_monotonic_time(void) { } #define FILE_DESCR(_logging_name) \ -static const char *__file_logging_name = _logging_name; +static const char *__attribute__((unused)) __file_logging_name = _logging_name; #ifdef DEBUG #define DEBUG_ASSERT(__cond) assert(__cond) @@ -443,8 +447,11 @@ static const char *__file_logging_name = _logging_name; #endif #define DEBUG_ASSERT_NOT_NULL(__var) DEBUG_ASSERT(__var != NULL) +#define DEBUG_ASSERT_NOT_NULL_MSG(__var, __msg) DEBUG_ASSERT_MSG(__var != NULL, __msg) #define DEBUG_ASSERT_EQUALS(__a, __b) DEBUG_ASSERT((__a) == (__b)) +#define DEBUG_ASSERT_EQUALS_MSG(__a, __b, __msg) DEBUG_ASSERT_MSG((__a) == (__b), __msg) #define DEBUG_ASSERT_EGL_TRUE(__var) DEBUG_ASSERT((__var) == EGL_TRUE) +#define DEBUG_ASSERT_EGL_TRUE_MSG(__var, __msg) DEBUG_ASSERT_MSG((__var) == EGL_TRUE, __msg) #if !(201112L <= __STDC_VERSION__ || (!defined __STRICT_ANSI__ && (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR >= 6)))) # error "Needs C11 or later or GCC (not in pedantic mode) 4.6.0 or later for compile time asserts." @@ -456,7 +463,7 @@ static const char *__file_logging_name = _logging_name; #define UNIMPLEMENTED() assert(0 && "Unimplemented") #ifndef __has_builtin -#define __has_builtin(x) 0 + #define __has_builtin(x) 0 #endif #if defined(__GNUC__) || __has_builtin(__builtin_unreachable) @@ -465,6 +472,12 @@ static const char *__file_logging_name = _logging_name; #define UNREACHABLE() assert(0 && "Unreachable") #endif +#if defined(__GNUC__) || __has_builtin(__builtin_popcount) +#define HWEIGHT(x) __builtin_popcount(x) +#else +#define HWEIGHT(x) UNIMPLEMENTED() +#endif + #if defined(__GNUC__) || defined(__clang__) #define MAYBE_UNUSED __attribute__((unused)) #define ATTR_MALLOC __attribute__((malloc)) @@ -479,6 +492,7 @@ static const char *__file_logging_name = _logging_name; #define ATTR_CONST #endif + static inline int refcount_inc_n(refcount_t *refcount, int n) { return atomic_fetch_add_explicit(refcount, n, memory_order_relaxed); } @@ -519,50 +533,91 @@ static inline int refcount_get_for_debug(refcount_t *refcount) { #define REFCOUNT_INIT_N(n) (n) #define DECLARE_REF_OPS(obj_name) \ -struct obj_name *obj_name ## _ref(struct obj_name *obj); \ -void obj_name ## _unref(struct obj_name *obj); \ -void obj_name ## _unrefp(struct obj_name **obj); \ +MAYBE_UNUSED struct obj_name *obj_name ## _ref(struct obj_name *obj); \ +MAYBE_UNUSED void obj_name ## _unref(struct obj_name *obj); \ +MAYBE_UNUSED void obj_name ## _unrefp(struct obj_name **obj); \ +MAYBE_UNUSED void obj_name ## _swap_ptrs(struct obj_name **objp, struct obj_name *obj); \ +MAYBE_UNUSED void obj_name ## _unref_void(void *obj); #define DEFINE_REF_OPS(obj_name, refcount_member_name) \ -struct obj_name *obj_name ## _ref(struct obj_name *obj) { \ +MAYBE_UNUSED struct obj_name *obj_name ## _ref(struct obj_name *obj) { \ + refcount_inc(&obj->refcount_member_name); \ + return obj; \ +} \ +MAYBE_UNUSED void obj_name ## _unref(struct obj_name *obj) { \ + if (refcount_dec(&obj->refcount_member_name) == false) { \ + obj_name ## _destroy(obj); \ + } \ +} \ +MAYBE_UNUSED void obj_name ## _unrefp(struct obj_name **obj) { \ + obj_name ## _unref(*obj); \ + *obj = NULL; \ +} \ +MAYBE_UNUSED void obj_name ## _swap_ptrs(struct obj_name **objp, struct obj_name *obj) { \ + if (obj != NULL) { \ + obj_name ## _ref(obj); \ + } \ + if (*objp != NULL) { \ + obj_name ## _unrefp(objp); \ + } \ + *objp = obj; \ +} \ +MAYBE_UNUSED void obj_name ## _unref_void(void *obj) { \ + obj_name ## _unref((struct obj_name*) obj); \ +} + +#define DEFINE_STATIC_REF_OPS(obj_name, refcount_member_name) \ +MAYBE_UNUSED static struct obj_name *obj_name ## _ref(struct obj_name *obj) { \ refcount_inc(&obj->refcount_member_name); \ return obj; \ } \ -void obj_name ## _unref(struct obj_name *obj) { \ +MAYBE_UNUSED static void obj_name ## _unref(struct obj_name *obj) { \ if (refcount_dec(&obj->refcount_member_name) == false) { \ obj_name ## _destroy(obj); \ } \ } \ -void obj_name ## _unrefp(struct obj_name **obj) { \ +MAYBE_UNUSED static void obj_name ## _unrefp(struct obj_name **obj) { \ obj_name ## _unref(*obj); \ *obj = NULL; \ +} \ +MAYBE_UNUSED static void obj_name ## _swap_ptrs(struct obj_name **objp, struct obj_name *obj) { \ + if (obj != NULL) { \ + obj_name ## _ref(obj); \ + } \ + if (*objp != NULL) { \ + obj_name ## _unrefp(objp); \ + } \ + *objp = obj; \ +} \ +MAYBE_UNUSED static void obj_name ## _unref_void(void *obj) { \ + obj_name ## _unref((struct obj_name*) obj); \ } #define DECLARE_LOCK_OPS(obj_name) \ -void obj_name ## _lock(struct obj_name *obj); \ -void obj_name ## _unlock(struct obj_name *obj); +MAYBE_UNUSED void obj_name ## _lock(struct obj_name *obj); \ +MAYBE_UNUSED void obj_name ## _unlock(struct obj_name *obj); #define DEFINE_LOCK_OPS(obj_name, mutex_member_name) \ -void obj_name ## _lock(struct obj_name *obj) { \ +MAYBE_UNUSED void obj_name ## _lock(struct obj_name *obj) { \ pthread_mutex_lock(&obj->mutex_member_name); \ } \ -void obj_name ## _unlock(struct obj_name *obj) { \ +MAYBE_UNUSED void obj_name ## _unlock(struct obj_name *obj) { \ pthread_mutex_unlock(&obj->mutex_member_name); \ } #define DEFINE_STATIC_LOCK_OPS(obj_name, mutex_member_name) \ -static void obj_name ## _lock(struct obj_name *obj) { \ +MAYBE_UNUSED static void obj_name ## _lock(struct obj_name *obj) { \ pthread_mutex_lock(&obj->mutex_member_name); \ } \ -static void obj_name ## _unlock(struct obj_name *obj) { \ +MAYBE_UNUSED static void obj_name ## _unlock(struct obj_name *obj) { \ pthread_mutex_unlock(&obj->mutex_member_name); \ } #define DEFINE_INLINE_LOCK_OPS(obj_name, mutex_member_name) \ -static inline void obj_name ## _lock(struct obj_name *obj) { \ +MAYBE_UNUSED static inline void obj_name ## _lock(struct obj_name *obj) { \ pthread_mutex_lock(&obj->mutex_member_name); \ } \ -static inline void obj_name ## _unlock(struct obj_name *obj) { \ +MAYBE_UNUSED static inline void obj_name ## _unlock(struct obj_name *obj) { \ pthread_mutex_unlock(&obj->mutex_member_name); \ } @@ -576,4 +631,252 @@ static inline uint32_t int32_to_uint32(const int32_t v) { return BITCAST(uint32_t, v); } -#endif \ No newline at end of file +static inline uint64_t int64_to_uint64(const int64_t v) { + return BITCAST(uint64_t, v); +} + +static inline int64_t uint64_to_int64(const uint64_t v) { + return BITCAST(int64_t, v); +} + +static inline int64_t ptr_to_int64(const void *const ptr) { + union { + const void *ptr; + int64_t int64; + } u; + + u.int64 = 0; + u.ptr = ptr; + return u.int64; +} + +static inline void *int64_to_ptr(const int64_t v) { + union { + void *ptr; + int64_t int64; + } u; + + u.int64 = v; + return u.ptr; +} + +static inline int64_t ptr_to_uint32(const void *const ptr) { + union { + const void *ptr; + uint32_t u32; + } u; + + u.u32 = 0; + u.ptr = ptr; + return u.u32; +} + +static inline void *uint32_to_ptr(const uint32_t v) { + union { + void *ptr; + uint32_t u32; + } u; + + u.ptr = NULL; + u.u32 = v; + return u.ptr; +} + +#define CONTAINER_OF(container_type, field_ptr, field_name) ({ \ + const typeof( ((container_type*) 0)->field_name ) *__field_ptr_2 = (field_ptr); \ + (container_type*) ((char*) __field_ptr_2 - offsetof(container_type, field_name)); \ +}) + +#define MAX_ALIGNMENT (__alignof__(max_align_t)) +#define IS_MAX_ALIGNED(num) ((num) % MAX_ALIGNMENT == 0) + +typedef struct { + uint8_t bytes[16]; +} uuid_t; +#define UUID(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) ((uuid_t) {.bytes = {_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15}}) +#define CONST_UUID(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) ((const uuid_t) {.bytes = {_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15}}) + + +static inline bool uuid_equals(const uuid_t a, const uuid_t b) { + return memcmp(&a, &b, sizeof(uuid_t)) == 0; +} + +static inline void uuid_copy(uuid_t *dst, const uuid_t src) { + memcpy(dst, &src, sizeof(uuid_t)); +} + +#define DOUBLE_TO_FP1616(v) ((uint32_t) ((v) * 65536)) +#define DOUBLE_TO_FP1616_ROUNDED(v) (((uint32_t) (v)) << 16) + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + +typedef void (*void_callback_t)(void *userdata); + +/** + * @brief A 2-dimensional vector with 2 float coordinates. + * + */ +struct vec2f { + double x, y; +}; + +#define VEC2F(_x, _y) ((struct vec2f) {.x = _x, .y = _y}) + +/** + * @brief A quadrilateral with 4 2-dimensional float coordinates. + * + */ +struct quad { + struct vec2f top_left, top_right, bottom_left, bottom_right; +}; + +#define QUAD(_top_left, _top_right, _bottom_left, _bottom_right) ((struct quad) {.top_left = _top_left, .top_right = _top_right, .bottom_left = _bottom_left, .bottom_right = _bottom_right}) +#define QUAD_FROM_COORDS(_x1, _y1, _x2, _y2, _x3, _y3, _x4, _y4) QUAD(VEC2F(_x1, _y1), VEC2F(_x2, _y2), VEC2F(_x3, _y3), VEC2F(_x4, _y4)) + +struct aa_rect { + struct vec2f offset, size; +}; + +#define AA_RECT(_offset, _size) ((struct aa_rect) {.offset = offset, .size = size}) +#define AA_RECT_FROM_COORDS(offset_x, offset_y, width, height) ((struct aa_rect) {.offset = VEC2F(offset_x, offset_y), .size = VEC2F(width, height)}) + +ATTR_CONST static inline struct aa_rect get_aa_bounding_rect(const struct quad _rect) { + double l = min(min(min(_rect.top_left.x, _rect.top_right.x), _rect.bottom_left.x), _rect.bottom_right.x); + double r = max(max(max(_rect.top_left.x, _rect.top_right.x), _rect.bottom_left.x), _rect.bottom_right.x); + double t = min(min(min(_rect.top_left.y, _rect.top_right.y), _rect.bottom_left.y), _rect.bottom_right.y); + double b = max(max(max(_rect.top_left.y, _rect.top_right.y), _rect.bottom_left.y), _rect.bottom_right.y); + return AA_RECT_FROM_COORDS(l, t, r - l, b - t); +} + +ATTR_CONST static inline struct quad get_quad(const struct aa_rect rect) { + return (struct quad) { + .top_left = rect.offset, + .top_right.x = rect.offset.x + rect.size.x, + .top_right.y = rect.offset.y, + .bottom_left.x = rect.offset.x, + .bottom_left.y = rect.offset.y + rect.size.y, + .bottom_right.x = rect.offset.x + rect.size.x, + .bottom_right.y = rect.offset.y + rect.size.y + }; +} + +struct mat3f { + double scaleX; + double skewX; + double transX; + double skewY; + double scaleY; + double transY; + double pers0; + double pers1; + double pers2; +}; + +#define FLUTTER_TRANSFORM_AS_MAT3F(_transform) (*(struct mat3f*) &(_transform)) +#define MAT3F_AS_FLUTTER_TRANSFORM(_transform) (*(FlutterTransformation*) &(_transform)) + + +#define MAT3F_TRANSLATION(translate_x, translate_y) ((struct mat3f) \ + {.scaleX = 1, .skewX = 0, .transX = translate_x, \ + .skewY = 0, .scaleY = 1, .transY = translate_y, \ + .pers0 = 0, .pers1 = 0, .pers2 = 1}) + +/** + * @brief A flutter transformation that rotates any coords around the x-axis, counter-clockwise. + */ +#define MAT3F_ROTX(deg) ((struct mat3f) \ + {.scaleX = 1, .skewX = 0, .transX = 0, \ + .skewY = 0, .scaleY = cos(((double) (deg))/180.0*M_PI), .transY = -sin(((double) (deg))/180.0*M_PI), \ + .pers0 = 0, .pers1 = sin(((double) (deg))/180.0*M_PI), .pers2 = cos(((double) (deg))/180.0*M_PI)}) + +/** + * @brief A flutter transformation that rotates any coords around the y-axis, counter-clockwise. + */ +#define MAT3F_ROTY(deg) ((struct mat3f) \ + {.scaleX = cos(((double) (deg))/180.0*M_PI), .skewX = 0, .transX = sin(((double) (deg))/180.0*M_PI), \ + .skewY = 0, .scaleY = 1, .transY = 0, \ + .pers0 = -sin(((double) (deg))/180.0*M_PI), .pers1 = 0, .pers2 = cos(((double) (deg))/180.0*M_PI)}) + +/** + * @brief A flutter transformation that rotates any coords around the z-axis, counter-clockwise. + */ +#define MAT3F_ROTZ(deg) ((struct mat3f) \ + {.scaleX = cos(((double) (deg))/180.0*M_PI), .skewX = -sin(((double) (deg))/180.0*M_PI), .transX = 0, \ + .skewY = sin(((double) (deg))/180.0*M_PI), .scaleY = cos(((double) (deg))/180.0*M_PI), .transY = 0, \ + .pers0 = 0, .pers1 = 0, .pers2 = 1}) + +/** + * @brief Returns a matrix that is the result of matrix-multiplying a with b. + * + * @param a The first (lhs) input matrix. + * @param b The second (rhs) input matrix. + * @return struct mat3f The product of a x b. + */ +ATTR_CONST static inline struct mat3f multiply_mat3f(const struct mat3f a, const struct mat3f b) { + return (struct mat3f) { + .scaleX = a.scaleX * b.scaleX + a.skewX * b.skewY + a.transX * b.pers0, + .skewX = a.scaleX * b.skewX + a.skewX * b.scaleY + a.transX * b.pers1, + .transX = a.scaleX * b.transX + a.skewX * b.transY + a.transX * b.pers2, + .skewY = a.skewY * b.scaleX + a.scaleY * b.skewY + a.transY * b.pers0, + .scaleY = a.skewY * b.skewX + a.scaleY * b.scaleY + a.transY * b.pers1, + .transY = a.skewY * b.transX + a.scaleY * b.transY + a.transY * b.pers2, + .pers0 = a.pers0 * b.scaleX + a.pers1 * b.skewY + a.pers2 * b.pers0, + .pers1 = a.pers0 * b.skewX + a.pers1 * b.scaleY + a.pers2 * b.pers1, + .pers2 = a.pers0 * b.transX + a.pers1 * b.transY + a.pers2 * b.pers2 + }; +} + +/** + * @brief Returns a matrix that is the result of element-wise addition of a and b. + * + * @param a The lhs input matrix. + * @param b The rhs input matrix. + * @return struct mat3f The result of a + b. (element-wise) + */ +ATTR_CONST static inline struct mat3f add_mat3f(const struct mat3f a, const struct mat3f b) { + return (struct mat3f) { + .scaleX = a.scaleX + b.scaleX, .skewX = a.skewX + b.skewX, .transX = a.transX + b.transX, + .skewY = a.skewY + b.skewY, .scaleY = a.scaleY + b.scaleY, .transY = a.transY + b.transY, + .pers0 = a.pers0 + b.pers0, .pers1 = a.pers1 + b.pers1, .pers2 = a.pers2 + b.pers2 + }; +} + +/** + * @brief Returns the transponated of a. + * + * @param a The input matrix. + * @return struct mat3f a transponated. + */ +ATTR_CONST static inline struct mat3f transponate_mat3f(const struct mat3f a) { + return (struct mat3f) { + .scaleX = a.scaleX, .skewX = a.skewY, .transX = a.pers0, + .skewY = a.skewX, .scaleY = a.scaleY, .transY = a.pers1, + .pers0 = a.transX, .pers1 = a.transY, .pers2 = a.pers2, + }; +} + +ATTR_CONST static inline struct vec2f transform_point(const struct mat3f transform, const struct vec2f point) { + return VEC2F( + transform.scaleX*point.x + transform.skewX*point.y + transform.transX, + transform.skewY*point.x + transform.scaleY*point.y + transform.transY + ); +} + +ATTR_CONST static inline struct quad transform_quad(const struct mat3f transform, const struct quad rect) { + return QUAD( + transform_point(transform, rect.top_left), + transform_point(transform, rect.top_right), + transform_point(transform, rect.bottom_left), + transform_point(transform, rect.bottom_right) + ); +} + +ATTR_CONST static inline struct quad transform_aa_rect(const struct mat3f transform, const struct aa_rect rect) { + return transform_quad(transform, get_quad(rect)); +} + +ATTR_CONST static inline struct vec2f vec2f_swap_xy(const struct vec2f point) { + return VEC2F(point.y, point.x); +} + +#endif diff --git a/include/compositor.h b/include/compositor.h index eb06a719..84fa9c5d 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -11,92 +11,13 @@ struct platform_view_params; -typedef int (*platform_view_mount_cb)( - int64_t view_id, - struct drmdev_atomic_req *req, - const struct platform_view_params *params, - int zpos, - void *userdata -); - -typedef int (*platform_view_unmount_cb)( - int64_t view_id, - struct drmdev_atomic_req *req, - void *userdata -); - -typedef int (*platform_view_update_view_cb)( - int64_t view_id, - struct drmdev_atomic_req *req, - const struct platform_view_params *params, - int zpos, - void *userdata -); - typedef int (*platform_view_present_cb)( int64_t view_id, - struct drmdev_atomic_req *req, + struct kms_req_builder *builder, const struct platform_view_params *params, - int zpos, void *userdata ); -struct point { - double x, y; -}; - -struct quad { - struct point top_left, top_right, bottom_left, bottom_right; -}; - -struct aa_rect { - struct point offset, size; -}; - -static inline struct aa_rect get_aa_bounding_rect(const struct quad _rect) { - double l = min(min(min(_rect.top_left.x, _rect.top_right.x), _rect.bottom_left.x), _rect.bottom_right.x); - double r = max(max(max(_rect.top_left.x, _rect.top_right.x), _rect.bottom_left.x), _rect.bottom_right.x); - double t = min(min(min(_rect.top_left.y, _rect.top_right.y), _rect.bottom_left.y), _rect.bottom_right.y); - double b = max(max(max(_rect.top_left.y, _rect.top_right.y), _rect.bottom_left.y), _rect.bottom_right.y); - - return (struct aa_rect) { - .offset.x = l, - .offset.y = t, - .size.x = r - l, - .size.y = b - t - }; -} - -static inline struct quad get_quad(const struct aa_rect rect) { - return (struct quad) { - .top_left = rect.offset, - .top_right.x = rect.offset.x + rect.size.x, - .top_right.y = rect.offset.y, - .bottom_left.x = rect.offset.x, - .bottom_left.y = rect.offset.y + rect.size.y, - .bottom_right.x = rect.offset.x + rect.size.x, - .bottom_right.y = rect.offset.y + rect.size.y - }; -} - -static inline void apply_transform_to_point(const FlutterTransformation transform, struct point *point) { - apply_flutter_transformation(transform, &point->x, &point->y); -} - -static inline void apply_transform_to_quad(const FlutterTransformation transform, struct quad *rect) { - apply_transform_to_point(transform, &rect->top_left); - apply_transform_to_point(transform, &rect->top_right); - apply_transform_to_point(transform, &rect->bottom_left); - apply_transform_to_point(transform, &rect->bottom_right); -} - -static inline struct quad apply_transform_to_aa_rect(const FlutterTransformation transform, const struct aa_rect rect) { - struct quad _quad = get_quad(rect); - apply_transform_to_quad(transform, &_quad); - return _quad; -} - - struct platform_view_params { struct quad rect; double rotation; @@ -179,14 +100,6 @@ struct compositor { bool do_blocking_atomic_commits; }; -/* -struct window_surface_backing_store { - struct compositor *compositor; - struct gbm_surface *gbm_surface; - struct gbm_bo *current_front_bo; - uint32_t drm_plane_id; -}; -*/ struct drm_rbo { EGLImage egl_image; @@ -201,54 +114,11 @@ struct drm_fb { uint32_t fb_id; }; -/* -struct drm_fb_backing_store { - struct compositor *compositor; - - GLuint gl_fbo_id; - struct drm_rbo rbos[2]; - - // The front FB is the one GL is rendering to right now, similiar - // to libgbm. - int current_front_rbo; - - uint32_t drm_plane_id; -}; - -enum backing_store_type { - kWindowSurface, - kDrmFb -}; - -struct backing_store_metadata { - enum backing_store_type type; - union { - struct window_surface_backing_store window_surface; - struct drm_fb_backing_store drm_fb; - }; -}; -*/ - struct rendertarget_gbm { struct gbm_surface *gbm_surface; struct gbm_bo *current_front_bo; }; -/** - * @brief No-GBM Rendertarget. - * A type of rendertarget that is not backed by a GBM-Surface, used for rendering into DRM overlay planes. - */ -struct rendertarget_nogbm { - GLuint gl_fbo_id; - struct drm_rbo rbos[2]; - - /** - * @brief The index of the @ref drm_rbo in the @ref rendertarget_nogbm::rbos array that - * OpenGL is currently rendering into. - */ - int current_front_rbo; -}; - struct rendertarget { bool is_gbm; @@ -256,7 +126,6 @@ struct rendertarget { union { struct rendertarget_gbm gbm; - struct rendertarget_nogbm nogbm; }; GLuint gl_fbo_id; @@ -264,24 +133,7 @@ struct rendertarget { void (*destroy)(struct rendertarget *target); int (*present)( struct rendertarget *target, - struct drmdev_atomic_req *atomic_req, - uint32_t drm_plane_id, - int offset_x, - int offset_y, - int width, - int height, - int zpos - ); - int (*present_legacy)( - struct rendertarget *target, - struct drmdev *drmdev, - uint32_t drm_plane_id, - int offset_x, - int offset_y, - int width, - int height, - int zpos, - bool set_mode + struct kms_req_builder *builder ); }; @@ -300,9 +152,6 @@ int compositor_on_page_flip( int compositor_set_view_callbacks( int64_t view_id, - platform_view_mount_cb mount, - platform_view_unmount_cb unmount, - platform_view_update_view_cb update_view, platform_view_present_cb present, void *userdata ); diff --git a/include/flutter-pi.h b/include/flutter-pi.h index 3849e078..50ab27a2 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -242,6 +242,10 @@ struct flutterpi { drmEventContext evctx; sd_event_source *drm_pageflip_event_source; bool platform_supports_get_sequence_ioctl; + + uint32_t selected_crtc_id; + uint32_t selected_connector_id; + const drmModeModeInfo *selected_mode; } drm; struct { diff --git a/include/modesetting.h b/include/modesetting.h index 8ea1a766..26969d49 100644 --- a/include/modesetting.h +++ b/include/modesetting.h @@ -1,307 +1,590 @@ -#ifndef _MODESETTING_H -#define _MODESETTING_H +// SPDX-License-Identifier: MIT +/* + * KMS Modesetting + * + * - implements the interface to linux kernel modesetting + * - allows querying connected screens, crtcs, planes, etc + * - allows setting video modes, showing things on screen + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_INCLUDE_MODESETTING_H +#define _FLUTTERPI_INCLUDE_MODESETTING_H #include + #include #include #include #include +#include + +#define DRM_ID_NONE ((uint32_t) 0xFFFFFFFF) + +#define DRM_ID_IS_VALID(id) ((id) != 0 && (id) != DRM_ID_NONE) + +// All commented out properties are not present on the RPi. +// Some of not-commented out properties we don't make usage of, +// but they could be useful in the future. +#define DRM_CONNECTOR_PROPERTIES(V) \ + V("Broadcast RGB", broadcast_rgb) \ + V("CRTC_ID", crtc_id) \ + V("Colorspace", colorspace) \ + /* V("Content Protection", content_protection) */ \ + V("DPMS", dpms) \ + V("EDID", edid) \ + /* V("HDCP Content Type", hdcp_content_type) */ \ + V("HDR_OUTPUT_METADATA", hdr_output_metadata) \ + /* V("HDR_SOURCE_METADATA", hdr_source_metadata) */ \ + /* V("PATH", path) */ \ + V("TILE", tile) \ + V("WRITEBACK_FB_ID", writeback_fb_id) \ + V("WRITEBACK_OUT_FENCE_PTR", writeback_out_fence_ptr) \ + V("WRITEBACK_PIXEL_FORMATS", writeback_pixel_formats) \ + /* V("abm level", abm_level) */ \ + /* V("aspect ratio", aspect_ratio) */ \ + /* V("audio", audio) */ \ + /* V("backlight", backlight) */ \ + V("bottom margin", bottom_margin) \ + /* V("coherent", coherent) */ \ + /* V("color vibrance", color_vibrance) */ \ + /* V("content type", content_type) */ \ + /* V("dither", dither) */ \ + /* V("dithering depth", dithering_depth) */ \ + /* V("dithering mode", dithering_mode) */ \ + /* V("flicker reduction", flicker_reduction) */ \ + /* V("hotplug_mode_update", hotplug_mode_update) */ \ + /* V("hue", hue) */ \ + V("left margin", left_margin) \ + V("link-status", link_status) \ + /* V("load detection", load_detection) */ \ + V("max bpc", max_bpc) \ + V("mode", mode) \ + V("non-desktop", non_desktop) \ + /* V("output_csc", output_csc) */ \ + /* V("overscan", overscan) */ \ + /* V("panel orientation", panel_orientation) */ \ + V("right margin", right_margin) \ + /* V("saturation", saturation) */ \ + /* V("scaling mode", scaling_mode) */ \ + /* V("select subconnector", select_subconnector) */ \ + /* V("subconnector", subconnector) */ \ + /* V("suggested X", suggested_x) */ \ + /* V("suggested Y", suggested_y) */ \ + V("top margin", top_margin) \ + /* V("tv standard", tv_standard) */ \ + /* V("underscan", underscan) */ \ + /* V("underscan hborder", underscan_hborder) */ \ + /* V("underscan vborder", underscan_vborder) */ \ + /* V("vibrant hue", vibrant_hue) */ \ + /* V("vrr_capable", vrr_capable) */ + +#define DRM_CRTC_PROPERTIES(V) \ + V("ACTIVE", active) \ + V("CTM", ctm) \ + /* V("DEGAMMA_LUT", degamma_lut) */ \ + /* V("DEGAMMA_LUT_SIZE", degamma_lut_size) */ \ + V("GAMMA_LUT", gamma_lut) \ + V("GAMMA_LUT_SIZE", gamma_lut_size) \ + V("MODE_ID", mode_id) \ + V("OUT_FENCE_PTR", out_fence_ptr) \ + /* V("SCALING_FILTER", scaling_filter) */ \ + V("VRR_ENABLED", vrr_enabled) \ + V("rotation", rotation) \ + /* V("zorder", zorder) */ + +#define DRM_PLANE_PROPERTIES(V) \ + V("COLOR_ENCODING", color_encoding) \ + V("COLOR_RANGE", color_range) \ + V("CRTC_ID", crtc_id) \ + V("CRTC_H", crtc_h) \ + V("CRTC_W", crtc_w) \ + V("CRTC_X", crtc_x) \ + V("CRTC_Y", crtc_y) \ + /* V("FB_DAMAGE_CLIPS", fb_damage_clips) */ \ + V("FB_ID", fb_id) \ + V("IN_FENCE_FD", in_fence_fd) \ + V("IN_FORMATS", in_formats) \ + /* V("SCALING_FILTER", scaling_filter) */ \ + V("SRC_H", src_h) \ + V("SRC_W", src_w) \ + V("SRC_X", src_x) \ + V("SRC_Y", src_y) \ + V("alpha", alpha) \ + /* V("brightness", brightness) */ \ + /* V("colorkey", colorkey) */ \ + /* V("contrast", contrast) */ \ + /* V("hue", hue) */ \ + V("pixel blend mode", pixel_blend_mode) \ + V("rotation", rotation) \ + /* V("saturation", saturation) */ \ + V("type", type) \ + /* V("zorder", zorder) */ \ + V("zpos", zpos) + +#define DECLARE_PROP_ID_AS_UINT32(prop_name, prop_var_name) uint32_t prop_var_name; + +#define DRM_BLEND_ALPHA_OPAQUE 0xFFFF + +enum drm_blend_mode { + kPremultiplied_DrmBlendMode, + kCoverage_DrmBlendMode, + kNone_DrmBlendMode, + + kMax_DrmBlendMode = kNone_DrmBlendMode, + kCount_DrmBlendMode = kMax_DrmBlendMode + 1 +}; -struct drm_connector { - drmModeConnector *connector; - drmModeObjectProperties *props; - drmModePropertyRes **props_info; +struct drm_connector_prop_ids { + DRM_CONNECTOR_PROPERTIES(DECLARE_PROP_ID_AS_UINT32) }; -struct drm_encoder { - drmModeEncoder *encoder; +static inline void drm_connector_prop_ids_init(struct drm_connector_prop_ids *ids) { + memset(ids, 0xFF, sizeof(*ids)); +} + +struct drm_crtc_prop_ids { + DRM_CRTC_PROPERTIES(DECLARE_PROP_ID_AS_UINT32) }; -struct drm_crtc { - drmModeCrtc *crtc; - drmModeObjectProperties *props; - drmModePropertyRes **props_info; - uint32_t bitmask; - uint8_t index; +static inline void drm_crtc_prop_ids_init(struct drm_crtc_prop_ids *ids) { + memset(ids, 0xFF, sizeof(*ids)); +} + +struct drm_plane_prop_ids { + DRM_PLANE_PROPERTIES(DECLARE_PROP_ID_AS_UINT32) }; -struct drm_plane { - int type; - drmModePlane *plane; - drmModeObjectProperties *props; - drmModePropertyRes **props_info; +static inline void drm_plane_prop_ids_init(struct drm_plane_prop_ids *ids) { + memset(ids, 0xFF, sizeof(*ids)); +} + +#undef DECLARE_PROP_ID_AS_UINT32 + +// This is quite hacky, but if it works it pretty nice. +// There's asserts in modesetting.c (fn assert_rotations_work()) that make sure these rotations all work as expected. +// If any of these fail we gotta use a more conservative approach instead. +typedef struct { + union { + struct { + bool rotate_0 : 1; + bool rotate_90 : 1; + bool rotate_180 : 1; + bool rotate_270 : 1; + bool reflect_x : 1; + bool reflect_y : 1; + }; + uint32_t u32; + uint64_t u64; + }; +} drm_plane_transform_t; + +#define PLANE_TRANSFORM_NONE ((const drm_plane_transform_t){ .u32 = 0 }) +#define PLANE_TRANSFORM_ROTATE_0 ((const drm_plane_transform_t){ .u32 = DRM_MODE_ROTATE_0 }) +#define PLANE_TRANSFORM_ROTATE_90 ((const drm_plane_transform_t){ .u32 = DRM_MODE_ROTATE_90 }) +#define PLANE_TRANSFORM_ROTATE_180 ((const drm_plane_transform_t){ .u32 = DRM_MODE_ROTATE_180 }) +#define PLANE_TRANSFORM_ROTATE_270 ((const drm_plane_transform_t){ .u32 = DRM_MODE_ROTATE_270 }) +#define PLANE_TRANSFORM_REFLECT_X ((const drm_plane_transform_t){ .u32 = DRM_MODE_REFLECT_X }) +#define PLANE_TRANSFORM_REFLECT_Y ((const drm_plane_transform_t){ .u32 = DRM_MODE_REFLECT_Y }) + +#define PLANE_TRANSFORM_IS_VALID(t) (((t).u64 & ~(DRM_MODE_ROTATE_MASK | DRM_MODE_REFLECT_MASK)) == 0) +#define PLANE_TRANSFORM_IS_ONLY_ROTATION(t) (((t).u64 & ~DRM_MODE_ROTATE_MASK) == 0 && (HWEIGHT((t).u64) == 1)) +#define PLANE_TRANSFORM_IS_ONLY_REFLECTION(t) (((t).u64 & ~DRM_MODE_REFLECT_MASK) == 0 && (HWEIGHT((t).u64) == 1)) + +#define PLANE_TRANSFORM_ROTATE_CW(t) \ + (assert(PLANE_TRANSFORM_IS_ONLY_ROTATION(t)), \ + (t).u64 == DRM_MODE_ROTATE_0 ? PLANE_TRANSFORM_ROTATE_90 : \ + (t).u64 == DRM_MODE_ROTATE_90 ? PLANE_TRANSFORM_ROTATE_180 : \ + (t).u64 == DRM_MODE_ROTATE_180 ? PLANE_TRANSFORM_ROTATE_270 : \ + PLANE_TRANSFORM_ROTATE_0) + +#define PLANE_TRANSFORM_ROTATE_CCW(t) \ + (assert(PLANE_TRANSFORM_IS_ONLY_ROTATION(t)), \ + (t).u64 == DRM_MODE_ROTATE_0 ? PLANE_TRANSFORM_ROTATE_270 : \ + (t).u64 == DRM_MODE_ROTATE_90 ? PLANE_TRANSFORM_ROTATE_0 : \ + (t).u64 == DRM_MODE_ROTATE_180 ? PLANE_TRANSFORM_ROTATE_90 : \ + PLANE_TRANSFORM_ROTATE_180) + +/* +enum drm_plane_rotation { + kRotate0_DrmPlaneRotation = DRM_MODE_ROTATE_0, + kRotate90_DrmPlaneRotation = DRM_MODE_ROTATE_90, + kRotate180_DrmPlaneRotation = DRM_MODE_ROTATE_180, + kRotate270_DrmPlaneRotation = DRM_MODE_ROTATE_270, + kReflectX_DrmPlaneRotation = DRM_MODE_REFLECT_X, + kReflectY_DrmPlaneRotation = DRM_MODE_REFLECT_Y }; +*/ -struct drmdev { - int fd; +enum drm_plane_type { + kPrimary_DrmPlaneType = DRM_PLANE_TYPE_PRIMARY, + kOverlay_DrmPlaneType = DRM_PLANE_TYPE_OVERLAY, + kCursor_DrmPlaneType = DRM_PLANE_TYPE_CURSOR +}; - pthread_mutex_t mutex; - bool supports_atomic_modesetting; +struct drm_mode_blob { + int drm_fd; + uint32_t blob_id; + drmModeModeInfo mode; +}; - size_t n_connectors; - struct drm_connector *connectors; +enum drm_connector_type { + kUnknown_DrmConnectorType = DRM_MODE_CONNECTOR_Unknown, + kVGA_DrmConnectorType = DRM_MODE_CONNECTOR_VGA, + kDVII_DrmConnectorType = DRM_MODE_CONNECTOR_DVII, + kDVID_DrmConnectorType = DRM_MODE_CONNECTOR_DVID, + kDVIA_DrmConnectorType = DRM_MODE_CONNECTOR_DVIA, + kComposite_DrmConnectorType = DRM_MODE_CONNECTOR_Composite, + kSVIDEO_DrmConnectorType = DRM_MODE_CONNECTOR_SVIDEO, + kLVDS_DrmConnectorType = DRM_MODE_CONNECTOR_LVDS, + kComponent_DrmConnectorType = DRM_MODE_CONNECTOR_Component, + k9PinDIN_DrmConnectorType = DRM_MODE_CONNECTOR_9PinDIN, + kDisplayPort_DrmConnectorType = DRM_MODE_CONNECTOR_DisplayPort, + kHDMIA_DrmConnectorType = DRM_MODE_CONNECTOR_HDMIA, + kHDMIB_DrmConnectorType = DRM_MODE_CONNECTOR_HDMIB, + kTV_DrmConnectorType = DRM_MODE_CONNECTOR_TV, + keDP_DrmConnectorType = DRM_MODE_CONNECTOR_eDP, + kVIRTUAL_DrmConnectorType = DRM_MODE_CONNECTOR_VIRTUAL, + kDSI_DrmConnectorType = DRM_MODE_CONNECTOR_DSI, + kDPI_DrmConnectorType = DRM_MODE_CONNECTOR_DPI, + kWRITEBACK_DrmConnectorType = DRM_MODE_CONNECTOR_WRITEBACK, +#ifdef DRM_MODE_CONNECTOR_SPI + kSPI_DrmConnectorType = DRM_MODE_CONNECTOR_SPI +#endif +}; - size_t n_encoders; - struct drm_encoder *encoders; +enum drm_connection_state { + kConnected_DrmConnectionState = DRM_MODE_CONNECTED, + kDisconnected_DrmConnectionState = DRM_MODE_DISCONNECTED, + kUnknown_DrmConnectionState = DRM_MODE_UNKNOWNCONNECTION +}; - size_t n_crtcs; - struct drm_crtc *crtcs; +enum drm_subpixel_layout { + kUnknown_DrmSubpixelLayout = DRM_MODE_SUBPIXEL_UNKNOWN, + kHorizontalRRB_DrmSubpixelLayout = DRM_MODE_SUBPIXEL_HORIZONTAL_RGB, + kHorizontalBGR_DrmSubpixelLayout = DRM_MODE_SUBPIXEL_HORIZONTAL_BGR, + kVerticalRGB_DrmSubpixelLayout = DRM_MODE_SUBPIXEL_VERTICAL_RGB, + kVerticalBGR_DrmSubpixelLayout = DRM_MODE_SUBPIXEL_VERTICAL_BGR, + kNone_DrmSubpixelLayout = DRM_MODE_SUBPIXEL_NONE +}; - size_t n_planes; - struct drm_plane *planes; +struct drm_connector { + uint32_t id; - drmModeRes *res; - drmModePlaneRes *plane_res; + enum drm_connector_type type; + uint32_t type_id; - bool is_configured; - const struct drm_connector *selected_connector; - const struct drm_encoder *selected_encoder; - const struct drm_crtc *selected_crtc; - const drmModeModeInfo *selected_mode; - uint32_t selected_mode_blob_id; + struct drm_connector_prop_ids ids; + + int n_encoders; + uint32_t encoders[32]; + + struct { + enum drm_connection_state connection_state; + enum drm_subpixel_layout subpixel_layout; + uint32_t width_mm, height_mm; + uint32_t n_modes; + drmModeModeInfo *modes; + } variable_state; + + struct { + uint32_t crtc_id; + uint32_t encoder_id; + } committed_state; +}; + +struct drm_encoder { + drmModeEncoder *encoder; }; -struct drmdev_atomic_req { - struct drmdev *drmdev; - drmModeAtomicReq *atomic_req; +struct drm_crtc { + uint32_t id; + uint32_t bitmask; + uint8_t index; + + struct drm_crtc_prop_ids ids; - void *available_planes_storage[32]; - struct pointer_set available_planes; + struct { + bool has_mode; + drmModeModeInfo mode; + struct drm_mode_blob *mode_blob; + } committed_state; }; -int drmdev_new_from_fd( - struct drmdev **drmdev_out, - int fd -); +struct modified_format { + enum pixfmt format; + uint64_t modifier; +}; -int drmdev_new_from_path( - struct drmdev **drmdev_out, - const char *path -); +struct drm_plane { + uint32_t id; + + /** + * @brief Bitmap of the indexes of the CRTCs that this plane can be scanned out on. + * + * i.e. if bit 0 is set, this plane can be scanned out on the CRTC with index 0. + * if bit 0 is not set, this plane can not be scanned out on that CRTC. + * + */ + uint32_t possible_crtcs; + + /// The ids of all properties associated with this plane. + /// Any property that is not supported has the value DRM_PLANE_ID_NONE + struct drm_plane_prop_ids ids; + + /// The type of this plane (primary, overlay, cursor) + /// The type has some influence on what you can do with the plane. + /// For example, it's possible the driver enforces the primary plane to be + /// the bottom-most plane or have an opaque pixel format. + enum drm_plane_type type; + + /// True if this plane has a zpos property, whether readonly (hardcoded) + /// or read/write. + /// The docs say if one plane has a zpos property, all planes should have one. + bool has_zpos; + + /// The minimum and maximum possible zpos, if @ref has_zpos is true. + /// If @ref has_hardcoded_zpos is true, min_zpos should equal max_zpos. + int64_t min_zpos, max_zpos; + + /// True if this plane has a hardcoded zpos that can't + /// be changed by userspace. + bool has_hardcoded_zpos; + + /// The specific hardcoded zpos of this plane. Only valid if + /// @ref has_hardcoded_zpos is true. + int64_t hardcoded_zpos; + + /// True if this plane has a rotation property. + bool has_rotation; + + /// Query the set booleans of the supported_rotations struct + /// to find out of a given rotation is supported. + /// It is assumed if both a and b are listed as supported in this struct, + /// a rotation value of a | b is supported as well. + /// Only valid if @ref has_rotation is supported as well. + drm_plane_transform_t supported_rotations; + + /// True if this plane has a hardcoded rotation. + bool has_hardcoded_rotation; + + /// The specific hardcoded rotation, only valid if @ref has_hardcoded_rotation is true. + drm_plane_transform_t hardcoded_rotation; + + /// The framebuffer formats this plane supports. (Assuming no modifier) + /// For example, kARGB8888_FpiPixelFormat is supported if supported_formats[kARGB8888_FpiPixelFormat] is true. + bool supported_formats[kCount_PixFmt]; + + /// True if this plane has an IN_FORMATS property attached an + /// supports scanning out buffers with explicit format modifiers. + bool supports_modifiers; + + /// The number of entries in the @ref supported_format_modifier_pairs array below. + int n_supported_modified_formats; + + /// A pair of pixel format / modifier that is definitely supported. + /// DRM_FORMAT_MOD_LINEAR is supported for most (but not all pixel formats). + /// There are some format & modifier pairs that may be faster to scanout by the GPU. + struct modified_format *supported_modified_formats; + + /// Whether this plane has a mutable alpha property we can set. + bool has_alpha; + + /// Whether this plane has a pixel blend mode we can set. + bool has_blend_mode; + + /// The supported blend modes, if @ref has_blend_mode is true. + bool supported_blend_modes[kCount_DrmBlendMode]; + + struct { + uint32_t crtc_id; + uint32_t fb_id; + uint32_t src_x, src_y, src_w, src_h; + uint32_t crtc_x, crtc_y, crtc_w, crtc_h; + int64_t zpos; + drm_plane_transform_t rotation; + uint16_t alpha; + enum drm_blend_mode blend_mode; + } committed_state; +}; -int drmdev_configure( - struct drmdev *drmdev, - uint32_t connector_id, - uint32_t encoder_id, - uint32_t crtc_id, - const drmModeModeInfo *mode -); +struct drmdev; +struct _drmModeModeInfo; -int drmdev_plane_get_type( - struct drmdev *drmdev, - uint32_t plane_id -); +struct drmdev_interface { + int (*open)(const char *path, int flags, void **fd_metadata_out, void *userdata); + void (*close)(int fd, void *fd_metadata, void *userdata); +}; -int drmdev_plane_supports_setting_rotation_value( - struct drmdev *drmdev, - uint32_t plane_id, - int drm_rotation, - bool *result -); +struct drmdev *drmdev_new_from_fd(int fd, const struct drmdev_interface *interface, void *userdata); -int drmdev_plane_get_min_zpos_value( - struct drmdev *drmdev, - uint32_t plane_id, - int64_t *min_zpos_out -); +struct drmdev *drmdev_new_from_path(const char *path, const struct drmdev_interface *interface, void *userdata); + +void drmdev_destroy(struct drmdev *drmdev); + +DECLARE_REF_OPS(drmdev) + +int drmdev_get_fd(struct drmdev *drmdev); +int drmdev_get_event_fd(struct drmdev *drmdev); +int drmdev_on_event_fd_ready(struct drmdev *drmdev); +const struct drm_connector *drmdev_get_selected_connector(struct drmdev *drmdev); +const struct drm_encoder *drmdev_get_selected_encoder(struct drmdev *drmdev); +const struct drm_crtc *drmdev_get_selected_crtc(struct drmdev *drmdev); +const struct _drmModeModeInfo *drmdev_get_selected_mode(struct drmdev *drmdev); + +struct gbm_device *drmdev_get_gbm_device(struct drmdev *drmdev); -int drmdev_plane_get_max_zpos_value( +uint32_t drmdev_add_fb( struct drmdev *drmdev, - uint32_t plane_id, - int64_t *max_zpos_out + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + uint32_t bo_handle, + uint32_t pitch, + uint32_t offset, + bool has_modifier, + uint64_t modifier ); -int drmdev_plane_supports_setting_zpos( +uint32_t drmdev_add_fb_multiplanar( struct drmdev *drmdev, - uint32_t plane_id, - bool *result + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + uint32_t bo_handles[4], + uint32_t pitches[4], + uint32_t offsets[4], + bool has_modifiers, + uint64_t modifiers[4] ); -int drmdev_plane_supports_setting_zpos_value( +uint32_t drmdev_add_fb_from_dmabuf( struct drmdev *drmdev, - uint32_t plane_id, - int64_t zpos, - bool *result + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + int prime_fd, + uint32_t pitch, + uint32_t offset, + bool has_modifier, + uint64_t modifier ); -int drmdev_new_atomic_req( +uint32_t drmdev_add_fb_from_dmabuf_multiplanar( struct drmdev *drmdev, - struct drmdev_atomic_req **req_out + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + int prime_fds[4], + uint32_t pitches[4], + uint32_t offsets[4], + bool has_modifiers, + uint64_t modifiers[4] ); -void drmdev_destroy_atomic_req( - struct drmdev_atomic_req *req -); +int drmdev_rm_fb(struct drmdev *drmdev, uint32_t fb_id); -int drmdev_atomic_req_put_connector_property( - struct drmdev_atomic_req *req, - const char *name, - uint64_t value -); +int drmdev_get_last_vblank(struct drmdev *drmdev, uint32_t crtc_id, uint64_t *last_vblank_ns_out); -int drmdev_atomic_req_put_crtc_property( - struct drmdev_atomic_req *req, - const char *name, - uint64_t value -); +bool drmdev_can_modeset(struct drmdev *drmdev); -int drmdev_atomic_req_put_plane_property( - struct drmdev_atomic_req *req, - uint32_t plane_id, - const char *name, - uint64_t value -); +void drmdev_suspend(struct drmdev *drmdev); -int drmdev_atomic_req_put_modeset_props( - struct drmdev_atomic_req *req, - uint32_t *flags -); +int drmdev_resume(struct drmdev *drmdev); -inline static int drmdev_atomic_req_reserve_plane( - struct drmdev_atomic_req *req, - struct drm_plane *plane -) { - return pset_remove(&req->available_planes, plane); +static inline double mode_get_vrefresh(const drmModeModeInfo *mode) { + return mode->clock * 1000.0 / (mode->htotal * mode->vtotal); } -int drmdev_atomic_req_commit( - struct drmdev_atomic_req *req, - uint32_t flags, - void *userdata -); +typedef void (*kms_scanout_cb_t)(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata); -int drmdev_legacy_set_mode_and_fb( - struct drmdev *drmdev, - uint32_t fb_id -); +struct kms_fb_layer { + uint32_t drm_fb_id; + enum pixfmt format; + bool has_modifier; + uint64_t modifier; -/** - * @brief Do a nonblocking, vblank-synced framebuffer swap. - */ -int drmdev_legacy_primary_plane_pageflip( - struct drmdev *drmdev, - uint32_t fb_id, + int32_t src_x, src_y, src_w, src_h; + int32_t dst_x, dst_y, dst_w, dst_h; + + bool has_rotation; + drm_plane_transform_t rotation; + + bool has_in_fence_fd; + int in_fence_fd; +}; + +typedef void (*kms_fb_release_cb_t)(void *userdata); + +typedef void (*kms_deferred_fb_release_cb_t)(void *userdata, int syncfile_fd); + +struct kms_req_builder; + +struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uint32_t crtc_id); + +void kms_req_builder_destroy(struct kms_req_builder *builder); + +DECLARE_REF_OPS(kms_req_builder); + +struct drmdev *kms_req_builder_get_drmdev(struct kms_req_builder *builder); + +int kms_req_builder_set_mode(struct kms_req_builder *builder, const drmModeModeInfo *mode); + +int kms_req_builder_unset_mode(struct kms_req_builder *builder); + +int kms_req_builder_set_connector(struct kms_req_builder *builder, uint32_t connector_id); + +bool kms_req_builder_prefer_next_layer_opaque(struct kms_req_builder *builder); + +int kms_req_builder_push_fb_layer( + struct kms_req_builder *builder, + const struct kms_fb_layer *layer, + kms_fb_release_cb_t release_callback, + kms_deferred_fb_release_cb_t deferred_release_callback, void *userdata ); -/** - * @brief Do a blocking, vblank-synced framebuffer swap. - * Using this in combination with @ref drmdev_legacy_primary_plane_pageflip - * is not a good idea, since it will block until the primary plane pageflip is complete, - * and then block even longer till the overlay plane pageflip completes the vblank after. - */ -int drmdev_legacy_overlay_plane_pageflip( - struct drmdev *drmdev, - uint32_t plane_id, - uint32_t fb_id, - int32_t crtc_x, - int32_t crtc_y, - int32_t crtc_w, - int32_t crtc_h, - uint32_t src_x, - uint32_t src_y, - uint32_t src_w, - uint32_t src_h -); +int kms_req_builder_push_zpos_placeholder_layer(struct kms_req_builder *builder, int64_t *zpos_out); -int drmdev_legacy_set_connector_property( - struct drmdev *drmdev, - const char *name, - uint64_t value -); +struct kms_req; -int drmdev_legacy_set_crtc_property( - struct drmdev *drmdev, - const char *name, - uint64_t value -); +DECLARE_REF_OPS(kms_req); -int drmdev_legacy_set_plane_property( - struct drmdev *drmdev, - uint32_t plane_id, - const char *name, - uint64_t value -); +struct kms_req *kms_req_builder_build(struct kms_req_builder *builder); -float mode_get_vrefresh(const drmModeModeInfo *mode); +int kms_req_commit_blocking(struct kms_req *req, uint64_t *vblank_ns_out); -inline static struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector) { - bool found = connector == NULL; - for (size_t i = 0; i < drmdev->n_connectors; i++) { - if (drmdev->connectors + i == connector) { - found = true; - } else if (found) { - return drmdev->connectors + i; - } - } +int kms_req_commit_nonblocking(struct kms_req *req, kms_scanout_cb_t scanout_cb, void *userdata, void_callback_t destroy_cb); - return NULL; -} +struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector); -inline static struct drm_encoder *__next_encoder(const struct drmdev *drmdev, const struct drm_encoder *encoder) { - bool found = encoder == NULL; - for (size_t i = 0; i < drmdev->n_encoders; i++) { - if (drmdev->encoders + i == encoder) { - found = true; - } else if (found) { - return drmdev->encoders + i; - } - } - - return NULL; -} +struct drm_encoder *__next_encoder(const struct drmdev *drmdev, const struct drm_encoder *encoder); -inline static struct drm_crtc *__next_crtc(const struct drmdev *drmdev, const struct drm_crtc *crtc) { - bool found = crtc == NULL; - for (size_t i = 0; i < drmdev->n_crtcs; i++) { - if (drmdev->crtcs + i == crtc) { - found = true; - } else if (found) { - return drmdev->crtcs + i; - } - } - - return NULL; -} +struct drm_crtc *__next_crtc(const struct drmdev *drmdev, const struct drm_crtc *crtc); -inline static struct drm_plane *__next_plane(const struct drmdev *drmdev, const struct drm_plane *plane) { - bool found = plane == NULL; - for (size_t i = 0; i < drmdev->n_planes; i++) { - if (drmdev->planes + i == plane) { - found = true; - } else if (found) { - return drmdev->planes + i; - } - } - - return NULL; -} +struct drm_plane *__next_plane(const struct drmdev *drmdev, const struct drm_plane *plane); -inline static drmModeModeInfo *__next_mode(const struct drm_connector *connector, const drmModeModeInfo *mode) { - bool found = mode == NULL; - for (int i = 0; i < connector->connector->count_modes; i++) { - if (connector->connector->modes + i == mode) { - found = true; - } else if (found) { - return connector->connector->modes + i; - } - } - - return NULL; -} +drmModeModeInfo *__next_mode(const struct drm_connector *connector, const drmModeModeInfo *mode); -#define for_each_connector_in_drmdev(drmdev, connector) for (connector = __next_connector(drmdev, NULL); connector != NULL; connector = __next_connector(drmdev, connector)) +#define for_each_connector_in_drmdev(drmdev, connector) \ + for (connector = __next_connector(drmdev, NULL); connector != NULL; connector = __next_connector(drmdev, connector)) -#define for_each_encoder_in_drmdev(drmdev, encoder) for (encoder = __next_encoder(drmdev, NULL); encoder != NULL; encoder = __next_encoder(drmdev, encoder)) +#define for_each_encoder_in_drmdev(drmdev, encoder) \ + for (encoder = __next_encoder(drmdev, NULL); encoder != NULL; encoder = __next_encoder(drmdev, encoder)) -#define for_each_crtc_in_drmdev(drmdev, crtc) for (crtc = __next_crtc(drmdev, NULL); crtc != NULL; crtc = __next_crtc(drmdev, crtc)) +#define for_each_crtc_in_drmdev(drmdev, crtc) \ + for (crtc = __next_crtc(drmdev, NULL); crtc != NULL; crtc = __next_crtc(drmdev, crtc)) -#define for_each_plane_in_drmdev(drmdev, plane) for (plane = __next_plane(drmdev, NULL); plane != NULL; plane = __next_plane(drmdev, plane)) +#define for_each_plane_in_drmdev(drmdev, plane) \ + for (plane = __next_plane(drmdev, NULL); plane != NULL; plane = __next_plane(drmdev, plane)) -#define for_each_mode_in_connector(connector, mode) for (mode = __next_mode(connector, NULL); mode != NULL; mode = __next_mode(connector, mode)) +#define for_each_mode_in_connector(connector, mode) \ + for (mode = __next_mode(connector, NULL); mode != NULL; mode = __next_mode(connector, mode)) -#define for_each_unreserved_plane_in_atomic_req(atomic_req, plane) for_each_pointer_in_pset(&(atomic_req)->available_planes, plane) +#define for_each_unreserved_plane_in_atomic_req(atomic_req, plane) \ + for_each_pointer_in_pset(&(atomic_req)->available_planes, plane) -#endif +#endif // _FLUTTERPI_INCLUDE_MODESETTING_H diff --git a/include/notifier_listener.h b/include/notifier_listener.h index ac32397d..aba0f559 100644 --- a/include/notifier_listener.h +++ b/include/notifier_listener.h @@ -10,8 +10,6 @@ enum listener_return { typedef enum listener_return (*listener_cb_t)(void *arg, void *userdata); -typedef void (*void_callback_t)(void *arg); - struct listener; struct notifier { diff --git a/include/pixel_format.h b/include/pixel_format.h index 7314ceb2..94c76bb8 100644 --- a/include/pixel_format.h +++ b/include/pixel_format.h @@ -24,6 +24,10 @@ struct fbdev_pixfmt { #include #endif +#ifdef HAS_VULKAN +#include +#endif + /** * @brief A specific pixel format. Use @ref get_pixfmt_info to get information * about this pixel format. @@ -31,29 +35,63 @@ struct fbdev_pixfmt { */ enum pixfmt { kRGB565_FpiPixelFormat, + kARGB4444_FpiPixelFormat, + kXRGB4444_FpiPixelFormat, + kARGB1555_FpiPixelFormat, + kXRGB1555_FpiPixelFormat, kARGB8888_FpiPixelFormat, kXRGB8888_FpiPixelFormat, kBGRA8888_FpiPixelFormat, + kBGRX8888_FpiPixelFormat, kRGBA8888_FpiPixelFormat, - kMax_PixFmt = kRGBA8888_FpiPixelFormat, + kRGBX8888_FpiPixelFormat, + kMax_PixFmt = kRGBX8888_FpiPixelFormat, kCount_PixFmt = kMax_PixFmt + 1 }; // Just a pedantic check so we don't update the pixfmt enum without changing kMax_PixFmt -COMPILE_ASSERT(kMax_PixFmt == kRGBA8888_FpiPixelFormat); +COMPILE_ASSERT(kMax_PixFmt == kRGBX8888_FpiPixelFormat); + + +// Vulkan doesn't support that many sRGB formats actually. +// There's two more (one packed and one non-packed) that aren't listed here. +/// TODO: We could support other formats as well though with manual colorspace conversions. #define PIXFMT_LIST(V) \ - V( "RGB 5:6:5", "RGB565", kRGB565_FpiPixelFormat, /*bpp*/ 16, /*opaque*/ true, /*R*/ 5, 11, /*G*/ 6, 5, /*B*/ 5, 0, /*A*/ 0, 0, /*GBM fourcc*/ GBM_FORMAT_RGB565, /*DRM fourcc*/ DRM_FORMAT_RGB565) \ - V("ARGB 8:8:8:8", "ARGB8888", kARGB8888_FpiPixelFormat, /*bpp*/ 32, /*opaque*/ false, /*R*/ 8, 16, /*G*/ 8, 8, /*B*/ 8, 0, /*A*/ 8, 24, /*GBM fourcc*/ GBM_FORMAT_ARGB8888, /*DRM fourcc*/ DRM_FORMAT_RGB565) \ - V("XRGB 8:8:8:8", "XRGB8888", kXRGB8888_FpiPixelFormat, /*bpp*/ 32, /*opaque*/ true, /*R*/ 8, 16, /*G*/ 8, 8, /*B*/ 8, 0, /*A*/ 0, 24, /*GBM fourcc*/ GBM_FORMAT_XRGB8888, /*DRM fourcc*/ DRM_FORMAT_XRGB8888) \ - V("BGRA 8:8:8:8", "BGRA8888", kBGRA8888_FpiPixelFormat, /*bpp*/ 32, /*opaque*/ false, /*R*/ 8, 8, /*G*/ 8, 16, /*B*/ 8, 24, /*A*/ 8, 0, /*GBM fourcc*/ GBM_FORMAT_BGRA8888, /*DRM fourcc*/ DRM_FORMAT_BGRA8888) \ - V("RGBA 8:8:8:8", "RGBA8888", kRGBA8888_FpiPixelFormat, /*bpp*/ 32, /*opaque*/ false, /*R*/ 8, 24, /*G*/ 8, 16, /*B*/ 8, 8, /*A*/ 8, 0, /*GBM fourcc*/ GBM_FORMAT_RGBA8888, /*DRM fourcc*/ DRM_FORMAT_RGBA8888) + V( "RGB 5:6:5", "RGB565", kRGB565_FpiPixelFormat, /*bpp*/ 16, /*bit_depth*/ 16, /*opaque*/ true, /*Vulkan format*/ VK_FORMAT_UNDEFINED, /*R*/ 5, 11, /*G*/ 6, 5, /*B*/ 5, 0, /*A*/ 0, 0, /*GBM fourcc*/ GBM_FORMAT_RGB565, /*DRM fourcc*/ DRM_FORMAT_RGB565 ) \ + V("ARGB 4:4:4:4", "ARGB4444", kARGB4444_FpiPixelFormat, /*bpp*/ 16, /*bit_depth*/ 12, /*opaque*/ false, /*Vulkan format*/ VK_FORMAT_UNDEFINED, /*R*/ 4, 8, /*G*/ 4, 4, /*B*/ 4, 0, /*A*/ 4, 12, /*GBM fourcc*/ GBM_FORMAT_ARGB4444, /*DRM fourcc*/ DRM_FORMAT_ARGB4444) \ + V("XRGB 4:4:4:4", "XRGB4444", kXRGB4444_FpiPixelFormat, /*bpp*/ 16, /*bit_depth*/ 12, /*opaque*/ true, /*Vulkan format*/ VK_FORMAT_UNDEFINED, /*R*/ 4, 8, /*G*/ 4, 4, /*B*/ 4, 0, /*A*/ 0, 0, /*GBM fourcc*/ GBM_FORMAT_XRGB4444, /*DRM fourcc*/ DRM_FORMAT_XRGB4444) \ + V("ARGB 1:5:5:5", "ARGB1555", kARGB1555_FpiPixelFormat, /*bpp*/ 16, /*bit_depth*/ 15, /*opaque*/ false, /*Vulkan format*/ VK_FORMAT_UNDEFINED, /*R*/ 5, 10, /*G*/ 5, 5, /*B*/ 5, 0, /*A*/ 1, 15, /*GBM fourcc*/ GBM_FORMAT_ARGB1555, /*DRM fourcc*/ DRM_FORMAT_ARGB1555) \ + V("XRGB 1:5:5:5", "XRGB1555", kXRGB1555_FpiPixelFormat, /*bpp*/ 16, /*bit_depth*/ 15, /*opaque*/ true, /*Vulkan format*/ VK_FORMAT_UNDEFINED, /*R*/ 5, 10, /*G*/ 5, 5, /*B*/ 5, 0, /*A*/ 0, 0, /*GBM fourcc*/ GBM_FORMAT_XRGB1555, /*DRM fourcc*/ DRM_FORMAT_XRGB1555) \ + V("ARGB 8:8:8:8", "ARGB8888", kARGB8888_FpiPixelFormat, /*bpp*/ 32, /*bit_depth*/ 24, /*opaque*/ false, /*Vulkan format*/ VK_FORMAT_B8G8R8A8_SRGB, /*R*/ 8, 16, /*G*/ 8, 8, /*B*/ 8, 0, /*A*/ 8, 24, /*GBM fourcc*/ GBM_FORMAT_ARGB8888, /*DRM fourcc*/ DRM_FORMAT_ARGB8888) \ + V("XRGB 8:8:8:8", "XRGB8888", kXRGB8888_FpiPixelFormat, /*bpp*/ 32, /*bit_depth*/ 24, /*opaque*/ true, /*Vulkan format*/ VK_FORMAT_UNDEFINED, /*R*/ 8, 16, /*G*/ 8, 8, /*B*/ 8, 0, /*A*/ 0, 24, /*GBM fourcc*/ GBM_FORMAT_XRGB8888, /*DRM fourcc*/ DRM_FORMAT_XRGB8888) \ + V("BGRA 8:8:8:8", "BGRA8888", kBGRA8888_FpiPixelFormat, /*bpp*/ 32, /*bit_depth*/ 24, /*opaque*/ false, /*Vulkan format*/ VK_FORMAT_UNDEFINED, /*R*/ 8, 8, /*G*/ 8, 16, /*B*/ 8, 24, /*A*/ 8, 0, /*GBM fourcc*/ GBM_FORMAT_BGRA8888, /*DRM fourcc*/ DRM_FORMAT_BGRA8888) \ + V("BGRX 8:8:8:8", "BGRX8888", kBGRX8888_FpiPixelFormat, /*bpp*/ 32, /*bit_depth*/ 24, /*opaque*/ true, /*Vulkan format*/ VK_FORMAT_UNDEFINED, /*R*/ 8, 8, /*G*/ 8, 16, /*B*/ 8, 24, /*A*/ 0, 0, /*GBM fourcc*/ GBM_FORMAT_BGRX8888, /*DRM fourcc*/ DRM_FORMAT_BGRX8888) \ + V("RGBA 8:8:8:8", "RGBA8888", kRGBA8888_FpiPixelFormat, /*bpp*/ 32, /*bit_depth*/ 24, /*opaque*/ false, /*Vulkan format*/ VK_FORMAT_UNDEFINED, /*R*/ 8, 24, /*G*/ 8, 16, /*B*/ 8, 8, /*A*/ 8, 0, /*GBM fourcc*/ GBM_FORMAT_RGBA8888, /*DRM fourcc*/ DRM_FORMAT_RGBA8888) \ + V("RGBX 8:8:8:8", "RGBX8888", kRGBX8888_FpiPixelFormat, /*bpp*/ 32, /*bit_depth*/ 24, /*opaque*/ true, /*Vulkan format*/ VK_FORMAT_UNDEFINED, /*R*/ 8, 24, /*G*/ 8, 16, /*B*/ 8, 8, /*A*/ 0, 0, /*GBM fourcc*/ GBM_FORMAT_RGBX8888, /*DRM fourcc*/ DRM_FORMAT_RGBX8888) // make sure the macro list we defined has as many elements as the pixfmt enum. #define __COUNT(...) +1 COMPILE_ASSERT(0 PIXFMT_LIST(__COUNT) == kMax_PixFmt+1); #undef __COUNT +static inline enum pixfmt pixfmt_opaque(enum pixfmt format) { + if (format == kARGB8888_FpiPixelFormat) { + return kXRGB8888_FpiPixelFormat; + } else if (format == kARGB4444_FpiPixelFormat) { + return kXRGB4444_FpiPixelFormat; + } else if (format == kARGB1555_FpiPixelFormat) { + return kXRGB1555_FpiPixelFormat; + } else if (format == kBGRA8888_FpiPixelFormat) { + return kBGRX8888_FpiPixelFormat; + } else if (format == kRGBA8888_FpiPixelFormat) { + return kRGBX8888_FpiPixelFormat; + } + + /// TODO: We're potentially returning a non-opaque format here. + return format; +} + /** * @brief Information about a pixel format. * @@ -84,6 +122,12 @@ struct pixfmt_info { */ int bits_per_pixel; + /** + * @brief How many bits of the @ref bits_per_pixel are used for color (R / G / B)? + * + */ + int bit_depth; + /** * @brief True if there's no way to specify transparency with this format. */ @@ -103,10 +147,16 @@ struct pixfmt_info { #endif #ifdef HAS_KMS /** - * @brief The GBM format equivalent to this pixel format. + * @brief The DRM format equivalent to this pixel format. */ uint32_t drm_format; #endif +#ifdef HAS_VULKAN + /** + * @brief The vulkan equivalent of this pixel format. + */ + VkFormat vk_format; +#endif }; /** @@ -116,13 +166,24 @@ struct pixfmt_info { extern const struct pixfmt_info pixfmt_infos[]; extern const size_t n_pixfmt_infos; +#ifdef DEBUG +void assert_pixfmt_list_valid(); +#endif + /** * @brief Get the pixel format info for a specific pixel format. * */ static inline const struct pixfmt_info *get_pixfmt_info(enum pixfmt format) { - DEBUG_ASSERT(format > 0 && format <= kMax_PixFmt); + DEBUG_ASSERT(format >= 0 && format <= kMax_PixFmt); +#ifdef DEBUG + assert_pixfmt_list_valid(); +#endif return pixfmt_infos + format; } +COMPILE_ASSERT(kRGB565_FpiPixelFormat == 0); + +#define DEBUG_ASSERT_PIXFMT_VALID(format) DEBUG_ASSERT_MSG(format >= kRGB565 && format <= kMax_PixFmt, "Invalid pixel format") + #endif // _FLUTTERPI_INCLUDE_PIXEL_FORMAT_H diff --git a/src/compositor.c b/src/compositor.c index 274bf107..39415d90 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -25,9 +25,6 @@ FILE_DESCR("compositor") struct view_cb_data { int64_t view_id; - platform_view_mount_cb mount; - platform_view_unmount_cb unmount; - platform_view_update_view_cb update_view; platform_view_present_cb present; void *userdata; @@ -97,7 +94,7 @@ static void destroy_gbm_bo( struct drm_fb *fb = userdata; if (fb && fb->fb_id) - drmModeRmFB(flutterpi.drm.drmdev->fd, fb->fb_id); + drmModeRmFB(drmdev_get_fd(flutterpi.drm.drmdev), fb->fb_id); free(fb); } @@ -137,7 +134,7 @@ static uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { flags = DRM_MODE_FB_MODIFIERS; } - ok = drmModeAddFB2WithModifiers(flutterpi.drm.drmdev->fd, width, height, format, handles, strides, offsets, modifiers, &fb->fb_id, flags); + ok = drmModeAddFB2WithModifiers(drmdev_get_fd(flutterpi.drm.drmdev), width, height, format, handles, strides, offsets, modifiers, &fb->fb_id, flags); if (ok) { if (flags) @@ -147,7 +144,7 @@ static uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { memcpy(strides, (uint32_t [4]){gbm_bo_get_stride(bo),0,0,0}, 16); memset(offsets, 0, 16); - ok = drmModeAddFB2(flutterpi.drm.drmdev->fd, width, height, format, handles, strides, offsets, &fb->fb_id, 0); + ok = drmModeAddFB2(drmdev_get_fd(flutterpi.drm.drmdev), width, height, format, handles, strides, offsets, &fb->fb_id, 0); } if (ok) { @@ -161,346 +158,89 @@ static uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { return fb->fb_id; } - -/** - * @brief Create a GL renderbuffer that is backed by a DRM buffer-object and registered as a DRM framebuffer - */ -static int create_drm_rbo( - size_t width, - size_t height, - struct drm_rbo *out -) { - struct drm_rbo fbo; - EGLint egl_error; - GLenum gl_error; - int ok; - - eglGetError(); - glGetError(); - - fbo.egl_image = flutterpi.egl.createDRMImageMESA(flutterpi.egl.display, (const EGLint[]) { - EGL_WIDTH, width, - EGL_HEIGHT, height, - EGL_DRM_BUFFER_FORMAT_MESA, EGL_DRM_BUFFER_FORMAT_ARGB32_MESA, - EGL_DRM_BUFFER_USE_MESA, EGL_DRM_BUFFER_USE_SCANOUT_MESA, - EGL_NONE - }); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("error creating DRM EGL Image for flutter backing store, eglCreateDRMImageMESA: %" PRId32 "\n", egl_error); - return EINVAL; - } - - flutterpi.egl.exportDRMImageMESA(flutterpi.egl.display, fbo.egl_image, NULL, (EGLint*) &fbo.gem_handle, (EGLint*) &fbo.gem_stride); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("error getting handle & stride for DRM EGL Image, eglExportDRMImageMESA: %d\n", egl_error); - return EINVAL; - } - - glGenRenderbuffers(1, &fbo.gl_rbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error generating renderbuffers for flutter backing store, glGenRenderbuffers: %u\n", gl_error); - return EINVAL; - } - - glBindRenderbuffer(GL_RENDERBUFFER, fbo.gl_rbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error binding renderbuffer, glBindRenderbuffer: %d\n", gl_error); - return EINVAL; - } - - flutterpi.gl.EGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, fbo.egl_image); - if ((gl_error = glGetError())) { - LOG_ERROR("error binding DRM EGL Image to renderbuffer, glEGLImageTargetRenderbufferStorageOES: %u\n", gl_error); - return EINVAL; - } - - /* - glGenFramebuffers(1, &fbo.gl_fbo_id); - if (gl_error = glGetError()) { - LOG_ERROR("error generating FBOs for flutter backing store, glGenFramebuffers: %d\n", gl_error); - return EINVAL; - } - - glBindFramebuffer(GL_FRAMEBUFFER, fbo.gl_fbo_id); - if (gl_error = glGetError()) { - LOG_ERROR("error binding FBO for attaching the renderbuffer, glBindFramebuffer: %d\n", gl_error); - return EINVAL; - } - - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fbo.gl_rbo_id); - if (gl_error = glGetError()) { - LOG_ERROR("error attaching renderbuffer to FBO, glFramebufferRenderbuffer: %d\n", gl_error); - return EINVAL; - } - - GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - - */ - - glBindRenderbuffer(GL_RENDERBUFFER, 0); - - // glBindFramebuffer(GL_FRAMEBUFFER, 0); - - ok = drmModeAddFB2( - flutterpi.drm.drmdev->fd, - width, - height, - DRM_FORMAT_ARGB8888, - (const uint32_t*) &(uint32_t[4]) { - fbo.gem_handle, - 0, - 0, - 0 - }, - (const uint32_t*) &(uint32_t[4]) { - fbo.gem_stride, 0, 0, 0 - }, - (const uint32_t*) &(uint32_t[4]) { - 0, 0, 0, 0 - }, - &fbo.drm_fb_id, - 0 - ); - if (ok == -1) { - LOG_ERROR("Could not make DRM fb from EGL Image, drmModeAddFB2: %s", strerror(errno)); - return errno; - } - - *out = fbo; - - return 0; -} - -/** - * @brief Set the color attachment of a GL FBO to this DRM RBO. - */ -static int attach_drm_rbo_to_fbo( - GLuint fbo_id, - struct drm_rbo *rbo -) { - GLenum gl_error; - - eglGetError(); - glGetError(); - - glBindFramebuffer(GL_FRAMEBUFFER, fbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error binding FBO for attaching the renderbuffer, glBindFramebuffer: %d\n", gl_error); - return EINVAL; - } - - glBindRenderbuffer(GL_RENDERBUFFER, rbo->gl_rbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error binding renderbuffer, glBindRenderbuffer: %d\n", gl_error); - return EINVAL; - } - - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo->gl_rbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error attaching renderbuffer to FBO, glFramebufferRenderbuffer: %d\n", gl_error); - return EINVAL; - } - - return 0; +static void rendertarget_gbm_destroy(struct rendertarget *target) { + free(target); } -/** - * @brief Destroy this GL renderbuffer, and the associated DRM buffer-object and DRM framebuffer - */ -static void destroy_drm_rbo( - struct drm_rbo *rbo -) { - EGLint egl_error; - GLenum gl_error; - int ok; - - eglGetError(); - glGetError(); - - glDeleteRenderbuffers(1, &rbo->gl_rbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error destroying OpenGL RBO, glDeleteRenderbuffers: 0x%08X\n", gl_error); - } +struct rendertarget_release_data { + struct gbm_surface *surface; + struct gbm_bo *bo; +}; - ok = drmModeRmFB(flutterpi.drm.drmdev->fd, rbo->drm_fb_id); - if (ok < 0) { - LOG_ERROR("error removing DRM FB, drmModeRmFB: %s\n", strerror(errno)); - } +static void on_release_gbm_rendertarget_fb(void *userdata) { + struct rendertarget_release_data *data; - eglDestroyImage(flutterpi.egl.display, rbo->egl_image); - if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { - LOG_ERROR("error destroying EGL image, eglDestroyImage: 0x%08X\n", egl_error); - } -} + DEBUG_ASSERT_NOT_NULL(userdata); + data = userdata; -static void rendertarget_gbm_destroy(struct rendertarget *target) { - free(target); + gbm_surface_release_buffer(data->surface, data->bo); + free(data); } static int rendertarget_gbm_present( struct rendertarget *target, - struct drmdev_atomic_req *atomic_req, - uint32_t drm_plane_id, - int offset_x, - int offset_y, - int width, - int height, - int zpos + struct kms_req_builder *builder ) { + struct rendertarget_release_data *release_data; struct rendertarget_gbm *gbm_target; struct gbm_bo *next_front_bo; uint32_t next_front_fb_id; - bool supported; int ok; - (void)offset_x; - (void)offset_y; - (void)width; - (void)height; - gbm_target = &target->gbm; - next_front_bo = gbm_surface_lock_front_buffer(gbm_target->gbm_surface); - next_front_fb_id = gbm_bo_get_drm_fb_id(next_front_bo); - - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "FB_ID", next_front_fb_id); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_ID", target->compositor->drmdev->selected_crtc->crtc->crtc_id); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "SRC_X", 0); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "SRC_Y", 0); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "SRC_W", ((uint16_t) flutterpi.display.width) << 16); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "SRC_H", ((uint16_t) flutterpi.display.height) << 16); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_X", 0); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_Y", 0); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_W", flutterpi.display.width); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_H", flutterpi.display.height); - - ok = drmdev_plane_supports_setting_rotation_value(atomic_req->drmdev, drm_plane_id, DRM_MODE_ROTATE_0, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "rotation", DRM_MODE_ROTATE_0); - } else { - static bool printed = false; - - if (!printed) { - LOG_ERROR( - "GPU does not support reflecting the screen in Y-direction.\n" - " This is required for rendering into hardware overlay planes though.\n" - " Any UI that is drawn in overlay planes will look upside down.\n" - ); - printed = true; - } + release_data = malloc(sizeof *release_data); + if (release_data == NULL) { + return ENOMEM; } - ok = drmdev_plane_supports_setting_zpos_value(atomic_req->drmdev, drm_plane_id, zpos, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "zpos", zpos); - } else { - static bool printed = false; - - if (!printed) { - LOG_ERROR( - "GPU does not supported the desired HW plane order.\n" - " Some UI layers may be invisible.\n" - ); - printed = true; - } + next_front_bo = gbm_surface_lock_front_buffer(gbm_target->gbm_surface); + if (next_front_bo == NULL) { + LOG_ERROR("Couldn't lock front buffer.\n"); + ok = EIO; + goto fail_free_release_data; } - // TODO: move this to the page flip handler. - // We can only be sure the buffer can be released when the buffer swap - // ocurred. - if (gbm_target->current_front_bo != NULL) { - gbm_surface_release_buffer(gbm_target->gbm_surface, gbm_target->current_front_bo); + next_front_fb_id = gbm_bo_get_drm_fb_id(next_front_bo); + + ok = kms_req_builder_push_fb_layer( + builder, + &(const struct kms_fb_layer) { + .drm_fb_id = next_front_fb_id, + .format = flutterpi.gbm.format, + .has_modifier = flutterpi.gbm.modifier != DRM_FORMAT_MOD_NONE, + .modifier = flutterpi.gbm.modifier, + .src_x = 0, + .src_y = 0, + .src_w = ((uint16_t) flutterpi.display.width) << 16, + .src_h = ((uint16_t) flutterpi.display.height) << 16, + .dst_x = 0, + .dst_y = 0, + .dst_w = flutterpi.display.width, + .dst_h = flutterpi.display.height, + .has_rotation = false, + .rotation = PLANE_TRANSFORM_NONE, + .has_in_fence_fd = false, + .in_fence_fd = 0, + }, + on_release_gbm_rendertarget_fb, + NULL, + release_data + ); + if (ok != 0) { + LOG_ERROR("Couldn't add fb layer.\n"); + goto fail_release_next_front_buffer; } - gbm_target->current_front_bo = (struct gbm_bo *) next_front_bo; return 0; -} - -static int rendertarget_gbm_present_legacy( - struct rendertarget *target, - struct drmdev *drmdev, - uint32_t drm_plane_id, - int offset_x, - int offset_y, - int width, - int height, - int zpos, - bool set_mode -) { - struct rendertarget_gbm *gbm_target; - struct gbm_bo *next_front_bo; - uint32_t next_front_fb_id; - bool is_primary; - - (void)offset_x; - (void)offset_y; - (void)width; - (void)height; - (void)zpos; - - gbm_target = &target->gbm; - is_primary = drmdev_plane_get_type(drmdev, drm_plane_id) == DRM_PLANE_TYPE_PRIMARY; + fail_release_next_front_buffer: + gbm_surface_release_buffer(gbm_target->gbm_surface, next_front_bo); - next_front_bo = gbm_surface_lock_front_buffer(gbm_target->gbm_surface); - next_front_fb_id = gbm_bo_get_drm_fb_id(next_front_bo); - - if (is_primary) { - if (set_mode) { - drmdev_legacy_set_mode_and_fb( - drmdev, - next_front_fb_id - ); - } else { - /*drmdev_legacy_primary_plane_pageflip( - drmdev, - next_front_fb_id, - NULL - );*/ - - drmdev_legacy_overlay_plane_pageflip( - drmdev, - drm_plane_id, - next_front_fb_id, - 0, - 0, - flutterpi.display.width, - flutterpi.display.height, - 0, - 0, - ((uint16_t) flutterpi.display.width) << 16, - ((uint16_t) flutterpi.display.height) << 16 - ); - } - } else { - drmdev_legacy_overlay_plane_pageflip( - drmdev, - drm_plane_id, - next_front_fb_id, - 0, - 0, - flutterpi.display.width, - flutterpi.display.height, - 0, - 0, - ((uint16_t) flutterpi.display.width) << 16, - ((uint16_t) flutterpi.display.height) << 16 - ); - } - - // TODO: move this to the page flip handler. - // We can only be sure the buffer can be released when the buffer swap - // ocurred. - if (gbm_target->current_front_bo != NULL) { - gbm_surface_release_buffer(gbm_target->gbm_surface, gbm_target->current_front_bo); - } - gbm_target->current_front_bo = (struct gbm_bo *) next_front_bo; + fail_free_release_data: + free(release_data); - return 0; + return ok; } /** @@ -533,7 +273,6 @@ static int rendertarget_gbm_new( .gl_fbo_id = 0, .destroy = rendertarget_gbm_destroy, .present = rendertarget_gbm_present, - .present_legacy = rendertarget_gbm_present_legacy }; *out = target; @@ -541,266 +280,6 @@ static int rendertarget_gbm_new( return 0; } -static void rendertarget_nogbm_destroy(struct rendertarget *target) { - glDeleteFramebuffers(1, &target->nogbm.gl_fbo_id); - destroy_drm_rbo(target->nogbm.rbos + 1); - destroy_drm_rbo(target->nogbm.rbos + 0); - free(target); -} - -static int rendertarget_nogbm_present( - struct rendertarget *target, - struct drmdev_atomic_req *req, - uint32_t drm_plane_id, - int offset_x, - int offset_y, - int width, - int height, - int zpos -) { - struct rendertarget_nogbm *nogbm_target; - bool supported; - int ok; - - (void)offset_x; - (void)offset_y; - (void)width; - (void)height; - - nogbm_target = &target->nogbm; - - nogbm_target->current_front_rbo ^= 1; - ok = attach_drm_rbo_to_fbo(nogbm_target->gl_fbo_id, nogbm_target->rbos + nogbm_target->current_front_rbo); - if (ok != 0) return ok; - - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "FB_ID", nogbm_target->rbos[nogbm_target->current_front_rbo ^ 1].drm_fb_id); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_ID", target->compositor->drmdev->selected_crtc->crtc->crtc_id); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "SRC_X", 0); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "SRC_Y", 0); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "SRC_W", ((uint16_t) flutterpi.display.width) << 16); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "SRC_H", ((uint16_t) flutterpi.display.height) << 16); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_X", 0); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_Y", 0); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_W", flutterpi.display.width); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_H", flutterpi.display.height); - - ok = drmdev_plane_supports_setting_rotation_value(req->drmdev, drm_plane_id, DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "rotation", DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y); - } else { - static bool printed = false; - - if (!printed) { - LOG_ERROR( - "GPU does not support reflecting the screen in Y-direction.\n" - " This is required for rendering into hardware overlay planes though.\n" - " Any UI that is drawn in overlay planes will look upside down.\n" - ); - printed = true; - } - } - - ok = drmdev_plane_supports_setting_zpos_value(req->drmdev, drm_plane_id, zpos, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "zpos", zpos); - } else { - static bool printed = false; - - if (!printed) { - LOG_ERROR( - "GPU does not supported the desired HW plane order.\n" - " Some UI layers may be invisible.\n" - ); - printed = true; - } - } - - return 0; -} - -static int rendertarget_nogbm_present_legacy( - struct rendertarget *target, - struct drmdev *drmdev, - uint32_t drm_plane_id, - int offset_x, - int offset_y, - int width, - int height, - int zpos, - bool set_mode -) { - struct rendertarget_nogbm *nogbm_target; - uint32_t fb_id; - bool supported, is_primary; - int ok; - - (void)offset_x; - (void)offset_y; - (void)width; - (void)height; - - nogbm_target = &target->nogbm; - - is_primary = drmdev_plane_get_type(drmdev, drm_plane_id) == DRM_PLANE_TYPE_PRIMARY; - - nogbm_target->current_front_rbo ^= 1; - ok = attach_drm_rbo_to_fbo(nogbm_target->gl_fbo_id, nogbm_target->rbos + nogbm_target->current_front_rbo); - if (ok != 0) return ok; - - fb_id = nogbm_target->rbos[nogbm_target->current_front_rbo ^ 1].drm_fb_id; - - if (is_primary) { - if (set_mode) { - drmdev_legacy_set_mode_and_fb( - drmdev, - fb_id - ); - } else { - drmdev_legacy_primary_plane_pageflip( - drmdev, - fb_id, - NULL - ); - } - } else { - drmdev_legacy_overlay_plane_pageflip( - drmdev, - drm_plane_id, - fb_id, - 0, - 0, - flutterpi.display.width, - flutterpi.display.height, - 0, - 0, - ((uint16_t) flutterpi.display.width) << 16, - ((uint16_t) flutterpi.display.height) << 16 - ); - } - - ok = drmdev_plane_supports_setting_rotation_value(drmdev, drm_plane_id, DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_legacy_set_plane_property(drmdev, drm_plane_id, "rotation", DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y); - } else { - static bool printed = false; - - if (!printed) { - LOG_ERROR( - "GPU does not support reflecting the screen in Y-direction.\n" - " This is required for rendering into hardware overlay planes though.\n" - " Any UI that is drawn in overlay planes will look upside down.\n" - ); - printed = true; - } - } - - ok = drmdev_plane_supports_setting_zpos_value(drmdev, drm_plane_id, zpos, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_legacy_set_plane_property(drmdev, drm_plane_id, "zpos", zpos); - } else { - static bool printed = false; - - if (!printed) { - LOG_ERROR( - "GPU does not supported the desired HW plane order.\n" - " Some UI layers may be invisible.\n" - ); - printed = true; - } - } - - return 0; -} - -/** - * @brief Create a type of rendertarget that is not backed by a GBM-Surface, used for rendering into DRM overlay planes. - * - * @param[out] out A pointer to the pointer of the created rendertarget. - * @param[in] compositor The compositor which this rendertarget should be associated with. - * - * @see rendertarget_nogbm - */ -static int rendertarget_nogbm_new( - struct rendertarget **out, - struct compositor *compositor -) { - struct rendertarget *target; - GLenum gl_error; - int ok; - - target = calloc(1, sizeof *target); - if (target == NULL) { - return ENOMEM; - } - - target->is_gbm = false; - target->compositor = compositor; - target->destroy = rendertarget_nogbm_destroy; - target->present = rendertarget_nogbm_present; - target->present_legacy = rendertarget_nogbm_present_legacy; - - eglGetError(); - glGetError(); - - glGenFramebuffers(1, &target->nogbm.gl_fbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error generating FBOs for flutter backing store, glGenFramebuffers: %d\n", gl_error); - ok = EINVAL; - goto fail_free_target; - } - - ok = create_drm_rbo( - flutterpi.display.width, - flutterpi.display.height, - target->nogbm.rbos + 0 - ); - if (ok != 0) { - goto fail_delete_fb; - } - - ok = create_drm_rbo( - flutterpi.display.width, - flutterpi.display.height, - target->nogbm.rbos + 1 - ); - if (ok != 0) { - goto fail_destroy_drm_rbo_0; - } - - ok = attach_drm_rbo_to_fbo(target->nogbm.gl_fbo_id, target->nogbm.rbos + target->nogbm.current_front_rbo); - if (ok != 0) { - goto fail_destroy_drm_rbo_1; - } - - target->gl_fbo_id = target->nogbm.gl_fbo_id; - - *out = target; - return 0; - - - fail_destroy_drm_rbo_1: - destroy_drm_rbo(target->nogbm.rbos + 1); - - fail_destroy_drm_rbo_0: - destroy_drm_rbo(target->nogbm.rbos + 0); - - fail_delete_fb: - glDeleteFramebuffers(1, &target->nogbm.gl_fbo_id); - - fail_free_target: - free(target); - *out = NULL; - return ok; -} - /** * @brief Called by flutter when the OpenGL FBO of a backing store should be destroyed. * Called on an internal engine-managed thread. This is actually called after the engine @@ -893,32 +372,18 @@ static bool on_create_backing_store( // try to find a stale No-GBM rendertarget. If there is none, // create one. if (target == NULL) { - if (compositor->should_create_window_surface_backing_store) { - // We create 1 "backing store" that is rendering to the DRM_PLANE_PRIMARY - // plane. That backing store isn't really a backing store at all, it's - // FBO id is 0, so it's actually rendering to the window surface. - - ok = rendertarget_gbm_new( - &target, - compositor - ); - - if (ok != 0) { - free(store); - return false; - } + // We create 1 "backing store" that is rendering to the DRM_PLANE_PRIMARY + // plane. That backing store isn't really a backing store at all, it's + // FBO id is 0, so it's actually rendering to the window surface. - compositor->should_create_window_surface_backing_store = false; - } else { - ok = rendertarget_nogbm_new( - &target, - compositor - ); + ok = rendertarget_gbm_new( + &target, + compositor + ); - if (ok != 0) { - free(store); - return false; - } + if (ok != 0) { + free(store); + return false; } } @@ -989,8 +454,10 @@ static void fill_platform_view_params( * ``` */ - struct quad quad = apply_transform_to_aa_rect( - *display_to_view_transform, + + + struct quad quad = transform_aa_rect( + FLUTTER_TRANSFORM_AS_MAT3F(display_to_view_transform), (struct aa_rect) { .offset.x = offset->x, .offset.y = offset->y, @@ -1032,7 +499,7 @@ static void fill_platform_view_params( double rotation = 0, opacity = 1; for (int i = n_mutations - 1; i >= 0; i--) { if (mutations[i]->type == kFlutterPlatformViewMutationTypeTransformation) { - apply_transform_to_quad(mutations[i]->transformation, &quad); + quad = transform_quad(FLUTTER_TRANSFORM_AS_MAT3F(mutations[i]->transformation), quad); double rotz = atan2(mutations[i]->transformation.skewX, mutations[i]->transformation.scaleX) * 180.0 / M_PI; if (rotz < 0) { @@ -1054,31 +521,44 @@ static void fill_platform_view_params( params_out->n_clip_rects = 0; } +static void on_scanout(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata) { + struct compositor *compositor; + + DEBUG_ASSERT_NOT_NULL(drmdev); + DEBUG_ASSERT_NOT_NULL(userdata); + compositor = userdata; + + (void) drmdev; + (void) vblank_ns; + (void) compositor; + + // disabled because vsync is broken + /* + on_pageflip_event( + drmdev_get_fd(drmdev), + 0, + vblank_ns / 1000000000ull, + (vblank_ns % 1000000000ull) / 1000, + NULL + ); + */ +} + /// PRESENT FUNCS static bool on_present_layers( const FlutterLayer **layers, size_t layers_count, void *userdata ) { - struct drmdev_atomic_req *req; + struct kms_req_builder *builder; struct view_cb_data *cb_data; - struct pointer_set planes; struct compositor *compositor; - struct drm_plane *plane; - struct drmdev *drmdev; - uint32_t req_flags; - void *planes_storage[32] = {0}; - bool legacy_rendertarget_set_mode = false; - bool schedule_fake_page_flip_event; - bool use_atomic_modesetting; + EGLBoolean egl_ok; int ok; // TODO: proper error handling compositor = userdata; - drmdev = compositor->drmdev; - schedule_fake_page_flip_event = compositor->do_blocking_atomic_commits; - use_atomic_modesetting = drmdev->supports_atomic_modesetting; #ifdef DUMP_ENGINE_LAYERS LOG_DEBUG("layers:\n"); @@ -1131,362 +611,61 @@ static bool on_present_layers( } #endif - req = NULL; - if (use_atomic_modesetting) { - ok = drmdev_new_atomic_req(compositor->drmdev, &req); - if (ok != 0) { - return false; - } - } else { - planes = PSET_INITIALIZER_STATIC(planes_storage, 32); - for_each_plane_in_drmdev(drmdev, plane) { - if (plane->plane->possible_crtcs & drmdev->selected_crtc->bitmask) { - ok = pset_put(&planes, plane); - if (ok != 0) { - return false; - } - } - } + builder = drmdev_create_request_builder(compositor->drmdev, flutterpi.drm.selected_crtc_id); + if (builder == NULL) { + LOG_ERROR("Could not create KMS request builder.\n"); + return EINVAL; } - cpset_lock(&compositor->cbs); - - EGLDisplay stored_display = eglGetCurrentDisplay(); - EGLSurface stored_read_surface = eglGetCurrentSurface(EGL_READ); - EGLSurface stored_write_surface = eglGetCurrentSurface(EGL_DRAW); - EGLContext stored_context = eglGetCurrentContext(); - - eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.root_context); - eglSwapBuffers(flutterpi.egl.display, flutterpi.egl.surface); - - req_flags = 0 /* DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK*/; if (compositor->has_applied_modeset == false) { - if (use_atomic_modesetting) { - ok = drmdev_atomic_req_put_modeset_props(req, &req_flags); - if (ok != 0) { - return false; - } - } else { - legacy_rendertarget_set_mode = true; - schedule_fake_page_flip_event = true; + ok = kms_req_builder_set_mode(builder, flutterpi.drm.selected_mode); + if (ok != 0) { + LOG_ERROR("Could not apply videomode.\n"); + goto fail_destroy_builder; } - if (use_atomic_modesetting) { - for_each_unreserved_plane_in_atomic_req(req, plane) { - if (plane->type == DRM_PLANE_TYPE_CURSOR) { - // make sure the cursor is in front of everything - int64_t max_zpos; - bool supported; - - ok = drmdev_plane_get_max_zpos_value(req->drmdev, plane->plane->plane_id, &max_zpos); - if (ok != 0) { - LOG_ERROR("Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_get_max_zpos_value: %s\n", strerror(ok)); - continue; - } - - ok = drmdev_plane_supports_setting_zpos_value(req->drmdev, plane->plane->plane_id, max_zpos, &supported); - if (ok != 0) { - LOG_ERROR("Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); - continue; - } - - if (supported) { - drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "zpos", max_zpos); - } else { - LOG_ERROR("Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); - continue; - } - } - } - } else { - for_each_pointer_in_pset(&planes, plane) { - if (plane->type == DRM_PLANE_TYPE_CURSOR) { - // make sure the cursor is in front of everything - int64_t max_zpos; - bool supported; - - ok = drmdev_plane_get_max_zpos_value(drmdev, plane->plane->plane_id, &max_zpos); - if (ok != 0) { - LOG_ERROR("Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_get_max_zpos_value: %s\n", strerror(ok)); - continue; - } - - ok = drmdev_plane_supports_setting_zpos_value(drmdev, plane->plane->plane_id, max_zpos, &supported); - if (ok != 0) { - LOG_ERROR("Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); - continue; - } - - if (supported) { - drmdev_legacy_set_plane_property(drmdev, plane->plane->plane_id, "zpos", max_zpos); - } else { - LOG_ERROR("Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); - continue; - } - } - } + ok = kms_req_builder_set_connector(builder, flutterpi.drm.selected_connector_id); + if (ok != 0) { + LOG_ERROR("Could not apply output connector.\n"); + goto fail_destroy_builder; } compositor->has_applied_modeset = true; } - // first, the state machine phase. - // go through the layers, update - // all platform views accordingly. - // unmount, update, mount. in that order - { - void *mounted_views_storage[layers_count]; - memset(mounted_views_storage, 0, layers_count * sizeof(void*)); - struct pointer_set mounted_views = PSET_INITIALIZER_STATIC(mounted_views_storage, layers_count); - - void *unmounted_views_storage[layers_count]; - memset(unmounted_views_storage, 0, layers_count * sizeof(void*)); - struct pointer_set unmounted_views = PSET_INITIALIZER_STATIC(unmounted_views_storage, layers_count); - - void *updated_views_storage[layers_count]; - memset(updated_views_storage, 0, layers_count * sizeof(void*)); - struct pointer_set updated_views = PSET_INITIALIZER_STATIC(updated_views_storage, layers_count); - - for_each_pointer_in_cpset(&compositor->cbs, cb_data) { - const FlutterLayer *layer = NULL; - bool is_present = false; - int zpos; - - for (int i = 0; i < layers_count; i++) { - if (layers[i]->type == kFlutterLayerContentTypePlatformView && - layers[i]->platform_view->identifier == cb_data->view_id) { - is_present = true; - layer = layers[i]; - zpos = i; - break; - } - } - - DEBUG_ASSERT_NOT_NULL(layer); - - if (!is_present && cb_data->was_present_last_frame) { - pset_put(&unmounted_views, cb_data); - } else if (is_present && cb_data->was_present_last_frame) { - if (cb_data->update_view != NULL) { - bool did_update_view = false; - - did_update_view = did_update_view || (zpos != cb_data->last_zpos); - did_update_view = did_update_view || memcmp(&cb_data->last_size, &layer->size, sizeof(FlutterSize)); - did_update_view = did_update_view || memcmp(&cb_data->last_offset, &layer->offset, sizeof(FlutterPoint)); - did_update_view = did_update_view || (cb_data->last_num_mutations != layer->platform_view->mutations_count); - for (int i = 0; (i < layer->platform_view->mutations_count) && !did_update_view; i++) { - did_update_view = did_update_view || memcmp(cb_data->last_mutations + i, layer->platform_view->mutations[i], sizeof(FlutterPlatformViewMutation)); - } - - if (did_update_view) { - pset_put(&updated_views, cb_data); - } - } - } else if (is_present && !cb_data->was_present_last_frame) { - pset_put(&mounted_views, cb_data); - } - } - - for_each_pointer_in_pset(&unmounted_views, cb_data) { - if (cb_data->unmount != NULL) { - ok = cb_data->unmount( - cb_data->view_id, - req, - cb_data->userdata - ); - if (ok != 0) { - LOG_ERROR("Could not unmount platform view. unmount: %s\n", strerror(ok)); - } - - cb_data->was_present_last_frame = false; - } - } - - for_each_pointer_in_pset(&updated_views, cb_data) { - const FlutterLayer *layer = NULL; - int zpos = 0; - - for (int i = 0; i < layers_count; i++) { - if (layers[i]->type == kFlutterLayerContentTypePlatformView && - layers[i]->platform_view->identifier == cb_data->view_id) { - layer = layers[i]; - zpos = i; - break; - } - } - - DEBUG_ASSERT_NOT_NULL(layer); - - struct platform_view_params params; - fill_platform_view_params( - ¶ms, - &layer->offset, - &layer->size, - layer->platform_view->mutations, - layer->platform_view->mutations_count, - &flutterpi.view.display_to_view_transform, - &flutterpi.view.view_to_display_transform, - flutterpi.display.pixel_ratio - ); - - ok = cb_data->update_view( - cb_data->view_id, - req, - ¶ms, - zpos, - cb_data->userdata - ); - if (ok != 0) { - LOG_ERROR("Could not update platform view. update_view: %s\n", strerror(ok)); - } - - cb_data->last_zpos = zpos; - cb_data->last_size = layer->size; - cb_data->last_offset = layer->offset; - cb_data->last_num_mutations = layer->platform_view->mutations_count; - for (int i = 0; i < layer->platform_view->mutations_count; i++) { - memcpy(cb_data->last_mutations + i, layer->platform_view->mutations[i], sizeof(FlutterPlatformViewMutation)); - } - } - - for_each_pointer_in_pset(&mounted_views, cb_data) { - const FlutterLayer *layer = NULL; - int zpos = 0; - - for (int i = 0; i < layers_count; i++) { - if (layers[i]->type == kFlutterLayerContentTypePlatformView && - layers[i]->platform_view->identifier == cb_data->view_id) { - layer = layers[i]; - zpos = i; - break; - } - } - - DEBUG_ASSERT_NOT_NULL(layer); - - struct platform_view_params params; - fill_platform_view_params( - ¶ms, - &layer->offset, - &layer->size, - layer->platform_view->mutations, - layer->platform_view->mutations_count, - &flutterpi.view.display_to_view_transform, - &flutterpi.view.view_to_display_transform, - flutterpi.display.pixel_ratio - ); - - if (cb_data->mount != NULL) { - ok = cb_data->mount( - layer->platform_view->identifier, - req, - ¶ms, - zpos, - cb_data->userdata - ); - if (ok != 0) { - LOG_ERROR("Could not mount platform view. %s\n", strerror(ok)); - } - } + EGLDisplay stored_display = eglGetCurrentDisplay(); + EGLSurface stored_read_surface = eglGetCurrentSurface(EGL_READ); + EGLSurface stored_write_surface = eglGetCurrentSurface(EGL_DRAW); + EGLContext stored_context = eglGetCurrentContext(); - cb_data->was_present_last_frame = true; - cb_data->last_zpos = zpos; - cb_data->last_size = layer->size; - cb_data->last_offset = layer->offset; - cb_data->last_num_mutations = layer->platform_view->mutations_count; - for (int i = 0; i < layer->platform_view->mutations_count; i++) { - memcpy(cb_data->last_mutations + i, layer->platform_view->mutations[i], sizeof(FlutterPlatformViewMutation)); - } + if (stored_display != flutterpi.egl.display || + stored_read_surface != flutterpi.egl.surface || + stored_write_surface != flutterpi.egl.surface || + stored_context == EGL_NO_CONTEXT + ) { + egl_ok = eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.root_context); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not make EGL context current.\n"); + goto fail_destroy_builder; } } - int64_t min_zpos; - if (use_atomic_modesetting) { - for_each_unreserved_plane_in_atomic_req(req, plane) { - if (plane->type == DRM_PLANE_TYPE_PRIMARY) { - ok = drmdev_plane_get_min_zpos_value(req->drmdev, plane->plane->plane_id, &min_zpos); - if (ok != 0) { - min_zpos = 0; - } - break; - } - } - } else { - for_each_pointer_in_pset(&planes, plane) { - if (plane->type == DRM_PLANE_TYPE_PRIMARY) { - ok = drmdev_plane_get_min_zpos_value(drmdev, plane->plane->plane_id, &min_zpos); - if (ok != 0) { - min_zpos = 0; - } - break; - } - } + egl_ok = eglSwapBuffers(flutterpi.egl.display, flutterpi.egl.surface); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not flush EGL rendering. eglSwapBuffers: 0x%08X\n", eglGetError()); + goto fail_restore_context; } + cpset_lock(&compositor->cbs); + for (int i = 0; i < layers_count; i++) { if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { - if (use_atomic_modesetting) { - for_each_unreserved_plane_in_atomic_req(req, plane) { - // choose a plane which has an "intrinsic" zpos that matches - // the zpos we want the plane to have. - // (Since planes are buggy and we can't rely on the zpos we explicitly - // configure the plane to have to be actually applied to the hardware. - // In short, assigning a different value to the zpos property won't always - // take effect.) - if ((i == 0) && (plane->type == DRM_PLANE_TYPE_PRIMARY)) { - ok = drmdev_atomic_req_reserve_plane(req, plane); - break; - } else if ((i != 0) && (plane->type == DRM_PLANE_TYPE_OVERLAY)) { - ok = drmdev_atomic_req_reserve_plane(req, plane); - break; - } - } - } else { - for_each_pointer_in_pset(&planes, plane) { - if ((i == 0) && (plane->type == DRM_PLANE_TYPE_PRIMARY)) { - break; - } else if ((i != 0) && (plane->type == DRM_PLANE_TYPE_OVERLAY)) { - break; - } - } - if (plane != NULL) { - pset_remove(&planes, plane); - } - } - if (plane == NULL) { - LOG_ERROR("Could not find a free primary/overlay DRM plane for presenting the backing store. drmdev_atomic_req_reserve_plane: %s\n", strerror(ok)); - continue; - } - struct flutterpi_backing_store *store = layers[i]->backing_store->user_data; struct rendertarget *target = store->target; - - if (use_atomic_modesetting) { - ok = target->present( - target, - req, - plane->plane->plane_id, - 0, - 0, - compositor->drmdev->selected_mode->hdisplay, - compositor->drmdev->selected_mode->vdisplay, - i + min_zpos - ); - if (ok != 0) { - LOG_ERROR("Could not present backing store. rendertarget->present: %s\n", strerror(ok)); - } - } else { - ok = target->present_legacy( - target, - drmdev, - plane->plane->plane_id, - 0, - 0, - compositor->drmdev->selected_mode->hdisplay, - compositor->drmdev->selected_mode->vdisplay, - i + min_zpos, - legacy_rendertarget_set_mode && (plane->type == DRM_PLANE_TYPE_PRIMARY) - ); + + ok = target->present(target, builder); + if (ok != 0) { + LOG_ERROR("Could not present backing store. rendertarget->present: %s\n", strerror(ok)); } } else { DEBUG_ASSERT(layers[i]->type == kFlutterLayerContentTypePlatformView); @@ -1507,9 +686,8 @@ static bool on_present_layers( ok = cb_data->present( cb_data->view_id, - req, + builder, ¶ms, - i + min_zpos, cb_data->userdata ); if (ok != 0) { @@ -1519,63 +697,87 @@ static bool on_present_layers( } } - if (use_atomic_modesetting) { - for_each_unreserved_plane_in_atomic_req(req, plane) { - if ((plane->type == DRM_PLANE_TYPE_PRIMARY) || (plane->type == DRM_PLANE_TYPE_OVERLAY)) { - drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "FB_ID", 0); - drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "CRTC_ID", 0); - } + cpset_unlock(&compositor->cbs); + + if (stored_display != flutterpi.egl.display || + stored_read_surface != flutterpi.egl.surface || + stored_write_surface != flutterpi.egl.surface || + stored_context == EGL_NO_CONTEXT + ) { + ok = eglMakeCurrent(stored_display, stored_read_surface, stored_write_surface, stored_context); + if (ok != EGL_TRUE) { + LOG_ERROR("Could not restore EGL context.\n"); + goto fail_destroy_builder; } } - eglMakeCurrent(stored_display, stored_read_surface, stored_write_surface, stored_context); + struct kms_req *req = kms_req_builder_build(builder); + if (req == NULL) { + LOG_ERROR("Could not build atomic request.\n"); + goto fail_destroy_builder; + } - if (use_atomic_modesetting) { - do_commit: - if (compositor->do_blocking_atomic_commits) { - req_flags &= ~(DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT); - } else { - req_flags |= DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT; - } + kms_req_builder_unref(builder); + builder = NULL; - ok = drmdev_atomic_req_commit(req, req_flags, NULL); - if ((compositor->do_blocking_atomic_commits == false) && (ok == EBUSY)) { + if (!compositor->do_blocking_atomic_commits) { + ok = kms_req_commit_nonblocking(req, on_scanout, compositor, NULL); + if (ok == EBUSY) { LOG_ERROR( - "Non-blocking drmModeAtomicCommit failed with EBUSY.\n" - " Future drmModeAtomicCommits will be executed blockingly.\n" + "Non-blocking kms_req_commit_nonblocking failed with EBUSY.\n" + " Future commits will be executed blockingly.\n" " This may have have an impact on performance.\n" ); - compositor->do_blocking_atomic_commits = true; - schedule_fake_page_flip_event = true; - goto do_commit; } else if (ok != 0) { - LOG_ERROR("Could not present frame. drmModeAtomicCommit: %s\n", strerror(ok)); - drmdev_destroy_atomic_req(req); - cpset_unlock(&compositor->cbs); - return false; + LOG_ERROR("Could not present frame. kms_req_commit_nonblocking: %s\n", strerror(ok)); + goto fail_unref_req; } - - drmdev_destroy_atomic_req(req); } - cpset_unlock(&compositor->cbs); + if (compositor->do_blocking_atomic_commits) { + uint64_t vblank_ns = 0; - if (schedule_fake_page_flip_event) { - uint64_t time = flutterpi.flutter.libflutter_engine.FlutterEngineGetCurrentTime(); + ok = kms_req_commit_blocking(req, &vblank_ns); + if (ok != 0) { + LOG_ERROR("Could not present frame. kms_req_commit_blocking: %s\n", strerror(ok)); + goto fail_unref_req; + } struct simulated_page_flip_event_data *data = malloc(sizeof(struct simulated_page_flip_event_data)); if (data == NULL) { - return false; + goto fail_unref_req; } - data->sec = time / 1000000000llu; - data->usec = (time % 1000000000llu) / 1000; + data->sec = vblank_ns / 1000000000llu; + data->usec = (vblank_ns % 1000000000llu) / 1000; flutterpi_post_platform_task(execute_simulate_page_flip_event, data); } + kms_req_unref(req); + req = NULL; return true; + + + fail_unref_req: + kms_req_unref(req); + goto fail_return_false; + + fail_restore_context: + if (stored_display != flutterpi.egl.display || + stored_read_surface != flutterpi.egl.surface || + stored_write_surface != flutterpi.egl.surface || + stored_context == EGL_NO_CONTEXT + ) { + eglMakeCurrent(stored_display, stored_read_surface, stored_write_surface, stored_context); + } + + fail_destroy_builder: + kms_req_builder_destroy(builder); + + fail_return_false: + return false; } int compositor_on_page_flip( @@ -1590,9 +792,6 @@ int compositor_on_page_flip( /// PLATFORM VIEW CALLBACKS int compositor_set_view_callbacks( int64_t view_id, - platform_view_mount_cb mount, - platform_view_unmount_cb unmount, - platform_view_update_view_cb update_view, platform_view_present_cb present, void *userdata ) { @@ -1613,9 +812,6 @@ int compositor_set_view_callbacks( } entry->view_id = view_id; - entry->mount = mount; - entry->unmount = unmount; - entry->update_view = update_view; entry->present = present; entry->userdata = userdata; @@ -1650,12 +846,12 @@ static void destroy_cursor_buffer(void) { munmap(compositor.cursor.buffer, compositor.cursor.buffer_size); - drmModeRmFB(compositor.drmdev->fd, compositor.cursor.drm_fb_id); + drmdev_rm_fb(compositor.drmdev, compositor.cursor.drm_fb_id); memset(&destroy_req, 0, sizeof destroy_req); destroy_req.handle = compositor.cursor.gem_bo_handle; - ioctl(compositor.drmdev->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); + ioctl(drmdev_get_fd(compositor.drmdev), DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); compositor.cursor.has_buffer = false; compositor.cursor.buffer_depth = 0; @@ -1677,7 +873,7 @@ static int create_cursor_buffer(int width, int height, int bpp) { uint8_t depth; int ok; - ok = drmGetCap(compositor.drmdev->fd, DRM_CAP_DUMB_BUFFER, &cap); + ok = drmGetCap(drmdev_get_fd(compositor.drmdev), DRM_CAP_DUMB_BUFFER, &cap); if (ok < 0) { ok = errno; LOG_ERROR("Could not query GPU Driver support for dumb buffers. drmGetCap: %s\n", strerror(errno)); @@ -1690,7 +886,7 @@ static int create_cursor_buffer(int width, int height, int bpp) { goto fail_return_ok; } - ok = drmGetCap(compositor.drmdev->fd, DRM_CAP_DUMB_PREFERRED_DEPTH, &cap); + ok = drmGetCap(drmdev_get_fd(compositor.drmdev), DRM_CAP_DUMB_PREFERRED_DEPTH, &cap); if (ok < 0) { ok = errno; LOG_ERROR("Could not query dumb buffer preferred depth capability. drmGetCap: %s\n", strerror(errno)); @@ -1709,31 +905,42 @@ static int create_cursor_buffer(int width, int height, int bpp) { create_req.bpp = bpp; create_req.flags = 0; - ok = ioctl(compositor.drmdev->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req); + ok = ioctl(drmdev_get_fd(compositor.drmdev), DRM_IOCTL_MODE_CREATE_DUMB, &create_req); if (ok < 0) { ok = errno; LOG_ERROR("Could not create a dumb buffer for the hardware cursor. ioctl: %s\n", strerror(errno)); goto fail_return_ok; } - ok = drmModeAddFB(compositor.drmdev->fd, create_req.width, create_req.height, 32, create_req.bpp, create_req.pitch, create_req.handle, &drm_fb_id); + ok = drmdev_add_fb( + compositor.drmdev, + create_req.width, create_req.height, + kARGB8888_FpiPixelFormat, + create_req.handle, + create_req.pitch, + 0, + false, + 0 + ); if (ok < 0) { ok = errno; LOG_ERROR("Could not make a DRM FB out of the hardware cursor buffer. drmModeAddFB: %s\n", strerror(errno)); goto fail_destroy_dumb_buffer; } + drm_fb_id = ok, + memset(&map_req, 0, sizeof map_req); map_req.handle = create_req.handle; - ok = ioctl(compositor.drmdev->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req); + ok = ioctl(drmdev_get_fd(compositor.drmdev), DRM_IOCTL_MODE_MAP_DUMB, &map_req); if (ok < 0) { ok = errno; LOG_ERROR("Could not prepare dumb buffer mmap for uploading the hardware cursor icon. ioctl: %s\n", strerror(errno)); goto fail_rm_drm_fb; } - buffer = mmap(0, create_req.size, PROT_READ | PROT_WRITE, MAP_SHARED, compositor.drmdev->fd, map_req.offset); + buffer = mmap(0, create_req.size, PROT_READ | PROT_WRITE, MAP_SHARED, drmdev_get_fd(compositor.drmdev), map_req.offset); if (buffer == MAP_FAILED) { ok = errno; LOG_ERROR("Could not mmap dumb buffer for uploading the hardware cursor icon. mmap: %s\n", strerror(errno)); @@ -1754,13 +961,13 @@ static int create_cursor_buffer(int width, int height, int bpp) { fail_rm_drm_fb: - drmModeRmFB(compositor.drmdev->fd, drm_fb_id); + drmModeRmFB(drmdev_get_fd(compositor.drmdev), drm_fb_id); fail_destroy_dumb_buffer: ; struct drm_mode_destroy_dumb destroy_req; memset(&destroy_req, 0, sizeof destroy_req); destroy_req.handle = create_req.handle; - ioctl(compositor.drmdev->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); + ioctl(drmdev_get_fd(compositor.drmdev), DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); fail_return_ok: return ok; @@ -1844,8 +1051,8 @@ int compositor_apply_cursor_state( } ok = drmModeSetCursor2( - compositor.drmdev->fd, - compositor.drmdev->selected_crtc->crtc->crtc_id, + drmdev_get_fd(compositor.drmdev), + flutterpi.drm.selected_crtc_id, compositor.cursor.gem_bo_handle, compositor.cursor.cursor_size, compositor.cursor.cursor_size, @@ -1862,8 +1069,8 @@ int compositor_apply_cursor_state( } ok = drmModeMoveCursor( - compositor.drmdev->fd, - compositor.drmdev->selected_crtc->crtc->crtc_id, + drmdev_get_fd(compositor.drmdev), + flutterpi.drm.selected_crtc_id, compositor.cursor.x - compositor.cursor.hot_x, compositor.cursor.y - compositor.cursor.hot_y ); @@ -1883,8 +1090,8 @@ int compositor_apply_cursor_state( return 0; } else if ((is_enabled == false) && (compositor.cursor.is_enabled == true)) { drmModeSetCursor( - compositor.drmdev->fd, - compositor.drmdev->selected_crtc->crtc->crtc_id, + drmdev_get_fd(compositor.drmdev), + flutterpi.drm.selected_crtc_id, 0, 0, 0 ); @@ -1912,7 +1119,7 @@ int compositor_set_cursor_pos(int x, int y) { return 0; } - ok = drmModeMoveCursor(compositor.drmdev->fd, compositor.drmdev->selected_crtc->crtc->crtc_id, x - compositor.cursor.hot_x, y - compositor.cursor.hot_y); + ok = drmModeMoveCursor(drmdev_get_fd(compositor.drmdev), flutterpi.drm.selected_crtc_id, x - compositor.cursor.hot_x, y - compositor.cursor.hot_y); if (ok < 0) { LOG_ERROR("Could not move cursor. drmModeMoveCursor: %s", strerror(errno)); return errno; diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 0d882cf1..d782e0af 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -104,7 +104,8 @@ OPTIONS:\n\ \n\ --pixelformat Selects the pixel format to use for the framebuffers.\n\ Available pixel formats:\n\ - RGB565, ARGB8888, XRGB8888, BGRA8888, RGBA8888\n\ + RGB565, ARGB4444, XRGB4444, ARGB1555, XRGB1555, ARGB8888,\n\ + XRGB8888, BGRA8888, BGRX8888, RGBA8888, RGBX8888\n\ --videomode widthxheight\n\ --videomode widthxheight@hz Uses an output videomode that satisfies the argument.\n\ If no hz value is given, the highest possible refreshrate\n\ @@ -133,7 +134,7 @@ SEE ALSO:\n\ "; // If this fails, update the accepted value list for --pixelformat above too. -COMPILE_ASSERT(kCount_PixFmt == 5); +COMPILE_ASSERT(kCount_PixFmt == 11); struct flutterpi flutterpi; @@ -396,7 +397,7 @@ static int on_execute_frame_request( uint64_t ns; if (flutterpi.drm.platform_supports_get_sequence_ioctl) { ns = 0; - ok = drmCrtcGetSequence(flutterpi.drm.drmdev->fd, flutterpi.drm.drmdev->selected_crtc->crtc->crtc_id, NULL, &ns); + ok = drmCrtcGetSequence(drmdev_get_fd(flutterpi.drm.drmdev), flutterpi.drm.selected_crtc_id, NULL, &ns); if (ok < 0) { perror("[flutter-pi] Couldn't get last vblank timestamp. drmCrtcGetSequence"); cqueue_unlock(&flutterpi.frame_queue); @@ -1375,14 +1376,14 @@ static int init_display(void) { continue; } - ok = drmdev_new_from_path(&flutterpi.drm.drmdev, device->nodes[DRM_NODE_PRIMARY]); - if (ok != 0) { + flutterpi.drm.drmdev = drmdev_new_from_path(device->nodes[DRM_NODE_PRIMARY], NULL, NULL); + if (flutterpi.drm.drmdev == NULL) { LOG_ERROR("Could not create drmdev from device at \"%s\". Continuing.\n", device->nodes[DRM_NODE_PRIMARY]); continue; } for_each_connector_in_drmdev(flutterpi.drm.drmdev, connector) { - if (connector->connector->connection == DRM_MODE_CONNECTED) { + if (connector->variable_state.connection_state == kConnected_DrmConnectionState) { goto found_connected_connector; } } @@ -1403,24 +1404,24 @@ static int init_display(void) { // find a connected connector for_each_connector_in_drmdev(flutterpi.drm.drmdev, connector) { - if (connector->connector->connection == DRM_MODE_CONNECTED) { + if (connector->variable_state.connection_state == kConnected_DrmConnectionState) { // only update the physical size of the display if the values // are not yet initialized / not set with a commandline option if ((flutterpi.display.width_mm == 0) || (flutterpi.display.height_mm == 0)) { - if ((connector->connector->connector_type == DRM_MODE_CONNECTOR_DSI) && - (connector->connector->mmWidth == 0) && - (connector->connector->mmHeight == 0)) + if ((connector->type == kDSI_DrmConnectorType) && + (connector->variable_state.width_mm == 0) && + (connector->variable_state.height_mm == 0)) { // if it's connected via DSI, and the width & height are 0, // it's probably the official 7 inch touchscreen. flutterpi.display.width_mm = 155; flutterpi.display.height_mm = 86; - } else if ((connector->connector->mmHeight % 10 == 0) && - (connector->connector->mmWidth % 10 == 0)) { + } else if ((connector->variable_state.width_mm % 10 == 0) && + (connector->variable_state.height_mm % 10 == 0)) { // don't change anything. } else { - flutterpi.display.width_mm = connector->connector->mmWidth; - flutterpi.display.height_mm = connector->connector->mmHeight; + flutterpi.display.width_mm = connector->variable_state.width_mm; + flutterpi.display.height_mm = connector->variable_state.height_mm; } } @@ -1516,15 +1517,15 @@ static int init_display(void) { } for_each_encoder_in_drmdev(flutterpi.drm.drmdev, encoder) { - if (encoder->encoder->encoder_id == connector->connector->encoder_id) { + if (encoder->encoder->encoder_id == connector->committed_state.encoder_id) { break; } } if (encoder == NULL) { - for (int i = 0; i < connector->connector->count_encoders; i++, encoder = NULL) { + for (int i = 0; i < connector->n_encoders; i++, encoder = NULL) { for_each_encoder_in_drmdev(flutterpi.drm.drmdev, encoder) { - if (encoder->encoder->encoder_id == connector->connector->encoders[i]) { + if (encoder->encoder->encoder_id == connector->encoders[i]) { break; } } @@ -1542,7 +1543,7 @@ static int init_display(void) { } for_each_crtc_in_drmdev(flutterpi.drm.drmdev, crtc) { - if (crtc->crtc->crtc_id == encoder->encoder->crtc_id) { + if (crtc->id == encoder->encoder->crtc_id) { break; } } @@ -1561,13 +1562,14 @@ static int init_display(void) { return EINVAL; } - ok = drmdev_configure(flutterpi.drm.drmdev, connector->connector->connector_id, encoder->encoder->encoder_id, crtc->crtc->crtc_id, mode); - if (ok != 0) return ok; + flutterpi.drm.selected_connector_id = connector->id; + flutterpi.drm.selected_crtc_id = crtc->id; + flutterpi.drm.selected_mode = mode; // only enable vsync if the kernel supplies valid vblank timestamps { uint64_t ns = 0; - ok = drmCrtcGetSequence(flutterpi.drm.drmdev->fd, flutterpi.drm.drmdev->selected_crtc->crtc->crtc_id, NULL, &ns); + ok = drmCrtcGetSequence(drmdev_get_fd(flutterpi.drm.drmdev), flutterpi.drm.selected_crtc_id, NULL, &ns); int _errno = errno; if ((ok == 0) && (ns != 0)) { @@ -1590,7 +1592,7 @@ static int init_display(void) { ok = sd_event_add_io( flutterpi.event_loop, &flutterpi.drm.drm_pageflip_event_source, - flutterpi.drm.drmdev->fd, + drmdev_get_fd(flutterpi.drm.drmdev), EPOLLIN | EPOLLHUP | EPOLLPRI, on_drm_fd_ready, NULL @@ -1618,7 +1620,7 @@ static int init_display(void) { /********************** * GBM INITIALIZATION * **********************/ - flutterpi.gbm.device = gbm_create_device(flutterpi.drm.drmdev->fd); + flutterpi.gbm.device = gbm_create_device(drmdev_get_fd(flutterpi.drm.drmdev)); flutterpi.gbm.format = DRM_FORMAT_ARGB8888; flutterpi.gbm.surface = NULL; flutterpi.gbm.modifier = DRM_FORMAT_MOD_LINEAR; @@ -2424,14 +2426,14 @@ static bool parse_cmd_args(int argc, char **argv) { LOG_ERROR( "ERROR: Invalid argument for --pixelformat passed.\n" - "Valid values are: RGB565, ARGB8888, XRGB8888, BGRA8888, RGBA8888\n" + "Valid values are: RGB565, ARGB4444, XRGB4444, ARGB1555, XRGB1555, ARGB8888, XRGB8888, BGRA8888, BGRX8888, RGBA8888, RGBX8888\n" "%s", usage ); // Just so we get a compile error when we update the pixel format list // but don't update the valid values above. - COMPILE_ASSERT(kCount_PixFmt == 5); + COMPILE_ASSERT(kCount_PixFmt == 11); valid_format: break; diff --git a/src/modesetting.c b/src/modesetting.c index 2d062c23..78210809 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -1,99 +1,272 @@ -#include -#include +#include +#include #include -#include +#include #include -#include +#include + +#include #include +#include #include #include #include +#include + +FILE_DESCR("modesetting") + +struct kms_req_layer { + struct kms_fb_layer layer; + + uint32_t plane_id; + + bool set_zpos; + int64_t zpos; + + bool set_rotation; + drm_plane_transform_t rotation; + + kms_fb_release_cb_t release_callback; + kms_deferred_fb_release_cb_t deferred_release_callback; + void *release_callback_userdata; +}; + +struct kms_req_builder { + refcount_t n_refs; + + struct drmdev *drmdev; + bool use_legacy; + bool supports_atomic; + + struct drm_connector *connector; + struct drm_crtc *crtc; + + BMAP_DECLARATION(planes, 32); + drmModeAtomicReq *req; + int64_t next_zpos; + + int n_layers; + struct kms_req_layer layers[32]; + + bool unset_mode; + bool has_mode; + drmModeModeInfo mode; +}; + +struct drmdev { + int fd; + + refcount_t n_refs; + pthread_mutex_t mutex; + bool supports_atomic_modesetting; + + size_t n_connectors; + struct drm_connector *connectors; + + size_t n_encoders; + struct drm_encoder *encoders; + + size_t n_crtcs; + struct drm_crtc *crtcs; -static int drmdev_lock(struct drmdev *drmdev) { - return pthread_mutex_lock(&drmdev->mutex); + size_t n_planes; + struct drm_plane *planes; + + drmModeRes *res; + drmModePlaneRes *plane_res; + + struct gbm_device *gbm_device; + + int event_fd; + + struct { + kms_scanout_cb_t scanout_callback; + void *userdata; + void_callback_t destroy_callback; + + struct kms_req *last_flipped; + } per_crtc_state[32]; + + int master_fd; + void *master_fd_metadata; + + struct drmdev_interface interface; + void *userdata; +}; + +struct drmdev_atomic_req { + struct drmdev *drmdev; + drmModeAtomicReq *atomic_req; + + void *available_planes_storage[32]; + struct pointer_set available_planes; +}; + +static struct drm_mode_blob *drm_mode_blob_new(int drm_fd, const drmModeModeInfo *mode) { + struct drm_mode_blob *blob; + uint32_t blob_id; + int ok; + + blob = malloc(sizeof *blob); + if (blob == NULL) { + return NULL; + } + + ok = drmModeCreatePropertyBlob(drm_fd, mode, sizeof *mode, &blob_id); + if (ok != 0) { + ok = errno; + LOG_ERROR("Couldn't upload mode to kernel. drmModeCreatePropertyBlob: %s\n", strerror(ok)); + free(blob); + return NULL; + } + + blob->drm_fd = dup(drm_fd); + blob->blob_id = blob_id; + blob->mode = *mode; + return blob; } -static int drmdev_unlock(struct drmdev *drmdev) { - return pthread_mutex_unlock(&drmdev->mutex); +void drm_mode_blob_destroy(struct drm_mode_blob *blob) { + int ok; + + DEBUG_ASSERT_NOT_NULL(blob); + + ok = drmModeDestroyPropertyBlob(blob->drm_fd, blob->blob_id); + if (ok != 0) { + ok = errno; + LOG_ERROR("Couldn't destroy mode property blob. drmModeDestroyPropertyBlob: %s\n", strerror(ok)); + } + // we dup()-ed it in drm_mode_blob_new. + close(blob->drm_fd); + free(blob); } -static int fetch_connectors(struct drmdev *drmdev, struct drm_connector **connectors_out, size_t *n_connectors_out) { - struct drm_connector *connectors; - int n_allocated_connectors; +DEFINE_STATIC_LOCK_OPS(drmdev, mutex) + +static int fetch_connector(int drm_fd, uint32_t connector_id, struct drm_connector *connector_out) { + struct drm_connector_prop_ids ids; + drmModeObjectProperties *props; + drmModePropertyRes *prop_info; + drmModeConnector *connector; + drmModeModeInfo *modes; + uint32_t crtc_id; int ok; - connectors = calloc(drmdev->res->count_connectors, sizeof *connectors); - if (connectors == NULL) { - *connectors_out = NULL; - return ENOMEM; + drm_connector_prop_ids_init(&ids); + + connector = drmModeGetConnector(drm_fd, connector_id); + if (connector == NULL) { + ok = errno; + LOG_ERROR("Could not get DRM device connector. drmModeGetConnector"); + return ok; } - n_allocated_connectors = 0; - for (int i = 0; i < drmdev->res->count_connectors; i++, n_allocated_connectors++) { - drmModeObjectProperties *props; - drmModePropertyRes **props_info; - drmModeConnector *connector; + props = drmModeObjectGetProperties(drm_fd, connector_id, DRM_MODE_OBJECT_CONNECTOR); + if (props == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device connectors properties. drmModeObjectGetProperties"); + goto fail_free_connector; + } - connector = drmModeGetConnector(drmdev->fd, drmdev->res->connectors[i]); - if (connector == NULL) { + crtc_id = DRM_ID_NONE; + for (int i = 0; i < props->count_props; i++) { + prop_info = drmModeGetProperty(drm_fd, props->props[i]); + if (prop_info == NULL) { ok = errno; - perror("[modesetting] Could not get DRM device connector. drmModeGetConnector"); - goto fail_free_connectors; + LOG_ERROR("Could not get DRM device connector properties' info. drmModeGetProperty: %s\n", strerror(ok)); + goto fail_free_props; } - props = drmModeObjectGetProperties(drmdev->fd, drmdev->res->connectors[i], DRM_MODE_OBJECT_CONNECTOR); - if (props == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device connectors properties. drmModeObjectGetProperties"); - drmModeFreeConnector(connector); - goto fail_free_connectors; - } +#define CHECK_ASSIGN_PROPERTY_ID(_name_str, _name) \ + if (strncmp(prop_info->name, _name_str, DRM_PROP_NAME_LEN) == 0) { \ + ids._name = prop_info->prop_id; \ + } else - props_info = calloc(props->count_props, sizeof *props_info); - if (props_info == NULL) { - ok = ENOMEM; - drmModeFreeObjectProperties(props); - drmModeFreeConnector(connector); - goto fail_free_connectors; + DRM_CONNECTOR_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) { + // this is the trailing else case + LOG_DEBUG("Unknown DRM connector property: %s\n", prop_info->name); } - for (int j = 0; j < props->count_props; j++) { - props_info[j] = drmModeGetProperty(drmdev->fd, props->props[j]); - if (props_info[j] == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device connector properties' info. drmModeGetProperty"); - for (int k = 0; k < (j-1); k++) - drmModeFreeProperty(props_info[j]); - free(props_info); - drmModeFreeObjectProperties(props); - drmModeFreeConnector(connector); - goto fail_free_connectors; - } +#undef CHECK_ASSIGN_PROPERTY_ID + + if (strncmp(prop_info->name, "CRTC_ID", DRM_PROP_NAME_LEN) == 0) { + crtc_id = props->prop_values[i]; } - connectors[i].connector = connector; - connectors[i].props = props; - connectors[i].props_info = props_info; + drmModeFreeProperty(prop_info); + prop_info = NULL; } - *connectors_out = connectors; - *n_connectors_out = drmdev->res->count_connectors; + DEBUG_ASSERT((connector->modes == NULL) == (connector->count_modes == 0)); + if (connector->modes != NULL) { + modes = memdup(connector->modes, connector->count_modes * sizeof(*connector->modes)); + if (modes == NULL) { + goto fail_free_props; + } + } else { + modes = NULL; + } + + connector_out->id = connector->connector_id; + connector_out->type = connector->connector_type; + connector_out->type_id = connector->connector_type_id; + connector_out->ids = ids; + connector_out->n_encoders = connector->count_encoders; + DEBUG_ASSERT(connector->count_encoders <= 32); + memcpy(connector_out->encoders, connector->encoders, connector->count_encoders * sizeof(uint32_t)); + connector_out->variable_state.connection_state = (enum drm_connection_state) connector->connection; + connector_out->variable_state.subpixel_layout = (enum drm_subpixel_layout) connector->subpixel; + connector_out->variable_state.width_mm = connector->mmWidth; + connector_out->variable_state.height_mm = connector->mmHeight; + connector_out->variable_state.n_modes = connector->count_modes; + connector_out->variable_state.modes = modes; + connector_out->committed_state.crtc_id = crtc_id; + connector_out->committed_state.encoder_id = connector->encoder_id; + drmModeFreeObjectProperties(props); + drmModeFreeConnector(connector); return 0; - fail_free_connectors: - for (int i = 0; i < n_allocated_connectors; i++) { - for (int j = 0; j < connectors[i].props->count_props; j++) - drmModeFreeProperty(connectors[i].props_info[j]); - free(connectors[i].props_info); - drmModeFreeObjectProperties(connectors[i].props); - drmModeFreeConnector(connectors[i].connector); + +fail_free_props: + drmModeFreeObjectProperties(props); + +fail_free_connector: + drmModeFreeConnector(connector); + return ok; +} + +static void free_connector(struct drm_connector *connector) { + free(connector->variable_state.modes); +} + +static int fetch_connectors(struct drmdev *drmdev, struct drm_connector **connectors_out, size_t *n_connectors_out) { + struct drm_connector *connectors; + int ok; + + connectors = calloc(drmdev->res->count_connectors, sizeof *connectors); + if (connectors == NULL) { + *connectors_out = NULL; + return ENOMEM; + } + + for (int i = 0; i < drmdev->res->count_connectors; i++) { + ok = fetch_connector(drmdev->fd, drmdev->res->connectors[i], connectors + i); + if (ok != 0) { + for (int j = 0; j < i; j++) + free_connector(connectors + j); + goto fail_free_connectors; + } } - free(connectors); + *connectors_out = connectors; + *n_connectors_out = drmdev->res->count_connectors; + return 0; +fail_free_connectors: + free(connectors); *connectors_out = NULL; *n_connectors_out = 0; return ok; @@ -101,18 +274,31 @@ static int fetch_connectors(struct drmdev *drmdev, struct drm_connector **connec static int free_connectors(struct drm_connector *connectors, size_t n_connectors) { for (int i = 0; i < n_connectors; i++) { - for (int j = 0; j < connectors[i].props->count_props; j++) - drmModeFreeProperty(connectors[i].props_info[j]); - free(connectors[i].props_info); - drmModeFreeObjectProperties(connectors[i].props); - drmModeFreeConnector(connectors[i].connector); + free_connector(connectors + i); } - free(connectors); + return 0; +} + +static int fetch_encoder(int drm_fd, uint32_t encoder_id, struct drm_encoder *encoder_out) { + drmModeEncoder *encoder; + int ok; + encoder = drmModeGetEncoder(drm_fd, encoder_id); + if (encoder == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device encoder. drmModeGetEncoder"); + return ok; + } + + encoder_out->encoder = encoder; return 0; } +static void free_encoder(struct drm_encoder *encoder) { + drmModeFreeEncoder(encoder->encoder); +} + static int fetch_encoders(struct drmdev *drmdev, struct drm_encoder **encoders_out, size_t *n_encoders_out) { struct drm_encoder *encoders; int n_allocated_encoders; @@ -127,24 +313,17 @@ static int fetch_encoders(struct drmdev *drmdev, struct drm_encoder **encoders_o n_allocated_encoders = 0; for (int i = 0; i < drmdev->res->count_encoders; i++, n_allocated_encoders++) { - drmModeEncoder *encoder; - - encoder = drmModeGetEncoder(drmdev->fd, drmdev->res->encoders[i]); - if (encoder == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device encoder. drmModeGetEncoder"); + ok = fetch_encoder(drmdev->fd, drmdev->res->encoders[i], encoders + i); + if (ok != 0) { goto fail_free_encoders; } - - encoders[i].encoder = encoder; } *encoders_out = encoders; *n_encoders_out = drmdev->res->count_encoders; - return 0; - fail_free_encoders: +fail_free_encoders: for (int i = 0; i < n_allocated_encoders; i++) { drmModeFreeEncoder(encoders[i].encoder); } @@ -156,95 +335,111 @@ static int fetch_encoders(struct drmdev *drmdev, struct drm_encoder **encoders_o return ok; } -static int free_encoders(struct drm_encoder *encoders, size_t n_encoders) { +static void free_encoders(struct drm_encoder *encoders, size_t n_encoders) { for (int i = 0; i < n_encoders; i++) { - drmModeFreeEncoder(encoders[i].encoder); + free_encoder(encoders + i); } - free(encoders); +} + +static int fetch_crtc(int drm_fd, int crtc_index, uint32_t crtc_id, struct drm_crtc *crtc_out) { + struct drm_crtc_prop_ids ids; + drmModeObjectProperties *props; + drmModePropertyRes *prop_info; + drmModeCrtc *crtc; + int ok; + drm_crtc_prop_ids_init(&ids); + + crtc = drmModeGetCrtc(drm_fd, crtc_id); + if (crtc == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device CRTC. drmModeGetCrtc"); + return ok; + } + + props = drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC); + if (props == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device CRTCs properties. drmModeObjectGetProperties"); + goto fail_free_crtc; + } + + for (int i = 0; i < props->count_props; i++) { + prop_info = drmModeGetProperty(drm_fd, props->props[i]); + if (prop_info == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device CRTCs properties' info. drmModeGetProperty"); + goto fail_free_props; + } + +#define CHECK_ASSIGN_PROPERTY_ID(_name_str, _name) \ + if (strncmp(prop_info->name, _name_str, ARRAY_SIZE(prop_info->name)) == 0) { \ + ids._name = prop_info->prop_id; \ + } else + + DRM_CRTC_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) { + // this is the trailing else case + LOG_DEBUG("Unknown DRM crtc property: %s\n", prop_info->name); + } + +#undef CHECK_ASSIGN_PROPERTY_ID + + drmModeFreeProperty(prop_info); + prop_info = NULL; + } + + crtc_out->id = crtc->crtc_id; + crtc_out->index = crtc_index; + crtc_out->bitmask = 1u << crtc_index; + crtc_out->ids = ids; + crtc_out->committed_state.has_mode = crtc->mode_valid; + crtc_out->committed_state.mode = crtc->mode; + crtc_out->committed_state.mode_blob = NULL; + drmModeFreeObjectProperties(props); + drmModeFreeCrtc(crtc); return 0; + + +fail_free_props: + drmModeFreeObjectProperties(props); + +fail_free_crtc: + drmModeFreeCrtc(crtc); + return ok; +} + +static void free_crtc(struct drm_crtc *crtc) { + /// TODO: Implement + (void) crtc; } static int fetch_crtcs(struct drmdev *drmdev, struct drm_crtc **crtcs_out, size_t *n_crtcs_out) { struct drm_crtc *crtcs; - int n_allocated_crtcs; int ok; crtcs = calloc(drmdev->res->count_crtcs, sizeof *crtcs); if (crtcs == NULL) { *crtcs_out = NULL; + *n_crtcs_out = 0; return ENOMEM; } - n_allocated_crtcs = 0; - for (int i = 0; i < drmdev->res->count_crtcs; i++, n_allocated_crtcs++) { - drmModeObjectProperties *props; - drmModePropertyRes **props_info; - drmModeCrtc *crtc; - - crtc = drmModeGetCrtc(drmdev->fd, drmdev->res->crtcs[i]); - if (crtc == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device CRTC. drmModeGetCrtc"); - goto fail_free_crtcs; - } - - props = drmModeObjectGetProperties(drmdev->fd, drmdev->res->crtcs[i], DRM_MODE_OBJECT_CRTC); - if (props == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device CRTCs properties. drmModeObjectGetProperties"); - drmModeFreeCrtc(crtc); - goto fail_free_crtcs; - } - - props_info = calloc(props->count_props, sizeof *props_info); - if (props_info == NULL) { - ok = ENOMEM; - drmModeFreeObjectProperties(props); - drmModeFreeCrtc(crtc); + for (int i = 0; i < drmdev->res->count_crtcs; i++) { + ok = fetch_crtc(drmdev->fd, i, drmdev->res->crtcs[i], crtcs + i); + if (ok != 0) { + for (int j = 0; j < i; j++) + free_crtc(crtcs + i); goto fail_free_crtcs; } - - for (int j = 0; j < props->count_props; j++) { - props_info[j] = drmModeGetProperty(drmdev->fd, props->props[j]); - if (props_info[j] == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device CRTCs properties' info. drmModeGetProperty"); - for (int k = 0; k < (j-1); k++) - drmModeFreeProperty(props_info[j]); - free(props_info); - drmModeFreeObjectProperties(props); - drmModeFreeCrtc(crtc); - goto fail_free_crtcs; - } - } - - crtcs[i].crtc = crtc; - crtcs[i].props = props; - crtcs[i].props_info = props_info; - - crtcs[i].index = i; - crtcs[i].bitmask = 1 << i; } *crtcs_out = crtcs; *n_crtcs_out = drmdev->res->count_crtcs; - return 0; - - fail_free_crtcs: - for (int i = 0; i < n_allocated_crtcs; i++) { - for (int j = 0; j < crtcs[i].props->count_props; j++) - drmModeFreeProperty(crtcs[i].props_info[j]); - free(crtcs[i].props_info); - drmModeFreeObjectProperties(crtcs[i].props); - drmModeFreeCrtc(crtcs[i].crtc); - } - +fail_free_crtcs: free(crtcs); - *crtcs_out = NULL; *n_crtcs_out = 0; return ok; @@ -252,174 +447,537 @@ static int fetch_crtcs(struct drmdev *drmdev, struct drm_crtc **crtcs_out, size_ static int free_crtcs(struct drm_crtc *crtcs, size_t n_crtcs) { for (int i = 0; i < n_crtcs; i++) { - for (int j = 0; j < crtcs[i].props->count_props; j++) - drmModeFreeProperty(crtcs[i].props_info[j]); - free(crtcs[i].props_info); - drmModeFreeObjectProperties(crtcs[i].props); - drmModeFreeCrtc(crtcs[i].crtc); + free_crtc(crtcs + i); } - free(crtcs); - return 0; } -static int fetch_planes(struct drmdev *drmdev, struct drm_plane **planes_out, size_t *n_planes_out) { - struct drm_plane *planes; - int n_allocated_planes; - int ok; +static int get_supported_modified_formats( + struct drm_format_modifier_blob *blob, + int max_formats_out, + int *n_formats_out, + struct modified_format *formats_out +) { + struct drm_format_modifier *modifiers; + uint32_t *formats; + + DEBUG_ASSERT_NOT_NULL(blob); + DEBUG_ASSERT_NOT_NULL(n_formats_out); + DEBUG_ASSERT(blob->version == FORMAT_BLOB_CURRENT); + + modifiers = (void *) (((char *) blob) + blob->modifiers_offset); + formats = (void *) (((char *) blob) + blob->formats_offset); + + int index = 0; + for (int i = 0; i < blob->count_modifiers; i++) { + for (int j = modifiers[i].offset; (j < blob->count_formats) && (j < modifiers[i].offset + 64); j++) { + bool is_format_bit_set = (modifiers[i].formats & (1ull << (j % 64))) != 0; + if (!is_format_bit_set) { + continue; + } - planes = calloc(drmdev->plane_res->count_planes, sizeof *planes); - if (planes == NULL) { - *planes_out = NULL; - return ENOMEM; + for (int k = 0; k < kCount_PixFmt; k++) { + if (get_pixfmt_info(k)->drm_format == formats[j]) { + if ((index >= max_formats_out) && formats_out) { + return ENOMEM; + } else if (formats_out) { + formats_out[index].format = k; + formats_out[index].modifier = modifiers[i].modifier; + } + index++; + } + } + } } - n_allocated_planes = 0; - for (int i = 0; i < drmdev->plane_res->count_planes; i++, n_allocated_planes++) { - drmModeObjectProperties *props; - drmModePropertyRes **props_info; - drmModePlane *plane; + *n_formats_out = index; + return 0; +} - plane = drmModeGetPlane(drmdev->fd, drmdev->plane_res->planes[i]); - if (plane == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device plane. drmModeGetPlane"); - goto fail_free_planes; - } +static int fetch_plane(int drm_fd, uint32_t plane_id, struct drm_plane *plane_out) { + struct drm_plane_prop_ids ids; + drmModeObjectProperties *props; + struct modified_format *supported_modified_formats; + drm_plane_transform_t hardcoded_rotation, supported_rotations, committed_rotation; + enum drm_blend_mode committed_blend_mode; + enum drm_plane_type type; + drmModePropertyRes *info; + drmModePlane *plane; + uint32_t comitted_crtc_x, comitted_crtc_y, comitted_crtc_w, comitted_crtc_h; + uint32_t comitted_src_x, comitted_src_y, comitted_src_w, comitted_src_h; + uint16_t committed_alpha; + int64_t min_zpos, max_zpos, hardcoded_zpos, committed_zpos; + bool supported_blend_modes[kCount_DrmBlendMode] = { 0 }; + bool supported_formats[kCount_PixFmt] = { 0 }; + bool has_type, has_rotation, has_zpos, has_hardcoded_zpos, has_hardcoded_rotation, supports_modifiers, has_alpha, + has_blend_mode; + int ok, n_supported_modified_formats; + + drm_plane_prop_ids_init(&ids); + + plane = drmModeGetPlane(drm_fd, plane_id); + if (plane == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device plane. drmModeGetPlane"); + return ok; + } - props = drmModeObjectGetProperties(drmdev->fd, drmdev->plane_res->planes[i], DRM_MODE_OBJECT_PLANE); - if (props == NULL) { + props = drmModeObjectGetProperties(drm_fd, plane_id, DRM_MODE_OBJECT_PLANE); + if (props == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device planes' properties. drmModeObjectGetProperties"); + goto fail_free_plane; + } + + has_type = false; + has_rotation = false; + has_hardcoded_rotation = false; + has_zpos = false; + has_hardcoded_zpos = false; + supports_modifiers = false; + has_alpha = false; + has_blend_mode = false; + n_supported_modified_formats = 0; + supported_modified_formats = NULL; + comitted_crtc_x = comitted_crtc_y = comitted_crtc_w = comitted_crtc_h = 0; + comitted_src_x = comitted_src_y = comitted_src_w = comitted_src_h = 0; + for (int j = 0; j < props->count_props; j++) { + info = drmModeGetProperty(drm_fd, props->props[j]); + if (info == NULL) { ok = errno; - perror("[modesetting] Could not get DRM device planes' properties. drmModeObjectGetProperties"); - drmModeFreePlane(plane); - goto fail_free_planes; + perror("[modesetting] Could not get DRM device planes' properties' info. drmModeGetProperty"); + goto fail_maybe_free_supported_formats; } - props_info = calloc(props->count_props, sizeof *props_info); - if (props_info == NULL) { - ok = ENOMEM; - drmModeFreeObjectProperties(props); - drmModeFreePlane(plane); - goto fail_free_planes; - } + if (strcmp(info->name, "type") == 0) { + DEBUG_ASSERT(has_type == false); + has_type = true; + type = props->prop_values[j]; + } else if (strcmp(info->name, "rotation") == 0) { + DEBUG_ASSERT(has_rotation == false); + has_rotation = true; + + supported_rotations = PLANE_TRANSFORM_NONE; + DEBUG_ASSERT(info->flags & DRM_MODE_PROP_BITMASK); + + for (int k = 0; k < info->count_enums; k++) { + supported_rotations.u32 |= 1 << info->enums[k].value; + } + + DEBUG_ASSERT(PLANE_TRANSFORM_IS_VALID(supported_rotations)); - for (int j = 0; j < props->count_props; j++) { - props_info[j] = drmModeGetProperty(drmdev->fd, props->props[j]); - if (props_info[j] == NULL) { + if (info->flags & DRM_MODE_PROP_IMMUTABLE) { + has_hardcoded_rotation = true; + hardcoded_rotation.u64 = props->prop_values[j]; + } + + committed_rotation.u64 = props->prop_values[j]; + } else if (strcmp(info->name, "zpos") == 0) { + DEBUG_ASSERT(has_zpos == false); + has_zpos = true; + + if (info->flags & DRM_MODE_PROP_SIGNED_RANGE) { + min_zpos = *(int64_t *) (info->values + 0); + max_zpos = *(int64_t *) (info->values + 1); + committed_zpos = *(int64_t *) (props->prop_values + j); + DEBUG_ASSERT(min_zpos <= max_zpos); + DEBUG_ASSERT(min_zpos <= committed_zpos); + DEBUG_ASSERT(committed_zpos <= max_zpos); + } else if (info->flags & DRM_MODE_PROP_RANGE) { + DEBUG_ASSERT(info->values[0] < (uint64_t) INT64_MAX); + DEBUG_ASSERT(info->values[1] < (uint64_t) INT64_MAX); + min_zpos = info->values[0]; + max_zpos = info->values[1]; + committed_zpos = props->prop_values[j]; + DEBUG_ASSERT(min_zpos <= max_zpos); + } else { + DEBUG_ASSERT_MSG(info->flags && false, "Invalid property type for zpos property."); + } + + if (info->flags & DRM_MODE_PROP_IMMUTABLE) { + has_hardcoded_zpos = true; + DEBUG_ASSERT(props->prop_values[j] < (uint64_t) INT64_MAX); + hardcoded_zpos = committed_zpos; + if (min_zpos != max_zpos) { + LOG_DEBUG( + "DRM plane minimum supported zpos does not equal maximum supported zpos, even though zpos is " + "immutable.\n" + ); + min_zpos = max_zpos = hardcoded_zpos; + } + } + } else if (strcmp(info->name, "SRC_X") == 0) { + comitted_src_x = props->prop_values[j]; + } else if (strcmp(info->name, "SRC_Y") == 0) { + comitted_src_y = props->prop_values[j]; + } else if (strcmp(info->name, "SRC_W") == 0) { + comitted_src_w = props->prop_values[j]; + } else if (strcmp(info->name, "SRC_H") == 0) { + comitted_src_h = props->prop_values[j]; + } else if (strcmp(info->name, "CRTC_X") == 0) { + comitted_crtc_x = props->prop_values[j]; + } else if (strcmp(info->name, "CRTC_Y") == 0) { + comitted_crtc_y = props->prop_values[j]; + } else if (strcmp(info->name, "CRTC_W") == 0) { + comitted_crtc_w = props->prop_values[j]; + } else if (strcmp(info->name, "CRTC_H") == 0) { + comitted_crtc_h = props->prop_values[j]; + } else if (strcmp(info->name, "IN_FORMATS") == 0) { + drmModePropertyBlobRes *blob; + + blob = drmModeGetPropertyBlob(drm_fd, props->prop_values[j]); + if (blob == NULL) { ok = errno; - perror("[modesetting] Could not get DRM device planes' properties' info. drmModeGetProperty"); - for (int k = 0; k < (j-1); k++) - drmModeFreeProperty(props_info[j]); - free(props_info); - drmModeFreeObjectProperties(props); - drmModeFreePlane(plane); - goto fail_free_planes; + LOG_ERROR( + "Couldn't get list of supported format modifiers for plane %u. drmModeGetPropertyBlob: %s\n", + plane_id, + strerror(ok) + ); + drmModeFreeProperty(info); + goto fail_free_props; } - if (strcmp(props_info[j]->name, "type") == 0) { - planes[i].type = 0; - for (int k = 0; k < props->count_props; k++) { - if (props->props[k] == props_info[j]->prop_id) { - planes[i].type = props->prop_values[k]; - break; - } + supports_modifiers = true; + n_supported_modified_formats = 0; + + get_supported_modified_formats(blob->data, 0, &n_supported_modified_formats, NULL); + + supported_modified_formats = calloc(sizeof *supported_modified_formats, n_supported_modified_formats); + if (supported_modified_formats == NULL) { + ok = ENOMEM; + drmModeFreePropertyBlob(blob); + drmModeFreeProperty(info); + goto fail_free_props; + } + + ok = get_supported_modified_formats( + blob->data, + n_supported_modified_formats, + &n_supported_modified_formats, + supported_modified_formats + ); + if (ok != 0) { + free(supported_modified_formats); + drmModeFreePropertyBlob(blob); + drmModeFreeProperty(info); + goto fail_free_props; + } + + drmModeFreePropertyBlob(blob); + } else if (strcmp(info->name, "alpha") == 0) { + has_alpha = true; + DEBUG_ASSERT(info->flags == DRM_MODE_PROP_RANGE); + DEBUG_ASSERT(info->values[0] == 0); + DEBUG_ASSERT(info->values[1] == 0xFFFF); + DEBUG_ASSERT(props->prop_values[j] <= 0xFFFF); + + committed_alpha = (uint16_t) props->prop_values[j]; + } else if (strcmp(info->name, "pixel blend mode") == 0) { + has_blend_mode = true; + DEBUG_ASSERT(info->flags == DRM_MODE_PROP_ENUM); + + for (int i = 0; i < info->count_enums; i++) { + if (strcmp(info->enums[i].name, "None") == 0) { + DEBUG_ASSERT_EQUALS(info->enums[i].value, kNone_DrmBlendMode); + supported_blend_modes[kNone_DrmBlendMode] = true; + } else if (strcmp(info->enums[i].name, "Pre-multiplied") == 0) { + DEBUG_ASSERT_EQUALS(info->enums[i].value, kPremultiplied_DrmBlendMode); + supported_blend_modes[kPremultiplied_DrmBlendMode] = true; + } else if (strcmp(info->enums[i].name, "Coverage") == 0) { + DEBUG_ASSERT_EQUALS(info->enums[i].value, kCoverage_DrmBlendMode); + supported_blend_modes[kCoverage_DrmBlendMode] = true; + } else { + LOG_DEBUG( + "Unknown KMS pixel blend mode: %s (value: %" PRIu64 ")\n", + info->enums[i].name, + (uint64_t) info->enums[i].value + ); } } + + committed_blend_mode = props->prop_values[j]; + DEBUG_ASSERT(committed_blend_mode >= 0 && committed_blend_mode <= kMax_DrmBlendMode); + DEBUG_ASSERT(supported_blend_modes[committed_blend_mode]); } - planes[i].plane = plane; - planes[i].props = props; - planes[i].props_info = props_info; +#define CHECK_ASSIGN_PROPERTY_ID(_name_str, _name) \ + if (strncmp(info->name, _name_str, ARRAY_SIZE(info->name)) == 0) { \ + ids._name = info->prop_id; \ } - *planes_out = planes; - *n_planes_out = drmdev->plane_res->count_planes; + DRM_PLANE_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) - return 0; +#undef CHECK_ASSIGN_PROPERTY_ID + drmModeFreeProperty(info); + } - fail_free_planes: - for (int i = 0; i < n_allocated_planes; i++) { - for (int j = 0; j < planes[i].props->count_props; j++) - drmModeFreeProperty(planes[i].props_info[j]); - free(planes[i].props_info); - drmModeFreeObjectProperties(planes[i].props); - drmModeFreePlane(planes[i].plane); + DEBUG_ASSERT(has_type); + for (int i = 0; i < plane->count_formats; i++) { + for (int j = 0; j < kCount_PixFmt; j++) { + if (get_pixfmt_info(j)->drm_format == plane->formats[i]) { + supported_formats[j] = true; + break; + } + } } - free(planes); + plane_out->id = plane->plane_id; + plane_out->possible_crtcs = plane->possible_crtcs; + plane_out->ids = ids; + plane_out->type = type; + plane_out->has_zpos = has_zpos; + plane_out->min_zpos = min_zpos; + plane_out->max_zpos = max_zpos; + plane_out->has_hardcoded_zpos = has_hardcoded_zpos; + plane_out->hardcoded_zpos = hardcoded_zpos; + plane_out->has_rotation = has_rotation; + plane_out->supported_rotations = supported_rotations; + plane_out->has_hardcoded_rotation = has_hardcoded_rotation; + plane_out->hardcoded_rotation = hardcoded_rotation; + memcpy(plane_out->supported_formats, supported_formats, sizeof supported_formats); + plane_out->supports_modifiers = supports_modifiers; + plane_out->n_supported_modified_formats = n_supported_modified_formats; + plane_out->supported_modified_formats = supported_modified_formats; + plane_out->has_alpha = has_alpha; + plane_out->has_blend_mode = has_blend_mode; + memcpy(plane_out->supported_blend_modes, supported_blend_modes, sizeof supported_blend_modes); + plane_out->committed_state.crtc_id = plane->crtc_id; + plane_out->committed_state.fb_id = plane->fb_id; + plane_out->committed_state.src_x = comitted_src_x; + plane_out->committed_state.src_y = comitted_src_y; + plane_out->committed_state.src_w = comitted_src_w; + plane_out->committed_state.src_h = comitted_src_h; + plane_out->committed_state.crtc_x = comitted_crtc_x; + plane_out->committed_state.crtc_y = comitted_crtc_y; + plane_out->committed_state.crtc_w = comitted_crtc_w; + plane_out->committed_state.crtc_h = comitted_crtc_h; + plane_out->committed_state.zpos = committed_zpos; + plane_out->committed_state.rotation = committed_rotation; + plane_out->committed_state.alpha = committed_alpha; + plane_out->committed_state.blend_mode = committed_blend_mode; + drmModeFreeObjectProperties(props); + drmModeFreePlane(plane); + return 0; + +fail_maybe_free_supported_formats: + if (supported_modified_formats != NULL) + free(supported_modified_formats); - *planes_out = NULL; - *n_planes_out = 0; +fail_free_props: + drmModeFreeObjectProperties(props); + +fail_free_plane: + drmModeFreePlane(plane); return ok; } -MAYBE_UNUSED static int free_planes(struct drm_plane *planes, size_t n_planes) { - for (int i = 0; i < n_planes; i++) { - for (int j = 0; j < planes[i].props->count_props; j++) - drmModeFreeProperty(planes[i].props_info[j]); - free(planes[i].props_info); - drmModeFreeObjectProperties(planes[i].props); - drmModeFreePlane(planes[i].plane); +static void free_plane(struct drm_plane *plane) { + if (plane->supported_modified_formats != NULL) { + free(plane->supported_modified_formats); } +} - free(planes); +static int fetch_planes(struct drmdev *drmdev, struct drm_plane **planes_out, size_t *n_planes_out) { + struct drm_plane *planes; + int ok; + + planes = calloc(drmdev->plane_res->count_planes, sizeof *planes); + if (planes == NULL) { + *planes_out = NULL; + return ENOMEM; + } + + for (int i = 0; i < drmdev->plane_res->count_planes; i++) { + ok = fetch_plane(drmdev->fd, drmdev->plane_res->planes[i], planes + i); + if (ok != 0) { + for (int j = 0; j < i; j++) { + free_plane(planes + i); + } + free(planes); + return ENOMEM; + } + + DEBUG_ASSERT_MSG( + planes[0].has_zpos == planes[i].has_zpos, + "If one plane has a zpos property, all planes need to have one." + ); + } + + *planes_out = planes; + *n_planes_out = drmdev->plane_res->count_planes; return 0; } +static void free_planes(struct drm_plane *planes, size_t n_planes) { + for (int i = 0; i < n_planes; i++) { + free_plane(planes + i); + } + free(planes); +} -float mode_get_vrefresh(const drmModeModeInfo *mode) { - return mode->clock * 1000.0 / (mode->htotal * mode->vtotal); +static void assert_rotations_work() { + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_0.rotate_0 == true); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_0.rotate_90 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_0.rotate_180 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_0.rotate_270 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_0.reflect_x == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_0.reflect_y == false); + + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_90.rotate_0 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_90.rotate_90 == true); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_90.rotate_180 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_90.rotate_270 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_90.reflect_x == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_90.reflect_y == false); + + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_180.rotate_0 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_180.rotate_90 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_180.rotate_180 == true); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_180.rotate_270 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_180.reflect_x == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_180.reflect_y == false); + + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_270.rotate_0 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_270.rotate_90 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_270.rotate_180 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_270.rotate_270 == true); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_270.reflect_x == false); + DEBUG_ASSERT(PLANE_TRANSFORM_ROTATE_270.reflect_y == false); + + DEBUG_ASSERT(PLANE_TRANSFORM_REFLECT_X.rotate_0 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_REFLECT_X.rotate_90 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_REFLECT_X.rotate_180 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_REFLECT_X.rotate_270 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_REFLECT_X.reflect_x == true); + DEBUG_ASSERT(PLANE_TRANSFORM_REFLECT_X.reflect_y == false); + + DEBUG_ASSERT(PLANE_TRANSFORM_REFLECT_Y.rotate_0 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_REFLECT_Y.rotate_90 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_REFLECT_Y.rotate_180 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_REFLECT_Y.rotate_270 == false); + DEBUG_ASSERT(PLANE_TRANSFORM_REFLECT_Y.reflect_x == false); + DEBUG_ASSERT(PLANE_TRANSFORM_REFLECT_Y.reflect_y == true); + + drm_plane_transform_t r = PLANE_TRANSFORM_NONE; + + r.rotate_0 = true; + r.reflect_x = true; + DEBUG_ASSERT(r.u32 == (DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_X)); + + r.u32 = DRM_MODE_ROTATE_90 | DRM_MODE_REFLECT_Y; + DEBUG_ASSERT(r.rotate_0 == false); + DEBUG_ASSERT(r.rotate_90 == true); + DEBUG_ASSERT(r.rotate_180 == false); + DEBUG_ASSERT(r.rotate_270 == false); + DEBUG_ASSERT(r.reflect_x == false); + DEBUG_ASSERT(r.reflect_y == true); + (void) r; } -int drmdev_new_from_fd( - struct drmdev **drmdev_out, - int fd -) { - struct drmdev *drmdev; +static int set_drm_client_caps(int fd, bool *supports_atomic_modesetting) { int ok; - drmdev = calloc(1, sizeof *drmdev); - if (drmdev == NULL) { - return ENOMEM; + ok = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not set DRM client universal planes capable. drmSetClientCap: %s\n", strerror(ok)); + return ok; } - drmdev->fd = fd; + ok = drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1); + if ((ok < 0) && (errno == EOPNOTSUPP)) { + if (supports_atomic_modesetting != NULL) { + *supports_atomic_modesetting = false; + } + } else if (ok < 0) { + ok = errno; + LOG_ERROR("Could not set DRM client atomic capable. drmSetClientCap: %s\n", strerror(ok)); + return ok; + } else { + if (supports_atomic_modesetting != NULL) { + *supports_atomic_modesetting = true; + } + } + + return 0; +} + +struct drmdev *drmdev_new_from_fd(int fd, const struct drmdev_interface *interface, void *userdata) { + struct gbm_device *gbm_device; + struct drmdev *drmdev; + drmDevicePtr device; + bool supports_atomic_modesetting; + void *master_fd_metadata; + int ok, master_fd, event_fd; + + assert_rotations_work(); + + drmdev = malloc(sizeof *drmdev); + if (drmdev == NULL) { + return NULL; + } + + if (drmIsMaster(fd)) { + ok = drmDropMaster(fd); + if (ok < 0) { + LOG_ERROR("Couldn't drop DRM master. drmDropMaster: %s\n", strerror(errno)); + } + } - ok = drmSetClientCap(drmdev->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + ok = drmGetDevice(fd, &device); if (ok < 0) { ok = errno; - perror("[modesetting] Could not set DRM client universal planes capable. drmSetClientCap"); + LOG_ERROR("Couldn't query DRM device info. drmGetDevice: %s\n", strerror(ok)); goto fail_free_drmdev; } - - ok = drmSetClientCap(drmdev->fd, DRM_CLIENT_CAP_ATOMIC, 1); - if ((ok < 0) && (errno == EOPNOTSUPP)) { - drmdev->supports_atomic_modesetting = false; - } else if (ok < 0) { - ok = errno; - perror("[modesetting] Could not set DRM client atomic capable. drmSetClientCap"); - goto fail_free_drmdev; - } else { - drmdev->supports_atomic_modesetting = true; + + ok = interface->open(device->nodes[DRM_NODE_PRIMARY], O_CLOEXEC | O_NONBLOCK, &master_fd_metadata, userdata); + if (ok < 0) { + ok = -ok; + LOG_ERROR("Couldn't open DRM device.\n"); + master_fd = -1; + master_fd_metadata = NULL; + } + + master_fd = ok; + + drmFreeDevice(&device); + + ok = set_drm_client_caps(fd, &supports_atomic_modesetting); + if (ok != 0) { + goto fail_close_master_fd; + } + + if (master_fd > 0) { + bool master_fd_supports_atomic_modesetting; + + ok = set_drm_client_caps(master_fd, &master_fd_supports_atomic_modesetting); + if (ok != 0) { + goto fail_close_master_fd; + } + + DEBUG_ASSERT_EQUALS(supports_atomic_modesetting, master_fd_supports_atomic_modesetting); } - drmdev->res = drmModeGetResources(drmdev->fd); + drmdev->res = drmModeGetResources(fd); if (drmdev->res == NULL) { ok = errno; - perror("[modesetting] Could not get DRM device resources. drmModeGetResources"); + LOG_ERROR("Could not get DRM device resources. drmModeGetResources: %s\n", strerror(ok)); goto fail_free_drmdev; } - drmdev->plane_res = drmModeGetPlaneResources(drmdev->fd); + drmdev->plane_res = drmModeGetPlaneResources(fd); if (drmdev->plane_res == NULL) { ok = errno; - perror("[modesetting] Could not get DRM device planes resources. drmModeGetPlaneResources"); + LOG_ERROR("Could not get DRM device planes resources. drmModeGetPlaneResources: %s\n", strerror(ok)); goto fail_free_resources; } + drmdev->fd = fd; + ok = fetch_connectors(drmdev, &drmdev->connectors, &drmdev->n_connectors); if (ok != 0) { goto fail_free_plane_resources; @@ -440,829 +998,1358 @@ int drmdev_new_from_fd( goto fail_free_crtcs; } - *drmdev_out = drmdev; + gbm_device = gbm_create_device(drmdev->fd); + if (gbm_device == NULL) { + LOG_ERROR("Could not create GBM device.\n"); + goto fail_free_planes; + } - return 0; + event_fd = epoll_create1(EPOLL_CLOEXEC); + if (event_fd < 0) { + LOG_ERROR("Could not create modesetting epoll instance.\n"); + goto fail_destroy_gbm_device; + } + ok = + epoll_ctl(event_fd, EPOLL_CTL_ADD, fd, &(struct epoll_event){ .events = EPOLLIN | EPOLLPRI, .data.ptr = NULL }); + if (ok != 0) { + LOG_ERROR("Could not add DRM file descriptor to epoll instance.\n"); + goto fail_close_event_fd; + } - fail_free_crtcs: + pthread_mutex_init(&drmdev->mutex, NULL); + drmdev->n_refs = REFCOUNT_INIT_1; + drmdev->fd = fd; + drmdev->supports_atomic_modesetting = supports_atomic_modesetting; + drmdev->gbm_device = gbm_device; + drmdev->event_fd = event_fd; + memset(drmdev->per_crtc_state, 0, sizeof(drmdev->per_crtc_state)); + drmdev->master_fd = master_fd; + drmdev->master_fd_metadata = master_fd_metadata; + drmdev->interface = *interface; + drmdev->userdata = userdata; + return drmdev; + +fail_close_event_fd: + close(event_fd); + +fail_destroy_gbm_device: + gbm_device_destroy(gbm_device); + +fail_free_planes: + free_planes(drmdev->planes, drmdev->n_planes); + +fail_free_crtcs: free_crtcs(drmdev->crtcs, drmdev->n_crtcs); - fail_free_encoders: +fail_free_encoders: free_encoders(drmdev->encoders, drmdev->n_encoders); - fail_free_connectors: +fail_free_connectors: free_connectors(drmdev->connectors, drmdev->n_connectors); - fail_free_plane_resources: +fail_free_plane_resources: drmModeFreePlaneResources(drmdev->plane_res); - fail_free_resources: +fail_free_resources: drmModeFreeResources(drmdev->res); - fail_free_drmdev: +fail_close_master_fd: + interface->close(master_fd, master_fd_metadata, userdata); + +fail_free_drmdev: free(drmdev); - return ok; + return NULL; } -int drmdev_new_from_path( - struct drmdev **drmdev_out, - const char *path -) { - int ok, fd; +struct drmdev *drmdev_new_from_path(const char *path, const struct drmdev_interface *interface, void *userdata) { + struct drmdev *drmdev; + int fd; + + DEBUG_ASSERT_NOT_NULL(path); + DEBUG_ASSERT_NOT_NULL(interface); fd = open(path, O_RDWR); if (fd < 0) { - perror("[modesetting] Could not open DRM device. open"); - return errno; + LOG_ERROR("Could not open DRM device. open: %s\n", strerror(errno)); + return NULL; } - ok = drmdev_new_from_fd(drmdev_out, fd); - if (ok != 0) { + drmdev = drmdev_new_from_fd(fd, interface, userdata); + if (drmdev == NULL) { close(fd); - return ok; + return NULL; } - return 0; + return drmdev; } -int drmdev_configure( - struct drmdev *drmdev, - uint32_t connector_id, - uint32_t encoder_id, - uint32_t crtc_id, - const drmModeModeInfo *mode +void drmdev_destroy(struct drmdev *drmdev) { + DEBUG_ASSERT(refcount_is_zero(&drmdev->n_refs)); + + close(drmdev->event_fd); + gbm_device_destroy(drmdev->gbm_device); + free_planes(drmdev->planes, drmdev->n_planes); + free_crtcs(drmdev->crtcs, drmdev->n_crtcs); + free_encoders(drmdev->encoders, drmdev->n_encoders); + free_connectors(drmdev->connectors, drmdev->n_connectors); + drmModeFreePlaneResources(drmdev->plane_res); + drmModeFreeResources(drmdev->res); + free(drmdev); +} + +DEFINE_REF_OPS(drmdev, n_refs) + +int drmdev_get_fd(struct drmdev *drmdev) { + DEBUG_ASSERT_NOT_NULL(drmdev); + return drmdev->master_fd; +} + +int drmdev_get_event_fd(struct drmdev *drmdev) { + DEBUG_ASSERT_NOT_NULL(drmdev); + return drmdev->master_fd; +} + +static void drmdev_on_page_flip_locked( + int fd, + unsigned int sequence, + unsigned int tv_sec, + unsigned int tv_usec, + unsigned int crtc_id, + void *userdata ) { - struct drm_connector *connector; - struct drm_encoder *encoder; + struct kms_req_builder *builder; struct drm_crtc *crtc; - uint32_t mode_id; - int ok; + struct kms_req **last_flipped; + struct kms_req *req; + struct drmdev *drmdev; - drmdev_lock(drmdev); + DEBUG_ASSERT_NOT_NULL(userdata); + builder = userdata; + req = userdata; - for_each_connector_in_drmdev(drmdev, connector) { - if (connector->connector->connector_id == connector_id) { + (void) fd; + (void) sequence; + (void) crtc_id; + + drmdev = builder->drmdev; + for_each_crtc_in_drmdev(drmdev, crtc) { + if (crtc->id == crtc_id) { break; } } - if (connector == NULL) { - drmdev_unlock(drmdev); - return EINVAL; - } + DEBUG_ASSERT_NOT_NULL_MSG(crtc, "Invalid CRTC id"); - for_each_encoder_in_drmdev(drmdev, encoder) { - if (encoder->encoder->encoder_id == encoder_id) { - break; - } - } + if (drmdev->per_crtc_state[crtc->index].scanout_callback != NULL) { + uint64_t vblank_ns = tv_sec * 1000000000ull + tv_usec * 1000ull; + drmdev->per_crtc_state[crtc->index].scanout_callback(drmdev, vblank_ns, drmdev->per_crtc_state[crtc->index].userdata); - if (encoder == NULL) { - drmdev_unlock(drmdev); - return EINVAL; + // clear the scanout callback + drmdev->per_crtc_state[crtc->index].scanout_callback = NULL; + drmdev->per_crtc_state[crtc->index].destroy_callback = NULL; + drmdev->per_crtc_state[crtc->index].userdata = NULL; } - for_each_crtc_in_drmdev(drmdev, crtc) { - if (crtc->crtc->crtc_id == crtc_id) { - break; - } + last_flipped = &drmdev->per_crtc_state[crtc->index].last_flipped; + if (*last_flipped != NULL) { + /// TODO: Remove this if we ever cache KMS reqs. + /// FIXME: This will fail if we're using blocking commits. + // DEBUG_ASSERT(refcount_is_one(&((struct kms_req_builder*) *last_flipped)->n_refs)); } - if (crtc == NULL) { - drmdev_unlock(drmdev); - return EINVAL; + kms_req_swap_ptrs(last_flipped, req); + kms_req_unref(req); +} + +static int drmdev_on_modesetting_fd_ready_locked(struct drmdev *drmdev) { + int ok; + + static drmEventContext ctx = { + .version = DRM_EVENT_CONTEXT_VERSION, + .vblank_handler = NULL, + .page_flip_handler = NULL, + .page_flip_handler2 = drmdev_on_page_flip_locked, + .sequence_handler = NULL, + }; + + ok = drmHandleEvent(drmdev->master_fd, &ctx); + if (ok != 0) { + return EIO; } - mode_id = 0; - if (drmdev->supports_atomic_modesetting) { - ok = drmModeCreatePropertyBlob(drmdev->fd, mode, sizeof(*mode), &mode_id); - if (ok < 0) { - perror("[modesetting] Could not create property blob for DRM mode. drmModeCreatePropertyBlob"); - drmdev_unlock(drmdev); - return errno; + return 0; +} + +int drmdev_on_event_fd_ready(struct drmdev *drmdev) { + struct epoll_event events[16]; + int ok, n_events; + + DEBUG_ASSERT_NOT_NULL(drmdev); + + drmdev_lock(drmdev); + + while (1) { + ok = epoll_wait(drmdev->event_fd, events, ARRAY_SIZE(events), 0); + if ((ok < 0) && (errno == EINTR)) { + // retry + continue; + } else if (ok < 0) { + ok = errno; + LOG_ERROR("Could read kernel modesetting events. epoll_wait: %s\n", strerror(ok)); + goto fail_unlock; + } else { + break; } + } - if (drmdev->selected_mode != NULL) { - ok = drmModeDestroyPropertyBlob(drmdev->fd, drmdev->selected_mode_blob_id); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not destroy old DRM mode property blob. drmModeDestroyPropertyBlob"); - drmModeDestroyPropertyBlob(drmdev->fd, mode_id); - drmdev_unlock(drmdev); - return ok; - } + n_events = ok; + for (int i = 0; i < n_events; i++) { + // currently this could only be the root drmdev fd. + DEBUG_ASSERT_EQUALS(events[i].data.ptr, NULL); + ok = drmdev_on_modesetting_fd_ready_locked(drmdev); + if (ok != 0) { + goto fail_unlock; } } - drmdev->selected_connector = connector; - drmdev->selected_encoder = encoder; - drmdev->selected_crtc = crtc; - drmdev->selected_mode = mode; - drmdev->selected_mode_blob_id = mode_id; + drmdev_unlock(drmdev); - drmdev->is_configured = true; + return 0; +fail_unlock: drmdev_unlock(drmdev); + return ok; +} + +struct gbm_device *drmdev_get_gbm_device(struct drmdev *drmdev) { + DEBUG_ASSERT_NOT_NULL(drmdev); + return drmdev->gbm_device; +} + +int drmdev_get_last_vblank(struct drmdev *drmdev, uint32_t crtc_id, uint64_t *last_vblank_ns_out) { + int ok; + + DEBUG_ASSERT_NOT_NULL(drmdev); + DEBUG_ASSERT_NOT_NULL(last_vblank_ns_out); + + ok = drmCrtcGetSequence(drmdev->fd, crtc_id, NULL, last_vblank_ns_out); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not get next vblank timestamp. drmCrtcGetSequence: %s\n", strerror(ok)); + return ok; + } return 0; } -static struct drm_plane *get_plane_by_id( +uint32_t drmdev_add_fb( struct drmdev *drmdev, - uint32_t plane_id + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + uint32_t bo_handle, + uint32_t pitch, + uint32_t offset, + bool has_modifier, + uint64_t modifier ) { - struct drm_plane *plane; + uint32_t fb_id; + int ok; - plane = NULL; - for (int i = 0; i < drmdev->n_planes; i++) { - if (drmdev->planes[i].plane->plane_id == plane_id) { - plane = drmdev->planes + i; - break; + DEBUG_ASSERT_NOT_NULL(drmdev); + DEBUG_ASSERT(width > 0 && height > 0); + DEBUG_ASSERT(bo_handle != 0); + DEBUG_ASSERT(pitch != 0); + + fb_id = 0; + if (has_modifier) { + LOG_DEBUG("adding fb with pixel format %"PRIu32" and modifier %"PRIu64"\n", get_pixfmt_info(pixel_format)->drm_format, modifier); + ok = drmModeAddFB2WithModifiers( + drmdev->fd, + width, + height, + get_pixfmt_info(pixel_format)->drm_format, + (uint32_t[4]){ bo_handle, 0 }, + (uint32_t[4]){ pitch, 0 }, + (uint32_t[4]){ offset, 0 }, + (const uint64_t[4]){ modifier, 0 }, + &fb_id, + DRM_MODE_FB_MODIFIERS + ); + if (ok < 0) { + LOG_ERROR("Couldn't add buffer as DRM fb. drmModeAddFB2WithModifiers: %s\n", strerror(-ok)); + return 0; + } + } else { + ok = drmModeAddFB2( + drmdev->fd, + width, + height, + get_pixfmt_info(pixel_format)->drm_format, + (uint32_t[4]){ bo_handle, 0 }, + (uint32_t[4]){ pitch, 0 }, + (uint32_t[4]){ offset, 0 }, + &fb_id, + 0 + ); + if (ok < 0) { + LOG_ERROR("Couldn't add buffer as DRM fb. drmModeAddFB2: %s\n", strerror(-ok)); + return 0; } } - return plane; + DEBUG_ASSERT(fb_id != 0); + return fb_id; } -static int get_plane_property_index_by_name( - struct drm_plane *plane, - const char *property_name +uint32_t drmdev_add_fb_multiplanar( + struct drmdev *drmdev, + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + uint32_t bo_handles[4], + uint32_t pitches[4], + uint32_t offsets[4], + bool has_modifiers, + uint64_t modifiers[4] ) { - if (plane == NULL) { - return -1; - } + uint32_t fb_id; + int ok; - int prop_index = -1; - for (int i = 0; i < plane->props->count_props; i++) { - if (strcmp(plane->props_info[i]->name, property_name) == 0) { - prop_index = i; - break; + DEBUG_ASSERT_NOT_NULL(drmdev); + DEBUG_ASSERT(width > 0 && height > 0); + DEBUG_ASSERT(bo_handles[0] != 0); + DEBUG_ASSERT(pitches[0] != 0); + + fb_id = 0; + if (has_modifiers) { + ok = drmModeAddFB2WithModifiers( + drmdev->fd, + width, + height, + get_pixfmt_info(pixel_format)->drm_format, + bo_handles, + pitches, + offsets, + modifiers, + &fb_id, + DRM_MODE_FB_MODIFIERS + ); + if (ok < 0) { + LOG_ERROR("Couldn't add buffer as DRM fb. drmModeAddFB2WithModifiers: %s\n", strerror(-ok)); + return 0; + } + } else { + ok = drmModeAddFB2( + drmdev->fd, + width, + height, + get_pixfmt_info(pixel_format)->drm_format, + bo_handles, + pitches, + offsets, + &fb_id, + 0 + ); + if (ok < 0) { + LOG_ERROR("Couldn't add buffer as DRM fb. drmModeAddFB2: %s\n", strerror(-ok)); + return 0; } } - return prop_index; + DEBUG_ASSERT(fb_id != 0); + return fb_id; } -int drmdev_plane_get_type( +uint32_t drmdev_add_fb_from_dmabuf( struct drmdev *drmdev, - uint32_t plane_id + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + int prime_fd, + uint32_t pitch, + uint32_t offset, + bool has_modifier, + uint64_t modifier ) { - struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); - if (plane == NULL) { - return -1; + uint32_t bo_handle; + int ok; + + ok = drmPrimeFDToHandle(drmdev->fd, prime_fd, &bo_handle); + if (ok < 0) { + LOG_ERROR("Couldn't import DMA-buffer as GEM buffer. drmPrimeFDToHandle: %s\n", strerror(errno)); + return 0; } - return plane->type; + return drmdev_add_fb(drmdev, width, height, pixel_format, prime_fd, pitch, offset, has_modifier, modifier); } -int drmdev_plane_supports_setting_rotation_value( +uint32_t drmdev_add_fb_from_dmabuf_multiplanar( struct drmdev *drmdev, - uint32_t plane_id, - int drm_rotation, - bool *result + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + int prime_fds[4], + uint32_t pitches[4], + uint32_t offsets[4], + bool has_modifiers, + uint64_t modifiers[4] ) { - struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); - if (plane == NULL) { - return EINVAL; - } - - int prop_index = get_plane_property_index_by_name(plane, "rotation"); - if (prop_index == -1) { - *result = false; - return 0; - } + uint32_t bo_handles[4] = { 0 }; + int ok; - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_IMMUTABLE) { - *result = false; - return 0; + for (int i = 0; (i < 4) && (prime_fds[i] != 0); i++) { + ok = drmPrimeFDToHandle(drmdev->fd, prime_fds[i], bo_handles + i); + if (ok < 0) { + LOG_ERROR("Couldn't import DMA-buffer as GEM buffer. drmPrimeFDToHandle: %s\n", strerror(errno)); + return 0; + } } - if (!(plane->props_info[prop_index]->flags & DRM_MODE_PROP_BITMASK)) { - *result = false; - return 0; - } + return drmdev_add_fb_multiplanar( + drmdev, + width, + height, + pixel_format, + bo_handles, + pitches, + offsets, + has_modifiers, + modifiers + ); +} - uint64_t value = drm_rotation; +int drmdev_rm_fb(struct drmdev *drmdev, uint32_t fb_id) { + int ok; - for (int i = 0; i < plane->props_info[prop_index]->count_enums; i++) { - value &= ~(1 << plane->props_info[prop_index]->enums[i].value); + ok = drmModeRmFB(drmdev->fd, fb_id); + if (ok < 0) { + LOG_ERROR("Could not remove DRM framebuffer. drmModeRmFB: %s\n", strerror(-ok)); + return -ok; } - *result = !value; return 0; } -int drmdev_plane_supports_setting_zpos_value( - struct drmdev *drmdev, - uint32_t plane_id, - int64_t zpos, - bool *result -) { - struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); - if (plane == NULL) { - return EINVAL; - } - - int prop_index = get_plane_property_index_by_name(plane, "zpos"); - if (prop_index == -1) { - *result = false; - return 0; - } +bool drmdev_can_modeset(struct drmdev *drmdev) { + bool can_modeset; - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_IMMUTABLE) { - *result = false; - return 0; - } + DEBUG_ASSERT_NOT_NULL(drmdev); - if (plane->props_info[prop_index]->count_values != 2) { - *result = false; - return 0; - } + drmdev_lock(drmdev); + can_modeset = drmdev->master_fd > 0; + drmdev_unlock(drmdev); - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_SIGNED_RANGE) { - int64_t min = *((int64_t*) (plane->props_info[prop_index]->values + 0)); - int64_t max = *((int64_t*) (plane->props_info[prop_index]->values + 1)); + return can_modeset; +} - if ((min <= zpos) && (max >= zpos)) { - *result = true; - return 0; - } else { - *result = false; - return 0; - } - } else if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_RANGE) { - uint64_t min = plane->props_info[prop_index]->values[0]; - uint64_t max = plane->props_info[prop_index]->values[1]; +void drmdev_suspend(struct drmdev *drmdev) { + DEBUG_ASSERT_NOT_NULL(drmdev); - if ((min <= zpos) && (max >= zpos)) { - *result = true; - return 0; - } else { - *result = false; - return 0; - } - } else { - *result = false; - return 0; + drmdev_lock(drmdev); + + if (drmdev->master_fd <= 0) { + LOG_ERROR("drmdev_suspend was called, but drmdev is already suspended\n"); + drmdev_unlock(drmdev); + return; } - - return 0; + + drmdev->interface.close(drmdev->master_fd, drmdev->master_fd_metadata, drmdev->userdata); + drmdev->master_fd = -1; + drmdev->master_fd_metadata = NULL; + + drmdev_unlock(drmdev); } -int drmdev_plane_get_min_zpos_value( - struct drmdev *drmdev, - uint32_t plane_id, - int64_t *min_zpos_out -) { - struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); - if (plane == NULL) { - return EINVAL; - } +int drmdev_resume(struct drmdev *drmdev) { + drmDevicePtr device; + void *fd_metadata; + int ok, master_fd; + + DEBUG_ASSERT_NOT_NULL(drmdev); + + drmdev_lock(drmdev); - int prop_index = get_plane_property_index_by_name(plane, "zpos"); - if (prop_index == -1) { - return EINVAL; + if (drmdev->master_fd > 0) { + ok = EINVAL; + LOG_ERROR("drmdev_resume was called, but drmdev is already resumed\n"); + goto fail_unlock; } - if (plane->props_info[prop_index]->count_values != 2) { - return EINVAL; + ok = drmGetDevice(drmdev->fd, &device); + if (ok < 0) { + ok = errno; + LOG_ERROR("Couldn't query DRM device info. drmGetDevice: %s\n", strerror(ok)); + goto fail_unlock; } - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_SIGNED_RANGE) { - int64_t min = *((int64_t*) (plane->props_info[prop_index]->values + 0)); - - *min_zpos_out = min; - return 0; - } else if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_RANGE) { - uint64_t min = plane->props_info[prop_index]->values[0]; - - *min_zpos_out = (int64_t) min; - return 0; - } else { - return EINVAL; + ok = drmdev->interface.open(device->nodes[DRM_NODE_PRIMARY], O_CLOEXEC | O_NONBLOCK, &fd_metadata, drmdev->userdata); + if (ok < 0) { + ok = -ok; + LOG_ERROR("Couldn't open DRM device.\n"); + goto fail_free_device; } - - return EINVAL; -} -int drmdev_plane_get_max_zpos_value( - struct drmdev *drmdev, - uint32_t plane_id, - int64_t *max_zpos_out -) { - struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); - if (plane == NULL) { - return EINVAL; - } - - int prop_index = get_plane_property_index_by_name(plane, "zpos"); - if (prop_index == -1) { - return EINVAL; - } + master_fd = ok; - if (plane->props_info[prop_index]->count_values != 2) { - return EINVAL; + drmFreeDevice(&device); + + ok = set_drm_client_caps(master_fd, NULL); + if (ok != 0) { + goto fail_close_device; } - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_SIGNED_RANGE) { - int64_t max = *((int64_t*) (plane->props_info[prop_index]->values + 1)); - - *max_zpos_out = max; - return 0; - } else if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_RANGE) { - uint64_t max = plane->props_info[prop_index]->values[1]; + drmdev->master_fd = master_fd; + drmdev->master_fd_metadata = fd_metadata; + drmdev_unlock(drmdev); + return 0; - *max_zpos_out = (int64_t) max; - return 0; - } else { - return EINVAL; - } - - return EINVAL; + fail_close_device: + drmdev->interface.close(master_fd, fd_metadata, drmdev->userdata); + goto fail_unlock; + + fail_free_device: + drmFreeDevice(&device); + + fail_unlock: + drmdev_unlock(drmdev); + return ok; } -int drmdev_plane_supports_setting_zpos( +static void drmdev_set_scanout_callback_locked( struct drmdev *drmdev, - uint32_t plane_id, - bool *result + uint32_t crtc_id, + kms_scanout_cb_t scanout_callback, + void *userdata, + void_callback_t destroy_callback ) { - struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); - if (plane == NULL) { - return EINVAL; - } - - int prop_index = get_plane_property_index_by_name(plane, "zpos"); - if (prop_index == -1) { - *result = false; - return 0; - } + struct drm_crtc *crtc; - if (plane->props_info[prop_index]->count_values != 2) { - *result = false; - return 0; - } + DEBUG_ASSERT_NOT_NULL(drmdev); - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_IMMUTABLE) { - *result = false; - return 0; + for_each_crtc_in_drmdev(drmdev, crtc) { + if (crtc->id == crtc_id) { + break; + } } - if (!(plane->props_info[prop_index]->flags & (DRM_MODE_PROP_RANGE | DRM_MODE_PROP_SIGNED_RANGE))) { - *result = false; - return 0; - } + DEBUG_ASSERT_NOT_NULL_MSG(crtc, "Could not find CRTC with given id."); - *result = true; - return 0; + // If there's already a scanout callback configured, this is probably a state machine error. + // The scanout callback is configured in kms_req_commit and is cleared after it was called. + // So if this is called again this mean kms_req_commit is called but the previous frame wasn't committed yet. + DEBUG_ASSERT_EQUALS_MSG(drmdev->per_crtc_state[crtc->index].scanout_callback, NULL, "There's already a scanout callback configured for this CRTC."); + drmdev->per_crtc_state[crtc->index].scanout_callback = scanout_callback; + drmdev->per_crtc_state[crtc->index].destroy_callback = destroy_callback; + drmdev->per_crtc_state[crtc->index].userdata = userdata; } -int drmdev_new_atomic_req( - struct drmdev *drmdev, - struct drmdev_atomic_req **req_out -) { - struct drmdev_atomic_req *req; +MAYBE_UNUSED static struct drm_plane *get_plane_by_id(struct drmdev *drmdev, uint32_t plane_id) { struct drm_plane *plane; - if (drmdev->supports_atomic_modesetting == false) { - return EOPNOTSUPP; + plane = NULL; + for (int i = 0; i < drmdev->n_planes; i++) { + if (drmdev->planes[i].id == plane_id) { + plane = drmdev->planes + i; + break; + } } - req = calloc(1, sizeof *req); - if (req == NULL) { - return ENOMEM; + return plane; +} + +struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector) { + bool found = connector == NULL; + for (size_t i = 0; i < drmdev->n_connectors; i++) { + if (drmdev->connectors + i == connector) { + found = true; + } else if (found) { + return drmdev->connectors + i; + } } - req->drmdev = drmdev; + return NULL; +} - req->atomic_req = drmModeAtomicAlloc(); - if (req->atomic_req == NULL) { - free(req); - return ENOMEM; +struct drm_encoder *__next_encoder(const struct drmdev *drmdev, const struct drm_encoder *encoder) { + bool found = encoder == NULL; + for (size_t i = 0; i < drmdev->n_encoders; i++) { + if (drmdev->encoders + i == encoder) { + found = true; + } else if (found) { + return drmdev->encoders + i; + } } - req->available_planes = PSET_INITIALIZER_STATIC(req->available_planes_storage, 32); + return NULL; +} - for_each_plane_in_drmdev(drmdev, plane) { - if (plane->plane->possible_crtcs & drmdev->selected_crtc->bitmask) { - pset_put(&req->available_planes, plane); +struct drm_crtc *__next_crtc(const struct drmdev *drmdev, const struct drm_crtc *crtc) { + bool found = crtc == NULL; + for (size_t i = 0; i < drmdev->n_crtcs; i++) { + if (drmdev->crtcs + i == crtc) { + found = true; + } else if (found) { + return drmdev->crtcs + i; } } - *req_out = req; - - return 0; + return NULL; } -void drmdev_destroy_atomic_req( - struct drmdev_atomic_req *req -) { - drmModeAtomicFree(req->atomic_req); - free(req); +struct drm_plane *__next_plane(const struct drmdev *drmdev, const struct drm_plane *plane) { + bool found = plane == NULL; + for (size_t i = 0; i < drmdev->n_planes; i++) { + if (drmdev->planes + i == plane) { + found = true; + } else if (found) { + return drmdev->planes + i; + } + } + + return NULL; } -int drmdev_atomic_req_put_connector_property( - struct drmdev_atomic_req *req, - const char *name, - uint64_t value -) { - int ok; +drmModeModeInfo *__next_mode(const struct drm_connector *connector, const drmModeModeInfo *mode) { + bool found = mode == NULL; + for (int i = 0; i < connector->variable_state.n_modes; i++) { + if (connector->variable_state.modes + i == mode) { + found = true; + } else if (found) { + return connector->variable_state.modes + i; + } + } - drmdev_lock(req->drmdev); + return NULL; +} - for (int i = 0; i < req->drmdev->selected_connector->props->count_props; i++) { - drmModePropertyRes *prop = req->drmdev->selected_connector->props_info[i]; - if (strcmp(prop->name, name) == 0) { - ok = drmModeAtomicAddProperty( - req->atomic_req, - req->drmdev->selected_connector->connector->connector_id, - prop->prop_id, value - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not add connector property to atomic request. drmModeAtomicAddProperty"); - drmdev_unlock(req->drmdev); - return ok; +static bool plane_qualifies( + // clang-format off + struct drm_plane *plane, + bool allow_primary, + bool allow_overlay, + bool allow_cursor, + enum pixfmt format, + bool has_modifier, uint64_t modifier, + bool has_zpos, int64_t zpos_lower_limit, int64_t zpos_upper_limit, + bool has_rotation, drm_plane_transform_t rotation, + bool has_id_range, uint32_t id_lower_limit + // clang-format on +) { + if (plane->type == kPrimary_DrmPlaneType) { + if (!allow_primary) { + return false; + } + } else if (plane->type == kOverlay_DrmPlaneType) { + if (!allow_overlay) { + return false; + } + } else if (plane->type == kCursor_DrmPlaneType) { + if (!allow_cursor) { + return false; + } + } + if (has_modifier) { + if (!plane->supported_modified_formats) { + // return false if we want a modified format but the plane doesn't support modified formats + return false; + } + + // search for the modified format in the list of supported modified formats + for (int i = 0; i < plane->n_supported_modified_formats; i++) { + if (plane->supported_modified_formats[i].format == format && + plane->supported_modified_formats[i].modifier == modifier) { + goto found; } + } - drmdev_unlock(req->drmdev); - return 0; + // not found in the supported modified format list + return false; + } else { + // we don't want a modified format, return false if the format is not in the list + // of supported (unmodified) formats + if (!plane->supported_formats[format]) { + return false; } } - drmdev_unlock(req->drmdev); - return EINVAL; +found: + if (has_zpos) { + if (!plane->has_zpos) { + // return false if we want a zpos but the plane doesn't support one + return false; + } else if (zpos_lower_limit > plane->max_zpos || zpos_upper_limit < plane->min_zpos) { + // return false if the zpos we want is outside the supported range of the plane + return false; + } + } + if (has_id_range && plane->id < id_lower_limit) { + return false; + } + if (has_rotation) { + if (!plane->has_rotation) { + // return false if the plane doesn't support rotation + return false; + } else if (plane->has_hardcoded_rotation && plane->hardcoded_rotation.u32 != rotation.u32) { + // return false if the plane has a hardcoded rotation and the rotation we want + // is not the hardcoded one + return false; + } else if (rotation.u32 & ~plane->hardcoded_rotation.u32) { + // return false if we can't construct the rotation using the rotation + // bits that are supported by the plane + return false; + } + } + return true; } -int drmdev_atomic_req_put_crtc_property( - struct drmdev_atomic_req *req, - const char *name, - uint64_t value +static struct drm_plane *allocate_plane( + // clang-format off + struct kms_req_builder *builder, + bool allow_primary, + bool allow_overlay, + bool allow_cursor, + enum pixfmt format, + bool has_modifier, uint64_t modifier, + bool has_zpos, int64_t zpos_lower_limit, int64_t zpos_upper_limit, + bool has_rotation, drm_plane_transform_t rotation, + bool has_id_range, uint32_t id_lower_limit + // clang-format on ) { - int ok; - - drmdev_lock(req->drmdev); - - for (int i = 0; i < req->drmdev->selected_crtc->props->count_props; i++) { - drmModePropertyRes *prop = req->drmdev->selected_crtc->props_info[i]; - if (strcmp(prop->name, name) == 0) { - ok = drmModeAtomicAddProperty( - req->atomic_req, - req->drmdev->selected_crtc->crtc->crtc_id, - prop->prop_id, - value + for (int i = 0; i < BMAP_SIZE(builder->planes); i++) { + struct drm_plane *plane = builder->drmdev->planes + i; + + if (BMAP_IS_SET(builder->planes, i)) { + // find out if the plane matches our criteria + bool qualifies = plane_qualifies( + plane, + allow_primary, + allow_overlay, + allow_cursor, + format, + has_modifier, + modifier, + has_zpos, + zpos_lower_limit, + zpos_upper_limit, + has_rotation, + rotation, + has_id_range, + id_lower_limit ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not add crtc property to atomic request. drmModeAtomicAddProperty"); - drmdev_unlock(req->drmdev); - return ok; + + // if it doesn't, look for the next one + if (!qualifies) { + continue; } - - drmdev_unlock(req->drmdev); - return 0; + + // we found one, mark it as used and return it + BMAP_CLEAR(builder->planes, i); + return plane; } } - drmdev_unlock(req->drmdev); - return EINVAL; + // we didn't find an available plane matching our criteria + return NULL; } -int drmdev_atomic_req_put_plane_property( - struct drmdev_atomic_req *req, - uint32_t plane_id, - const char *name, - uint64_t value -) { +static void release_plane(struct kms_req_builder *builder, uint32_t plane_id) { struct drm_plane *plane; - int ok; - - drmdev_lock(req->drmdev); + int index; - plane = NULL; - for (int i = 0; i < req->drmdev->n_planes; i++) { - if (req->drmdev->planes[i].plane->plane_id == plane_id) { - plane = req->drmdev->planes + i; + index = 0; + for_each_plane_in_drmdev(builder->drmdev, plane) { + if (plane->id == plane_id) { break; } + index++; } if (plane == NULL) { - drmdev_unlock(req->drmdev); - return EINVAL; + LOG_ERROR("Could not release invalid plane %" PRIu32 ".\n", plane_id); + return; } - for (int i = 0; i < plane->props->count_props; i++) { - drmModePropertyRes *prop; - - prop = plane->props_info[i]; - - if (strcmp(prop->name, name) == 0) { - ok = drmModeAtomicAddProperty( - req->atomic_req, - plane_id, - prop->prop_id, - value - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not add plane property to atomic request. drmModeAtomicAddProperty"); - drmdev_unlock(req->drmdev); - return ok; - } - - drmdev_unlock(req->drmdev); - return 0; - } - } - - drmdev_unlock(req->drmdev); - return EINVAL; + DEBUG_ASSERT(!BMAP_IS_SET(builder->planes, index)); + BMAP_SET(builder->planes, index); } -int drmdev_atomic_req_put_modeset_props( - struct drmdev_atomic_req *req, - uint32_t *flags -) { - struct drmdev_atomic_req *augment; - int ok; +struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uint32_t crtc_id) { + struct kms_req_builder *builder; + drmModeAtomicReq *req; + struct drm_crtc *crtc; + int64_t min_zpos; - ok = drmdev_new_atomic_req(req->drmdev, &augment); - if (ok != 0) { - return ok; - } + DEBUG_ASSERT_NOT_NULL(drmdev); + DEBUG_ASSERT(crtc_id != 0 && crtc_id != 0xFFFFFFFF); - ok = drmdev_atomic_req_put_connector_property(req, "CRTC_ID", req->drmdev->selected_crtc->crtc->crtc_id); - if (ok != 0) { - drmdev_destroy_atomic_req(augment); - return ok; + for_each_crtc_in_drmdev(drmdev, crtc) { + if (crtc->id == crtc_id) { + break; + } } - ok = drmdev_atomic_req_put_crtc_property(req, "MODE_ID", req->drmdev->selected_mode_blob_id); - if (ok != 0) { - drmdev_destroy_atomic_req(augment); - return ok; + if (crtc == NULL) { + LOG_ERROR("Invalid CRTC id: %" PRId32 "\n", crtc_id); + return NULL; } - ok = drmdev_atomic_req_put_crtc_property(req, "ACTIVE", 1); - if (ok != 0) { - drmdev_destroy_atomic_req(augment); - return ok; + builder = malloc(sizeof *builder); + if (builder == NULL) { + return NULL; } - ok = drmModeAtomicMerge(req->atomic_req, augment->atomic_req); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not apply modesetting properties to atomic request. drmModeAtomicMerge"); - drmdev_destroy_atomic_req(augment); - return ok; + if (drmdev->supports_atomic_modesetting) { + req = drmModeAtomicAlloc(); + if (req == NULL) { + free(builder); + return NULL; + } + } else { + req = NULL; } - drmdev_destroy_atomic_req(augment); + min_zpos = INT64_MAX; + BMAP_ZERO(builder->planes); + for (int i = 0; i < drmdev->n_planes; i++) { + struct drm_plane *plane = drmdev->planes + i; - if (flags != NULL) { - *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + if (plane->possible_crtcs & crtc->bitmask) { + BMAP_SET(builder->planes, i); + if (plane->has_zpos && plane->min_zpos < min_zpos) { + min_zpos = plane->min_zpos; + } + } } - return 0; + builder->n_refs = REFCOUNT_INIT_1; + builder->drmdev = drmdev; + // right now they're the same, but they might not be in the future. + builder->use_legacy = !drmdev->supports_atomic_modesetting; + builder->supports_atomic = drmdev->supports_atomic_modesetting; + builder->crtc = crtc; + builder->req = req; + + /// TODO: Use the actual min zpos here + builder->next_zpos = min_zpos; + builder->n_layers = 0; + builder->has_mode = false; + builder->unset_mode = false; + return builder; } -int drmdev_atomic_req_commit( - struct drmdev_atomic_req *req, - uint32_t flags, - void *userdata -) { - int ok; +void kms_req_builder_destroy(struct kms_req_builder *builder) { + /// TODO: Is this complete? + for (int i = 0; i < builder->n_layers; i++) { + if (builder->layers[i].release_callback != NULL) { + builder->layers[i].release_callback(builder->layers[i].release_callback_userdata); + } + } + free(builder); +} - drmdev_lock(req->drmdev); +DEFINE_REF_OPS(kms_req_builder, n_refs) - ok = drmModeAtomicCommit(req->drmdev->fd, req->atomic_req, flags, userdata); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not commit atomic request. drmModeAtomicCommit"); - drmdev_unlock(req->drmdev); - return ok; - } +struct drmdev *kms_req_builder_get_drmdev(struct kms_req_builder *builder) { + return builder->drmdev; +} + +bool kms_req_builder_prefer_next_layer_opaque(struct kms_req_builder *builder) { + DEBUG_ASSERT_NOT_NULL(builder); + return builder->n_layers == 0; +} - drmdev_unlock(req->drmdev); +int kms_req_builder_set_mode(struct kms_req_builder *builder, const drmModeModeInfo *mode) { + DEBUG_ASSERT_NOT_NULL(builder); + DEBUG_ASSERT_NOT_NULL(mode); + builder->has_mode = true; + builder->mode = *mode; return 0; } -int drmdev_legacy_set_mode_and_fb( - struct drmdev *drmdev, - uint32_t fb_id -) { - int ok; +int kms_req_builder_unset_mode(struct kms_req_builder *builder) { + DEBUG_ASSERT_NOT_NULL(builder); + DEBUG_ASSERT(!builder->has_mode); + builder->unset_mode = true; + return 0; +} - drmdev_lock(drmdev); +int kms_req_builder_set_connector(struct kms_req_builder *builder, uint32_t connector_id) { + struct drm_connector *conn; - ok = drmModeSetCrtc( - drmdev->fd, - drmdev->selected_crtc->crtc->crtc_id, - fb_id, - 0, - 0, - &drmdev->selected_connector->connector->connector_id, - 1, - (drmModeModeInfoPtr) drmdev->selected_mode - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not set CRTC mode and framebuffer. drmModeSetCrtc"); - drmdev_unlock(drmdev); - return ok; + DEBUG_ASSERT_NOT_NULL(builder); + DEBUG_ASSERT(DRM_ID_IS_VALID(connector_id)); + + for_each_connector_in_drmdev(builder->drmdev, conn) { + if (conn->id == connector_id) { + break; + } } - drmdev_unlock(drmdev); + if (conn == NULL) { + LOG_ERROR("Could not find connector with id %" PRIu32 "\n", connector_id); + return EINVAL; + } + builder->connector = conn; return 0; } -int drmdev_legacy_primary_plane_pageflip( - struct drmdev *drmdev, - uint32_t fb_id, +int kms_req_builder_push_fb_layer( + struct kms_req_builder *builder, + const struct kms_fb_layer *layer, + kms_fb_release_cb_t release_callback, + kms_deferred_fb_release_cb_t deferred_release_callback, void *userdata ) { - int ok; + struct drm_plane *plane; + int64_t zpos; + bool close_in_fence_fd_after; + int ok, index; + + DEBUG_ASSERT_NOT_NULL(builder); + DEBUG_ASSERT_NOT_NULL(layer); + DEBUG_ASSERT_NOT_NULL(release_callback); + DEBUG_ASSERT_EQUALS_MSG(deferred_release_callback, NULL, "deferred release callbacks are not supported right now."); + + if (builder->use_legacy && builder->supports_atomic && builder->n_layers > 1) { + // if we already have a first layer and we should use legacy modesetting even though the kernel driver + // supports atomic modesetting, return EINVAL. + // if the driver supports atomic modesetting, drmModeSetPlane will block for vblank, so we can't use it, + // and we can't use drmModeAtomicCommit for non-blocking multi-plane commits of course. + // For the first layer we can use drmModePageFlip though. + LOG_DEBUG( + "Can't do multi-plane commits when using legacy modesetting (and driver supports atomic modesetting).\n" + ); + return EINVAL; + } - drmdev_lock(drmdev); + close_in_fence_fd_after = false; + if (builder->use_legacy && layer->has_in_fence_fd) { + LOG_DEBUG("Explicit fencing is not supported for legacy modesetting. Implicit fencing will be used instead.\n"); + close_in_fence_fd_after = true; + } + + // Index of our layer. + index = builder->n_layers; + + /// TODO: Not sure we can use crtc_x, crtc_y, etc with primary planes + if (index == 0) { + // if this is the first layer, try using a + // primary plane for it. + + /// TODO: Use cursor_plane->max_zpos - 1 as the upper zpos limit, instead of INT64_MAX + plane = allocate_plane( + builder, + /* allow_primary */ true, + /* allow_overlay */ false, + /* allow_cursor */ false, + layer->format, + layer->has_modifier, + layer->modifier, + false, + 0, + 0, + layer->has_rotation, + layer->rotation, + false, + 0 + ); + + if (plane == NULL && !get_pixfmt_info(layer->format)->is_opaque) { + // maybe we can find a plane if we use the opaque version of this pixel format? + plane = allocate_plane( + builder, + /* allow_primary */ true, + /* allow_overlay */ false, + /* allow_cursor */ false, + pixfmt_opaque(layer->format), + layer->has_modifier, + layer->modifier, + false, + 0, + 0, + layer->has_rotation, + layer->rotation, + false, + 0 + ); + } + } else { + // First try to find an overlay plane with a higher zpos. + plane = allocate_plane( + builder, + /* allow_primary */ false, + /* allow_overlay */ true, + /* allow_cursor */ false, + layer->format, + layer->has_modifier, + layer->modifier, + true, + builder->next_zpos, + INT64_MAX, + layer->has_rotation, + layer->rotation, + false, + 0 + ); + + // If we can't find one, find an overlay plane with the next highest plane_id. + // (According to some comments in the kernel, that's the fallback KMS uses for the + // occlusion order if no zpos property is supported, i.e. planes with plane id occlude + // planes with lower id) + if (plane == NULL) { + plane = allocate_plane( + builder, + /* allow_primary */ false, + /* allow_overlay */ true, + /* allow_cursor */ false, + layer->format, + layer->has_modifier, + layer->modifier, + false, + 0, + 0, + layer->has_rotation, + layer->rotation, + true, + builder->layers[index - 1].plane_id + 1 + ); + } + } - ok = drmModePageFlip( - drmdev->fd, - drmdev->selected_crtc->crtc->crtc_id, - fb_id, - DRM_MODE_PAGE_FLIP_EVENT, - userdata - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not schedule pageflip on primary plane. drmModePageFlip"); - drmdev_unlock(drmdev); - return ok; + if (plane == NULL) { + LOG_ERROR("Could not find a suitable unused DRM plane for pushing the framebuffer.\n"); + return EIO; + } + + // Now that we have a plane, use the minimum zpos + // that's both higher than the last layers zpos and + // also supported by the plane. + // This will also work for planes with hardcoded zpos. + if (plane->has_zpos) { + zpos = builder->next_zpos; + if (plane->min_zpos > zpos) { + zpos = plane->min_zpos; + } } - drmdev_unlock(drmdev); + if (builder->use_legacy) { + UNIMPLEMENTED(); + } else { + uint32_t plane_id = plane->id; + + /// TODO: Error checking + /// TODO: Maybe add these in the kms_req_builder_commit instead? + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.crtc_id, builder->crtc->id); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.fb_id, layer->drm_fb_id); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.crtc_x, layer->dst_x); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.crtc_y, layer->dst_y); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.crtc_w, layer->dst_w); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.crtc_h, layer->dst_h); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.src_x, layer->src_x); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.src_y, layer->src_y); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.src_w, layer->src_w); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.src_h, layer->src_h); + + if (plane->has_zpos && !plane->has_hardcoded_zpos) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.zpos, zpos); + } - return 0; -} + if (layer->has_rotation && plane->has_rotation && !plane->has_hardcoded_rotation) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.rotation, layer->rotation.u64); + } -int drmdev_legacy_overlay_plane_pageflip( - struct drmdev *drmdev, - uint32_t plane_id, - uint32_t fb_id, - int32_t crtc_x, - int32_t crtc_y, - int32_t crtc_w, - int32_t crtc_h, - uint32_t src_x, - uint32_t src_y, - uint32_t src_w, - uint32_t src_h -) { - int ok; + if (index == 0) { + if (plane->has_alpha) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.alpha, DRM_BLEND_ALPHA_OPAQUE); + } - drmdev_lock(drmdev); + if (plane->has_blend_mode && plane->supported_blend_modes[kNone_DrmBlendMode]) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.pixel_blend_mode, kNone_DrmBlendMode); + } + } + } - ok = drmModeSetPlane( - drmdev->fd, - plane_id, - drmdev->selected_crtc->crtc->crtc_id, - fb_id, - 0, - crtc_x, crtc_y, crtc_w, crtc_h, - src_x, src_y, src_w, src_h - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not do blocking pageflip on overlay plane. drmModeSetPlane"); - drmdev_unlock(drmdev); - return ok; + // This should be done when we're sure we're not failing. + // Because on failure it would be the callers job to close the fd. + if (close_in_fence_fd_after) { + ok = close(layer->in_fence_fd); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not close layer in_fence_fd. close: %s\n", strerror(ok)); + goto fail_release_plane; + } } - drmdev_unlock(drmdev); + /// TODO: Right now we're adding zpos, rotation to the atomic request unconditionally + /// when specified in the fb layer. Ideally we would check for updates + /// on commit and only add to the atomic request when zpos / rotation changed. + builder->n_layers++; + if (plane->has_zpos) { + builder->next_zpos = zpos + 1; + } + builder->layers[index].layer = *layer; + builder->layers[index].plane_id = plane->id; + builder->layers[index].set_zpos = plane->has_zpos; + builder->layers[index].zpos = zpos; + builder->layers[index].set_rotation = layer->has_rotation; + builder->layers[index].rotation = layer->rotation; + builder->layers[index].release_callback = release_callback; + builder->layers[index].deferred_release_callback = deferred_release_callback; + builder->layers[index].release_callback_userdata = userdata; + return 0; + +fail_release_plane: + release_plane(builder, plane->id); + return ok; +} +int kms_req_builder_push_zpos_placeholder_layer(struct kms_req_builder *builder, int64_t *zpos_out) { + DEBUG_ASSERT_NOT_NULL(builder); + DEBUG_ASSERT_NOT_NULL(zpos_out); + *zpos_out = builder->next_zpos++; return 0; } -int drmdev_legacy_set_connector_property( - struct drmdev *drmdev, - const char *name, - uint64_t value -) { - int ok; +struct kms_req *kms_req_builder_build(struct kms_req_builder *builder) { + return (struct kms_req *) kms_req_builder_ref(builder); +} - drmdev_lock(drmdev); +MAYBE_UNUSED struct kms_req *kms_req_ref(struct kms_req *req) { + return (struct kms_req*) kms_req_builder_ref((struct kms_req_builder *) req); +} - for (int i = 0; i < drmdev->selected_connector->props->count_props; i++) { - drmModePropertyRes *prop = drmdev->selected_connector->props_info[i]; - if (strcmp(prop->name, name) == 0) { - ok = drmModeConnectorSetProperty( - drmdev->fd, - drmdev->selected_connector->connector->connector_id, - prop->prop_id, - value - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not set connector property. drmModeConnectorSetProperty"); - drmdev_unlock(drmdev); - return ok; - } +MAYBE_UNUSED void kms_req_unref(struct kms_req *req) { + return kms_req_builder_unref((struct kms_req_builder *) req); +} - drmdev_unlock(drmdev); - return 0; - } - } +MAYBE_UNUSED void kms_req_unrefp(struct kms_req **req) { + return kms_req_builder_unrefp((struct kms_req_builder **) req); +} - drmdev_unlock(drmdev); - return EINVAL; +MAYBE_UNUSED void kms_req_swap_ptrs(struct kms_req **oldp, struct kms_req *new) { + return kms_req_builder_swap_ptrs((struct kms_req_builder**) oldp, (struct kms_req_builder*) new); } -int drmdev_legacy_set_crtc_property( - struct drmdev *drmdev, - const char *name, - uint64_t value +static int kms_req_commit_common( + struct kms_req *req, + bool blocking, + kms_scanout_cb_t scanout_cb, + void *userdata, + void_callback_t destroy_cb ) { + struct kms_req_builder *builder; + struct drm_mode_blob *mode_blob; + uint32_t flags; + bool update_mode; int ok; - drmdev_lock(drmdev); + update_mode = false; + mode_blob = NULL; + + DEBUG_ASSERT_NOT_NULL(req); + builder = (struct kms_req_builder *) req; + + drmdev_lock(builder->drmdev); - for (int i = 0; i < drmdev->selected_crtc->props->count_props; i++) { - drmModePropertyRes *prop = drmdev->selected_crtc->props_info[i]; - if (strcmp(prop->name, name) == 0) { - ok = drmModeObjectSetProperty( - drmdev->fd, - drmdev->selected_crtc->crtc->crtc_id, - DRM_MODE_OBJECT_CRTC, - prop->prop_id, - value + if (builder->drmdev->master_fd < 0) { + LOG_ERROR("Commit requested, but drmdev doesn't have a DRM master fd right now.\n"); + drmdev_unlock(builder->drmdev); + return EBUSY; + } + + if (!drmIsMaster(builder->drmdev->master_fd)) { + LOG_ERROR("Commit requested, but drmdev is paused right now.\n"); + drmdev_unlock(builder->drmdev); + return EBUSY; + } + + // only change the mode if the new mode differs from the old one + + /// TOOD: If this is not a standard mode reported by connector/CRTC, + /// is there a way to verify if it is valid? (maybe use DRM_MODE_ATOMIC_TEST) + + // this could be a single expression but this way you see a bit better what's going on. + // We need to upload the new mode blob if: + // - we have a new mode + // - and: we don't have an old mode + // - or: the old mode differs from the new mode + bool upload_mode = false; + if (builder->has_mode) { + if (!builder->crtc->committed_state.has_mode) { + upload_mode = true; + } else if (memcmp(&builder->crtc->committed_state.mode, &builder->mode, sizeof(drmModeModeInfo)) != 0) { + upload_mode = true; + } + } + + if (upload_mode) { + update_mode = true; + mode_blob = drm_mode_blob_new(builder->drmdev->fd, &builder->mode); + if (mode_blob == NULL) { + ok = EIO; + goto fail_unlock; + } + } else if (builder->unset_mode) { + update_mode = true; + mode_blob = NULL; + } + + if (builder->use_legacy) { + DEBUG_ASSERT_EQUALS(builder->layers[0].layer.dst_x, 0); + DEBUG_ASSERT_EQUALS(builder->layers[0].layer.dst_y, 0); + DEBUG_ASSERT_EQUALS(builder->layers[0].layer.dst_w, builder->mode.hdisplay); + DEBUG_ASSERT_EQUALS(builder->layers[0].layer.dst_h, builder->mode.vdisplay); + + /// TODO: Do we really need to assert this? + DEBUG_ASSERT(get_pixfmt_info(builder->layers[0].layer.format)->is_opaque); + + if (update_mode) { + /// TODO: Fetch new connector or current connector here since we seem to need it for drmModeSetCrtc + UNIMPLEMENTED(); + + ok = drmModeSetCrtc( + builder->drmdev->master_fd, + builder->crtc->id, + builder->layers[0].layer.drm_fb_id, + 0, + 0, + NULL, + 0, + builder->unset_mode ? NULL : &builder->mode ); - if (ok < 0) { + if (ok != 0) { ok = errno; - perror("[modesetting] Could not set CRTC property. drmModeObjectSetProperty"); - drmdev_unlock(drmdev); - return ok; + LOG_ERROR("Could not commit display update. drmModeSetCrtc: %s\n", strerror(ok)); + goto fail_maybe_destroy_mode_blob; } + } else { + ok = drmModePageFlip( + builder->drmdev->master_fd, + builder->crtc->id, + builder->layers[0].layer.drm_fb_id, + DRM_MODE_PAGE_FLIP_EVENT, + kms_req_builder_ref(builder) + ); + if (ok != 0) { + ok = errno; + LOG_ERROR("Could not commit display update. drmModePageFlip: %s\n", strerror(ok)); + goto fail_unref_builder; + } + } - drmdev_unlock(drmdev); - return 0; + // This should also be ensured by kms_req_builder_push_fb_layer + DEBUG_ASSERT_MSG( + !(builder->supports_atomic && builder->n_layers > 1), + "There can be at most one framebuffer layer when the KMS device supports atomic modesetting but we are " + "using legacy modesetting." + ); + + /// TODO: Call drmModeSetPlane for all other layers + /// TODO: Assert here + + } else { + /// TODO: If we can do explicit fencing, don't use the page flip event. + /// TODO: Can we set OUT_FENCE_PTR even though we didn't set any IN_FENCE_FDs? + flags = DRM_MODE_PAGE_FLIP_EVENT | (blocking ? 0 : DRM_MODE_ATOMIC_NONBLOCK) | (update_mode ? DRM_MODE_ATOMIC_ALLOW_MODESET : 0); + + /// TODO: If we're on raspberry pi and only have one layer, we can do an async pageflip + /// on the primary plane to replace the next queued frame. (To do _real_ triple buffering + /// with fully decoupled framerate, potentially) + ok = drmModeAtomicCommit(builder->drmdev->master_fd, builder->req, flags, kms_req_builder_ref(builder)); + if (ok != 0) { + ok = errno; + LOG_ERROR("Could not commit display update. drmModeAtomicCommit: %s\n", strerror(ok)); + goto fail_unref_builder; } } - drmdev_unlock(drmdev); - return EINVAL; -} + if (update_mode) { + // destroy the old mode blob + if (builder->crtc->committed_state.mode_blob != NULL) { + /// TODO: Should we defer this to after the pageflip? + drm_mode_blob_destroy(builder->crtc->committed_state.mode_blob); + } -int drmdev_legacy_set_plane_property( - struct drmdev *drmdev, - uint32_t plane_id, - const char *name, - uint64_t value -) { - struct drm_plane *plane; - int ok; + // store the new mode + if (mode_blob != NULL) { + builder->crtc->committed_state.has_mode = true; + builder->crtc->committed_state.mode = builder->mode; + builder->crtc->committed_state.mode_blob = mode_blob; + } else { + builder->crtc->committed_state.has_mode = false; + builder->crtc->committed_state.mode_blob = NULL; + } + } - drmdev_lock(drmdev); + drmdev_set_scanout_callback_locked(builder->drmdev, builder->crtc->id, scanout_cb, userdata, destroy_cb); - plane = NULL; - for (int i = 0; i < drmdev->n_planes; i++) { - if (drmdev->planes[i].plane->plane_id == plane_id) { - plane = drmdev->planes + i; - break; + if (blocking) { + // handle the page-flip event here, rather than via the eventfd + ok = drmdev_on_modesetting_fd_ready_locked(builder->drmdev); + if (ok != 0) { + LOG_ERROR("Couldn't synchronously handle pageflip event.\n"); + goto fail_unlock; } } - if (plane == NULL) { - drmdev_unlock(drmdev); - return EINVAL; + drmdev_unlock(builder->drmdev); + + return 0; + +fail_unref_builder: + kms_req_builder_unref(builder); + +fail_maybe_destroy_mode_blob: + if (mode_blob != NULL) + drm_mode_blob_destroy(mode_blob); + +fail_unlock: + drmdev_unlock(builder->drmdev); + + return ok; +} + +void set_vblank_ns(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata) { + uint64_t *vblank_ns_out; + + DEBUG_ASSERT_NOT_NULL(userdata); + vblank_ns_out = userdata; + (void) drmdev; + + *vblank_ns_out = vblank_ns; +} + +int kms_req_commit_blocking(struct kms_req *req, uint64_t *vblank_ns_out) { + uint64_t vblank_ns; + int ok; + + vblank_ns = int64_to_uint64(-1); + ok = kms_req_commit_common( + req, + true, + set_vblank_ns, &vblank_ns, NULL + ); + if (ok != 0) { + return ok; } - for (int i = 0; i < plane->props->count_props; i++) { - drmModePropertyRes *prop; - - prop = plane->props_info[i]; - - if (strcmp(prop->name, name) == 0) { - ok = drmModeObjectSetProperty( - drmdev->fd, - plane_id, - DRM_MODE_OBJECT_PLANE, - prop->prop_id, - value - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not set plane property. drmModeObjectSetProperty"); - drmdev_unlock(drmdev); - return ok; - } - - drmdev_unlock(drmdev); - return 0; - } + // make sure the vblank_ns is actually set + DEBUG_ASSERT(vblank_ns != int64_to_uint64(-1)); + if (vblank_ns_out != NULL) { + *vblank_ns_out = vblank_ns; } - drmdev_unlock(drmdev); - return EINVAL; -} \ No newline at end of file + return 0; +} + +int kms_req_commit_nonblocking(struct kms_req *req, kms_scanout_cb_t scanout_cb, void *userdata, void_callback_t destroy_cb) { + return kms_req_commit_common( + req, + false, + scanout_cb, userdata, destroy_cb + ); +} diff --git a/src/pixel_format.c b/src/pixel_format.c index e7c19e06..2a5bedc1 100644 --- a/src/pixel_format.c +++ b/src/pixel_format.c @@ -26,13 +26,22 @@ # define DRM_FORMAT_FIELD_INITIALIZER(_drm_format) #endif -#define PIXFMT_MAPPING(_name, _arg_name, _format, _bpp, _is_opaque, r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset, _gbm_format, _drm_format) \ +#ifdef HAS_VULKAN +# include +# define VK_FORMAT_FIELD_INITIALIZER(_vk_format) .vk_format = _vk_format, +#else +# define VK_FORMAT_FIELD_INITIALIZER(_vk_format) +#endif + +#define PIXFMT_MAPPING(_name, _arg_name, _format, _bpp, _bit_depth, _is_opaque, _vk_format, r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset, _gbm_format, _drm_format) \ { \ .name = _name, \ .arg_name = _arg_name, \ .format = _format, \ .bits_per_pixel = _bpp, \ + .bit_depth = _bit_depth, \ .is_opaque = _is_opaque, \ + VK_FORMAT_FIELD_INITIALIZER(_vk_format) \ FBDEV_FORMAT_FIELD_INITIALIZER(r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset) \ GBM_FORMAT_FIELD_INITIALIZER(_gbm_format) \ DRM_FORMAT_FIELD_INITIALIZER(_drm_format) \ @@ -50,3 +59,11 @@ enum { const size_t n_pixfmt_infos = n_pixfmt_infos_constexpr; COMPILE_ASSERT(n_pixfmt_infos_constexpr == kMax_PixFmt+1); + +#ifdef DEBUG +void assert_pixfmt_list_valid() { + for (enum pixfmt format = 0; format < kCount_PixFmt; format++) { + assert(pixfmt_infos[format].format == format); + } +} +#endif diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index e88b57df..ee283e63 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -261,11 +261,14 @@ int get_plane_infos( // Taken from: https://github.com/GStreamer/gstreamer/blob/621604aa3e4caa8db27637f63fa55fac2f7721e5/subprojects/gst-plugins-base/gst-libs/gst/video/video-info.c#L1278-L1301 for (int i = 0; i < GST_VIDEO_MAX_PLANES; i++) { if (i < GST_VIDEO_INFO_N_PLANES(frame_info->gst_info)) { +#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 21, 3) if (GST_VIDEO_FORMAT_INFO_IS_TILED (frame_info->gst_info->finfo)) { guint x_tiles = GST_VIDEO_TILE_X_TILES (frame_info->gst_info->stride[i]); guint y_tiles = GST_VIDEO_TILE_Y_TILES (frame_info->gst_info->stride[i]); plane_sizes[i] = x_tiles * y_tiles * GST_VIDEO_FORMAT_INFO_TILE_SIZE(frame_info->gst_info->finfo, i); } else { +#endif + gint comp[GST_VIDEO_MAX_COMPONENTS]; guint plane_height; @@ -275,7 +278,10 @@ int get_plane_infos( GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT(frame_info->gst_info->finfo, comp[0], GST_VIDEO_INFO_FIELD_HEIGHT(frame_info->gst_info)); plane_sizes[i] = plane_height * GST_VIDEO_INFO_PLANE_STRIDE(frame_info->gst_info, i); + +#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 21, 3) } +#endif } else { plane_sizes[i] = 0; } diff --git a/src/plugins/omxplayer_video_player.c b/src/plugins/omxplayer_video_player.c deleted file mode 100644 index 18224e90..00000000 --- a/src/plugins/omxplayer_video_player.c +++ /dev/null @@ -1,1554 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -FILE_DESCR("omxplayer video_player plugin") - -static struct { - bool initialized; - - /// On creation of a new player, - /// the id stored here will be used and incremented. - int64_t next_unused_player_id; - - /// Collection of players. - /* - struct omxplayer_video_player **players; - size_t size_players; - size_t n_players; - */ - - struct concurrent_pointer_set players; - - /// The D-Bus that where omxplayer instances can be talked to. - /// Typically the session dbus. - //sd_bus *dbus; - //pthread_t dbus_processor_thread; -} omxpvidpp = { - .initialized = false, - .next_unused_player_id = 1, - .players = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE) -}; - -/// Add a player instance to the player collection. -static int add_player(struct omxplayer_video_player *player) { - return cpset_put(&omxpvidpp.players, player); -} - -/// Get a player instance by its id. -struct omxplayer_video_player *get_player_by_id(int64_t player_id) { - struct omxplayer_video_player *player; - - cpset_lock(&omxpvidpp.players); - for_each_pointer_in_cpset(&omxpvidpp.players, player) { - if (player->player_id == player_id) { - cpset_unlock(&omxpvidpp.players); - return player; - } - } - - cpset_unlock(&omxpvidpp.players); - return NULL; -} - -/// Get a player instance by its event channel name. -struct omxplayer_video_player *get_player_by_evch(const char *const event_channel_name) { - struct omxplayer_video_player *player; - - cpset_lock(&omxpvidpp.players); - for_each_pointer_in_cpset(&omxpvidpp.players, player) { - if (strcmp(player->event_channel_name, event_channel_name) == 0) { - cpset_unlock(&omxpvidpp.players); - return player; - } - } - - cpset_unlock(&omxpvidpp.players); - return NULL; -} - -/// Remove a player instance from the player collection. -static int remove_player(struct omxplayer_video_player *player) { - return cpset_remove(&omxpvidpp.players, player); -} - -/// Get the player id from the given arg, which is a kStdMap. -/// (*player_id_out = arg['playerId']) -/// If an error ocurrs, this will respond with an illegal argument error to the given responsehandle. -static int get_player_id_from_map_arg( - struct std_value *arg, - int64_t *player_id_out, - FlutterPlatformMessageResponseHandle *responsehandle -) { - int ok; - - if (arg->type != kStdMap) { - ok = platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg` to be a Map" - ); - if (ok != 0) return ok; - - return EINVAL; - } - - struct std_value *id = stdmap_get_str(arg, "playerId"); - if (id == NULL || !STDVALUE_IS_INT(*id)) { - ok = platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['playerId']` to be an integer" - ); - if (ok != 0) return ok; - - return EINVAL; - } - - *player_id_out = STDVALUE_AS_INT(*id); - - return 0; -} - -/// Get the player associated with the id in the given arg, which is a kStdMap. -/// (*player_out = get_player_by_id(get_player_id_from_map_arg(arg))) -/// If an error ocurrs, this will respond with an illegal argument error to the given responsehandle. -static int get_player_from_map_arg( - struct std_value *arg, - struct omxplayer_video_player **player_out, - FlutterPlatformMessageResponseHandle *responsehandle -) { - struct omxplayer_video_player *player; - int64_t player_id; - int ok; - - player_id = 0; - ok = get_player_id_from_map_arg(arg, &player_id, responsehandle); - if (ok != 0) { - return ok; - } - - player = get_player_by_id(player_id); - if (player == NULL) { - ok = platch_respond_illegal_arg_std(responsehandle, "Expected `arg['playerId']` to be a valid player id."); - if (ok != 0) return ok; - - return EINVAL; - } - - *player_out = player; - - return 0; -} - -static int get_orientation_from_rotation(double rotation) { - if ((rotation <= 45) || (rotation > 315)) { - rotation = 0; - } else if (rotation <= 135) { - rotation = 90; - } else if (rotation <= 225) { - rotation = 180; - } else if (rotation <= 315) { - rotation = 270; - } - - return rotation; -} - -/// Called on the flutter rasterizer thread when a players platform view is presented -/// for the first time after it was unmounted or initialized. -static int on_mount( - int64_t view_id, - struct drmdev_atomic_req *req, - const struct platform_view_params *params, - int zpos, - void *userdata -) { - struct omxplayer_video_player *player = userdata; - - (void) view_id; - (void) req; - - if (zpos == 1) { - zpos = -126; - } - - struct aa_rect rect = get_aa_bounding_rect(params->rect); - - return cqueue_enqueue( - &player->mgr->task_queue, - &(struct omxplayer_mgr_task) { - .type = kUpdateView, - .responsehandle = NULL, - .offset_x = round(rect.offset.x), - .offset_y = round(rect.offset.y), - .width = round(rect.size.x), - .height = round(rect.size.y), - .zpos = zpos, - .orientation = get_orientation_from_rotation(params->rotation) - } - ); -} - -/// Called on the flutter rasterizer thread when a players platform view is not present -/// in the currently being drawn frame after it was present in the previous frame. -static int on_unmount( - int64_t view_id, - struct drmdev_atomic_req *req, - void *userdata -) { - struct omxplayer_video_player *player = userdata; - - (void) view_id; - (void) req; - - return cqueue_enqueue( - &player->mgr->task_queue, - &(struct omxplayer_mgr_task) { - .type = kUpdateView, - .offset_x = 0, - .offset_y = 0, - .width = 1, - .height = 1, - .zpos = -128 - } - ); -} - -/// Called on the flutter rasterizer thread when the presentation details (offset, mutations, dimensions, zpos) -/// changed from the previous frame. -static int on_update_view( - int64_t view_id, - struct drmdev_atomic_req *req, - const struct platform_view_params *params, - int zpos, - void *userdata -) { - struct omxplayer_video_player *player = userdata; - - (void) view_id; - (void) req; - - if (zpos == 1) { - zpos = -126; - } - - struct aa_rect rect = get_aa_bounding_rect(params->rect); - - return cqueue_enqueue( - &player->mgr->task_queue, - &(struct omxplayer_mgr_task) { - .type = kUpdateView, - .responsehandle = NULL, - .offset_x = rect.offset.x, - .offset_y = rect.offset.y, - .width = rect.size.x, - .height = rect.size.y, - .zpos = zpos, - .orientation = get_orientation_from_rotation(params->rotation) - } - ); -} - -static int respond_sd_bus_error( - FlutterPlatformMessageResponseHandle *handle, - sd_bus_error *err -) { - char str[256]; - - snprintf(str, sizeof(str), "%s: %s", err->name, err->message); - - return platch_respond_error_std( - handle, - "dbus-error", - str, - NULL - ); -} - -/// Unfortunately, we can't use sd_bus for this, because it -/// wraps some things in containers. -static int get_dbus_property( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *ret_error, - char type, - void *ret_ptr -) { - sd_bus_message *msg; - int ok; - - ok = sd_bus_call_method( - bus, - destination, - path, - DBUS_PROPERTY_FACE, - DBUS_PROPERTY_GET, - ret_error, - &msg, - "ss", - interface, - member - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not read DBus property: %s, %s\n", ret_error->name, ret_error->message); - return -ok; - } - - ok = sd_bus_message_read_basic(msg, type, ret_ptr); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not read DBus property: %s\n", strerror(-ok)); - sd_bus_message_unref(msg); - return -ok; - } - - sd_bus_message_unref(msg); - - return 0; -} - -/// Callback to be called when the omxplayer manager receives -/// a DBus message. (Currently only used for listening to NameOwnerChanged messages, -/// to find out when omxplayer registers to the dbus.) -static int mgr_on_dbus_message( - sd_bus_message *m, - void *userdata, - sd_bus_error *ret_error -) { - struct omxplayer_mgr_task *task; - const char *sender, *member; - char *old_owner, *new_owner, *name; - int ok; - - (void) ret_error; - - task = userdata; - - sender = sd_bus_message_get_sender(m); - member = sd_bus_message_get_member(m); - - if (STREQ(sender, "org.freedesktop.DBus") && STREQ(member, "NameOwnerChanged")) { - ok = sd_bus_message_read(m, "sss", &name, &old_owner, &new_owner); - if (ok < 0) { - fprintf(stderr, "Could not read message"); - return -1; - } - - if STREQ(name, task->omxplayer_dbus_name) { - task->omxplayer_online = true; - } - } - - return 0; -} - -/// The entry function of the manager thread. -/// Manager thread has the ownership over the player / manager / task queue objects -/// and must free them when it quits. -static void *mgr_entry(void *userdata) { - struct omxplayer_mgr_task task; - struct concurrent_queue *q; - struct omxplayer_mgr *mgr; - sd_bus_message *msg; - sd_bus_error err; - sd_bus_slot *slot; - int64_t duration_us, video_width, video_height, current_zpos, current_orientation; - sd_bus *bus; - pid_t omxplayer_pid; - char dbus_name[256]; - bool has_sent_initialized_event; - bool is_stream; - int ok; - - (void) current_orientation; - - mgr = userdata; - q = &mgr->task_queue; - - // dequeue the first task of the queue (creation task) - ok = cqueue_dequeue(q, &task); - if (ok != 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not dequeue creation task in manager thread. cqueue_dequeue: %s\n", strerror(ok)); - platch_respond_error_std( - task.responsehandle, - "internal-error", - "Could not dequeue creation task in manager thread.", - NULL - ); - goto fail_remove_evch_listener; - } - - // check that it really is a creation task - if (task.type != kCreate || task.responsehandle == NULL) { - fprintf(stderr, "[omxplayer_video_player plugin] First task of manager thread is not a creation task.\n"); - platch_respond_error_std( - task.responsehandle, - "internal-error", - "First task of manager thread is not a creation task.", - NULL - ); - goto fail_remove_evch_listener; - } - - // determine whether we're watching a stream or not. - // this is a heuristic. unfortunately, omxplayer itself doesn't even know whether it's playing - // back a stream or a video file. - if (strstr(mgr->player->video_uri, "rtsp://") == mgr->player->video_uri) { - is_stream = true; - } else { - is_stream = false; - } - - // generate the player name - snprintf( - dbus_name, - sizeof(dbus_name), - "org.mpris.MediaPlayer2.omxplayer_%d_%" PRId64, - (int) getpid(), - mgr->player->player_id - ); - - // open the session dbus - ok = sd_bus_open_user(&bus); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not open DBus in manager thread. sd_bus_open_user: %s\n", strerror(-ok)); - platch_respond_native_error_std(task.responsehandle, -ok); - goto fail_remove_evch_listener; - } - - // register a callbacks that tells us when - // omxplayer has registered to the dbus - task.omxplayer_online = false; - task.omxplayer_dbus_name = dbus_name; - ok = sd_bus_match_signal( - bus, - &slot, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "NameOwnerChanged", - mgr_on_dbus_message, - &task - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for omxplayer DBus registration in manager thread. sd_bus_match_signal: %s\n", strerror(-ok)); - platch_respond_native_error_std(task.responsehandle, -ok); - goto fail_close_dbus; - } - - // spawn the omxplayer process - current_zpos = -128; - current_orientation = task.orientation; - pid_t me = fork(); - if (me == 0) { - char orientation_str[16] = {0}; - snprintf(orientation_str, sizeof orientation_str, "%d", task.orientation); - - // I'm the child! - prctl(PR_SET_PDEATHSIG, SIGKILL); - int _ok = execvp( - "omxplayer.bin", - (char*[]) { - "omxplayer.bin", - "--nohdmiclocksync", - "--no-osd", - "--no-keys", - "--loop", - "--layer", "-128", - "--win", "0,0,1,1", - "--orientation", orientation_str, - "--dbus_name", dbus_name, - mgr->player->video_uri, - NULL - } - ); - - if (_ok != 0) { - exit(_ok); - } - exit(0); - } else if (me > 0) { - // I'm the parent! - omxplayer_pid = me; - } else if (me < 0) { - // something went wrong. - ok = errno; - perror("[omxplayer_video_player plugin] Could not spawn omxplayer subprocess. fork"); - platch_respond_native_error_std(task.responsehandle, ok); - goto fail_unref_slot; - } - - while (!task.omxplayer_online) { - ok = sd_bus_wait(bus, 1000*1000*5); - if (ok < 0) { - ok = -ok; - fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for sd bus messages on manager thread: %s\n", strerror(ok)); - platch_respond_native_error_std(task.responsehandle, ok); - goto fail_kill_unregistered_player; - } - - ok = sd_bus_process(bus, NULL); - if (ok < 0) { - ok = -ok; - fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for sd bus messages on manager thread: %s\n", strerror(ok)); - platch_respond_native_error_std(task.responsehandle, ok); - goto fail_kill_unregistered_player; - } - } - - sd_bus_slot_unref(slot); - slot = NULL; - - duration_us = 0; - ok = get_dbus_property( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "Duration", - &err, - 'x', - &duration_us - ); - if (ok != 0) { - respond_sd_bus_error(task.responsehandle, &err); - goto fail_kill_registered_player; - } - - // wait for the first frame to appear - { - struct timespec delta = { - .tv_sec = 0, - .tv_nsec = 300*1000*1000 - }; - while (nanosleep(&delta, &delta)); - } - - // pause right on the first frame - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "Play", - &err, - &msg, - "" - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not send initial pause message: %s, %s\n", err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - goto fail_kill_registered_player; - } - - sd_bus_message_unref(msg); - msg = NULL; - - // get the video duration - duration_us = 0; - ok = get_dbus_property( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "Duration", - &err, - 'x', - &duration_us - ); - if (ok != 0) { - respond_sd_bus_error(task.responsehandle, &err); - goto fail_kill_registered_player; - } - - // get the video width - video_width = 0; - ok = get_dbus_property( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "ResWidth", - &err, - 'x', - &video_width - ); - if (ok < 0) { - respond_sd_bus_error(task.responsehandle, &err); - goto fail_kill_registered_player; - } - - // get the video width - video_height = 0; - ok = get_dbus_property( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "ResHeight", - &err, - 'x', - &video_height - ); - if (ok < 0) { - respond_sd_bus_error(task.responsehandle, &err); - goto fail_kill_registered_player; - } - - - LOG_DEBUG("respond success on_create(). player_id: %" PRId64 "\n", mgr->player->player_id); - // creation was a success! respond to the dart-side with our player id. - platch_respond_success_std(task.responsehandle, &STDINT64(mgr->player->player_id)); - - has_sent_initialized_event = false; - while (1) { - ok = cqueue_dequeue(q, &task); - - if (task.type == kUpdateView) { - struct omxplayer_mgr_task *peek; - - cqueue_lock(q); - - cqueue_peek_locked(q, (void**) &peek); - while ((peek != NULL) && (peek->type == kUpdateView)) { - cqueue_dequeue_locked(q, &task); - cqueue_peek_locked(q, (void**) &peek); - } - - cqueue_unlock(q); - } - - if (task.type == kCreate) { - printf("[omxplayer_video_player plugin] Omxplayer manager got a creation task, even though the player is already running.\n"); - } else if (task.type == kDispose) { - if (mgr->player->has_view) { - fprintf(stderr, "[omxplayer_video_player plugin] flutter attempted to dispose the video player before its view was disposed.\n"); - - compositor_remove_view_callbacks(mgr->player->view_id); - - mgr->player->has_view = false; - mgr->player->view_id = -1; - } - - // tell omxplayer to quit - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_ROOT_FACE, - "Quit", - &err, - NULL, - "" - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not send Quit message to omxplayer: %s, %s\n", err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - continue; - } - - ok = (int) waitpid(omxplayer_pid, NULL, 0); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] omxplayer quit with exit code %d\n", ok); - } - - sd_bus_unref(bus); - - plugin_registry_remove_receiver(mgr->player->event_channel_name); - - remove_player(mgr->player); - - free(mgr->player); - mgr->player = NULL; - - cqueue_deinit(&mgr->task_queue); - - free(mgr); - mgr = NULL; - - platch_respond_success_std(task.responsehandle, NULL); - - break; - } else if (task.type == kListen) { - platch_respond_success_std(task.responsehandle, NULL); - - if (!has_sent_initialized_event) { - platch_send_success_event_std( - mgr->player->event_channel_name, - &(struct std_value) { - .type = kStdMap, - .size = 4, - .keys = (struct std_value[4]) { - STDSTRING("event"), - STDSTRING("duration"), - STDSTRING("width"), - STDSTRING("height") - }, - .values = (struct std_value[4]) { - STDSTRING("initialized"), - STDINT64(is_stream? INT64_MAX : duration_us / 1000), - STDINT32(video_width), - STDINT32(video_height) - } - } - ); - - has_sent_initialized_event = true; - } - } else if (task.type == kUnlisten) { - platch_respond_success_std(task.responsehandle, NULL); - } else if (task.type == kPlay) { - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "Play", - &err, - NULL, - "" - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not send play message: %s, %s\n", err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - continue; - } - - platch_respond_success_std(task.responsehandle, NULL); - } else if (task.type == kPause) { - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "Pause", - &err, - NULL, - "" - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not send pause message: %s, %s\n", err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - continue; - } - - msg = NULL; - - platch_respond_success_std(task.responsehandle, NULL); - } else if (task.type == kUpdateView) { - char video_pos_str[256]; - - // Use integers here even if omxplayer supports floats because if we print floats, - // snprintf might use `,` as the decimal delimiter depending on the locale. - // This is only an issue because I set the application to be locale-aware using setlocale(LC_ALL, "") - // since that's needed for locale support. - snprintf( - video_pos_str, - sizeof(video_pos_str), - "%d %d %d %d", - task.offset_x, - task.offset_y, - task.offset_x + task.width, - task.offset_y + task.height - ); - - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "VideoPos", - &err, - NULL, - "os", - "/obj/not/used", - video_pos_str - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not update omxplayer viewport. %s, %s\n", err.name, err.message); - continue; - } - - if (current_zpos != task.zpos) { - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "SetLayer", - &err, - NULL, - "x", - (int64_t) task.zpos - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not update omxplayer layer. %s, %s\n", err.name, err.message); - continue; - } - - current_zpos = task.zpos; - } - -#ifdef OMXPLAYER_SUPPORTS_RUNTIME_ROTATION - if (current_orientation != task.orientation) { - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "SetTransform", - &err, - NULL, - "x", - (360 - (int64_t) task.orientation) % 360 - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not update omxplayer rotation. %s, %s\n", err.name, err.message); - continue; - } - current_orientation = task.orientation; - } -#endif - } else if (task.type == kGetPosition) { - int64_t position = 0; - - ok = get_dbus_property( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "Position", - &err, - 'x', - &position - ); - if (ok != 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not get omxplayer position: %s, %s\n", err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - continue; - } - - position = position / 1000; - - platch_respond_success_std(task.responsehandle, &STDINT64(position)); - } else if (task.type == kSetPosition) { - if (is_stream) { - if (task.position == -1) { - // TODO: implement seek-to-end - - platch_respond_success_std( - task.responsehandle, - NULL - ); - } else { - // Don't allow flutter to seek to anything other than the end on a stream. - fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to seek on non-seekable video (a stream).\n"); - - platch_respond_error_std( - task.responsehandle, - "state-error", - "Attempted to seek on non-seekable video (a stream)", - NULL - ); - } - } else { - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "SetPosition", - &err, - NULL, - "ox", - "/path/not/used", - (int64_t) (task.position * 1000) - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not set omxplayer position: %s, %s, %s\n", strerror(-ok), err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - continue; - } - - platch_respond_success_std(task.responsehandle, NULL); - } - } else if (task.type == kSetLooping) { - platch_respond_success_std(task.responsehandle, NULL); - } else if (task.type == kSetVolume) { - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_PROPERTY_FACE, - DBUS_PROPRETY_SET, - &err, - NULL, - "ssd", - DBUS_OMXPLAYER_PLAYER_FACE, - "Volume", - (double) task.volume - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not set omxplayer volume: %s, %s\n", err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - continue; - } - - platch_respond_success_std(task.responsehandle, NULL); - } - } - - return (void*) EXIT_SUCCESS; - - - fail_kill_registered_player: - kill(omxplayer_pid, SIGKILL); - waitpid(omxplayer_pid, NULL, 0); - goto fail_close_dbus; - - fail_kill_unregistered_player: - kill(omxplayer_pid, SIGKILL); - waitpid(omxplayer_pid, NULL, 0); - - fail_unref_slot: - sd_bus_slot_unref(slot); - slot = NULL; - - fail_close_dbus: - sd_bus_unref(bus); - - fail_remove_evch_listener: - plugin_registry_remove_receiver(mgr->player->event_channel_name); - remove_player(mgr->player); - free(mgr->player); - cqueue_deinit(&mgr->task_queue); - free(mgr); - mgr = NULL; - return (void*) EXIT_FAILURE; -} - -/// Ensures the bindings to libsystemd are initialized. -static int ensure_binding_initialized(void) { - int ok; - - if (omxpvidpp.initialized) return 0; - - ok = access("/usr/bin/omxplayer.bin", X_OK); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] omxplayer doesn't seem to be installed. Please install using 'sudo apt install omxplayer'. access: %s\n", strerror(errno)); - return errno; - } - - omxpvidpp.initialized = true; - - return 0; -} - -/// Respond to the handle with a "initialization failed" message. -static int respond_init_failed(FlutterPlatformMessageResponseHandle *handle) { - return platch_respond_error_std( - handle, - "couldnotinit", - "omxplayer_video_player plugin failed to initialize libsystemd bindings. See flutter-pi log for details.", - NULL - ); -} - -/******************************************************* - * CHANNEL HANDLERS * - * handle method calls on the method and event channel * - *******************************************************/ -static int on_receive_evch( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { - struct omxplayer_video_player *player; - - player = get_player_by_evch(channel); - if (player == NULL) { - return platch_respond_not_implemented(responsehandle); - } - - if STREQ("listen", object->method) { - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kListen, - .responsehandle = responsehandle - }); - } else if STREQ("cancel", object->method) { - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kUnlisten, - .responsehandle = responsehandle - }); - } else { - return platch_respond_not_implemented(responsehandle); - } -} - -static int on_initialize( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - int ok; - - (void) arg; - - ok = ensure_binding_initialized(); - if (ok != 0) { - return respond_init_failed(responsehandle); - } - - LOG_DEBUG("on_initialize\n"); - - return platch_respond_success_std(responsehandle, NULL); -} - -/// Creates a new video player. -/// Should respond to the platform message when the player has established its viewport. -static int on_create( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - struct omxplayer_mgr *mgr; - enum data_source_type source_type; - struct std_value *temp; - char *asset, *uri, *package_name, *format_hint; - int ok; - - (void) source_type; - (void) package_name; - (void) format_hint; - - ok = ensure_binding_initialized(); - if (ok != 0) { - return respond_init_failed(responsehandle); - } - - temp = stdmap_get_str(arg, "sourceType"); - if (temp != NULL && STDVALUE_IS_STRING(*temp)) { - char *source_type_str = temp->string_value; - - if STREQ("DataSourceType.asset", source_type_str) { - source_type = kDataSourceTypeAsset; - } else if STREQ("DataSourceType.network", source_type_str) { - source_type = kDataSourceTypeNetwork; - } else if STREQ("DataSourceType.file", source_type_str) { - source_type = kDataSourceTypeFile; - } else { - goto invalid_source_type; - } - } else { - invalid_source_type: - - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['sourceType']` to be a stringification of the [DataSourceType] enum." - ); - } - - temp = stdmap_get_str(arg, "asset"); - if (temp == NULL || temp->type == kStdNull) { - asset = NULL; - } else if (temp != NULL && temp->type == kStdString) { - asset = temp->string_value; - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['asset']` to be a String or null." - ); - } - - temp = stdmap_get_str(arg, "uri"); - if (temp == NULL || temp->type == kStdNull) { - uri = NULL; - } else if (temp != NULL && temp->type == kStdString) { - uri = temp->string_value; - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['uri']` to be a String or null." - ); - } - - temp = stdmap_get_str(arg, "packageName"); - if (temp == NULL || temp->type == kStdNull) { - package_name = NULL; - } else if (temp != NULL && temp->type == kStdString) { - package_name = temp->string_value; - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['packageName']` to be a String or null." - ); - } - - temp = stdmap_get_str(arg, "formatHint"); - if (temp == NULL || temp->type == kStdNull) { - format_hint = NULL; - } else if (temp != NULL && temp->type == kStdString) { - format_hint = temp->string_value; - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['formatHint']` to be a String or null." - ); - } - - LOG_DEBUG( - "on_create(sourceType: %s, asset: %s, uri: \"%s\", packageName: %s, formatHint: %s)\n", - source_type == kDataSourceTypeAsset ? "asset" : source_type == kDataSourceTypeNetwork ? "network" : "file", - asset, - uri, - package_name, - format_hint - ); - - mgr = calloc(1, sizeof *mgr); - if (mgr == NULL) { - return platch_respond_native_error_std(responsehandle, ENOMEM); - } - - ok = cqueue_init(&mgr->task_queue, sizeof(struct omxplayer_mgr_task), CQUEUE_DEFAULT_MAX_SIZE); - if (ok != 0) { - goto fail_free_mgr; - } - - // Allocate the player metadata - player = calloc(1, sizeof(*player)); - if (player == NULL) { - goto fail_deinit_task_queue; - } - - player->player_id = omxpvidpp.next_unused_player_id++; - player->mgr = mgr; - if (asset != NULL) { - asprintf(&player->video_uri, "%s/%s", flutterpi.flutter.paths->asset_bundle_path, asset); - } else { - player->video_uri = strdup(uri); - } - - mgr->player = player; - - ok = cqueue_enqueue(&mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kCreate, - .responsehandle = responsehandle, - .orientation = flutterpi.view.rotation - }); - - if (ok != 0) { - goto fail_free_player; - } - - snprintf( - player->event_channel_name, - sizeof(player->event_channel_name), - "flutter.io/omxplayerVideoPlayer/videoEvents%" PRId64, - player->player_id - ); - - // add it to our player collection - ok = add_player(player); - if (ok != 0) { - goto fail_free_player; - } - - // set a receiver on the videoEvents event channel - ok = plugin_registry_set_receiver( - player->event_channel_name, - kStandardMethodCall, - on_receive_evch - ); - if (ok != 0) { - goto fail_remove_player; - } - - ok = pthread_create(&mgr->thread, NULL, mgr_entry, mgr); - if (ok != 0) { - goto fail_remove_evch_listener; - } - - return 0; - - - fail_remove_evch_listener: - plugin_registry_remove_receiver(player->event_channel_name); - - fail_remove_player: - remove_player(player); - - fail_free_player: - free(player); - player = NULL; - - fail_deinit_task_queue: - cqueue_deinit(&mgr->task_queue); - - fail_free_mgr: - free(mgr); - mgr = NULL; - - return platch_respond_native_error_std(responsehandle, ok); -} - -static int on_dispose( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) { - return ok; - } - - LOG_DEBUG("on_dispose(%"PRId64")\n", player->player_id); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kDispose, - .responsehandle = responsehandle - }); -} - -static int on_set_looping( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - struct std_value *temp; - bool loop; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - temp = stdmap_get_str(arg, "looping"); - if (STDVALUE_IS_BOOL(*temp)) { - loop = STDVALUE_AS_BOOL(*temp); - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['looping']` to be a boolean." - ); - } - - LOG_DEBUG("on_set_looping(player_id: %"PRId64", looping: %s)\n", player->player_id, loop ? "yes" : "no"); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kSetLooping, - .loop = loop, - .responsehandle = responsehandle - }); -} - -static int on_set_volume( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - struct std_value *temp; - float volume; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - temp = stdmap_get_str(arg, "volume"); - if (STDVALUE_IS_FLOAT(*temp)) { - volume = STDVALUE_AS_FLOAT(*temp); - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['volume']` to be a float/double." - ); - } - - LOG_DEBUG("on_set_volume(player_id: %"PRId64", volume: %f)\n", player->player_id, volume); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kSetVolume, - .volume = volume, - .responsehandle = responsehandle - }); -} - -static int on_play( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - LOG_DEBUG("on_play(player_id: %"PRId64")\n", player->player_id); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kPlay, - .responsehandle = responsehandle - }); -} - -static int on_get_position( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - LOG_DEBUG("on_get_position(player_id: %"PRId64")\n", player->player_id); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kGetPosition, - .responsehandle = responsehandle - }); -} - -static int on_seek_to( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - struct std_value *temp; - int64_t position; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - temp = stdmap_get_str(arg, "position"); - if (STDVALUE_IS_INT(*temp)) { - position = STDVALUE_AS_INT(*temp); - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['position']` to be an integer." - ); - } - - LOG_DEBUG("on_seek_to(player_id: %"PRId64", position: %"PRId64")\n", player->player_id, position); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kSetPosition, - .position = position, - .responsehandle = responsehandle - }); -} - -static int on_pause( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - LOG_DEBUG("on_pause(player_id: %"PRId64")\n", player->player_id); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kPause, - .responsehandle = responsehandle - }); -} - -static int on_create_platform_view( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - struct std_value *temp; - int64_t view_id; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - temp = stdmap_get_str(arg, "platformViewId"); - if (STDVALUE_IS_INT(*temp)) { - view_id = STDVALUE_AS_INT(*temp); - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['platformViewId']` to be an integer." - ); - } - - LOG_DEBUG("on_create_platform_view(player_id: %"PRId64", platform view id: %"PRId64")\n", player->player_id, view_id); - - if (player->has_view) { - LOG_ERROR("Flutter attempted to register more than one platform view for one player instance.\n"); - return platch_respond_illegal_arg_std( - responsehandle, - "Attempted to register more than one platform view for this player instance." - ); - } else { - ok = compositor_set_view_callbacks( - view_id, - on_mount, - on_unmount, - on_update_view, - NULL, - player - ); - if (ok != 0) { - return platch_respond_native_error_std(responsehandle, ok); - } - - player->has_view = true; - player->view_id = view_id; - - return platch_respond_success_std(responsehandle, NULL); - } -} - -static int on_dispose_platform_view( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - struct std_value *temp; - int64_t view_id; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - temp = stdmap_get_str(arg, "platformViewId"); - if (STDVALUE_IS_INT(*temp)) { - view_id = STDVALUE_AS_INT(*temp); - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['platformViewId']` to be an integer." - ); - } - - LOG_DEBUG("on_dispose_platform_view(player_id: %"PRId64", platform view id: %"PRId64")\n", player->player_id, view_id); - - if (player->view_id != view_id) { - LOG_ERROR("Flutter attempted to dispose an omxplayer platform view that is not associated with this player.\n"); - - return platch_respond_illegal_arg_std(responsehandle, "Attempted to dispose on omxplayer view that is not associated with `arg['playerId']`."); - } else { - ok = compositor_remove_view_callbacks(view_id); - if (ok != 0) { - LOG_ERROR("Could not remove view callbacks for platform view %" PRId64 ". compositor_remove_view_callbacks: %s\n", view_id, strerror(ok)); - return platch_respond_native_error_std(responsehandle, ok); - } - - player->has_view = false; - player->view_id = -1; - - // hide omxplayer - cqueue_enqueue(&player->mgr->task_queue, &(struct omxplayer_mgr_task) { - .type = kUpdateView, - .offset_x = 0, - .offset_y = 0, - .width = 1, - .height = 1, - .zpos = -128 - }); - - return platch_respond_success_std(responsehandle, NULL); - } -} - -/// Called when a platform channel object is received on the method channel. -/// Finds out which method was called an then calls the corresponding above method, -/// or else responds with not implemented. -static int on_receive_mch( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { - (void) channel; - - if STREQ("init", object->method) { - return on_initialize(&object->std_arg, responsehandle); - } else if STREQ("create", object->method) { - return on_create(&object->std_arg, responsehandle); - } else if STREQ("dispose", object->method) { - return on_dispose(&object->std_arg, responsehandle); - } else if STREQ("setLooping", object->method) { - return on_set_looping(&object->std_arg, responsehandle); - } else if STREQ("setVolume", object->method) { - return on_set_volume(&object->std_arg, responsehandle); - } else if STREQ("play", object->method) { - return on_play(&object->std_arg, responsehandle); - } else if STREQ("pause", object->method) { - return on_pause(&object->std_arg, responsehandle); - } else if STREQ("getPosition", object->method) { - return on_get_position(&object->std_arg, responsehandle); - } else if STREQ("seekTo", object->method) { - return on_seek_to(&object->std_arg, responsehandle); - } else if STREQ("createPlatformView", object->method) { - return on_create_platform_view(&object->std_arg, responsehandle); - } else if STREQ("disposePlatformView", object->method) { - return on_dispose_platform_view(&object->std_arg, responsehandle); - } - - return platch_respond_not_implemented(responsehandle); -} - -int8_t omxpvidpp_is_present(void) { - return plugin_registry_is_plugin_present(flutterpi.plugin_registry, "omxplayer video_player plugin"); -} - -enum plugin_init_result omxpvidpp_init(struct flutterpi *flutterpi, void **userdata_out) { - int ok; - - (void) flutterpi; - (void) userdata_out; - - ok = plugin_registry_set_receiver("flutter.io/omxplayerVideoPlayer", kStandardMethodCall, on_receive_mch); - if (ok != 0) { - return kError_PluginInitResult; - } - - return kInitialized_PluginInitResult; -} - -void omxpvidpp_deinit(struct flutterpi *flutterpi, void *userdata) { - plugin_registry_remove_receiver("flutter.io/omxplayerVideoPlayer"); - (void) flutterpi; - (void) userdata; -} - -FLUTTERPI_PLUGIN( - "omxplayer video_player plugin", - omxpvidpp, - omxpvidpp_init, - omxpvidpp_deinit -) \ No newline at end of file From ed5760dda57e5ad58c70a7621d9fe09b44c4ea85 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sat, 18 Feb 2023 17:08:36 +0100 Subject: [PATCH 26/55] use custom is_drm_master function --- src/modesetting.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modesetting.c b/src/modesetting.c index 78210809..20b4bf42 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -104,6 +104,10 @@ struct drmdev_atomic_req { struct pointer_set available_planes; }; +static bool is_drm_master(int fd) { + return drmAuthMagic(fd, 0) != -EACCES; +} + static struct drm_mode_blob *drm_mode_blob_new(int drm_fd, const drmModeModeInfo *mode) { struct drm_mode_blob *blob; uint32_t blob_id; @@ -920,7 +924,7 @@ struct drmdev *drmdev_new_from_fd(int fd, const struct drmdev_interface *interfa return NULL; } - if (drmIsMaster(fd)) { + if (is_drm_master(fd)) { ok = drmDropMaster(fd); if (ok < 0) { LOG_ERROR("Couldn't drop DRM master. drmDropMaster: %s\n", strerror(errno)); @@ -2161,7 +2165,7 @@ static int kms_req_commit_common( return EBUSY; } - if (!drmIsMaster(builder->drmdev->master_fd)) { + if (!is_drm_master(builder->drmdev->master_fd)) { LOG_ERROR("Commit requested, but drmdev is paused right now.\n"); drmdev_unlock(builder->drmdev); return EBUSY; From dca3acf6c268799de5f42a3e59731b542c5b8647 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sat, 18 Feb 2023 17:12:30 +0100 Subject: [PATCH 27/55] fix gstreamer <1.18 compilation --- src/plugins/gstreamer_video_player/frame.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index ee283e63..040223a2 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -344,7 +344,6 @@ int get_plane_infos( ) { GstVideoMeta *meta; GstMemory *memory; - gboolean gst_ok; unsigned int n_mems; bool is_dmabuf_memory; int n_planes, dmabuf_fd; @@ -366,6 +365,7 @@ int get_plane_infos( if (n_mems > 1) { LOG_ERROR("Multiple dmabufs for a single frame buffer is not supported right now.\n"); close(dmabuf_fd); + return EINVAL; } n_planes = GST_VIDEO_INFO_N_PLANES(frame_info->gst_info); @@ -388,6 +388,8 @@ int get_plane_infos( plane_infos[i].modifier = DRM_FORMAT_MOD_LINEAR; } } + + return 0; } #else # error "Unsupported gstreamer version." From e5be4046eaa6fff49e2e48a0d09ee0f37fd08374 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 23 Feb 2023 01:36:24 +0100 Subject: [PATCH 28/55] rework gl texture uploading --- include/plugins/gstreamer_video_player.h | 4 +- src/plugins/gstreamer_video_player/frame.c | 378 ++++++++++++-------- src/plugins/gstreamer_video_player/player.c | 79 +--- 3 files changed, 241 insertions(+), 220 deletions(-) diff --git a/include/plugins/gstreamer_video_player.h b/include/plugins/gstreamer_video_player.h index 131542d1..ff487d79 100644 --- a/include/plugins/gstreamer_video_player.h +++ b/include/plugins/gstreamer_video_player.h @@ -252,8 +252,8 @@ struct _GstSample; struct video_frame *frame_new( struct frame_interface *interface, - const struct frame_info *meta, - struct _GstSample *sample + GstSample *sample, + const GstVideoInfo *info ); void frame_destroy(struct video_frame *frame); diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index 040223a2..95f30699 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -235,10 +235,38 @@ struct plane_info { uint64_t modifier; }; -#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 18, 0) -int get_plane_infos( +#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 14, 0) +static size_t calculate_plane_size( + const GstVideoInfo *info, + int plane_index +) { + // Taken from: https://github.com/GStreamer/gstreamer/blob/621604aa3e4caa8db27637f63fa55fac2f7721e5/subprojects/gst-plugins-base/gst-libs/gst/video/video-info.c#L1278-L1301 + +#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 21, 3) + if (GST_VIDEO_FORMAT_INFO_IS_TILED (info->finfo)) { + guint x_tiles = GST_VIDEO_TILE_X_TILES (info->stride[i]); + guint y_tiles = GST_VIDEO_TILE_Y_TILES (info->stride[i]); + return x_tiles * y_tiles * GST_VIDEO_FORMAT_INFO_TILE_SIZE(info->finfo, i); + } +#endif + + gint comp[GST_VIDEO_MAX_COMPONENTS]; + guint plane_height; + + /* Convert plane index to component index */ + gst_video_format_info_component (info->finfo, plane_index, comp); + plane_height = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT( + info->finfo, + comp[0], + GST_VIDEO_INFO_FIELD_HEIGHT(info) + ); + + return plane_height * GST_VIDEO_INFO_PLANE_STRIDE(info, plane_index); +} + +static int get_plane_infos( GstBuffer *buffer, - const struct frame_info *frame_info, + const GstVideoInfo *info, struct gbm_device *gbm_device, struct plane_info plane_infos[MAX_N_PLANES] ) { @@ -248,7 +276,7 @@ int get_plane_infos( size_t plane_sizes[4] = {0}; int n_planes; - n_planes = GST_VIDEO_INFO_N_PLANES(frame_info->gst_info); + n_planes = GST_VIDEO_INFO_N_PLANES(info); meta = gst_buffer_get_video_meta(buffer); if (meta != NULL) { @@ -258,43 +286,29 @@ int get_plane_infos( return EIO; } } else { - // Taken from: https://github.com/GStreamer/gstreamer/blob/621604aa3e4caa8db27637f63fa55fac2f7721e5/subprojects/gst-plugins-base/gst-libs/gst/video/video-info.c#L1278-L1301 - for (int i = 0; i < GST_VIDEO_MAX_PLANES; i++) { - if (i < GST_VIDEO_INFO_N_PLANES(frame_info->gst_info)) { -#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 21, 3) - if (GST_VIDEO_FORMAT_INFO_IS_TILED (frame_info->gst_info->finfo)) { - guint x_tiles = GST_VIDEO_TILE_X_TILES (frame_info->gst_info->stride[i]); - guint y_tiles = GST_VIDEO_TILE_Y_TILES (frame_info->gst_info->stride[i]); - plane_sizes[i] = x_tiles * y_tiles * GST_VIDEO_FORMAT_INFO_TILE_SIZE(frame_info->gst_info->finfo, i); - } else { -#endif - - gint comp[GST_VIDEO_MAX_COMPONENTS]; - guint plane_height; - - /* Convert plane index to component index */ - gst_video_format_info_component (frame_info->gst_info->finfo, i, comp); - plane_height = - GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT(frame_info->gst_info->finfo, comp[0], - GST_VIDEO_INFO_FIELD_HEIGHT(frame_info->gst_info)); - plane_sizes[i] = plane_height * GST_VIDEO_INFO_PLANE_STRIDE(frame_info->gst_info, i); - -#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 21, 3) - } -#endif - } else { - plane_sizes[i] = 0; - } + for (int i = 0; i < n_planes; i++) { + plane_sizes[i] = calculate_plane_size(info, i); } } for (int i = 0; i < n_planes; i++) { - unsigned memory_index = 0, n_memories = 0; size_t offset_in_memory = 0; + size_t offset_in_buffer = 0; + unsigned memory_index = 0; + unsigned n_memories = 0; + int stride, ok; + + if (meta) { + offset_in_buffer = meta->offset[i]; + stride = meta->stride[i]; + } else { + offset_in_buffer = GST_VIDEO_INFO_PLANE_OFFSET(info, i); + stride = GST_VIDEO_INFO_PLANE_STRIDE(info, i); + } gst_ok = gst_buffer_find_memory( buffer, - meta ? meta->offset[i] : GST_VIDEO_INFO_PLANE_OFFSET(frame_info->gst_info, i), + offset_in_buffer, plane_sizes[i], &memory_index, &n_memories, @@ -312,21 +326,34 @@ int get_plane_infos( memory = gst_buffer_peek_memory(buffer, memory_index); if (gst_is_dmabuf_memory(memory)) { - plane_infos[i].fd = dup(gst_dmabuf_memory_get_fd(memory)); - if (plane_infos[i].fd < 0) { + ok = gst_dmabuf_memory_get_fd(memory); + if (ok < 0) { LOG_ERROR("Could not get gstreamer memory as dmabuf.\n"); return EIO; } + + ok = dup(ok); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not dup fd. dup: %s\n", strerror(ok)); + return ok; + } + + plane_infos[i].fd = ok; } else { - plane_infos[i].fd = dup_gst_memory_as_dmabuf(gbm_device, memory); - if (plane_infos[i].fd < 0) { + /// TODO: When duping, duplicate all non-dmabuf memories into one + /// gbm buffer instead. + ok = dup_gst_memory_as_dmabuf(gbm_device, memory); + if (ok < 0) { LOG_ERROR("Could not duplicate gstreamer memory as dmabuf.\n"); return EIO; } + + plane_infos[i].fd = ok; } plane_infos[i].offset = offset_in_memory; - plane_infos[i].pitch = meta ? meta->stride[i] : GST_VIDEO_INFO_PLANE_STRIDE(frame_info->gst_info, i); + plane_infos[i].pitch = stride; /// TODO: Detect modifiers here plane_infos[i].has_modifier = false; @@ -335,89 +362,146 @@ int get_plane_infos( return 0; } -#elif THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 14, 0) -int get_plane_infos( - GstBuffer *buffer, - const struct frame_info *frame_info, - struct gbm_device *gbm_device, - struct plane_info plane_infos[MAX_N_PLANES] -) { - GstVideoMeta *meta; - GstMemory *memory; - unsigned int n_mems; - bool is_dmabuf_memory; - int n_planes, dmabuf_fd; - - memory = gst_buffer_peek_memory(buffer, 0); - is_dmabuf_memory = gst_is_dmabuf_memory(memory); - n_mems = gst_buffer_n_memory(buffer); - - /// TODO: Do we really need to dup() here? - if (is_dmabuf_memory) { - dmabuf_fd = dup(gst_dmabuf_memory_get_fd(memory)); - } else { - dmabuf_fd = dup_gst_buffer_as_dmabuf(gbm_device, buffer); - - //LOG_ERROR("Only dmabuf memory is supported for video frame buffers right now, but gstreamer didn't provide a dmabuf memory buffer.\n"); - //goto fail_free_frame; - } - - if (n_mems > 1) { - LOG_ERROR("Multiple dmabufs for a single frame buffer is not supported right now.\n"); - close(dmabuf_fd); - return EINVAL; - } - - n_planes = GST_VIDEO_INFO_N_PLANES(frame_info->gst_info); - - meta = gst_buffer_get_video_meta(buffer); - if (meta != NULL) { - for (int i = 0; i < n_planes; i++) { - plane_infos[i].fd = dmabuf_fd; - plane_infos[i].offset = meta->offset[i]; - plane_infos[i].pitch = meta->stride[i]; - plane_infos[i].has_modifier = false; - plane_infos[i].modifier = DRM_FORMAT_MOD_LINEAR; - } - } else { - for (int i = 0; i < n_planes; i++) { - plane_infos[i].fd = dmabuf_fd; - plane_infos[i].offset = GST_VIDEO_INFO_PLANE_OFFSET(frame_info->gst_info, i); - plane_infos[i].pitch = GST_VIDEO_INFO_PLANE_STRIDE(frame_info->gst_info, i); - plane_infos[i].has_modifier = false; - plane_infos[i].modifier = DRM_FORMAT_MOD_LINEAR; - } - } - - return 0; -} #else # error "Unsupported gstreamer version." #endif +static uint32_t drm_format_from_gst_info(const GstVideoInfo *info) { + switch (GST_VIDEO_INFO_FORMAT(info)) { + case GST_VIDEO_FORMAT_YUY2: return DRM_FORMAT_YUYV; + case GST_VIDEO_FORMAT_YVYU: return DRM_FORMAT_YVYU; + case GST_VIDEO_FORMAT_UYVY: return DRM_FORMAT_UYVY; + case GST_VIDEO_FORMAT_VYUY: return DRM_FORMAT_VYUY; + case GST_VIDEO_FORMAT_AYUV: + case GST_VIDEO_FORMAT_VUYA: return DRM_FORMAT_AYUV; + case GST_VIDEO_FORMAT_NV12: return DRM_FORMAT_NV12; + case GST_VIDEO_FORMAT_NV21: return DRM_FORMAT_NV21; + case GST_VIDEO_FORMAT_NV16: return DRM_FORMAT_NV16; + case GST_VIDEO_FORMAT_NV61: return DRM_FORMAT_NV61; + case GST_VIDEO_FORMAT_NV24: return DRM_FORMAT_NV24; + case GST_VIDEO_FORMAT_YUV9: return DRM_FORMAT_YUV410; + case GST_VIDEO_FORMAT_YVU9: return DRM_FORMAT_YVU410; + case GST_VIDEO_FORMAT_Y41B: return DRM_FORMAT_YUV411; + case GST_VIDEO_FORMAT_I420: return DRM_FORMAT_YUV420; + case GST_VIDEO_FORMAT_YV12: return DRM_FORMAT_YVU420; + case GST_VIDEO_FORMAT_Y42B: return DRM_FORMAT_YUV422; + case GST_VIDEO_FORMAT_Y444: return DRM_FORMAT_YUV444; + case GST_VIDEO_FORMAT_RGB16: return DRM_FORMAT_RGB565; + case GST_VIDEO_FORMAT_BGR16: return DRM_FORMAT_BGR565; + case GST_VIDEO_FORMAT_RGBA: return DRM_FORMAT_ABGR8888; + case GST_VIDEO_FORMAT_RGBx: return DRM_FORMAT_XBGR8888; + case GST_VIDEO_FORMAT_BGRA: return DRM_FORMAT_ARGB8888; + case GST_VIDEO_FORMAT_BGRx: return DRM_FORMAT_XRGB8888; + case GST_VIDEO_FORMAT_ARGB: return DRM_FORMAT_BGRA8888; + case GST_VIDEO_FORMAT_xRGB: return DRM_FORMAT_BGRX8888; + case GST_VIDEO_FORMAT_ABGR: return DRM_FORMAT_RGBA8888; + case GST_VIDEO_FORMAT_xBGR: return DRM_FORMAT_RGBX8888; + default: return DRM_FORMAT_INVALID; + } +} + struct video_frame *frame_new( struct frame_interface *interface, - const struct frame_info *info, - GstSample *sample + GstSample *sample, + const GstVideoInfo *info ) { -# define PUT_ATTR(_key, _value) do { *attr_cursor++ = _key; *attr_cursor++ = _value; } while (false) +# define PUT_ATTR(_key, _value) \ + do { \ + DEBUG_ASSERT(attr_index + 2 <= ARRAY_SIZE(attributes)); \ + attributes[attr_index++] = (_key); \ + attributes[attr_index++] = (_value); \ + } while (false) struct video_frame *frame; + struct plane_info planes[MAX_N_PLANES]; + GstVideoInfo _info; EGLBoolean egl_ok; - EGLImage egl_image; GstBuffer *buffer; - GLenum gl_error; - EGLint attributes[2*7 + MAX_N_PLANES*2*5 + 1], *attr_cursor; + EGLImage egl_image; + gboolean gst_ok; + uint32_t drm_format; + GstCaps *caps; GLuint texture; + GLenum gl_error; EGLint egl_error; - int ok, n_planes, width, height; - - struct plane_info planes[MAX_N_PLANES] = {0}; + EGLint attributes[2*7 + MAX_N_PLANES*2*5 + 1]; + EGLint egl_color_space, egl_sample_range_hint, egl_horizontal_chroma_siting, egl_vertical_chroma_siting; + int ok, width, height, n_planes, attr_index; buffer = gst_sample_get_buffer(sample); + if (buffer == NULL) { + return NULL; + } + + if (info == NULL) { + caps = gst_sample_get_caps(sample); + if (caps == NULL) { + return NULL; + } + + info = &_info; + + gst_ok = gst_video_info_from_caps(&_info, caps); + if (gst_ok == FALSE) { + LOG_ERROR("Could not get video info from video sample caps.\n"); + return NULL; + } + } else { + caps = NULL; + } + + width = GST_VIDEO_INFO_WIDTH(info); + height = GST_VIDEO_INFO_HEIGHT(info); + n_planes = GST_VIDEO_INFO_N_PLANES(info); + + // query the drm format for this sample + drm_format = drm_format_from_gst_info(info); + if (drm_format == DRM_FORMAT_INVALID) { + LOG_ERROR("Video format has no EGL equivalent.\n"); + return NULL; + } + + // query the color space for this sample + if (gst_video_colorimetry_matches(&GST_VIDEO_INFO_COLORIMETRY(info), GST_VIDEO_COLORIMETRY_BT601)) { + egl_color_space = EGL_ITU_REC601_EXT; + } else if (gst_video_colorimetry_matches(&GST_VIDEO_INFO_COLORIMETRY(info), GST_VIDEO_COLORIMETRY_BT709)) { + egl_color_space = EGL_ITU_REC709_EXT; + } else if (gst_video_colorimetry_matches(&GST_VIDEO_INFO_COLORIMETRY(info), GST_VIDEO_COLORIMETRY_BT2020)) { + egl_color_space = EGL_ITU_REC2020_EXT; + } else { + LOG_DEBUG("Unsupported video colorimetry: %s\n", gst_video_colorimetry_to_string(&GST_VIDEO_INFO_COLORIMETRY(info))); + egl_color_space = EGL_NONE; + } + + // check the sample range + if (GST_VIDEO_INFO_COLORIMETRY(info).range == GST_VIDEO_COLOR_RANGE_0_255) { + egl_sample_range_hint = EGL_YUV_FULL_RANGE_EXT; + } else if (GST_VIDEO_INFO_COLORIMETRY(info).range == GST_VIDEO_COLOR_RANGE_16_235) { + egl_sample_range_hint = EGL_YUV_NARROW_RANGE_EXT; + } else { + egl_sample_range_hint = EGL_NONE; + } + + // check the chroma siting + if ((GST_VIDEO_INFO_CHROMA_SITE(info) & ~(GST_VIDEO_CHROMA_SITE_H_COSITED | GST_VIDEO_CHROMA_SITE_V_COSITED)) == 0) { + if (GST_VIDEO_INFO_CHROMA_SITE(info) & GST_VIDEO_CHROMA_SITE_H_COSITED) { + egl_horizontal_chroma_siting = EGL_YUV_CHROMA_SITING_0_EXT; + } else { + egl_horizontal_chroma_siting = EGL_YUV_CHROMA_SITING_0_5_EXT; + } + if (GST_VIDEO_INFO_CHROMA_SITE(info) & GST_VIDEO_CHROMA_SITE_V_COSITED) { + egl_vertical_chroma_siting = EGL_YUV_CHROMA_SITING_0_EXT; + } else { + egl_vertical_chroma_siting = EGL_YUV_CHROMA_SITING_0_5_EXT; + } + } else { + egl_horizontal_chroma_siting = EGL_NONE; + egl_vertical_chroma_siting = EGL_NONE; + } + frame = malloc(sizeof *frame); if (frame == NULL) { - goto fail_unref_buffer; + return NULL; } ok = get_plane_infos(buffer, info, interface->gbm_device, planes); @@ -425,47 +509,35 @@ struct video_frame *frame_new( goto fail_free_frame; } - width = GST_VIDEO_INFO_WIDTH(info->gst_info); - height = GST_VIDEO_INFO_HEIGHT(info->gst_info); - n_planes = GST_VIDEO_INFO_N_PLANES(info->gst_info); - - attr_cursor = attributes; + attr_index = 0; // first, put some of our basic attributes like // frame size and format PUT_ATTR(EGL_WIDTH, width); PUT_ATTR(EGL_HEIGHT, height); - PUT_ATTR(EGL_LINUX_DRM_FOURCC_EXT, info->drm_format); + PUT_ATTR(EGL_LINUX_DRM_FOURCC_EXT, drm_format); // if we have a color space, put that too // could be one of EGL_ITU_REC601_EXT, EGL_ITU_REC709_EXT or EGL_ITU_REC2020_EXT - if (info->egl_color_space != EGL_NONE) { - PUT_ATTR(EGL_YUV_COLOR_SPACE_HINT_EXT, info->egl_color_space); + if (egl_color_space != EGL_NONE) { + PUT_ATTR(EGL_YUV_COLOR_SPACE_HINT_EXT, egl_color_space); } // if we have information about the sample range, put that into the attributes too - if (GST_VIDEO_INFO_COLORIMETRY(info->gst_info).range == GST_VIDEO_COLOR_RANGE_0_255) { - PUT_ATTR(EGL_SAMPLE_RANGE_HINT_EXT, EGL_YUV_FULL_RANGE_EXT); - } else if (GST_VIDEO_INFO_COLORIMETRY(info->gst_info).range == GST_VIDEO_COLOR_RANGE_16_235) { - PUT_ATTR(EGL_SAMPLE_RANGE_HINT_EXT, EGL_YUV_NARROW_RANGE_EXT); + if (egl_sample_range_hint != EGL_NONE) { + PUT_ATTR(EGL_SAMPLE_RANGE_HINT_EXT, egl_sample_range_hint); } - // Check that we can actually represent that siting info using the attributes EGL gives us. - // For example, we can't represent GST_VIDEO_CHROMA_SITE_ALT_LINE. - if ((GST_VIDEO_INFO_CHROMA_SITE(info->gst_info) & ~(GST_VIDEO_CHROMA_SITE_H_COSITED | GST_VIDEO_CHROMA_SITE_V_COSITED)) == 0) { - if (GST_VIDEO_INFO_CHROMA_SITE(info->gst_info) & GST_VIDEO_CHROMA_SITE_H_COSITED) { - PUT_ATTR(EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_EXT); - } else { - PUT_ATTR(EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_5_EXT); - } - if (GST_VIDEO_INFO_CHROMA_SITE(info->gst_info) & GST_VIDEO_CHROMA_SITE_V_COSITED) { - PUT_ATTR(EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_EXT); - } else { - PUT_ATTR(EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_5_EXT); - } + // chroma siting + if (egl_horizontal_chroma_siting != EGL_NONE) { + PUT_ATTR(EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT, egl_horizontal_chroma_siting); + } + + if (egl_vertical_chroma_siting != EGL_NONE) { + PUT_ATTR(EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT, egl_vertical_chroma_siting); } - // now begin with putting in information about plane memory + // add plane 1 PUT_ATTR(EGL_DMA_BUF_PLANE0_FD_EXT, planes[0].fd); PUT_ATTR(EGL_DMA_BUF_PLANE0_OFFSET_EXT, planes[0].offset); PUT_ATTR(EGL_DMA_BUF_PLANE0_PITCH_EXT, planes[0].pitch); @@ -475,10 +547,11 @@ struct video_frame *frame_new( PUT_ATTR(EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, uint32_to_int32(planes[0].modifier >> 32)); } else { LOG_ERROR("video frame buffer uses modified format but EGL doesn't support the EGL_EXT_image_dma_buf_import_modifiers extension.\n"); - goto fail_close_dmabuf_fd; + goto fail_release_planes; } } + // add plane 2 (if present) if (n_planes >= 2) { PUT_ATTR(EGL_DMA_BUF_PLANE1_FD_EXT, planes[1].fd); PUT_ATTR(EGL_DMA_BUF_PLANE1_OFFSET_EXT, planes[1].offset); @@ -489,11 +562,12 @@ struct video_frame *frame_new( PUT_ATTR(EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, uint32_to_int32(planes[1].modifier >> 32)); } else { LOG_ERROR("video frame buffer uses modified format but EGL doesn't support the EGL_EXT_image_dma_buf_import_modifiers extension.\n"); - goto fail_close_dmabuf_fd; + goto fail_release_planes; } } } + // add plane 3 (if present) if (n_planes >= 3) { PUT_ATTR(EGL_DMA_BUF_PLANE2_FD_EXT, planes[2].fd); PUT_ATTR(EGL_DMA_BUF_PLANE2_OFFSET_EXT, planes[2].offset); @@ -504,37 +578,34 @@ struct video_frame *frame_new( PUT_ATTR(EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT, uint32_to_int32(planes[2].modifier >> 32)); } else { LOG_ERROR("video frame buffer uses modified format but EGL doesn't support the EGL_EXT_image_dma_buf_import_modifiers extension.\n"); - goto fail_close_dmabuf_fd; + goto fail_release_planes; } } } + // add plane 4 (if present) if (n_planes >= 4) { if (!interface->supports_extended_imports) { LOG_ERROR("The video frame has more than 3 planes but that can't be imported as a GL texture if EGL doesn't support the EGL_EXT_image_dma_buf_import_modifiers extension.\n"); - goto fail_close_dmabuf_fd; + goto fail_release_planes; } PUT_ATTR(EGL_DMA_BUF_PLANE3_FD_EXT, planes[3].fd); PUT_ATTR(EGL_DMA_BUF_PLANE3_OFFSET_EXT, planes[3].offset); PUT_ATTR(EGL_DMA_BUF_PLANE3_PITCH_EXT, planes[3].pitch); if (planes[3].has_modifier) { - if (interface->supports_extended_imports) { - PUT_ATTR(EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, uint32_to_int32(planes[3].modifier & 0xFFFFFFFFlu)); - PUT_ATTR(EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT, uint32_to_int32(planes[3].modifier >> 32)); - } else { - LOG_ERROR("video frame buffer uses modified format but EGL doesn't support the EGL_EXT_image_dma_buf_import_modifiers extension.\n"); - goto fail_close_dmabuf_fd; - } + PUT_ATTR(EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, uint32_to_int32(planes[3].modifier & 0xFFFFFFFFlu)); + PUT_ATTR(EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT, uint32_to_int32(planes[3].modifier >> 32)); } } - // add a EGL_NONE to mark the end of the buffer - *attr_cursor++ = EGL_NONE; + DEBUG_ASSERT(attr_index < ARRAY_SIZE(attributes)); + attributes[attr_index++] = EGL_NONE; egl_image = interface->eglCreateImageKHR(interface->display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attributes); if (egl_image == EGL_NO_IMAGE_KHR) { - goto fail_close_dmabuf_fd; + LOG_ERROR("Couldn't create EGL image from video sample.\n"); + goto fail_release_planes; } frame_interface_lock(interface); @@ -566,9 +637,9 @@ struct video_frame *frame_new( frame_interface_unlock(interface); - frame->sample = sample; + frame->sample = gst_sample_ref(sample); frame->interface = frame_interface_ref(interface); - frame->drm_format = info->drm_format; + frame->drm_format = drm_format; frame->n_dmabuf_fds = n_planes; frame->dmabuf_fds[0] = planes[0].fd; frame->dmabuf_fds[1] = planes[1].fd; @@ -580,8 +651,8 @@ struct video_frame *frame_new( frame->gl_frame.format = GL_RGBA8_OES; frame->gl_frame.width = 0; frame->gl_frame.height = 0; - - return frame; + return 0; + fail_delete_texture: glDeleteTextures(1, &texture); @@ -593,18 +664,13 @@ struct video_frame *frame_new( frame_interface_unlock(interface); interface->eglDestroyImageKHR(interface->display, egl_image); - fail_close_dmabuf_fd: + fail_release_planes: for (int i = 0; i < n_planes; i++) close(planes[i].fd); - + fail_free_frame: free(frame); - - fail_unref_buffer: - gst_sample_unref(sample); return NULL; - -# undef PUT_ATTR } void frame_destroy(struct video_frame *frame) { @@ -630,6 +696,8 @@ void frame_destroy(struct video_frame *frame) { ok = close(frame->dmabuf_fds[i]); DEBUG_ASSERT(ok == 0); (void) ok; } + + gst_sample_unref(frame->sample); free(frame); } diff --git a/src/plugins/gstreamer_video_player/player.c b/src/plugins/gstreamer_video_player/player.c index 17510839..5703b41b 100644 --- a/src/plugins/gstreamer_video_player/player.c +++ b/src/plugins/gstreamer_video_player/player.c @@ -150,10 +150,6 @@ struct gstplayer { GstElement *pipeline, *sink; GstBus *bus; sd_event_source *busfd_events; - uint32_t drm_format; - bool has_drm_modifier; - uint64_t drm_modifier; - EGLint egl_color_space; bool is_live; }; @@ -615,7 +611,6 @@ static void on_element_added(GstBin *bin, GstElement *element, void *userdata) { static GstPadProbeReturn on_probe_pad(GstPad *pad, GstPadProbeInfo *info, void *userdata) { struct gstplayer *player; - GstVideoInfo vinfo; GstEvent *event; GstCaps *caps; gboolean ok; @@ -635,59 +630,24 @@ static GstPadProbeReturn on_probe_pad(GstPad *pad, GstPadProbeInfo *info, void * return GST_PAD_PROBE_OK; } - ok = gst_video_info_from_caps(&vinfo, caps); + ok = gst_video_info_from_caps(&player->gst_info, caps); if (!ok) { LOG_ERROR("gstreamer: caps event with invalid video caps\n"); return GST_PAD_PROBE_OK; } - - switch (GST_VIDEO_INFO_FORMAT(&vinfo)) { - case GST_VIDEO_FORMAT_Y42B: - player->drm_format = DRM_FORMAT_YUV422; - break; - case GST_VIDEO_FORMAT_YV12: - player->drm_format = DRM_FORMAT_YVU420; - break; - case GST_VIDEO_FORMAT_I420: - player->drm_format = DRM_FORMAT_YUV420; - break; - case GST_VIDEO_FORMAT_NV12: - player->drm_format = DRM_FORMAT_NV12; - break; - case GST_VIDEO_FORMAT_NV21: - player->drm_format = DRM_FORMAT_NV21; - break; - case GST_VIDEO_FORMAT_YUY2: - player->drm_format = DRM_FORMAT_YUYV; - break; - default: - LOG_ERROR("unsupported video format: %s\n", GST_VIDEO_INFO_NAME(&vinfo)); - player->drm_format = 0; - break; - } - - const GstVideoColorimetry *color = &GST_VIDEO_INFO_COLORIMETRY(&vinfo); - - if (gst_video_colorimetry_matches(color, GST_VIDEO_COLORIMETRY_BT601)) { - player->egl_color_space = EGL_ITU_REC601_EXT; - } else if (gst_video_colorimetry_matches(color, GST_VIDEO_COLORIMETRY_BT709)) { - player->egl_color_space = EGL_ITU_REC709_EXT; - } else if (gst_video_colorimetry_matches(color, GST_VIDEO_COLORIMETRY_BT2020)) { - player->egl_color_space = EGL_ITU_REC2020_EXT; - } else { - LOG_ERROR("unsupported video colorimetry: %s\n", gst_video_colorimetry_to_string(color)); - player->egl_color_space = EGL_NONE; - } - - memcpy(&player->gst_info, &vinfo, sizeof vinfo); player->has_gst_info = true; - LOG_DEBUG("on_probe_pad, fps: %f, res: % 4d x % 4d\n", (double) vinfo.fps_n / vinfo.fps_d, vinfo.width, vinfo.height); + LOG_DEBUG( + "on_probe_pad, fps: %f, res: % 4d x % 4d\n", + (double) GST_VIDEO_INFO_FPS_N(&player->gst_info) / GST_VIDEO_INFO_FPS_D(&player->gst_info), + GST_VIDEO_INFO_WIDTH(&player->gst_info), + GST_VIDEO_INFO_HEIGHT(&player->gst_info) + ); - player->info.info.width = vinfo.width; - player->info.info.height = vinfo.height; - player->info.info.fps = (double) vinfo.fps_n / vinfo.fps_d; + player->info.info.width = GST_VIDEO_INFO_WIDTH(&player->gst_info); + player->info.info.height = GST_VIDEO_INFO_HEIGHT(&player->gst_info); + player->info.info.fps = (double) GST_VIDEO_INFO_FPS_N(&player->gst_info) / GST_VIDEO_INFO_FPS_D(&player->gst_info); player->info.has_resolution = true; player->info.has_fps = true; maybe_send_info(player); @@ -752,12 +712,8 @@ static GstFlowReturn on_appsink_new_preroll(GstAppSink *appsink, void *userdata) frame = frame_new( player->frame_interface, - &(struct frame_info) { - .drm_format = player->drm_format, - .egl_color_space = player->egl_color_space, - .gst_info = &player->gst_info - }, - sample + sample, + player->has_gst_info ? &player->gst_info : NULL ); if (frame != NULL) { @@ -789,14 +745,12 @@ static GstFlowReturn on_appsink_new_sample(GstAppSink *appsink, void *userdata) frame = frame_new( player->frame_interface, - &(struct frame_info) { - .drm_format = player->drm_format, - .egl_color_space = player->egl_color_space, - .gst_info = &player->gst_info - }, - sample + sample, + player->has_gst_info ? &player->gst_info : NULL ); + gst_sample_unref(sample); + if (frame != NULL) { texture_push_frame(player->texture, &(struct texture_frame) { .gl = *frame_get_gl_frame(frame), @@ -1104,7 +1058,6 @@ static struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char * player->sink = NULL; player->bus = NULL; player->busfd_events = NULL; - player->drm_format = 0; player->is_live = false; return player; From 90f0b14e51e49b604152574c72d77516bdcc441b Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 23 Feb 2023 14:43:40 +0100 Subject: [PATCH 29/55] further clean-up texture uploading code --- include/plugins/gstreamer_video_player.h | 19 +- src/plugins/gstreamer_video_player/frame.c | 534 +++++++++++++++++---- 2 files changed, 451 insertions(+), 102 deletions(-) diff --git a/include/plugins/gstreamer_video_player.h b/include/plugins/gstreamer_video_player.h index ff487d79..21948a44 100644 --- a/include/plugins/gstreamer_video_player.h +++ b/include/plugins/gstreamer_video_player.h @@ -208,26 +208,11 @@ struct notifier *gstplayer_get_error_notifier(struct gstplayer *player); struct video_frame; -struct frame_interface { - struct gbm_device *gbm_device; - EGLDisplay display; - - pthread_mutex_t context_lock; - EGLContext context; - PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; - PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; - PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; - - bool supports_extended_imports; - PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT; - PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT; - - refcount_t n_refs; -}; +struct frame_interface; struct frame_interface *frame_interface_new(); -DEFINE_INLINE_LOCK_OPS(frame_interface, context_lock) +DECLARE_LOCK_OPS(frame_interface) DECLARE_REF_OPS(frame_interface) diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index 95f30699..c6346fca 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -36,11 +36,144 @@ struct video_frame { struct gl_texture_frame gl_frame; }; +struct egl_modified_format { + uint32_t format; + uint64_t modifier; + bool external_only; +}; + +struct frame_interface { + struct gbm_device *gbm_device; + EGLDisplay display; + + pthread_mutex_t context_lock; + EGLContext context; + + PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; + PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; + + bool supports_external_target; + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; + + bool supports_extended_imports; + PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT; + + int n_formats; + struct egl_modified_format *formats; + + refcount_t n_refs; +}; + +static bool query_formats( + EGLDisplay display, + PFNEGLQUERYDMABUFFORMATSEXTPROC egl_query_dmabuf_formats, + PFNEGLQUERYDMABUFMODIFIERSEXTPROC egl_query_dmabuf_modifiers, + int *n_formats_out, + struct egl_modified_format **formats_out +) { + struct egl_modified_format *modified_formats; + EGLBoolean *external_only; + EGLuint64KHR *modifiers; + EGLint *formats; + EGLint egl_ok, n_modifiers; + int n_formats, n_modified_formats, max_n_modifiers; + + egl_ok = egl_query_dmabuf_formats(display, 0, NULL, &n_formats); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not query number of dmabuf formats supported by EGL.\n"); + goto fail; + } + + formats = malloc(n_formats * sizeof *formats); + if (formats == NULL) { + goto fail; + } + + egl_ok = egl_query_dmabuf_formats(display, n_formats, formats, &n_formats); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not query dmabuf formats supported by EGL.\n"); + goto fail_free_formats; + } + + // first, count how many modifiers we have for each format. + n_modified_formats = 0; + max_n_modifiers = 0; + for (int i = 0; i < n_formats; i++) { + egl_ok = egl_query_dmabuf_modifiers(display, formats[i], 0, NULL, NULL, &n_modifiers); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not query dmabuf formats supported by EGL.\n"); + goto fail_free_formats; + } + + n_modified_formats += n_modifiers; + + if (n_modifiers > max_n_modifiers) { + max_n_modifiers = n_modifiers; + } + } + + modified_formats = malloc(n_modified_formats * sizeof *modified_formats); + if (modified_formats == NULL) { + goto fail_free_formats; + } + + modifiers = malloc(max_n_modifiers * sizeof *modifiers); + if (modifiers == NULL) { + goto fail_free_modified_formats; + } + + external_only = malloc(max_n_modifiers * sizeof *external_only); + if (external_only == NULL) { + goto fail_free_modifiers; + } + + for (int i = 0, j = 0; i < n_formats; i++) { + egl_ok = egl_query_dmabuf_modifiers(display, formats[i], max_n_modifiers, modifiers, external_only, &n_modifiers); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not query dmabuf formats supported by EGL.\n"); + goto fail_free_formats; + } + + for (int k = 0; k < n_modifiers; k++, j++) { + modified_formats[j].format = formats[i]; + modified_formats[j].modifier = modifiers[k]; + modified_formats[j].external_only = external_only[k]; + } + } + + free(formats); + free(modifiers); + free(external_only); + + *n_formats_out = n_modified_formats; + *formats_out = modified_formats; + return true; + + + fail_free_modifiers: + free(modifiers); + + fail_free_modified_formats: + free(modified_formats); + + fail_free_formats: + free(formats); + + fail: + *n_formats_out = 0; + *formats_out = NULL; + return false; +} + struct frame_interface *frame_interface_new(struct flutterpi *flutterpi) { struct frame_interface *interface; EGLBoolean egl_ok; EGLContext context; EGLDisplay display; + struct egl_modified_format *formats; + bool supports_extended_imports, supports_external_target; + int n_formats; interface = malloc(sizeof *interface); if (interface == NULL) { @@ -57,6 +190,27 @@ struct frame_interface *frame_interface_new(struct flutterpi *flutterpi) { goto fail_free; } + const char *egl_client_exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + const char *egl_dpy_exts = eglQueryString(display, EGL_EXTENSIONS); + const char *gl_exts = (const char*) glGetString(GL_EXTENSIONS); + + if (!strstr(egl_client_exts, "EGL_EXT_image_dma_buf_import") && !strstr(egl_dpy_exts, "EGL_EXT_image_dma_buf_import")) { + LOG_ERROR("EGL does not support EGL_EXT_image_dma_buf_import extension. Video frames cannot be uploaded.\n"); + goto fail_free; + } + + if (strstr(egl_client_exts, "EGL_EXT_image_dma_buf_import_modifiers") || strstr(egl_dpy_exts, "EGL_EXT_image_dma_buf_import_modifiers")) { + supports_extended_imports = true; + } else { + supports_extended_imports = false; + } + + if (strstr(gl_exts, "GL_OES_EGL_image_external")) { + supports_external_target = true; + } else { + supports_external_target = false; + } + PFNEGLCREATEIMAGEKHRPROC create_image = (PFNEGLCREATEIMAGEKHRPROC) eglGetProcAddress("eglCreateImageKHR"); if (create_image == NULL) { LOG_ERROR("Could not resolve eglCreateImageKHR egl procedure.\n"); @@ -78,7 +232,29 @@ struct frame_interface *frame_interface_new(struct flutterpi *flutterpi) { // These two are optional. // Might be useful in the future. PFNEGLQUERYDMABUFFORMATSEXTPROC egl_query_dmabuf_formats = (PFNEGLQUERYDMABUFFORMATSEXTPROC) eglGetProcAddress("eglQueryDmaBufFormatsEXT"); + if (egl_query_dmabuf_formats == NULL && supports_extended_imports) { + LOG_ERROR("Could not resolve eglQueryDmaBufFormatsEXT egl procedure, even though it is listed as supported.\n"); + supports_extended_imports = false; + } + PFNEGLQUERYDMABUFMODIFIERSEXTPROC egl_query_dmabuf_modifiers = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); + if (egl_query_dmabuf_modifiers == NULL && supports_extended_imports) { + LOG_ERROR("Could not resolve eglQueryDmaBufModifiersEXT egl procedure, even though it is listed as supported.\n"); + supports_extended_imports = false; + } + + if (supports_extended_imports) { + query_formats( + display, + egl_query_dmabuf_formats, + egl_query_dmabuf_modifiers, + &n_formats, + &formats + ); + } else { + n_formats = 0; + formats = NULL; + } interface->gbm_device = flutterpi_get_gbm_device(flutterpi); interface->display = display; @@ -86,10 +262,13 @@ struct frame_interface *frame_interface_new(struct flutterpi *flutterpi) { interface->context = context; interface->eglCreateImageKHR = create_image; interface->eglDestroyImageKHR = destroy_image; + interface->supports_external_target = supports_external_target; interface->glEGLImageTargetTexture2DOES = gl_egl_image_target_texture2d; - interface->supports_extended_imports = false; + interface->supports_extended_imports = supports_extended_imports; interface->eglQueryDmaBufFormatsEXT = egl_query_dmabuf_formats; interface->eglQueryDmaBufModifiersEXT = egl_query_dmabuf_modifiers; + interface->n_formats = n_formats; + interface->formats = formats; interface->n_refs = REFCOUNT_INIT_1; return interface; @@ -110,9 +289,36 @@ void frame_interface_destroy(struct frame_interface *interface) { pthread_mutex_destroy(&interface->context_lock); egl_ok = eglDestroyContext(interface->display, interface->context); DEBUG_ASSERT_EGL_TRUE(egl_ok); (void) egl_ok; + if (interface->formats != NULL) { + free(interface->formats); + } free(interface); } +ATTR_PURE int frame_interface_get_n_formats(struct frame_interface *interface) { + return interface->n_formats; +} + +ATTR_PURE const struct egl_modified_format *frame_interface_get_format(struct frame_interface *interface, int index) { + DEBUG_ASSERT(index < interface->n_formats); + return interface->formats + index; +} + +#define for_each_format_in_frame_interface(index, format, interface) \ + for ( \ + const struct egl_modified_format *format = frame_interface_get_format((interface), 0), *guard = NULL; \ + guard == NULL; \ + guard = (void*) 1 \ + ) \ + for ( \ + size_t index = 0; \ + index < frame_interface_get_n_formats(interface); \ + index++, \ + format = (index) < frame_interface_get_n_formats(interface) ? frame_interface_get_format((interface), (index)) : NULL \ + ) + +DEFINE_LOCK_OPS(frame_interface, context_lock) + DEFINE_REF_OPS(frame_interface, n_refs) /** @@ -121,7 +327,7 @@ DEFINE_REF_OPS(frame_interface, n_refs) * Calls gst_buffer_map on the buffer, so buffer could have changed after the call. * */ -MAYBE_UNUSED int dup_gst_buffer_as_dmabuf(struct gbm_device *gbm_device, GstBuffer *buffer) { +MAYBE_UNUSED int dup_gst_buffer_range_as_dmabuf(struct gbm_device *gbm_device, GstBuffer *buffer, unsigned int memory_index, int n_memories) { struct gbm_bo *bo; GstMapInfo map_info; uint32_t stride; @@ -129,7 +335,7 @@ MAYBE_UNUSED int dup_gst_buffer_as_dmabuf(struct gbm_device *gbm_device, GstBuff void *map, *map_data; int fd; - gst_ok = gst_buffer_map(buffer, &map_info, GST_MAP_READ); + gst_ok = gst_buffer_map_range(buffer, memory_index, n_memories, &map_info, GST_MAP_READ); if (gst_ok == FALSE) { LOG_ERROR("Couldn't map gstreamer video frame buffer to copy it into a dma buffer.\n"); return -1; @@ -235,17 +441,70 @@ struct plane_info { uint64_t modifier; }; -#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 14, 0) -static size_t calculate_plane_size( +#if THIS_GSTREAMER_VER < GSTREAMER_VER(1, 14, 0) +# error "Unsupported gstreamer version." +#endif + +#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 18, 0) +static bool get_plane_sizes_from_meta(const GstVideoMeta *meta, size_t plane_sizes_out[4]) { + GstVideoMeta *meta_non_const; + gboolean gst_ok; + +#ifdef DEBUG + GstVideoMeta _meta_non_const; + meta_non_const = &_meta_non_const; + memcpy(meta_non_const, meta, sizeof *meta); +#else + meta_non_const = (GstVideoMeta*) meta; +#endif + + gst_ok = gst_video_meta_get_plane_size(meta_non_const, plane_sizes_out); + if (gst_ok != TRUE) { + LOG_ERROR("Could not query video frame plane size. gst_video_meta_get_plane_size\n"); + return false; + } + + return true; +} + +static bool get_plane_sizes_from_video_info(const GstVideoInfo *info, size_t plane_sizes_out[4]) { + GstVideoAlignment alignment; + GstVideoInfo *info_non_const; + gboolean gst_ok; + + gst_video_alignment_reset(&alignment); + +#ifdef DEBUG + info_non_const = gst_video_info_copy(info); +#else + info_non_const = (GstVideoInfo*) info; +#endif + + gst_ok = gst_video_info_align_full(info_non_const, &alignment, plane_sizes_out); + if (gst_ok != TRUE) { + LOG_ERROR("Could not query video frame plane size. gst_video_info_align_full\n"); + return false; + } + +#ifdef DEBUG + DEBUG_ASSERT(gst_video_info_is_equal(info, info_non_const)); + gst_video_info_free(info_non_const); +#endif + + return true; +} + +static bool calculate_plane_size( const GstVideoInfo *info, - int plane_index + int plane_index, + size_t *plane_size_out ) { // Taken from: https://github.com/GStreamer/gstreamer/blob/621604aa3e4caa8db27637f63fa55fac2f7721e5/subprojects/gst-plugins-base/gst-libs/gst/video/video-info.c#L1278-L1301 #if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 21, 3) - if (GST_VIDEO_FORMAT_INFO_IS_TILED (info->finfo)) { - guint x_tiles = GST_VIDEO_TILE_X_TILES (info->stride[i]); - guint y_tiles = GST_VIDEO_TILE_Y_TILES (info->stride[i]); + if (GST_VIDEO_FORMAT_INFO_IS_TILED(info->finfo)) { + guint x_tiles = GST_VIDEO_TILE_X_TILES(info->stride[i]); + guint y_tiles = GST_VIDEO_TILE_Y_TILES(info->stride[i]); return x_tiles * y_tiles * GST_VIDEO_FORMAT_INFO_TILE_SIZE(info->finfo, i); } #endif @@ -261,9 +520,26 @@ static size_t calculate_plane_size( GST_VIDEO_INFO_FIELD_HEIGHT(info) ); - return plane_height * GST_VIDEO_INFO_PLANE_STRIDE(info, plane_index); + *plane_size_out = plane_height * GST_VIDEO_INFO_PLANE_STRIDE(info, plane_index); + return true; } +#else +static bool get_plane_sizes_from_meta(MAYBE_UNUSED const GstVideoMeta *meta, MAYBE_UNUSED size_t plane_sizes_out[4]) { + return false; +} +static bool get_plane_sizes_from_video_info(MAYBE_UNUSED const GstVideoInfo *info, MAYBE_UNUSED size_t plane_sizes_out[4]) { + return false; +} +static bool calculate_plane_size( + MAYBE_UNUSED const GstVideoInfo *info, + MAYBE_UNUSED int plane_index, + MAYBE_UNUSED size_t *plane_size_out +) { + return false; +} +#endif + static int get_plane_infos( GstBuffer *buffer, const GstVideoInfo *info, @@ -274,21 +550,45 @@ static int get_plane_infos( GstMemory *memory; gboolean gst_ok; size_t plane_sizes[4] = {0}; + bool has_plane_sizes; int n_planes; n_planes = GST_VIDEO_INFO_N_PLANES(info); + // There's so many ways to get the plane sizes. + // 1. Preferably we should use the video meta. + // 2. If that doesn't work, we'll use gst_video_info_align_full() with the video info. + // 3. If that doesn't work, we'll calculate them ourselves. + // 4. If that doesn't work, we can't determine the plane sizes. + // In that case, we'll error if we have more than one plane. + has_plane_sizes = false; meta = gst_buffer_get_video_meta(buffer); if (meta != NULL) { - gst_ok = gst_video_meta_get_plane_size(meta, plane_sizes); - if (gst_ok != TRUE) { - LOG_ERROR("Could not query video frame plane size.\n"); - return EIO; - } - } else { + has_plane_sizes = get_plane_sizes_from_meta(meta, plane_sizes); + } + + if (!has_plane_sizes) { + has_plane_sizes = get_plane_sizes_from_video_info(info, plane_sizes); + } + + if (!has_plane_sizes) { + has_plane_sizes = true; for (int i = 0; i < n_planes; i++) { - plane_sizes[i] = calculate_plane_size(info, i); + has_plane_sizes = has_plane_sizes && calculate_plane_size(info, i, plane_sizes + i); + } + } + + if (!has_plane_sizes) { + // We couldn't determine the plane sizes. + // We can still continue if we have only one plane. + if (n_planes != 1) { + LOG_ERROR("Couldn't determine video frame plane sizes. Without plane sizes, only planar framebuffer formats are supported, but the supplied format was not planar.\n"); + return EINVAL; } + + // We'll just assume the first plane spans the entire buffer. + plane_sizes[0] = gst_buffer_get_size(buffer); + has_plane_sizes = true; } for (int i = 0; i < n_planes; i++) { @@ -316,55 +616,71 @@ static int get_plane_infos( ); if (gst_ok != TRUE) { LOG_ERROR("Could not find video frame memory for plane.\n"); - return EIO; + ok = EIO; + goto fail_close_fds; } if (n_memories != 1) { - LOG_ERROR("Gstreamer Image planes can't span multiple dmabufs.\n"); - return EINVAL; - } - - memory = gst_buffer_peek_memory(buffer, memory_index); - if (gst_is_dmabuf_memory(memory)) { - ok = gst_dmabuf_memory_get_fd(memory); - if (ok < 0) { - LOG_ERROR("Could not get gstreamer memory as dmabuf.\n"); - return EIO; - } - - ok = dup(ok); + ok = dup_gst_buffer_range_as_dmabuf(gbm_device, buffer, memory_index, n_memories); if (ok < 0) { - ok = errno; - LOG_ERROR("Could not dup fd. dup: %s\n", strerror(ok)); - return ok; + LOG_ERROR("Could not duplicate gstreamer memory as dmabuf.\n"); + ok = EIO; + goto fail_close_fds; } plane_infos[i].fd = ok; } else { - /// TODO: When duping, duplicate all non-dmabuf memories into one - /// gbm buffer instead. - ok = dup_gst_memory_as_dmabuf(gbm_device, memory); - if (ok < 0) { - LOG_ERROR("Could not duplicate gstreamer memory as dmabuf.\n"); - return EIO; + memory = gst_buffer_peek_memory(buffer, memory_index); + if (gst_is_dmabuf_memory(memory)) { + ok = gst_dmabuf_memory_get_fd(memory); + if (ok < 0) { + LOG_ERROR("Could not get gstreamer memory as dmabuf.\n"); + ok = EIO; + goto fail_close_fds; + } + + ok = dup(ok); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not dup fd. dup: %s\n", strerror(ok)); + goto fail_close_fds; + } + + plane_infos[i].fd = ok; + } else { + /// TODO: When duping, duplicate all non-dmabuf memories into one + /// gbm buffer instead. + ok = dup_gst_memory_as_dmabuf(gbm_device, memory); + if (ok < 0) { + LOG_ERROR("Could not duplicate gstreamer memory as dmabuf.\n"); + ok = EIO; + goto fail_close_fds; + } + + plane_infos[i].fd = ok; } - - plane_infos[i].fd = ok; } plane_infos[i].offset = offset_in_memory; plane_infos[i].pitch = stride; /// TODO: Detect modifiers here + /// Modifiers will be supported in future gstreamer, see: + /// https://gstreamer.freedesktop.org/documentation/additional/design/dmabuf.html?gi-language=c plane_infos[i].has_modifier = false; plane_infos[i].modifier = DRM_FORMAT_MOD_LINEAR; + continue; + + + fail_close_fds: + for (int j = i - 1; j > 0; j--) { + close(plane_infos[i].fd); + } + return ok; } return 0; } -#else -# error "Unsupported gstreamer version." -#endif static uint32_t drm_format_from_gst_info(const GstVideoInfo *info) { switch (GST_VIDEO_INFO_FORMAT(info)) { @@ -400,6 +716,53 @@ static uint32_t drm_format_from_gst_info(const GstVideoInfo *info) { } } +static EGLint egl_color_space_from_gst_info(const GstVideoInfo *info) { + if (gst_video_colorimetry_matches(&GST_VIDEO_INFO_COLORIMETRY(info), GST_VIDEO_COLORIMETRY_BT601)) { + return EGL_ITU_REC601_EXT; + } else if (gst_video_colorimetry_matches(&GST_VIDEO_INFO_COLORIMETRY(info), GST_VIDEO_COLORIMETRY_BT709)) { + return EGL_ITU_REC709_EXT; + } else if (gst_video_colorimetry_matches(&GST_VIDEO_INFO_COLORIMETRY(info), GST_VIDEO_COLORIMETRY_BT2020)) { + return EGL_ITU_REC2020_EXT; + } else { + LOG_DEBUG("Unsupported video colorimetry: %s\n", gst_video_colorimetry_to_string(&GST_VIDEO_INFO_COLORIMETRY(info))); + return EGL_NONE; + } +} + +static EGLint egl_sample_range_hint_from_gst_info(const GstVideoInfo *info) { + if (GST_VIDEO_INFO_COLORIMETRY(info).range == GST_VIDEO_COLOR_RANGE_0_255) { + return EGL_YUV_FULL_RANGE_EXT; + } else if (GST_VIDEO_INFO_COLORIMETRY(info).range == GST_VIDEO_COLOR_RANGE_16_235) { + return EGL_YUV_NARROW_RANGE_EXT; + } else { + return EGL_NONE; + } +} + +static EGLint egl_horizontal_chroma_siting_from_gst_info(const GstVideoInfo *info) { + if ((GST_VIDEO_INFO_CHROMA_SITE(info) & ~(GST_VIDEO_CHROMA_SITE_H_COSITED | GST_VIDEO_CHROMA_SITE_V_COSITED)) == 0) { + if (GST_VIDEO_INFO_CHROMA_SITE(info) & GST_VIDEO_CHROMA_SITE_H_COSITED) { + return EGL_YUV_CHROMA_SITING_0_EXT; + } else { + return EGL_YUV_CHROMA_SITING_0_5_EXT; + } + } + + return EGL_NONE; +} + +static EGLint egl_vertical_chroma_siting_from_gst_info(const GstVideoInfo *info) { + if ((GST_VIDEO_INFO_CHROMA_SITE(info) & ~(GST_VIDEO_CHROMA_SITE_H_COSITED | GST_VIDEO_CHROMA_SITE_V_COSITED)) == 0) { + if (GST_VIDEO_INFO_CHROMA_SITE(info) & GST_VIDEO_CHROMA_SITE_V_COSITED) { + return EGL_YUV_CHROMA_SITING_0_EXT; + } else { + return EGL_YUV_CHROMA_SITING_0_5_EXT; + } + } + + return EGL_NONE; +} + struct video_frame *frame_new( struct frame_interface *interface, GstSample *sample, @@ -429,9 +792,11 @@ struct video_frame *frame_new( buffer = gst_sample_get_buffer(sample); if (buffer == NULL) { + LOG_ERROR("Could not get buffer from video sample.\n"); return NULL; } + // If we don't have an explicit info given, we determine it from the sample caps. if (info == NULL) { caps = gst_sample_get_caps(sample); if (caps == NULL) { @@ -449,6 +814,7 @@ struct video_frame *frame_new( caps = NULL; } + // Determine some basic frame info. width = GST_VIDEO_INFO_WIDTH(info); height = GST_VIDEO_INFO_HEIGHT(info); n_planes = GST_VIDEO_INFO_N_PLANES(info); @@ -460,44 +826,28 @@ struct video_frame *frame_new( return NULL; } - // query the color space for this sample - if (gst_video_colorimetry_matches(&GST_VIDEO_INFO_COLORIMETRY(info), GST_VIDEO_COLORIMETRY_BT601)) { - egl_color_space = EGL_ITU_REC601_EXT; - } else if (gst_video_colorimetry_matches(&GST_VIDEO_INFO_COLORIMETRY(info), GST_VIDEO_COLORIMETRY_BT709)) { - egl_color_space = EGL_ITU_REC709_EXT; - } else if (gst_video_colorimetry_matches(&GST_VIDEO_INFO_COLORIMETRY(info), GST_VIDEO_COLORIMETRY_BT2020)) { - egl_color_space = EGL_ITU_REC2020_EXT; - } else { - LOG_DEBUG("Unsupported video colorimetry: %s\n", gst_video_colorimetry_to_string(&GST_VIDEO_INFO_COLORIMETRY(info))); - egl_color_space = EGL_NONE; + bool external_only; + for_each_format_in_frame_interface(i, format, interface) { + if (format->format == drm_format && format->modifier == DRM_FORMAT_MOD_LINEAR) { + external_only = format->external_only; + goto format_supported; + } } + LOG_ERROR("Video format is not supported by EGL.\n"); + return NULL; + + format_supported: + + // query the color space for this sample + egl_color_space = egl_color_space_from_gst_info(info); + // check the sample range - if (GST_VIDEO_INFO_COLORIMETRY(info).range == GST_VIDEO_COLOR_RANGE_0_255) { - egl_sample_range_hint = EGL_YUV_FULL_RANGE_EXT; - } else if (GST_VIDEO_INFO_COLORIMETRY(info).range == GST_VIDEO_COLOR_RANGE_16_235) { - egl_sample_range_hint = EGL_YUV_NARROW_RANGE_EXT; - } else { - egl_sample_range_hint = EGL_NONE; - } + egl_sample_range_hint = egl_sample_range_hint_from_gst_info(info); // check the chroma siting - if ((GST_VIDEO_INFO_CHROMA_SITE(info) & ~(GST_VIDEO_CHROMA_SITE_H_COSITED | GST_VIDEO_CHROMA_SITE_V_COSITED)) == 0) { - if (GST_VIDEO_INFO_CHROMA_SITE(info) & GST_VIDEO_CHROMA_SITE_H_COSITED) { - egl_horizontal_chroma_siting = EGL_YUV_CHROMA_SITING_0_EXT; - } else { - egl_horizontal_chroma_siting = EGL_YUV_CHROMA_SITING_0_5_EXT; - } - - if (GST_VIDEO_INFO_CHROMA_SITE(info) & GST_VIDEO_CHROMA_SITE_V_COSITED) { - egl_vertical_chroma_siting = EGL_YUV_CHROMA_SITING_0_EXT; - } else { - egl_vertical_chroma_siting = EGL_YUV_CHROMA_SITING_0_5_EXT; - } - } else { - egl_horizontal_chroma_siting = EGL_NONE; - egl_vertical_chroma_siting = EGL_NONE; - } + egl_horizontal_chroma_siting = egl_horizontal_chroma_siting_from_gst_info(info); + egl_vertical_chroma_siting = egl_vertical_chroma_siting_from_gst_info(info); frame = malloc(sizeof *frame); if (frame == NULL) { @@ -509,6 +859,7 @@ struct video_frame *frame_new( goto fail_free_frame; } + // Start putting together the EGL attributes. attr_index = 0; // first, put some of our basic attributes like @@ -624,9 +975,23 @@ struct video_frame *frame_new( goto fail_clear_context; } - glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture); - interface->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, egl_image); - glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0); + GLenum target; + if (external_only) { + target = GL_TEXTURE_2D; + } else { + target = GL_TEXTURE_EXTERNAL_OES; + } + + glBindTexture(target, texture); + + interface->glEGLImageTargetTexture2DOES(target, egl_image); + gl_error = glGetError(); + if (gl_error != GL_NO_ERROR) { + LOG_ERROR("Couldn't attach EGL Image to OpenGL texture. glEGLImageTargetTexture2DOES: %" PRIu32 "\n", gl_error); + goto fail_unbind_texture; + } + + glBindTexture(target, 0); egl_ok = eglMakeCurrent(interface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (egl_ok == EGL_FALSE) { @@ -646,13 +1011,15 @@ struct video_frame *frame_new( frame->dmabuf_fds[2] = planes[2].fd; frame->dmabuf_fds[3] = planes[3].fd; frame->image = egl_image; - frame->gl_frame.target = GL_TEXTURE_EXTERNAL_OES; + frame->gl_frame.target = target; frame->gl_frame.name = texture; frame->gl_frame.format = GL_RGBA8_OES; frame->gl_frame.width = 0; frame->gl_frame.height = 0; return 0; + fail_unbind_texture: + glBindTexture(texture, 0); fail_delete_texture: glDeleteTextures(1, &texture); @@ -677,9 +1044,6 @@ void frame_destroy(struct video_frame *frame) { EGLBoolean egl_ok; int ok; - /// TODO: See TODO in frame_new - gst_sample_unref(frame->sample); - frame_interface_lock(frame->interface); egl_ok = eglMakeCurrent(frame->interface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, frame->interface->context); DEBUG_ASSERT_EGL_TRUE(egl_ok); (void) egl_ok; From 392c17ba6ca9b5a28ca6aeba993cf575bbd057e0 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 23 Feb 2023 14:48:10 +0100 Subject: [PATCH 30/55] fix for new gstreamer video formats --- src/plugins/gstreamer_video_player/frame.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index c6346fca..670997a0 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -688,8 +688,10 @@ static uint32_t drm_format_from_gst_info(const GstVideoInfo *info) { case GST_VIDEO_FORMAT_YVYU: return DRM_FORMAT_YVYU; case GST_VIDEO_FORMAT_UYVY: return DRM_FORMAT_UYVY; case GST_VIDEO_FORMAT_VYUY: return DRM_FORMAT_VYUY; - case GST_VIDEO_FORMAT_AYUV: + case GST_VIDEO_FORMAT_AYUV: return DRM_FORMAT_AYUV; +#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 16, 0) case GST_VIDEO_FORMAT_VUYA: return DRM_FORMAT_AYUV; +#endif case GST_VIDEO_FORMAT_NV12: return DRM_FORMAT_NV12; case GST_VIDEO_FORMAT_NV21: return DRM_FORMAT_NV21; case GST_VIDEO_FORMAT_NV16: return DRM_FORMAT_NV16; From 6e83a79917229b0ac72f752e2f82bf1b3d2f22ad Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 23 Feb 2023 14:55:24 +0100 Subject: [PATCH 31/55] implement drmdev_interface --- src/flutter-pi.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/flutter-pi.c b/src/flutter-pi.c index d782e0af..cc29b5f4 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -1342,6 +1342,19 @@ static int load_egl_gl_procs(void) { return 0; } +static int drmdev_open(const char *path, int flags, MAYBE_UNUSED void **fd_metadata_out, MAYBE_UNUSED void *userdata) { + return open(path, flags | O_CLOEXEC); +} + +static void drmdev_close(int fd, MAYBE_UNUSED void *fd_metadata, MAYBE_UNUSED void *userdata) { + close(fd); +} + +static const struct drmdev_interface drmdev_interface = { + .open = drmdev_open, + .close = drmdev_close +}; + static int init_display(void) { /********************** * DRM INITIALIZATION * @@ -1376,7 +1389,7 @@ static int init_display(void) { continue; } - flutterpi.drm.drmdev = drmdev_new_from_path(device->nodes[DRM_NODE_PRIMARY], NULL, NULL); + flutterpi.drm.drmdev = drmdev_new_from_path(device->nodes[DRM_NODE_PRIMARY], &drmdev_interface, NULL); if (flutterpi.drm.drmdev == NULL) { LOG_ERROR("Could not create drmdev from device at \"%s\". Continuing.\n", device->nodes[DRM_NODE_PRIMARY]); continue; From 89f7851a5feb560feff17f48e8beac67a0bc9aaf Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 23 Feb 2023 15:00:34 +0100 Subject: [PATCH 32/55] make EGL context current before querying gl exts --- src/plugins/gstreamer_video_player/frame.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index 670997a0..24eff517 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -192,7 +192,6 @@ struct frame_interface *frame_interface_new(struct flutterpi *flutterpi) { const char *egl_client_exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); const char *egl_dpy_exts = eglQueryString(display, EGL_EXTENSIONS); - const char *gl_exts = (const char*) glGetString(GL_EXTENSIONS); if (!strstr(egl_client_exts, "EGL_EXT_image_dma_buf_import") && !strstr(egl_dpy_exts, "EGL_EXT_image_dma_buf_import")) { LOG_ERROR("EGL does not support EGL_EXT_image_dma_buf_import extension. Video frames cannot be uploaded.\n"); @@ -205,12 +204,25 @@ struct frame_interface *frame_interface_new(struct flutterpi *flutterpi) { supports_extended_imports = false; } + + egl_ok = eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not make EGL context current.\n"); + goto fail_free; + } + + const char *gl_exts = (const char*) glGetString(GL_EXTENSIONS); if (strstr(gl_exts, "GL_OES_EGL_image_external")) { supports_external_target = true; } else { supports_external_target = false; } + egl_ok = eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (egl_ok != EGL_TRUE) { + goto fail_free; + } + PFNEGLCREATEIMAGEKHRPROC create_image = (PFNEGLCREATEIMAGEKHRPROC) eglGetProcAddress("eglCreateImageKHR"); if (create_image == NULL) { LOG_ERROR("Could not resolve eglCreateImageKHR egl procedure.\n"); From 23b01f875540e9b85644219e5d0c7dc7e1f9ce0f Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 23 Feb 2023 15:08:38 +0100 Subject: [PATCH 33/55] fix incorrect pixel format usage --- include/flutter-pi.h | 5 +++-- src/flutter-pi.c | 27 ++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/include/flutter-pi.h b/include/flutter-pi.h index 50ab27a2..efc1077b 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -251,8 +251,9 @@ struct flutterpi { struct { struct gbm_device *device; struct gbm_surface *surface; - uint32_t format; - uint64_t modifier; + bool has_format; + enum pixfmt format; + uint64_t modifier; } gbm; struct { diff --git a/src/flutter-pi.c b/src/flutter-pi.c index cc29b5f4..c9e65949 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -1634,15 +1634,31 @@ static int init_display(void) { * GBM INITIALIZATION * **********************/ flutterpi.gbm.device = gbm_create_device(drmdev_get_fd(flutterpi.drm.drmdev)); - flutterpi.gbm.format = DRM_FORMAT_ARGB8888; + if (flutterpi.gbm.has_format) { + flutterpi.gbm.format = kARGB8888_FpiPixelFormat; + flutterpi.gbm.has_format = true; + } flutterpi.gbm.surface = NULL; flutterpi.gbm.modifier = DRM_FORMAT_MOD_LINEAR; - flutterpi.gbm.surface = gbm_surface_create_with_modifiers(flutterpi.gbm.device, flutterpi.display.width, flutterpi.display.height, flutterpi.gbm.format, &flutterpi.gbm.modifier, 1); + flutterpi.gbm.surface = gbm_surface_create_with_modifiers( + flutterpi.gbm.device, + flutterpi.display.width, + flutterpi.display.height, + get_pixfmt_info(flutterpi.gbm.format)->gbm_format, + &flutterpi.gbm.modifier, + 1 + ); if (flutterpi.gbm.surface == NULL) { perror("[flutter-pi] Could not create GBM Surface. gbm_surface_create_with_modifiers. Will attempt with gbm_surface_create"); - flutterpi.gbm.surface = gbm_surface_create(flutterpi.gbm.device, flutterpi.display.width, flutterpi.display.height, flutterpi.gbm.format, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR); + flutterpi.gbm.surface = gbm_surface_create( + flutterpi.gbm.device, + flutterpi.display.width, + flutterpi.display.height, + get_pixfmt_info(flutterpi.gbm.format)->gbm_format, + GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR + ); if (flutterpi.gbm.surface == NULL) { perror("[flutter-pi] Could not create GBM Surface even with gbm_surface_create"); @@ -1747,7 +1763,7 @@ static int init_display(void) { continue; } - if (native_visual_id == flutterpi.gbm.format) { + if (native_visual_id == get_pixfmt_info(flutterpi.gbm.format)->gbm_format) { flutterpi.egl.config = configs[i]; _found_matching_config = true; break; @@ -2432,7 +2448,8 @@ static bool parse_cmd_args(int argc, char **argv) { case 'p': for (int i = 0; i < n_pixfmt_infos; i++) { if (strcmp(optarg, pixfmt_infos[i].arg_name) == 0) { - flutterpi.gbm.format = pixfmt_infos[i].gbm_format; + flutterpi.gbm.has_format = true; + flutterpi.gbm.format = (enum pixfmt) i; goto valid_format; } } From 86860368be6949aed1721386d0a260ae6420560f Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 23 Feb 2023 15:15:45 +0100 Subject: [PATCH 34/55] print out video format if not supported --- src/plugins/gstreamer_video_player/frame.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index 24eff517..f9163f0f 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -848,7 +848,14 @@ struct video_frame *frame_new( } } - LOG_ERROR("Video format is not supported by EGL.\n"); + LOG_ERROR( + "Video format is not supported by EGL: %c%c%c%c (modifier: %"PRIu64").\n", + drm_format & 0xFF, + (drm_format >> 8) & 0xFF, + (drm_format >> 16) & 0xFF, + (drm_format >> 24) & 0xFF, + (uint64_t) DRM_FORMAT_MOD_LINEAR + ); return NULL; format_supported: From adea0caab78aead83b7e6c6ed4571be8d430d114 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 23 Feb 2023 15:20:38 +0100 Subject: [PATCH 35/55] log supported egl formats in debug mode --- src/plugins/gstreamer_video_player/frame.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index f9163f0f..c923def7 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -20,6 +20,9 @@ FILE_DESCR("gstreamer video_player") #define GSTREAMER_VER(major, minor, patch) ((((major) & 0xFF) << 16) | (((minor) & 0xFF) << 8) | ((patch) & 0xFF)) #define THIS_GSTREAMER_VER GSTREAMER_VER(LIBGSTREAMER_VERSION_MAJOR, LIBGSTREAMER_VERSION_MINOR, LIBGSTREAMER_VERSION_PATCH) +#define DRM_FOURCC_FORMAT "c%c%c%c" +#define DRM_FOURCC_ARGS(format) (format) & 0xFF, ((format) >> 8) & 0xFF, ((format) >> 16) & 0xFF, ((format) >> 24) & 0xFF + struct video_frame { GstSample *sample; @@ -128,6 +131,7 @@ static bool query_formats( goto fail_free_modifiers; } + LOG_DEBUG("supported formats for EGL import: "); for (int i = 0, j = 0; i < n_formats; i++) { egl_ok = egl_query_dmabuf_modifiers(display, formats[i], max_n_modifiers, modifiers, external_only, &n_modifiers); if (egl_ok != EGL_TRUE) { @@ -135,6 +139,8 @@ static bool query_formats( goto fail_free_formats; } + LOG_DEBUG_UNPREFIXED("%" DRM_FOURCC_FORMAT ", ", DRM_FOURCC_ARGS(formats[i])); + for (int k = 0; k < n_modifiers; k++, j++) { modified_formats[j].format = formats[i]; modified_formats[j].modifier = modifiers[k]; @@ -142,6 +148,8 @@ static bool query_formats( } } + LOG_DEBUG_UNPREFIXED("\n"); + free(formats); free(modifiers); free(external_only); @@ -849,11 +857,8 @@ struct video_frame *frame_new( } LOG_ERROR( - "Video format is not supported by EGL: %c%c%c%c (modifier: %"PRIu64").\n", - drm_format & 0xFF, - (drm_format >> 8) & 0xFF, - (drm_format >> 16) & 0xFF, - (drm_format >> 24) & 0xFF, + "Video format is not supported by EGL: %" DRM_FOURCC_FORMAT " (modifier: %"PRIu64").\n", + DRM_FOURCC_ARGS(drm_format), (uint64_t) DRM_FORMAT_MOD_LINEAR ); return NULL; From da4f39ec05d8842a0e2c8135950a9c52255e5aa2 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 23 Feb 2023 15:42:00 +0100 Subject: [PATCH 36/55] properly configure appsink caps --- include/plugins/gstreamer_video_player.h | 27 +++++++++- src/plugins/gstreamer_video_player/frame.c | 56 ++++++++++++++------- src/plugins/gstreamer_video_player/player.c | 21 ++++++++ 3 files changed, 83 insertions(+), 21 deletions(-) diff --git a/include/plugins/gstreamer_video_player.h b/include/plugins/gstreamer_video_player.h index 21948a44..90da01c4 100644 --- a/include/plugins/gstreamer_video_player.h +++ b/include/plugins/gstreamer_video_player.h @@ -204,14 +204,35 @@ struct notifier *gstplayer_get_buffering_state_notifier(struct gstplayer *player /// Gets notified when an error happens. (Not yet implemented) struct notifier *gstplayer_get_error_notifier(struct gstplayer *player); - - struct video_frame; +struct egl_modified_format { + uint32_t format; + uint64_t modifier; + bool external_only; +}; + struct frame_interface; struct frame_interface *frame_interface_new(); +ATTR_PURE int frame_interface_get_n_formats(struct frame_interface *interface); + +ATTR_PURE const struct egl_modified_format *frame_interface_get_format(struct frame_interface *interface, int index); + +#define for_each_format_in_frame_interface(index, format, interface) \ + for ( \ + const struct egl_modified_format *format = frame_interface_get_format((interface), 0), *guard = NULL; \ + guard == NULL; \ + guard = (void*) 1 \ + ) \ + for ( \ + size_t index = 0; \ + index < frame_interface_get_n_formats(interface); \ + index++, \ + format = (index) < frame_interface_get_n_formats(interface) ? frame_interface_get_format((interface), (index)) : NULL \ + ) + DECLARE_LOCK_OPS(frame_interface) DECLARE_REF_OPS(frame_interface) @@ -235,6 +256,8 @@ struct frame_info { struct _GstSample; +ATTR_CONST GstVideoFormat gst_video_format_from_drm_format(uint32_t drm_format); + struct video_frame *frame_new( struct frame_interface *interface, GstSample *sample, diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index c923def7..ba3d2a9f 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -39,12 +39,6 @@ struct video_frame { struct gl_texture_frame gl_frame; }; -struct egl_modified_format { - uint32_t format; - uint64_t modifier; - bool external_only; -}; - struct frame_interface { struct gbm_device *gbm_device; EGLDisplay display; @@ -324,19 +318,6 @@ ATTR_PURE const struct egl_modified_format *frame_interface_get_format(struct fr return interface->formats + index; } -#define for_each_format_in_frame_interface(index, format, interface) \ - for ( \ - const struct egl_modified_format *format = frame_interface_get_format((interface), 0), *guard = NULL; \ - guard == NULL; \ - guard = (void*) 1 \ - ) \ - for ( \ - size_t index = 0; \ - index < frame_interface_get_n_formats(interface); \ - index++, \ - format = (index) < frame_interface_get_n_formats(interface) ? frame_interface_get_format((interface), (index)) : NULL \ - ) - DEFINE_LOCK_OPS(frame_interface, context_lock) DEFINE_REF_OPS(frame_interface, n_refs) @@ -738,6 +719,43 @@ static uint32_t drm_format_from_gst_info(const GstVideoInfo *info) { } } +ATTR_CONST GstVideoFormat gst_video_format_from_drm_format(uint32_t drm_format) { + switch (drm_format) { + case DRM_FORMAT_YUYV: return GST_VIDEO_FORMAT_YUY2; + case DRM_FORMAT_YVYU: return GST_VIDEO_FORMAT_YVYU; + case DRM_FORMAT_UYVY: return GST_VIDEO_FORMAT_UYVY; + case DRM_FORMAT_VYUY: return GST_VIDEO_FORMAT_VYUY; + case DRM_FORMAT_AYUV: return GST_VIDEO_FORMAT_AYUV; +#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 16, 0) + // GST_VIDEO_FORMAT_AYUV and _VUYA both map to DRM_FORMAT_AYUV + // case DRM_FORMAT_AYUV: return GST_VIDEO_FORMAT_VUYA; +#endif + case DRM_FORMAT_NV12: return GST_VIDEO_FORMAT_NV12; + case DRM_FORMAT_NV21: return GST_VIDEO_FORMAT_NV21; + case DRM_FORMAT_NV16: return GST_VIDEO_FORMAT_NV16; + case DRM_FORMAT_NV61: return GST_VIDEO_FORMAT_NV61; + case DRM_FORMAT_NV24: return GST_VIDEO_FORMAT_NV24; + case DRM_FORMAT_YUV410: return GST_VIDEO_FORMAT_YUV9; + case DRM_FORMAT_YVU410: return GST_VIDEO_FORMAT_YVU9; + case DRM_FORMAT_YUV411: return GST_VIDEO_FORMAT_Y41B; + case DRM_FORMAT_YUV420: return GST_VIDEO_FORMAT_I420; + case DRM_FORMAT_YVU420: return GST_VIDEO_FORMAT_YV12; + case DRM_FORMAT_YUV422: return GST_VIDEO_FORMAT_Y42B; + case DRM_FORMAT_YUV444: return GST_VIDEO_FORMAT_Y444; + case DRM_FORMAT_RGB565: return GST_VIDEO_FORMAT_RGB16; + case DRM_FORMAT_BGR565: return GST_VIDEO_FORMAT_BGR16; + case DRM_FORMAT_ABGR8888: return GST_VIDEO_FORMAT_RGBA; + case DRM_FORMAT_XBGR8888: return GST_VIDEO_FORMAT_RGBx; + case DRM_FORMAT_ARGB8888: return GST_VIDEO_FORMAT_BGRA; + case DRM_FORMAT_XRGB8888: return GST_VIDEO_FORMAT_BGRx; + case DRM_FORMAT_BGRA8888: return GST_VIDEO_FORMAT_ARGB; + case DRM_FORMAT_BGRX8888: return GST_VIDEO_FORMAT_xRGB; + case DRM_FORMAT_RGBA8888: return GST_VIDEO_FORMAT_ABGR; + case DRM_FORMAT_RGBX8888: return GST_VIDEO_FORMAT_xBGR; + default: return GST_VIDEO_FORMAT_UNKNOWN; + } +} + static EGLint egl_color_space_from_gst_info(const GstVideoInfo *info) { if (gst_video_colorimetry_matches(&GST_VIDEO_INFO_COLORIMETRY(info), GST_VIDEO_COLORIMETRY_BT601)) { return EGL_ITU_REC601_EXT; diff --git a/src/plugins/gstreamer_video_player/player.c b/src/plugins/gstreamer_video_player/player.c index 5703b41b..ac908c37 100644 --- a/src/plugins/gstreamer_video_player/player.c +++ b/src/plugins/gstreamer_video_player/player.c @@ -863,6 +863,27 @@ static int init(struct gstplayer *player, bool force_sw_decoders) { gst_app_sink_set_emit_signals(GST_APP_SINK(sink), TRUE); gst_app_sink_set_drop(GST_APP_SINK(sink), FALSE); + // configure our caps + // we only accept video formats that we can actually upload to EGL + GstCaps *caps = gst_caps_new_empty(); + for_each_format_in_frame_interface(i, format, player->frame_interface) { + GstVideoFormat gst_format = gst_video_format_from_drm_format(format->format); + if (gst_format == GST_VIDEO_FORMAT_UNKNOWN) { + continue; + } + + gst_caps_append( + caps, + gst_caps_new_simple( + "video/x-raw", + "format", G_TYPE_STRING, gst_video_format_to_string(gst_format), + NULL + ) + ); + } + gst_app_sink_set_caps(GST_APP_SINK(sink), caps); + gst_caps_unref(caps); + gst_app_sink_set_callbacks( GST_APP_SINK(sink), &(GstAppSinkCallbacks) { From 09a3fb73d133d376872601b941a5ecc248aa82a0 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 23 Feb 2023 20:51:57 +0100 Subject: [PATCH 37/55] unset crtc scanout callback on failure --- src/modesetting.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modesetting.c b/src/modesetting.c index 20b4bf42..d28dbea2 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -2296,7 +2296,7 @@ static int kms_req_commit_common( ok = drmdev_on_modesetting_fd_ready_locked(builder->drmdev); if (ok != 0) { LOG_ERROR("Couldn't synchronously handle pageflip event.\n"); - goto fail_unlock; + goto fail_unset_scanout_callback; } } @@ -2304,6 +2304,12 @@ static int kms_req_commit_common( return 0; +fail_unset_scanout_callback: + builder->drmdev->per_crtc_state[builder->crtc->index].scanout_callback = NULL; + builder->drmdev->per_crtc_state[builder->crtc->index].destroy_callback = NULL; + builder->drmdev->per_crtc_state[builder->crtc->index].userdata = NULL; + goto fail_unlock; + fail_unref_builder: kms_req_builder_unref(builder); From 8b32a6ffa8c0231fbc83238e1b0afe18624c1050 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 23 Feb 2023 21:05:11 +0100 Subject: [PATCH 38/55] don't set O_NONBLOCK on drm fd --- src/modesetting.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modesetting.c b/src/modesetting.c index d28dbea2..7046b938 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -938,7 +938,7 @@ struct drmdev *drmdev_new_from_fd(int fd, const struct drmdev_interface *interfa goto fail_free_drmdev; } - ok = interface->open(device->nodes[DRM_NODE_PRIMARY], O_CLOEXEC | O_NONBLOCK, &master_fd_metadata, userdata); + ok = interface->open(device->nodes[DRM_NODE_PRIMARY], O_CLOEXEC, &master_fd_metadata, userdata); if (ok < 0) { ok = -ok; LOG_ERROR("Couldn't open DRM device.\n"); From 9d226beb9c73db331c24e523d53f19e4a6d7371d Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 23 Feb 2023 21:14:29 +0100 Subject: [PATCH 39/55] fix drm event handling --- src/flutter-pi.c | 17 +++-------------- src/modesetting.c | 8 ++++---- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/flutter-pi.c b/src/flutter-pi.c index c9e65949..268d5e51 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -1192,19 +1192,8 @@ void on_pageflip_event( cqueue_unlock(&flutterpi.frame_queue); } -static int on_drm_fd_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - int ok; - - (void) s; - (void) revents; - (void) userdata; - - ok = drmHandleEvent(fd, &flutterpi.drm.evctx); - if (ok < 0) { - perror("[flutter-pi] Could not handle DRM event. drmHandleEvent"); - return -errno; - } - +static int on_drm_fd_ready(MAYBE_UNUSED sd_event_source *s, MAYBE_UNUSED int fd, MAYBE_UNUSED uint32_t revents, MAYBE_UNUSED void *userdata) { + drmdev_on_event_fd_ready(flutterpi.drm.drmdev); return 0; } @@ -1605,7 +1594,7 @@ static int init_display(void) { ok = sd_event_add_io( flutterpi.event_loop, &flutterpi.drm.drm_pageflip_event_source, - drmdev_get_fd(flutterpi.drm.drmdev), + drmdev_get_event_fd(flutterpi.drm.drmdev), EPOLLIN | EPOLLHUP | EPOLLPRI, on_drm_fd_ready, NULL diff --git a/src/modesetting.c b/src/modesetting.c index 7046b938..2dc02e36 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -2161,14 +2161,14 @@ static int kms_req_commit_common( if (builder->drmdev->master_fd < 0) { LOG_ERROR("Commit requested, but drmdev doesn't have a DRM master fd right now.\n"); - drmdev_unlock(builder->drmdev); - return EBUSY; + ok = EBUSY; + goto fail_unlock; } if (!is_drm_master(builder->drmdev->master_fd)) { LOG_ERROR("Commit requested, but drmdev is paused right now.\n"); - drmdev_unlock(builder->drmdev); - return EBUSY; + ok = EBUSY; + goto fail_unlock; } // only change the mode if the new mode differs from the old one From 47565027bebb748483959ca545d6de102858be51 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sat, 25 Feb 2023 14:02:32 +0000 Subject: [PATCH 40/55] fixes - fix frame uploading not using GL_TEXTURE_EXTERNAL_OES target - fix gbm surface framebuffer release - clang fixes --- include/modesetting.h | 2 +- src/compositor.c | 3 ++ src/modesetting.c | 1 + src/plugins/gstreamer_video_player/frame.c | 10 +++---- src/plugins/gstreamer_video_player/player.c | 33 ++++----------------- src/plugins/gstreamer_video_player/plugin.c | 6 ++-- src/plugins/text_input.c | 2 +- 7 files changed, 19 insertions(+), 38 deletions(-) diff --git a/include/modesetting.h b/include/modesetting.h index 26969d49..be104ca1 100644 --- a/include/modesetting.h +++ b/include/modesetting.h @@ -182,7 +182,7 @@ typedef struct { }; } drm_plane_transform_t; -#define PLANE_TRANSFORM_NONE ((const drm_plane_transform_t){ .u32 = 0 }) +#define PLANE_TRANSFORM_NONE ((const drm_plane_transform_t){ .u64 = 0 }) #define PLANE_TRANSFORM_ROTATE_0 ((const drm_plane_transform_t){ .u32 = DRM_MODE_ROTATE_0 }) #define PLANE_TRANSFORM_ROTATE_90 ((const drm_plane_transform_t){ .u32 = DRM_MODE_ROTATE_90 }) #define PLANE_TRANSFORM_ROTATE_180 ((const drm_plane_transform_t){ .u32 = DRM_MODE_ROTATE_180 }) diff --git a/src/compositor.c b/src/compositor.c index 39415d90..ef544f2d 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -203,6 +203,9 @@ static int rendertarget_gbm_present( next_front_fb_id = gbm_bo_get_drm_fb_id(next_front_bo); + release_data->surface = gbm_target->gbm_surface; + release_data->bo = next_front_bo; + ok = kms_req_builder_push_fb_layer( builder, &(const struct kms_fb_layer) { diff --git a/src/modesetting.c b/src/modesetting.c index 2dc02e36..aad69bc7 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -208,6 +208,7 @@ static int fetch_connector(int drm_fd, uint32_t connector_id, struct drm_connect if (connector->modes != NULL) { modes = memdup(connector->modes, connector->count_modes * sizeof(*connector->modes)); if (modes == NULL) { + ok = ENOMEM; goto fail_free_props; } } else { diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index ba3d2a9f..a849243f 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -948,7 +948,7 @@ struct video_frame *frame_new( // add plane 2 (if present) if (n_planes >= 2) { - PUT_ATTR(EGL_DMA_BUF_PLANE1_FD_EXT, planes[1].fd); + PUT_ATTR(EGL_DMA_BUF_PLANE1_FD_EXT, planes[0].fd); PUT_ATTR(EGL_DMA_BUF_PLANE1_OFFSET_EXT, planes[1].offset); PUT_ATTR(EGL_DMA_BUF_PLANE1_PITCH_EXT, planes[1].pitch); if (planes[1].has_modifier) { @@ -964,7 +964,7 @@ struct video_frame *frame_new( // add plane 3 (if present) if (n_planes >= 3) { - PUT_ATTR(EGL_DMA_BUF_PLANE2_FD_EXT, planes[2].fd); + PUT_ATTR(EGL_DMA_BUF_PLANE2_FD_EXT, planes[0].fd); PUT_ATTR(EGL_DMA_BUF_PLANE2_OFFSET_EXT, planes[2].offset); PUT_ATTR(EGL_DMA_BUF_PLANE2_PITCH_EXT, planes[2].pitch); if (planes[2].has_modifier) { @@ -1021,9 +1021,9 @@ struct video_frame *frame_new( GLenum target; if (external_only) { - target = GL_TEXTURE_2D; - } else { target = GL_TEXTURE_EXTERNAL_OES; + } else { + target = GL_TEXTURE_2D; } glBindTexture(target, texture); @@ -1060,7 +1060,7 @@ struct video_frame *frame_new( frame->gl_frame.format = GL_RGBA8_OES; frame->gl_frame.width = 0; frame->gl_frame.height = 0; - return 0; + return frame; fail_unbind_texture: glBindTexture(texture, 0); diff --git a/src/plugins/gstreamer_video_player/player.c b/src/plugins/gstreamer_video_player/player.c index ac908c37..71510774 100644 --- a/src/plugins/gstreamer_video_player/player.c +++ b/src/plugins/gstreamer_video_player/player.c @@ -157,15 +157,15 @@ struct gstplayer { #define MAX_N_PLANES 4 #define MAX_N_EGL_DMABUF_IMAGE_ATTRIBUTES 6 + 6*MAX_N_PLANES + 1 -static inline void lock(struct gstplayer *player) { +MAYBE_UNUSED static inline void lock(struct gstplayer *player) { pthread_mutex_lock(&player->lock); } -static inline void unlock(struct gstplayer *player) { +MAYBE_UNUSED static inline void unlock(struct gstplayer *player) { pthread_mutex_unlock(&player->lock); } -static inline void trace_instant(struct gstplayer *player, const char *name) { +MAYBE_UNUSED static inline void trace_instant(struct gstplayer *player, const char *name) { return flutterpi_trace_event_instant(player->flutterpi, name); } @@ -639,10 +639,11 @@ static GstPadProbeReturn on_probe_pad(GstPad *pad, GstPadProbeInfo *info, void * player->has_gst_info = true; LOG_DEBUG( - "on_probe_pad, fps: %f, res: % 4d x % 4d\n", + "on_probe_pad, fps: %f, res: % 4d x % 4d, format: %s\n", (double) GST_VIDEO_INFO_FPS_N(&player->gst_info) / GST_VIDEO_INFO_FPS_D(&player->gst_info), GST_VIDEO_INFO_WIDTH(&player->gst_info), - GST_VIDEO_INFO_HEIGHT(&player->gst_info) + GST_VIDEO_INFO_HEIGHT(&player->gst_info), + gst_video_format_to_string(player->gst_info.finfo->format) ); player->info.info.width = GST_VIDEO_INFO_WIDTH(&player->gst_info); @@ -958,28 +959,6 @@ static int init(struct gstplayer *player, bool force_sw_decoders) { } static void maybe_deinit(struct gstplayer *player) { - struct my_gst_object { - GInitiallyUnowned object; - - /*< public >*/ /* with LOCK */ - GMutex lock; /* object LOCK */ - gchar *name; /* object name */ - GstObject *parent; /* this object's parent, weak ref */ - guint32 flags; - - /*< private >*/ - GList *control_bindings; /* List of GstControlBinding */ - guint64 control_rate; - guint64 last_sync; - - gpointer _gst_reserved; - }; - - struct my_gst_object *sink = (struct my_gst_object*) player->sink, *bus = (struct my_gst_object*) player->bus, *pipeline = (struct my_gst_object*) player->pipeline; - (void) sink; - (void) bus; - (void) pipeline; - if (player->busfd_events != NULL) { sd_event_source_unrefp(&player->busfd_events); } diff --git a/src/plugins/gstreamer_video_player/plugin.c b/src/plugins/gstreamer_video_player/plugin.c index c7fec988..5b051981 100644 --- a/src/plugins/gstreamer_video_player/plugin.c +++ b/src/plugins/gstreamer_video_player/plugin.c @@ -381,10 +381,8 @@ static int on_receive_evch( meta->has_listener = true; meta->video_info_listener = notifier_listen(gstplayer_get_video_info_notifier(player), on_video_info_notify, NULL, meta); - if (meta->video_info_listener == NULL) { - LOG_ERROR("Couldn't listen for video info events in gstplayer.\n"); - } - + // We don't care if it's NULL, it could also be on_video_info_notify was called synchronously. (And returned kUnlisten) + meta->buffering_state_listener = notifier_listen(gstplayer_get_buffering_state_notifier(player), on_buffering_state_notify, NULL, meta); if (meta->buffering_state_listener == NULL) { LOG_ERROR("Couldn't listen for buffering events in gstplayer.\n"); diff --git a/src/plugins/text_input.c b/src/plugins/text_input.c index c4b80f2d..b8f65b91 100644 --- a/src/plugins/text_input.c +++ b/src/plugins/text_input.c @@ -58,7 +58,7 @@ static inline uint8_t *symbol_at(unsigned int symbol_index) { return symbol_index? NULL : cursor; } -static inline int to_byte_index(unsigned int symbol_index) { +MAYBE_UNUSED static inline int to_byte_index(unsigned int symbol_index) { char *cursor = text_input.text; while ((*cursor) && (symbol_index--)) From 40bb677a51b9bce8ba521c6d8dc55c7a4395676b Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sat, 25 Feb 2023 16:09:01 +0000 Subject: [PATCH 41/55] fix memory offsets for uploading - fix uploading for multi-dmabuf formats - fix chroma siting detection --- src/plugins/gstreamer_video_player/frame.c | 38 ++++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index a849243f..9216d513 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -660,6 +660,8 @@ static int get_plane_infos( plane_infos[i].fd = ok; } + + offset_in_memory += memory->offset; } plane_infos[i].offset = offset_in_memory; @@ -780,27 +782,27 @@ static EGLint egl_sample_range_hint_from_gst_info(const GstVideoInfo *info) { } static EGLint egl_horizontal_chroma_siting_from_gst_info(const GstVideoInfo *info) { - if ((GST_VIDEO_INFO_CHROMA_SITE(info) & ~(GST_VIDEO_CHROMA_SITE_H_COSITED | GST_VIDEO_CHROMA_SITE_V_COSITED)) == 0) { - if (GST_VIDEO_INFO_CHROMA_SITE(info) & GST_VIDEO_CHROMA_SITE_H_COSITED) { - return EGL_YUV_CHROMA_SITING_0_EXT; - } else { - return EGL_YUV_CHROMA_SITING_0_5_EXT; - } - } + GstVideoChromaSite chroma_site = GST_VIDEO_INFO_CHROMA_SITE(info); - return EGL_NONE; + if (chroma_site == GST_VIDEO_CHROMA_SITE_H_COSITED || chroma_site == GST_VIDEO_CHROMA_SITE_COSITED) { + return EGL_YUV_CHROMA_SITING_0_EXT; + } else if (chroma_site == GST_VIDEO_CHROMA_SITE_V_COSITED || chroma_site == GST_VIDEO_CHROMA_SITE_NONE) { + return EGL_YUV_CHROMA_SITING_0_5_EXT; + } else { + return EGL_NONE; + } } static EGLint egl_vertical_chroma_siting_from_gst_info(const GstVideoInfo *info) { - if ((GST_VIDEO_INFO_CHROMA_SITE(info) & ~(GST_VIDEO_CHROMA_SITE_H_COSITED | GST_VIDEO_CHROMA_SITE_V_COSITED)) == 0) { - if (GST_VIDEO_INFO_CHROMA_SITE(info) & GST_VIDEO_CHROMA_SITE_V_COSITED) { - return EGL_YUV_CHROMA_SITING_0_EXT; - } else { - return EGL_YUV_CHROMA_SITING_0_5_EXT; - } - } + GstVideoChromaSite chroma_site = GST_VIDEO_INFO_CHROMA_SITE(info); - return EGL_NONE; + if (chroma_site == GST_VIDEO_CHROMA_SITE_V_COSITED || chroma_site == GST_VIDEO_CHROMA_SITE_COSITED) { + return EGL_YUV_CHROMA_SITING_0_EXT; + } else if (chroma_site == GST_VIDEO_CHROMA_SITE_H_COSITED || chroma_site == GST_VIDEO_CHROMA_SITE_NONE) { + return EGL_YUV_CHROMA_SITING_0_5_EXT; + } else { + return EGL_NONE; + } } struct video_frame *frame_new( @@ -948,7 +950,7 @@ struct video_frame *frame_new( // add plane 2 (if present) if (n_planes >= 2) { - PUT_ATTR(EGL_DMA_BUF_PLANE1_FD_EXT, planes[0].fd); + PUT_ATTR(EGL_DMA_BUF_PLANE1_FD_EXT, planes[1].fd); PUT_ATTR(EGL_DMA_BUF_PLANE1_OFFSET_EXT, planes[1].offset); PUT_ATTR(EGL_DMA_BUF_PLANE1_PITCH_EXT, planes[1].pitch); if (planes[1].has_modifier) { @@ -964,7 +966,7 @@ struct video_frame *frame_new( // add plane 3 (if present) if (n_planes >= 3) { - PUT_ATTR(EGL_DMA_BUF_PLANE2_FD_EXT, planes[0].fd); + PUT_ATTR(EGL_DMA_BUF_PLANE2_FD_EXT, planes[2].fd); PUT_ATTR(EGL_DMA_BUF_PLANE2_OFFSET_EXT, planes[2].offset); PUT_ATTR(EGL_DMA_BUF_PLANE2_PITCH_EXT, planes[2].pitch); if (planes[2].has_modifier) { From f543947dace7275bd002bfa520a60bf69f414864 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sat, 25 Feb 2023 16:20:55 +0000 Subject: [PATCH 42/55] also test build using clang --- .github/workflows/cmake.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 0dd60941..288647e0 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -21,13 +21,17 @@ jobs: # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix runs-on: [linux, bullseye] + strategy: + matrix: + compiler: [gcc, clang] + steps: - name: Install dependencies run: | sudo apt-get install -y \ git cmake libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev \ ttf-mscorefonts-installer fontconfig libsystemd-dev libinput-dev libudev-dev \ - libxkbcommon-dev ninja-build libgstreamer-plugins-base1.0-dev + libxkbcommon-dev ninja-build libgstreamer-plugins-base1.0-dev clang - uses: actions/checkout@v3 with: @@ -44,6 +48,7 @@ jobs: -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On \ -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ -DENABLE_TESTS=On \ + -DCMAKE_C_COMPILER=${{ matrix.compiler }} \ -GNinja - name: Build @@ -66,13 +71,16 @@ jobs: defaults: run: shell: bash + strategy: + matrix: + compiler: [gcc, clang] steps: - name: Install dependencies run: | apt-get update && apt-get install -y \ git cmake libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev \ fonts-liberation fontconfig libsystemd-dev libinput-dev libudev-dev \ - libxkbcommon-dev ninja-build libgstreamer-plugins-base1.0-dev + libxkbcommon-dev ninja-build libgstreamer-plugins-base1.0-dev clang - uses: actions/checkout@v3 with: @@ -89,6 +97,7 @@ jobs: -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On \ -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ -DENABLE_TESTS=On \ + -DCMAKE_C_COMPILER=${{ matrix.compiler }} \ -GNinja - name: Build From 3b86f440a01c59a2a6567113f637a41e6aa57c6a Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sat, 25 Feb 2023 16:23:23 +0000 Subject: [PATCH 43/55] fix braces (clang warning) --- src/plugins/services.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/services.c b/src/plugins/services.c index 6cffe42f..efd0fc3e 100644 --- a/src/plugins/services.c +++ b/src/plugins/services.c @@ -10,7 +10,7 @@ static struct { char label[256]; uint32_t primary_color; // ARGB8888 (blue is the lowest byte) char isolate_id[32]; -} services = {0}; +} services; static int on_receive_navigation(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { From 23771cb51514aca74d7be2604ef388592c92146d Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 27 Feb 2023 15:59:26 +0100 Subject: [PATCH 44/55] add some more standard message codec tests - fix standard msg codec array value calculation - update readme for gstreamer player updates --- README.md | 5 +- src/platformchannel.c | 25 ++- test/platformchannel_test.c | 329 ++++++++++++++++++++++++++++++++++++ 3 files changed, 352 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ae16ee21..b9f01ce3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ ## 📰 NEWS -- There's now a new video player based on gstreamer. See [gstreamer video player](#gstreamer-video-player) section. +- The [gstreamer video player](#gstreamer-video-player) now supports creating players from a raw gstreamer pipeline. +- The deprecated `omxplayer`-based video player has been removed. - The new latest flutter gallery commit for flutter 3.7 is `9776b9fd916635e10a32bd426fcd7a20c3841faf` # flutter-pi @@ -342,7 +343,7 @@ of the flutter app you're trying to run. `[flutter engine options...]` 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). ### gstreamer video player -Gstreamer video player is a newer video player based on gstreamer. The older video player (omxplayer_video_player) was based on deprecated omxplayer and it was kind of a hack. So I recommend using the gstreamer one instead. +Gstreamer video player is a newer video player based on gstreamer. To use the gstreamer video player, just rebuild flutter-pi (delete your build folder and reconfigure) and make sure the necessary gstreamer packages are installed. (See [dependencies](#dependencies)) diff --git a/src/platformchannel.c b/src/platformchannel.c index 0575d127..afc119ec 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -1778,10 +1778,10 @@ ATTR_CONST static const void *get_array_value_ptr(const struct raw_std_value *va // skip initial size byte addr++; - if (size == 254) { + if (size >= 254 && size < 0x00010000) { // skip two additional size bytes addr += 2; - } else if (size == 255) { + } else if (size >= 0x00010000) { // skip four additional size bytes addr += 4; } @@ -2150,18 +2150,18 @@ ATTR_PURE size_t raw_std_value_get_size(const struct raw_std_value *value) { } else if (size == 254) { size = 0; memcpy(&size, byteptr + 1, 2); + DEBUG_ASSERT(size >= 254); return size; } else if (size == 255) { size = 0; memcpy(&size, byteptr + 1, 4); + DEBUG_ASSERT(size >= 0x10000); return size; } UNREACHABLE(); } - - ATTR_PURE const struct raw_std_value *raw_std_value_after(const struct raw_std_value *value) { size_t size; @@ -2180,7 +2180,7 @@ ATTR_PURE const struct raw_std_value *raw_std_value_after(const struct raw_std_v case kStdString: return get_array_after_ptr(value, 0, raw_std_value_get_size(value), 1); case kStdFloat64: - return get_array_after_ptr(value, 8, raw_std_value_get_size(value), 8); + return get_after_ptr(value, 8, 8); case kStdUInt8Array: return get_array_after_ptr(value, 0, raw_std_value_get_size(value), 1); case kStdInt32Array: @@ -2287,10 +2287,25 @@ ATTR_PURE static bool check_size(const struct raw_std_value *value, size_t buffe if (buffer_size < 2) { return false; } + + // Calculation in @ref get_array_value_ptr assumes an array size 254 <= s < 0x10000 uses 3 size bytes + // If we allow a size smaller than 254 here, it would break that calculation. + size = 0; + memcpy(&size, byteptr + 1, 2); + if (size < 254) { + return false; + } } else if (size == 255) { if (buffer_size < 4) { return false; } + + // See above. + size = 0; + memcpy(&size, byteptr + 1, 4); + if (size < 0x10000) { + return false; + } } return true; diff --git a/test/platformchannel_test.c b/test/platformchannel_test.c index 69b6b511..01c9f7c1 100644 --- a/test/platformchannel_test.c +++ b/test/platformchannel_test.c @@ -869,15 +869,344 @@ void test_raw_std_value_get_size() { } void test_raw_std_value_after() { + // null + { + alignas(16) uint8_t buffer[] = { + kStdNull, + 0, + }; + + TEST_ASSERT_EQUAL_PTR(buffer + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // true + { + alignas(16) uint8_t buffer[] = { + kStdTrue, + 0, + }; + + TEST_ASSERT_EQUAL_PTR(buffer + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // true + { + alignas(16) uint8_t buffer[] = { + kStdFalse, + 0, + }; + + TEST_ASSERT_EQUAL_PTR(buffer + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + // int32 + { + alignas(16) uint8_t buffer[] = { + kStdInt32, + 1, 2, 3, 4, + }; + + TEST_ASSERT_EQUAL_PTR(buffer + 5, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // int64 + { + alignas(16) uint8_t buffer[] = { + kStdInt64, + 1, 2, 3, 4, 5, 6, 7, 8 + }; + + TEST_ASSERT_EQUAL_PTR(buffer + 9, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // float64 + { + alignas(16) uint8_t buffer[] = { + // type byte + kStdFloat64, + // 7 alignment bytes + 0, 0, 0, 0, 0, 0, 0, + // bytes for 1 float64 + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 7 + 8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // string + { + const char *str = "The quick brown fox jumps over the lazy dog."; + + alignas(16) uint8_t buffer[1 + 1 + 4] = { + kStdString, + strlen(str), + 0 + }; + + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + strlen(str), raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2 + 254, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 1; + buffer[5] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 0x00010000, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // uint8array + { + alignas(16) uint8_t buffer[1 + 1 + 4 + 0x00010000] = { + kStdUInt8Array, + 4, + 1, 2, 3, 4 + }; + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2 + 254, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 1; + buffer[5] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 0x00010000, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // int32array + { + alignas(16) uint8_t buffer[1 + 1 + 4 + 2 + 0x010000*4] = { + // type + kStdInt32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 int32_t's + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2 + 8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2 + 254*4, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 1; + buffer[5] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 2 + 0x010000*4, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // int64array + { + alignas(16) uint8_t buffer[1 + 1 + 4 + 2 + 0x010000*8] = { + // type + kStdInt64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 int64_t's + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 6 + 2*8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 6, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 2 + 254*8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 1; + buffer[5] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 2 + 0x010000*8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // float64array + { + alignas(16) uint8_t buffer[1 + 1 + 4 + 2 + 0x010000*8] = { + // type + kStdFloat64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 doubles + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 6 + 2*8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 6, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 2 + 254*8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 1; + buffer[5] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 2 + 0x010000*8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // list + { + const char *str = "The quick brown fox jumps over the lazy dog."; + + alignas(16) uint8_t buffer[1 + 1 + 4 + 1 + 1 + 4 + strlen(str) + 1]; + buffer[0] = kStdList; + buffer[1] = 2; + buffer[2] = kStdString; + buffer[3] = strlen(str); + buffer[4 + strlen(str)] = kStdTrue; + + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 1 + 1 + strlen(str) + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 1; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 1 + 1 + strlen(str), raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // map + { + const char *str = "The quick brown fox jumps over the lazy dog."; + + alignas(16) uint8_t buffer[] = { + [0] = kStdMap, + [1] = 2, + [2] = kStdNull, + [3] = kStdInt64, + [4] = 0, 0, 0, 0, 0, 0, 0, 0, + [12] = kStdFloat32Array, + [13] = 2, + [16] = 0, 0, 0, 0, 0, 0, 0, 0, + [24] = kStdTrue, + }; + + TEST_ASSERT_EQUAL_PTR(buffer + 25, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 2, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 1; + TEST_ASSERT_EQUAL_PTR(buffer + 12, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // float32array + { + alignas(16) uint8_t buffer[1 + 1 + 4 + 2 + 0x040000] = { + // type + kStdFloat32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 int32_t's + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2 + 8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2 + 254*4, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 1; + buffer[5] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 2 + 0x010000*4, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } } void test_raw_std_list_get_first_element() { + // list + const char *str = "The quick brown fox jumps over the lazy dog."; + + alignas(16) uint8_t buffer[1 + 1 + 4 + 1 + 1 + 4 + strlen(str) + 1]; + buffer[0] = kStdList; + buffer[1] = 2; + buffer[2] = kStdString; + buffer[3] = strlen(str); + buffer[4 + strlen(str)] = kStdTrue; + + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1, raw_std_list_get_first_element(AS_RAW_STD_VALUE(buffer))); + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 1 + 1 + strlen(str), raw_std_value_after(raw_std_list_get_first_element(AS_RAW_STD_VALUE(buffer)))); } void test_raw_std_list_get_nth_element() { + // list + const char *str = "The quick brown fox jumps over the lazy dog."; + + alignas(16) uint8_t buffer[1 + 1 + 4 + 1 + 1 + 4 + strlen(str) + 1]; + buffer[0] = kStdList; + buffer[1] = 2; + buffer[2] = kStdString; + buffer[3] = strlen(str); + buffer[4 + strlen(str)] = kStdTrue; + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1, raw_std_list_get_nth_element(AS_RAW_STD_VALUE(buffer), 0)); + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 1 + 1 + strlen(str), raw_std_value_after(raw_std_list_get_first_element(AS_RAW_STD_VALUE(buffer)))); } void test_raw_std_map_get_first_key() { From 8eb4ccd37f04b6a6257a14b7457e464e0ca635ab Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 27 Feb 2023 17:48:54 +0100 Subject: [PATCH 45/55] don't differentiate between master and non-master DRM fd --- src/modesetting.c | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/src/modesetting.c b/src/modesetting.c index aad69bc7..1e1decf4 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -913,9 +913,7 @@ static int set_drm_client_caps(int fd, bool *supports_atomic_modesetting) { struct drmdev *drmdev_new_from_fd(int fd, const struct drmdev_interface *interface, void *userdata) { struct gbm_device *gbm_device; struct drmdev *drmdev; - drmDevicePtr device; bool supports_atomic_modesetting; - void *master_fd_metadata; int ok, master_fd, event_fd; assert_rotations_work(); @@ -925,31 +923,7 @@ struct drmdev *drmdev_new_from_fd(int fd, const struct drmdev_interface *interfa return NULL; } - if (is_drm_master(fd)) { - ok = drmDropMaster(fd); - if (ok < 0) { - LOG_ERROR("Couldn't drop DRM master. drmDropMaster: %s\n", strerror(errno)); - } - } - - ok = drmGetDevice(fd, &device); - if (ok < 0) { - ok = errno; - LOG_ERROR("Couldn't query DRM device info. drmGetDevice: %s\n", strerror(ok)); - goto fail_free_drmdev; - } - - ok = interface->open(device->nodes[DRM_NODE_PRIMARY], O_CLOEXEC, &master_fd_metadata, userdata); - if (ok < 0) { - ok = -ok; - LOG_ERROR("Couldn't open DRM device.\n"); - master_fd = -1; - master_fd_metadata = NULL; - } - - master_fd = ok; - - drmFreeDevice(&device); + master_fd = fd; ok = set_drm_client_caps(fd, &supports_atomic_modesetting); if (ok != 0) { @@ -1030,7 +1004,6 @@ struct drmdev *drmdev_new_from_fd(int fd, const struct drmdev_interface *interfa drmdev->event_fd = event_fd; memset(drmdev->per_crtc_state, 0, sizeof(drmdev->per_crtc_state)); drmdev->master_fd = master_fd; - drmdev->master_fd_metadata = master_fd_metadata; drmdev->interface = *interface; drmdev->userdata = userdata; return drmdev; @@ -1060,7 +1033,7 @@ struct drmdev *drmdev_new_from_fd(int fd, const struct drmdev_interface *interfa drmModeFreeResources(drmdev->res); fail_close_master_fd: - interface->close(master_fd, master_fd_metadata, userdata); + interface->close(master_fd, NULL, userdata); fail_free_drmdev: free(drmdev); From 017499409132a88f1649c7a9207488a39bf32ff7 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 28 Feb 2023 17:19:18 +0100 Subject: [PATCH 46/55] fix atomic request memory leak --- src/modesetting.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/modesetting.c b/src/modesetting.c index 1e1decf4..784c0fdb 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -1759,10 +1759,13 @@ struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uin drmModeAtomicReq *req; struct drm_crtc *crtc; int64_t min_zpos; + bool supports_atomic_modesetting; DEBUG_ASSERT_NOT_NULL(drmdev); DEBUG_ASSERT(crtc_id != 0 && crtc_id != 0xFFFFFFFF); + drmdev_lock(drmdev); + for_each_crtc_in_drmdev(drmdev, crtc) { if (crtc->id == crtc_id) { break; @@ -1771,19 +1774,20 @@ struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uin if (crtc == NULL) { LOG_ERROR("Invalid CRTC id: %" PRId32 "\n", crtc_id); - return NULL; + goto fail_unlock; } builder = malloc(sizeof *builder); if (builder == NULL) { - return NULL; + goto fail_unlock; } - if (drmdev->supports_atomic_modesetting) { + supports_atomic_modesetting = drmdev->supports_atomic_modesetting; + + if (supports_atomic_modesetting) { req = drmModeAtomicAlloc(); if (req == NULL) { - free(builder); - return NULL; + goto fail_free_builder; } } else { req = NULL; @@ -1802,20 +1806,27 @@ struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uin } } + drmdev_unlock(drmdev); + builder->n_refs = REFCOUNT_INIT_1; - builder->drmdev = drmdev; + builder->drmdev = drmdev_ref(drmdev); // right now they're the same, but they might not be in the future. - builder->use_legacy = !drmdev->supports_atomic_modesetting; - builder->supports_atomic = drmdev->supports_atomic_modesetting; + builder->use_legacy = !supports_atomic_modesetting; + builder->supports_atomic = supports_atomic_modesetting; builder->crtc = crtc; builder->req = req; - - /// TODO: Use the actual min zpos here builder->next_zpos = min_zpos; builder->n_layers = 0; builder->has_mode = false; builder->unset_mode = false; return builder; + + fail_free_builder: + free(builder); + + fail_unlock: + drmdev_unlock(drmdev); + return NULL; } void kms_req_builder_destroy(struct kms_req_builder *builder) { @@ -1825,6 +1836,10 @@ void kms_req_builder_destroy(struct kms_req_builder *builder) { builder->layers[i].release_callback(builder->layers[i].release_callback_userdata); } } + if (builder->req != NULL) { + drmModeAtomicFree(builder->req); + } + drmdev_unref(builder->drmdev); free(builder); } From 812b2d0ccb4e6965ef4e81221f93fb9483dc49e6 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 28 Feb 2023 17:20:31 +0100 Subject: [PATCH 47/55] test `raw_std_map_get_first_key` --- test/platformchannel_test.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/platformchannel_test.c b/test/platformchannel_test.c index 01c9f7c1..567a91ca 100644 --- a/test/platformchannel_test.c +++ b/test/platformchannel_test.c @@ -1210,7 +1210,32 @@ void test_raw_std_list_get_nth_element() { } void test_raw_std_map_get_first_key() { + // map + alignas(16) uint8_t buffer[] = { + [0] = kStdMap, + [1] = 2, + [2] = kStdNull, + [3] = kStdInt64, + [4] = 0, 0, 0, 0, 0, 0, 0, 0, + [12] = kStdFloat32Array, + [13] = 2, + [16] = 0, 0, 0, 0, 0, 0, 0, 0, + [24] = kStdTrue, + }; + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1, raw_std_map_get_first_key(AS_RAW_STD_VALUE(buffer))); + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2, raw_std_map_get_first_key(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = 0x01; + buffer[5] = 0x00; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4, raw_std_map_get_first_key(AS_RAW_STD_VALUE(buffer))); } void test_raw_std_map_find() { From 66f705cb7d5918f665c6e9b012af8dfb9db93a05 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 1 Mar 2023 15:29:47 +0000 Subject: [PATCH 48/55] reimplement cursor support --- include/compositor.h | 23 +- include/modesetting.h | 7 + src/compositor.c | 510 ++++++++++++++++++++---------------------- src/cursor.c | 7 +- src/modesetting.c | 191 +++++++++++----- 5 files changed, 397 insertions(+), 341 deletions(-) diff --git a/include/compositor.h b/include/compositor.h index 84fa9c5d..4bf7a567 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -26,6 +26,8 @@ struct platform_view_params { double opacity; }; +struct cursor_buffer; + struct compositor { struct drmdev *drmdev; @@ -69,24 +71,9 @@ struct compositor { * @brief Whether the mouse cursor is currently enabled and visible. */ - struct { - bool is_enabled; - int cursor_size; - const struct cursor_icon *current_cursor; - int current_rotation; - int hot_x, hot_y; - int x, y; - - bool has_buffer; - int buffer_depth; - int buffer_pitch; - int buffer_width; - int buffer_height; - int buffer_size; - uint32_t drm_fb_id; - uint32_t gem_bo_handle; - uint32_t *buffer; - } cursor; + struct cursor_buffer *cursor; + + int cursor_x, cursor_y; /** * If true, @ref on_present_layers will commit blockingly. diff --git a/include/modesetting.h b/include/modesetting.h index be104ca1..27d025e1 100644 --- a/include/modesetting.h +++ b/include/modesetting.h @@ -429,6 +429,11 @@ DECLARE_REF_OPS(drmdev) int drmdev_get_fd(struct drmdev *drmdev); int drmdev_get_event_fd(struct drmdev *drmdev); +bool drmdev_supports_dumb_buffers(struct drmdev *drmdev); +int drmdev_create_dumb_buffer(struct drmdev *drmdev, int width, int height, int bpp, uint32_t *gem_handle_out, uint32_t *pitch_out, size_t *size_out); +void drmdev_destroy_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle); +void *drmdev_map_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle, size_t size); +void drmdev_unmap_dumb_buffer(struct drmdev *drmdev, void *map, size_t size); int drmdev_on_event_fd_ready(struct drmdev *drmdev); const struct drm_connector *drmdev_get_selected_connector(struct drmdev *drmdev); const struct drm_encoder *drmdev_get_selected_encoder(struct drmdev *drmdev); @@ -515,6 +520,8 @@ struct kms_fb_layer { bool has_in_fence_fd; int in_fence_fd; + + bool prefer_cursor; }; typedef void (*kms_fb_release_cb_t)(void *userdata); diff --git a/src/compositor.c b/src/compositor.c index ef544f2d..d8e7547c 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -42,9 +42,203 @@ struct compositor compositor = { .has_applied_modeset = false, .should_create_window_surface_backing_store = true, .stale_rendertargets = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), + .cursor = NULL, + .cursor_x = 0, + .cursor_y = 0, .do_blocking_atomic_commits = true }; +enum cursor_size { + k32x32_CursorSize, + k48x48_CursorSize, + k64x64_CursorSize, + k96x96_CursorSize, + k128x128_CursorSize, + + kMax_CursorSize = k128x128_CursorSize, + kCount_CursorSize = kMax_CursorSize + 1 +}; + +struct cursor_buffer { + refcount_t n_refs; + + struct drmdev *drmdev; + uint32_t gem_handle; + int drm_fb_id; + enum pixfmt format; + int width, height; + enum cursor_size size; + int rotation; + + int hot_x, hot_y; +}; + +const static int size_for_cursor_size[] = { + [k32x32_CursorSize] = 32, + [k48x48_CursorSize] = 48, + [k64x64_CursorSize] = 64, + [k96x96_CursorSize] = 96, + [k128x128_CursorSize] = 128 +}; + +COMPILE_ASSERT(ARRAY_SIZE(size_for_cursor_size) == kCount_CursorSize); + + +static enum cursor_size cursor_size_from_pixel_ratio(double device_pixel_ratio) { + double last_diff = INFINITY; + enum cursor_size size; + + for (enum cursor_size size_iter = k32x32_CursorSize; size_iter < kCount_CursorSize; size_iter++) { + double cursor_dpr = (size_for_cursor_size[size_iter] * 3 * 10.0) / (25.4 * 38); + double cursor_screen_dpr_diff = device_pixel_ratio - cursor_dpr; + if ((-last_diff < cursor_screen_dpr_diff) && (cursor_screen_dpr_diff < last_diff)) { + size = size_iter; + last_diff = cursor_screen_dpr_diff; + } + } + + return size; +} + +static struct cursor_buffer *cursor_buffer_new(struct drmdev *drmdev, enum cursor_size size, int rotation) { + const struct cursor_icon *icon; + struct cursor_buffer *b; + uint32_t gem_handle, pitch; + uint32_t fb_id; + size_t buffer_size; + void *map_void; + int pixel_size, hot_x, hot_y; + int ok; + + DEBUG_ASSERT_NOT_NULL(drmdev); + DEBUG_ASSERT(rotation == 0 || rotation == 1 || rotation == 2 || rotation == 3); + + if (!drmdev_supports_dumb_buffers(drmdev)) { + LOG_ERROR("KMS doesn't support dumb buffers. Can't upload mouse cursor icon.\n"); + return NULL; + } + + b = malloc(sizeof *b); + if (b == NULL) { + return NULL; + } + + pixel_size = size_for_cursor_size[size]; + + ok = drmdev_create_dumb_buffer( + drmdev, + pixel_size, pixel_size, 32, + &gem_handle, + &pitch, + &buffer_size + ); + if (ok != 0) { + goto fail_free_b; + } + + map_void = drmdev_map_dumb_buffer( + drmdev, + gem_handle, + buffer_size + ); + if (map_void == NULL) { + goto fail_destroy_dumb_buffer; + } + + icon = cursors + size; + DEBUG_ASSERT_EQUALS(pixel_size, icon->width); + DEBUG_ASSERT_EQUALS(pixel_size, icon->height); + + if (rotation == 0) { + DEBUG_ASSERT_EQUALS(pixel_size * 4, pitch); + memcpy(map_void, icon->data, buffer_size); + hot_x = icon->hot_x; + hot_y = icon->hot_y; + } else if ((rotation == 1) || (rotation == 2) || (rotation == 3)) { + uint32_t *map_uint32 = (uint32_t*) map_void; + + for (int y = 0; y < pixel_size; y++) { + for (int x = 0; x < pixel_size; x++) { + int buffer_x, buffer_y; + if (rotation == 1) { + buffer_x = pixel_size - y - 1; + buffer_y = x; + } else if (rotation == 2) { + buffer_x = pixel_size - y - 1; + buffer_y = pixel_size - x - 1; + } else { + buffer_x = y; + buffer_y = pixel_size - x - 1; + } + + int buffer_offset = pitch * buffer_y + 4 * buffer_x; + int cursor_offset = pixel_size * y + x; + + map_uint32[buffer_offset / 4] = icon->data[cursor_offset]; + } + } + + if (rotation == 1) { + hot_x = pixel_size - icon->hot_y - 1; + hot_y = icon->hot_x; + } else if (rotation == 2) { + hot_x = pixel_size - icon->hot_x - 1; + hot_y = pixel_size - icon->hot_y - 1; + } else { + DEBUG_ASSERT(rotation == 3); + hot_x = icon->hot_y; + hot_y = pixel_size - icon->hot_x - 1; + } + } + + drmdev_unmap_dumb_buffer(drmdev, map_void, size); + + fb_id = drmdev_add_fb( + drmdev, + pixel_size, + pixel_size, + kARGB8888_FpiPixelFormat, + gem_handle, + pitch, + 0, + true, + DRM_FORMAT_MOD_LINEAR + ); + if (fb_id == 0) { + LOG_ERROR("Couldn't add mouse cursor buffer as KMS framebuffer.\n"); + goto fail_destroy_dumb_buffer; + } + + b->n_refs = REFCOUNT_INIT_1; + b->drmdev = drmdev_ref(drmdev); + b->gem_handle = gem_handle; + b->drm_fb_id = fb_id; + b->format = kARGB8888_FpiPixelFormat; + b->width = pixel_size; + b->height = pixel_size; + b->size = size; + b->rotation = rotation; + b->hot_x = hot_x; + b->hot_y = hot_y; + return b; + + fail_destroy_dumb_buffer: + drmdev_destroy_dumb_buffer(drmdev, gem_handle); + + fail_free_b: + free(b); + return NULL; +} + +static void cursor_buffer_destroy(struct cursor_buffer *buffer) { + drmdev_rm_fb(buffer->drmdev, buffer->drm_fb_id); + drmdev_destroy_dumb_buffer(buffer->drmdev, buffer->gem_handle); + drmdev_unref(buffer->drmdev); + free(buffer); +} + +DEFINE_STATIC_REF_OPS(cursor_buffer, n_refs) + static struct view_cb_data *get_cbs_for_view_id_locked(int64_t view_id) { struct view_cb_data *data; @@ -714,6 +908,40 @@ static bool on_present_layers( } } + // add cursor infos + if (compositor->cursor != NULL) { + ok = kms_req_builder_push_fb_layer( + builder, + &(const struct kms_fb_layer) { + .drm_fb_id = compositor->cursor->drm_fb_id, + .format = compositor->cursor->format, + .has_modifier = true, + .modifier = DRM_FORMAT_MOD_LINEAR, + .src_x = 0, + .src_y = 0, + .src_w = ((uint16_t) compositor->cursor->width) << 16, + .src_h = ((uint16_t) compositor->cursor->height) << 16, + .dst_x = compositor->cursor_x - compositor->cursor->hot_x, + .dst_y = compositor->cursor_y - compositor->cursor->hot_y, + .dst_w = compositor->cursor->width, + .dst_h = compositor->cursor->height, + .has_rotation = false, + .rotation = PLANE_TRANSFORM_NONE, + .has_in_fence_fd = false, + .in_fence_fd = 0, + .prefer_cursor = true, + }, + cursor_buffer_unref_void, + NULL, + compositor->cursor + ); + if (ok != 0) { + LOG_ERROR("Couldn't present cursor.\n"); + } else { + cursor_buffer_ref(compositor->cursor); + } + } + struct kms_req *req = kms_req_builder_build(builder); if (req == NULL) { LOG_ERROR("Could not build atomic request.\n"); @@ -844,293 +1072,33 @@ int compositor_initialize(struct drmdev *drmdev) { return 0; } -static void destroy_cursor_buffer(void) { - struct drm_mode_destroy_dumb destroy_req; - - munmap(compositor.cursor.buffer, compositor.cursor.buffer_size); - - drmdev_rm_fb(compositor.drmdev, compositor.cursor.drm_fb_id); - - memset(&destroy_req, 0, sizeof destroy_req); - destroy_req.handle = compositor.cursor.gem_bo_handle; - - ioctl(drmdev_get_fd(compositor.drmdev), DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); - - compositor.cursor.has_buffer = false; - compositor.cursor.buffer_depth = 0; - compositor.cursor.gem_bo_handle = 0; - compositor.cursor.buffer_pitch = 0; - compositor.cursor.buffer_width = 0; - compositor.cursor.buffer_height = 0; - compositor.cursor.buffer_size = 0; - compositor.cursor.drm_fb_id = 0; - compositor.cursor.buffer = NULL; -} - -static int create_cursor_buffer(int width, int height, int bpp) { - struct drm_mode_create_dumb create_req; - struct drm_mode_map_dumb map_req; - uint32_t drm_fb_id; - uint32_t *buffer; - uint64_t cap; - uint8_t depth; - int ok; - - ok = drmGetCap(drmdev_get_fd(compositor.drmdev), DRM_CAP_DUMB_BUFFER, &cap); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not query GPU Driver support for dumb buffers. drmGetCap: %s\n", strerror(errno)); - goto fail_return_ok; - } - - if (cap == 0) { - LOG_ERROR("Kernel / GPU Driver does not support dumb DRM buffers. Mouse cursor will not be displayed.\n"); - ok = ENOTSUP; - goto fail_return_ok; - } - - ok = drmGetCap(drmdev_get_fd(compositor.drmdev), DRM_CAP_DUMB_PREFERRED_DEPTH, &cap); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not query dumb buffer preferred depth capability. drmGetCap: %s\n", strerror(errno)); - goto fail_return_ok; - } - - depth = (uint8_t) cap; - - if (depth != 32) { - LOG_ERROR("Preferred framebuffer depth for hardware cursor is not supported by flutter-pi.\n"); - } - - memset(&create_req, 0, sizeof create_req); - create_req.width = width; - create_req.height = height; - create_req.bpp = bpp; - create_req.flags = 0; - - ok = ioctl(drmdev_get_fd(compositor.drmdev), DRM_IOCTL_MODE_CREATE_DUMB, &create_req); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not create a dumb buffer for the hardware cursor. ioctl: %s\n", strerror(errno)); - goto fail_return_ok; - } - - ok = drmdev_add_fb( - compositor.drmdev, - create_req.width, create_req.height, - kARGB8888_FpiPixelFormat, - create_req.handle, - create_req.pitch, - 0, - false, - 0 - ); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not make a DRM FB out of the hardware cursor buffer. drmModeAddFB: %s\n", strerror(errno)); - goto fail_destroy_dumb_buffer; - } - - drm_fb_id = ok, - - memset(&map_req, 0, sizeof map_req); - map_req.handle = create_req.handle; - - ok = ioctl(drmdev_get_fd(compositor.drmdev), DRM_IOCTL_MODE_MAP_DUMB, &map_req); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not prepare dumb buffer mmap for uploading the hardware cursor icon. ioctl: %s\n", strerror(errno)); - goto fail_rm_drm_fb; - } - - buffer = mmap(0, create_req.size, PROT_READ | PROT_WRITE, MAP_SHARED, drmdev_get_fd(compositor.drmdev), map_req.offset); - if (buffer == MAP_FAILED) { - ok = errno; - LOG_ERROR("Could not mmap dumb buffer for uploading the hardware cursor icon. mmap: %s\n", strerror(errno)); - goto fail_rm_drm_fb; - } - - compositor.cursor.has_buffer = true; - compositor.cursor.buffer_depth = depth; - compositor.cursor.gem_bo_handle = create_req.handle; - compositor.cursor.buffer_pitch = create_req.pitch; - compositor.cursor.buffer_width = width; - compositor.cursor.buffer_height = height; - compositor.cursor.buffer_size = create_req.size; - compositor.cursor.drm_fb_id = drm_fb_id; - compositor.cursor.buffer = buffer; - - return 0; - - - fail_rm_drm_fb: - drmModeRmFB(drmdev_get_fd(compositor.drmdev), drm_fb_id); - - fail_destroy_dumb_buffer: ; - struct drm_mode_destroy_dumb destroy_req; - memset(&destroy_req, 0, sizeof destroy_req); - destroy_req.handle = create_req.handle; - ioctl(drmdev_get_fd(compositor.drmdev), DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); - - fail_return_ok: - return ok; -} - int compositor_apply_cursor_state( bool is_enabled, int rotation, double device_pixel_ratio ) { - const struct cursor_icon *cursor; - int ok; - if (is_enabled == true) { // find the best fitting cursor icon. - { - double last_diff = INFINITY; - - cursor = NULL; - for (int i = 0; i < n_cursors; i++) { - double cursor_dpr = (cursors[i].width * 3 * 10.0) / (25.4 * 38); - double cursor_screen_dpr_diff = device_pixel_ratio - cursor_dpr; - if ((cursor_screen_dpr_diff >= 0) && (cursor_screen_dpr_diff < last_diff)) { - cursor = cursors + i; - } - } - } + enum cursor_size size = cursor_size_from_pixel_ratio(device_pixel_ratio); - // destroy the old cursor buffer, if necessary - if (compositor.cursor.has_buffer && (compositor.cursor.buffer_width != cursor->width)) { - destroy_cursor_buffer(); + if (compositor.cursor != NULL && (compositor.cursor->rotation != rotation || compositor.cursor->size != size)) { + cursor_buffer_unref(compositor.cursor); + compositor.cursor = NULL; } - // create a new cursor buffer, if necessary - if (compositor.cursor.has_buffer == false) { - create_cursor_buffer(cursor->width, cursor->width, 32); + if (compositor.cursor == NULL) { + compositor.cursor = cursor_buffer_new(compositor.drmdev, size, rotation); } - - if ((compositor.cursor.is_enabled == false) || (compositor.cursor.current_rotation != rotation) || (compositor.cursor.current_cursor != cursor)) { - int rotated_hot_x, rotated_hot_y; - - if (rotation == 0) { - memcpy(compositor.cursor.buffer, cursor->data, compositor.cursor.buffer_size); - rotated_hot_x = cursor->hot_x; - rotated_hot_y = cursor->hot_y; - } else if ((rotation == 90) || (rotation == 180) || (rotation == 270)) { - for (int y = 0; y < cursor->width; y++) { - for (int x = 0; x < cursor->width; x++) { - int buffer_x, buffer_y; - if (rotation == 90) { - buffer_x = cursor->width - y - 1; - buffer_y = x; - } else if (rotation == 180) { - buffer_x = cursor->width - y - 1; - buffer_y = cursor->width - x - 1; - } else { - buffer_x = y; - buffer_y = cursor->width - x - 1; - } - - int buffer_offset = compositor.cursor.buffer_pitch * buffer_y + (compositor.cursor.buffer_depth / 8) * buffer_x; - int cursor_offset = cursor->width * y + x; - - compositor.cursor.buffer[buffer_offset / 4] = cursor->data[cursor_offset]; - } - } - - if (rotation == 90) { - rotated_hot_x = cursor->width - cursor->hot_y - 1; - rotated_hot_y = cursor->hot_x; - } else if (rotation == 180) { - rotated_hot_x = cursor->width - cursor->hot_x - 1; - rotated_hot_y = cursor->width - cursor->hot_y - 1; - } else { - DEBUG_ASSERT(rotation == 270); - rotated_hot_x = cursor->hot_y; - rotated_hot_y = cursor->width - cursor->hot_x - 1; - } - } else { - return EINVAL; - } - - ok = drmModeSetCursor2( - drmdev_get_fd(compositor.drmdev), - flutterpi.drm.selected_crtc_id, - compositor.cursor.gem_bo_handle, - compositor.cursor.cursor_size, - compositor.cursor.cursor_size, - rotated_hot_x, - rotated_hot_y - ); - if (ok < 0) { - if (errno == ENXIO) { - LOG_ERROR("Could not configure cursor. Hardware cursor is not supported by device.\n"); - } else { - LOG_ERROR("Could not set the mouse cursor buffer. drmModeSetCursor: %s\n", strerror(errno)); - } - return errno; - } - - ok = drmModeMoveCursor( - drmdev_get_fd(compositor.drmdev), - flutterpi.drm.selected_crtc_id, - compositor.cursor.x - compositor.cursor.hot_x, - compositor.cursor.y - compositor.cursor.hot_y - ); - if (ok < 0) { - LOG_ERROR("Could not move cursor. drmModeMoveCursor: %s\n", strerror(errno)); - return errno; - } - - compositor.cursor.current_rotation = rotation; - compositor.cursor.current_cursor = cursor; - compositor.cursor.cursor_size = cursor->width; - compositor.cursor.hot_x = rotated_hot_x; - compositor.cursor.hot_y = rotated_hot_y; - compositor.cursor.is_enabled = true; - } - - return 0; - } else if ((is_enabled == false) && (compositor.cursor.is_enabled == true)) { - drmModeSetCursor( - drmdev_get_fd(compositor.drmdev), - flutterpi.drm.selected_crtc_id, - 0, 0, 0 - ); - - destroy_cursor_buffer(); - - compositor.cursor.cursor_size = 0; - compositor.cursor.current_cursor = NULL; - compositor.cursor.current_rotation = 0; - compositor.cursor.hot_x = 0; - compositor.cursor.hot_y = 0; - compositor.cursor.x = 0; - compositor.cursor.y = 0; - compositor.cursor.is_enabled = false; - - return 0; + } else if ((is_enabled == false) && (compositor.cursor != NULL)) { + cursor_buffer_unref(compositor.cursor); + compositor.cursor = NULL; } - return 0; } int compositor_set_cursor_pos(int x, int y) { - int ok; - - if (compositor.cursor.is_enabled == false) { - return 0; - } - - ok = drmModeMoveCursor(drmdev_get_fd(compositor.drmdev), flutterpi.drm.selected_crtc_id, x - compositor.cursor.hot_x, y - compositor.cursor.hot_y); - if (ok < 0) { - LOG_ERROR("Could not move cursor. drmModeMoveCursor: %s", strerror(errno)); - return errno; - } - - compositor.cursor.x = x; - compositor.cursor.y = y; - + compositor.cursor_x = x; + compositor.cursor_y = y; return 0; } diff --git a/src/cursor.c b/src/cursor.c index a59b1dd3..1e5f30bf 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -1,3 +1,4 @@ +#include #include const unsigned char cursor_32x32_data[32*32*4 + 1] = @@ -3945,4 +3946,8 @@ const struct cursor_icon cursors[5] = { } }; -int n_cursors = sizeof(cursors) / sizeof(*cursors); \ No newline at end of file +int n_cursors = ARRAY_SIZE(cursors); + +// So we get an error if we change the enum cursor_size in compositor.c +// without changing this array. +COMPILE_ASSERT(ARRAY_SIZE(cursors) == 5); \ No newline at end of file diff --git a/src/modesetting.c b/src/modesetting.c index 784c0fdb..4b5a617e 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -8,7 +8,9 @@ #include #include +#include #include +#include #include #include @@ -61,6 +63,7 @@ struct drmdev { refcount_t n_refs; pthread_mutex_t mutex; bool supports_atomic_modesetting; + bool supports_dumb_buffers; size_t n_connectors; struct drm_connector *connectors; @@ -913,7 +916,9 @@ static int set_drm_client_caps(int fd, bool *supports_atomic_modesetting) { struct drmdev *drmdev_new_from_fd(int fd, const struct drmdev_interface *interface, void *userdata) { struct gbm_device *gbm_device; struct drmdev *drmdev; + uint64_t cap; bool supports_atomic_modesetting; + bool supports_dumb_buffers; int ok, master_fd, event_fd; assert_rotations_work(); @@ -930,15 +935,12 @@ struct drmdev *drmdev_new_from_fd(int fd, const struct drmdev_interface *interfa goto fail_close_master_fd; } - if (master_fd > 0) { - bool master_fd_supports_atomic_modesetting; - - ok = set_drm_client_caps(master_fd, &master_fd_supports_atomic_modesetting); - if (ok != 0) { - goto fail_close_master_fd; - } - - DEBUG_ASSERT_EQUALS(supports_atomic_modesetting, master_fd_supports_atomic_modesetting); + cap = 0; + ok = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &cap); + if (ok < 0) { + supports_dumb_buffers = false; + } else { + supports_dumb_buffers = !!cap; } drmdev->res = drmModeGetResources(fd); @@ -1000,6 +1002,7 @@ struct drmdev *drmdev_new_from_fd(int fd, const struct drmdev_interface *interfa drmdev->n_refs = REFCOUNT_INIT_1; drmdev->fd = fd; drmdev->supports_atomic_modesetting = supports_atomic_modesetting; + drmdev->supports_dumb_buffers = supports_dumb_buffers; drmdev->gbm_device = gbm_device; drmdev->event_fd = event_fd; memset(drmdev->per_crtc_state, 0, sizeof(drmdev->per_crtc_state)); @@ -1089,6 +1092,93 @@ int drmdev_get_event_fd(struct drmdev *drmdev) { return drmdev->master_fd; } +bool drmdev_supports_dumb_buffers(struct drmdev *drmdev) { + return drmdev->supports_dumb_buffers; +} + +int drmdev_create_dumb_buffer(struct drmdev *drmdev, int width, int height, int bpp, uint32_t *gem_handle_out, uint32_t *pitch_out, size_t *size_out) { + struct drm_mode_create_dumb create_req; + int ok; + + DEBUG_ASSERT_NOT_NULL(drmdev); + DEBUG_ASSERT_NOT_NULL(gem_handle_out); + DEBUG_ASSERT_NOT_NULL(pitch_out); + DEBUG_ASSERT_NOT_NULL(size_out); + + memset(&create_req, 0, sizeof create_req); + create_req.width = width; + create_req.height = height; + create_req.bpp = bpp; + create_req.flags = 0; + + ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not create dumb buffer. ioctl: %s\n", strerror(errno)); + goto fail_return_ok; + } + + *gem_handle_out = create_req.handle; + *pitch_out = create_req.pitch; + *size_out = create_req.size; + return 0; + + fail_return_ok: + return ok; +} + +void drmdev_destroy_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle) { + struct drm_mode_destroy_dumb destroy_req; + int ok; + + DEBUG_ASSERT_NOT_NULL(drmdev); + + memset(&destroy_req, 0, sizeof destroy_req); + destroy_req.handle = gem_handle; + + ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); + if (ok < 0) { + LOG_ERROR("Could not destroy dumb buffer. ioctl: %s\n", strerror(errno)); + } +} + +void *drmdev_map_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle, size_t size) { + struct drm_mode_map_dumb map_req; + void *map; + int ok; + + DEBUG_ASSERT_NOT_NULL(drmdev); + + memset(&map_req, 0, sizeof map_req); + map_req.handle = gem_handle; + + ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req); + if (ok < 0) { + LOG_ERROR("Could not prepare dumb buffer mmap. ioctl: %s\n", strerror(errno)); + return NULL; + } + + map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, drmdev->fd, map_req.offset); + if (map == MAP_FAILED) { + LOG_ERROR("Could not mmap dumb buffer. mmap: %s\n", strerror(errno)); + return NULL; + } + + return map; +} + +void drmdev_unmap_dumb_buffer(struct drmdev *drmdev, void *map, size_t size) { + int ok; + + DEBUG_ASSERT_NOT_NULL(drmdev); + DEBUG_ASSERT_NOT_NULL(map); + + ok = munmap(map, size); + if (ok < 0) { + LOG_ERROR("Couldn't unmap dumb buffer. munmap: %s\n", strerror(ok)); + } +} + static void drmdev_on_page_flip_locked( int fd, unsigned int sequence, @@ -1928,8 +2018,27 @@ int kms_req_builder_push_fb_layer( // Index of our layer. index = builder->n_layers; + // If we should prefer a cursor plane, try to find one first. + plane = NULL; + if (layer->prefer_cursor) { + plane = allocate_plane( + builder, + /* allow_primary */ false, + /* allow_overlay */ false, + /* allow_cursor */ true, + /* format */ layer->format, + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ false, 0, 0, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ false, 0 + ); + if (plane == NULL) { + LOG_DEBUG("Couldn't find a fitting cursor plane.\n"); + } + } + /// TODO: Not sure we can use crtc_x, crtc_y, etc with primary planes - if (index == 0) { + if (plane == NULL && index == 0) { // if this is the first layer, try using a // primary plane for it. @@ -1939,16 +2048,11 @@ int kms_req_builder_push_fb_layer( /* allow_primary */ true, /* allow_overlay */ false, /* allow_cursor */ false, - layer->format, - layer->has_modifier, - layer->modifier, - false, - 0, - 0, - layer->has_rotation, - layer->rotation, - false, - 0 + /* format */ layer->format, + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ false, 0, 0, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ false, 0 ); if (plane == NULL && !get_pixfmt_info(layer->format)->is_opaque) { @@ -1958,35 +2062,25 @@ int kms_req_builder_push_fb_layer( /* allow_primary */ true, /* allow_overlay */ false, /* allow_cursor */ false, - pixfmt_opaque(layer->format), - layer->has_modifier, - layer->modifier, - false, - 0, - 0, - layer->has_rotation, - layer->rotation, - false, - 0 + /* format */ pixfmt_opaque(layer->format), + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ false, 0, 0, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ false, 0 ); } - } else { + } else if (plane == NULL) { // First try to find an overlay plane with a higher zpos. plane = allocate_plane( builder, /* allow_primary */ false, /* allow_overlay */ true, /* allow_cursor */ false, - layer->format, - layer->has_modifier, - layer->modifier, - true, - builder->next_zpos, - INT64_MAX, - layer->has_rotation, - layer->rotation, - false, - 0 + /* format */ layer->format, + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ true, builder->next_zpos, INT64_MAX, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ false, 0 ); // If we can't find one, find an overlay plane with the next highest plane_id. @@ -1999,16 +2093,11 @@ int kms_req_builder_push_fb_layer( /* allow_primary */ false, /* allow_overlay */ true, /* allow_cursor */ false, - layer->format, - layer->has_modifier, - layer->modifier, - false, - 0, - 0, - layer->has_rotation, - layer->rotation, - true, - builder->layers[index - 1].plane_id + 1 + /* format */ layer->format, + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ false, 0, 0, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ true, builder->layers[index - 1].plane_id + 1 ); } } From 385f72294be6999dfce9f45f558eb8652696d17a Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 1 Mar 2023 15:31:29 +0000 Subject: [PATCH 49/55] specify `prefer_cursor = false` for normal framebuffer planes --- src/compositor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compositor.c b/src/compositor.c index d8e7547c..9d4aa3c7 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -419,6 +419,7 @@ static int rendertarget_gbm_present( .rotation = PLANE_TRANSFORM_NONE, .has_in_fence_fd = false, .in_fence_fd = 0, + .prefer_cursor = false, }, on_release_gbm_rendertarget_fb, NULL, From 8f27f03df49107a4915cde9c855c5334c0cf73ad Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 1 Mar 2023 17:00:18 +0000 Subject: [PATCH 50/55] fix warnings - `static const` instead of `const static` - rename `size_for_cursor_size ==> pixel_size_for_cursor_size` - disable `-Wmissing-field-initializers` entirely instead of making it not an error for gcc < 11.3 --- CMakeLists.txt | 2 +- src/compositor.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 89f1017c..53c84576 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,7 +160,7 @@ target_compile_options(flutterpi_module PRIVATE # GCC prior to 11.3 reports false positives for missing-field-initializers warning. if (CMAKE_C_COMPILER_ID STREQUAL "GNU") if (CMAKE_C_COMPILER_VERSION VERSION_LESS "11.3") - target_compile_options(flutterpi_module PRIVATE -Wno-error=missing-field-initializers) + target_compile_options(flutterpi_module PRIVATE -Wno-missing-field-initializers) endif() endif() diff --git a/src/compositor.c b/src/compositor.c index 9d4aa3c7..3c1b34d3 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -73,7 +73,7 @@ struct cursor_buffer { int hot_x, hot_y; }; -const static int size_for_cursor_size[] = { +static const int pixel_size_for_cursor_size[] = { [k32x32_CursorSize] = 32, [k48x48_CursorSize] = 48, [k64x64_CursorSize] = 64, @@ -81,7 +81,7 @@ const static int size_for_cursor_size[] = { [k128x128_CursorSize] = 128 }; -COMPILE_ASSERT(ARRAY_SIZE(size_for_cursor_size) == kCount_CursorSize); +COMPILE_ASSERT(ARRAY_SIZE(pixel_size_for_cursor_size) == kCount_CursorSize); static enum cursor_size cursor_size_from_pixel_ratio(double device_pixel_ratio) { @@ -89,7 +89,7 @@ static enum cursor_size cursor_size_from_pixel_ratio(double device_pixel_ratio) enum cursor_size size; for (enum cursor_size size_iter = k32x32_CursorSize; size_iter < kCount_CursorSize; size_iter++) { - double cursor_dpr = (size_for_cursor_size[size_iter] * 3 * 10.0) / (25.4 * 38); + double cursor_dpr = (pixel_size_for_cursor_size[size_iter] * 3 * 10.0) / (25.4 * 38); double cursor_screen_dpr_diff = device_pixel_ratio - cursor_dpr; if ((-last_diff < cursor_screen_dpr_diff) && (cursor_screen_dpr_diff < last_diff)) { size = size_iter; @@ -123,7 +123,7 @@ static struct cursor_buffer *cursor_buffer_new(struct drmdev *drmdev, enum curso return NULL; } - pixel_size = size_for_cursor_size[size]; + pixel_size = pixel_size_for_cursor_size[size]; ok = drmdev_create_dumb_buffer( drmdev, From 4eba2e57168b0ffbdca61d2e9edbc1fc1949afc5 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Fri, 10 Mar 2023 14:49:33 +0000 Subject: [PATCH 51/55] fix build without gstreamer --- CMakeLists.txt | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 53c84576..c24ad443 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -221,20 +221,19 @@ if (BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) pkg_check_modules(LIBGSTREAMER_VIDEO REQUIRED IMPORTED_TARGET gstreamer-video-1.0) endif() - # There's no other way to query the libinput version (in code) somehow. - # So we need to roll our own libinput version macro - string(REPLACE "." ";" LIBGSTREAMER_VERSION_AS_LIST ${LIBGSTREAMER_VERSION}) - list(GET LIBGSTREAMER_VERSION_AS_LIST 0 LIBGSTREAMER_VERSION_MAJOR) - list(GET LIBGSTREAMER_VERSION_AS_LIST 1 LIBGSTREAMER_VERSION_MINOR) - list(GET LIBGSTREAMER_VERSION_AS_LIST 2 LIBGSTREAMER_VERSION_PATCH) - - target_compile_definitions(flutterpi_module PRIVATE - LIBGSTREAMER_VERSION_MAJOR=${LIBGSTREAMER_VERSION_MAJOR} - LIBGSTREAMER_VERSION_MINOR=${LIBGSTREAMER_VERSION_MINOR} - LIBGSTREAMER_VERSION_PATCH=${LIBGSTREAMER_VERSION_PATCH} - ) - if (LIBGSTREAMER_FOUND AND LIBGSTREAMER_PLUGINS_BASE_FOUND AND LIBGSTREAMER_APP_FOUND AND LIBGSTREAMER_ALLOCATORS_FOUND AND LIBGSTREAMER_VIDEO_FOUND) + # There's no other way to query the libinput version (in code) somehow. + # So we need to roll our own libinput version macro + string(REPLACE "." ";" LIBGSTREAMER_VERSION_AS_LIST ${LIBGSTREAMER_VERSION}) + list(GET LIBGSTREAMER_VERSION_AS_LIST 0 LIBGSTREAMER_VERSION_MAJOR) + list(GET LIBGSTREAMER_VERSION_AS_LIST 1 LIBGSTREAMER_VERSION_MINOR) + list(GET LIBGSTREAMER_VERSION_AS_LIST 2 LIBGSTREAMER_VERSION_PATCH) + + target_compile_definitions(flutterpi_module PRIVATE + LIBGSTREAMER_VERSION_MAJOR=${LIBGSTREAMER_VERSION_MAJOR} + LIBGSTREAMER_VERSION_MINOR=${LIBGSTREAMER_VERSION_MINOR} + LIBGSTREAMER_VERSION_PATCH=${LIBGSTREAMER_VERSION_PATCH} + ) target_sources(flutterpi_module PRIVATE src/plugins/gstreamer_video_player/plugin.c src/plugins/gstreamer_video_player/player.c From 6718d0ff5efa666c220720a1a62551fb1c8d06ae Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 13 Mar 2023 22:23:48 +0000 Subject: [PATCH 52/55] fix DPMS --- src/compositor.c | 19 +++++++++---------- src/modesetting.c | 3 +++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/compositor.c b/src/compositor.c index 3c1b34d3..07bb7c5d 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -619,7 +619,7 @@ extern void on_pageflip_event( void *userdata ); -static int execute_simulate_page_flip_event(void *userdata) { +MAYBE_UNUSED static int execute_simulate_page_flip_event(void *userdata) { struct simulated_page_flip_event_data *data; data = userdata; @@ -976,15 +976,14 @@ static bool on_present_layers( goto fail_unref_req; } - struct simulated_page_flip_event_data *data = malloc(sizeof(struct simulated_page_flip_event_data)); - if (data == NULL) { - goto fail_unref_req; - } - - data->sec = vblank_ns / 1000000000llu; - data->usec = (vblank_ns % 1000000000llu) / 1000; - - flutterpi_post_platform_task(execute_simulate_page_flip_event, data); + // Disabled because vsync callback is broken. + // struct simulated_page_flip_event_data *data = malloc(sizeof(struct simulated_page_flip_event_data)); + // if (data == NULL) { + // goto fail_unref_req; + // } + // data->sec = vblank_ns / 1000000000llu; + // data->usec = (vblank_ns % 1000000000llu) / 1000; + // flutterpi_post_platform_task(execute_simulate_page_flip_event, data); } kms_req_unref(req); diff --git a/src/modesetting.c b/src/modesetting.c index 4b5a617e..854214fa 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -1879,6 +1879,9 @@ struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uin if (req == NULL) { goto fail_free_builder; } + + // set the CRTC to active + drmModeAtomicAddProperty(req, crtc->id, crtc->ids.active, 1); } else { req = NULL; } From 43e556e7bb472ff446d2383632d48c6af593fdb2 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 14 Mar 2023 00:07:40 +0100 Subject: [PATCH 53/55] specify CRTC_ID property --- src/modesetting.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/modesetting.c b/src/modesetting.c index 854214fa..301628a7 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -1906,6 +1906,7 @@ struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uin // right now they're the same, but they might not be in the future. builder->use_legacy = !supports_atomic_modesetting; builder->supports_atomic = supports_atomic_modesetting; + builder->connector = NULL; builder->crtc = crtc; builder->req = req; builder->next_zpos = min_zpos; @@ -2341,6 +2342,11 @@ static int kms_req_commit_common( /// TODO: Can we set OUT_FENCE_PTR even though we didn't set any IN_FENCE_FDs? flags = DRM_MODE_PAGE_FLIP_EVENT | (blocking ? 0 : DRM_MODE_ATOMIC_NONBLOCK) | (update_mode ? DRM_MODE_ATOMIC_ALLOW_MODESET : 0); + if (builder->connector != NULL) { + // add the CRTC_ID property if that was explicitly set + drmModeAtomicAddProperty(builder->req, builder->connector->id, builder->connector->ids.crtc_id, builder->crtc->id); + } + /// TODO: If we're on raspberry pi and only have one layer, we can do an async pageflip /// on the primary plane to replace the next queued frame. (To do _real_ triple buffering /// with fully decoupled framerate, potentially) From 0f2b57d34b84c76426e42b1a1cfb501c6a7c1d86 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 14 Mar 2023 00:48:18 +0100 Subject: [PATCH 54/55] properly set MODE_ID --- src/modesetting.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/modesetting.c b/src/modesetting.c index 301628a7..dcd2fa3e 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -2347,6 +2347,14 @@ static int kms_req_commit_common( drmModeAtomicAddProperty(builder->req, builder->connector->id, builder->connector->ids.crtc_id, builder->crtc->id); } + if (update_mode) { + if (mode_blob != NULL) { + drmModeAtomicAddProperty(builder->req, builder->crtc->id, builder->crtc->ids.mode_id, mode_blob->blob_id); + } else { + drmModeAtomicAddProperty(builder->req, builder->crtc->id, builder->crtc->ids.mode_id, 0); + } + } + /// TODO: If we're on raspberry pi and only have one layer, we can do an async pageflip /// on the primary plane to replace the next queued frame. (To do _real_ triple buffering /// with fully decoupled framerate, potentially) From 4df6e226327cc6e0c275cd5f34f220f4e151b8b2 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 27 Mar 2023 12:26:36 +0200 Subject: [PATCH 55/55] fix some warnings - some cmake improvements --- .gitignore | 9 ++++++--- CMakeLists.txt | 8 +++++++- include/plugins/gstreamer_video_player.h | 4 +++- src/compositor.c | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index a6af02d6..b1c54e80 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ -.vscode -.clang-format -build \ No newline at end of file +/.vscode +/.clang-format +/build +/out +/CMakePresets.json +/cross-pi4-toolchain.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index c24ad443..7a9b3adf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,10 @@ pkg_check_modules(LIBINPUT REQUIRED IMPORTED_TARGET libinput) pkg_check_modules(LIBXKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon) pkg_check_modules(LIBUDEV REQUIRED IMPORTED_TARGET libudev) +# find pthreads +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + add_library( flutterpi_module OBJECT src/flutter-pi.c @@ -142,7 +146,9 @@ target_link_libraries(flutterpi_module PUBLIC PkgConfig::LIBINPUT PkgConfig::LIBXKBCOMMON PkgConfig::LIBUDEV - pthread dl rt m atomic + Threads::Threads + ${CMAKE_DL_LIBS} + rt m atomic ) target_include_directories(flutterpi_module PUBLIC diff --git a/include/plugins/gstreamer_video_player.h b/include/plugins/gstreamer_video_player.h index 90da01c4..2c819266 100644 --- a/include/plugins/gstreamer_video_player.h +++ b/include/plugins/gstreamer_video_player.h @@ -214,7 +214,9 @@ struct egl_modified_format { struct frame_interface; -struct frame_interface *frame_interface_new(); +struct flutterpi; + +struct frame_interface *frame_interface_new(struct flutterpi *flutterpi); ATTR_PURE int frame_interface_get_n_formats(struct frame_interface *interface); diff --git a/src/compositor.c b/src/compositor.c index 07bb7c5d..e4883779 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -713,7 +713,7 @@ static void fill_platform_view_params( rotation = fmod(rotation, 360.0); params_out->rect = quad; - params_out->opacity = 0; + params_out->opacity = opacity; params_out->rotation = rotation; params_out->clip_rects = NULL; params_out->n_clip_rects = 0;