diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..2588d541 --- /dev/null +++ b/.clang-format @@ -0,0 +1,167 @@ +# SPDX-License-Identifier: MIT +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: BlockIndent +#AlignArrayOfStructures: Right +AlignConsecutiveAssignments: false +#AlignConsecutiveBitFields: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +#AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +#AllowShortLambdasOnASingleLine: None +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +AttributeMacros: + - 'MAYBE_UNUSED' + - 'ATTR_MALLOC' + - 'NONNULL' + - 'ATTR_PURE' + - 'ATTR_CONST' + +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +AlwaysBreakBeforeMultilineStrings: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +#ExperimentalAutoDetectBinPacking: true +FixNamespaceComments: false +ForEachMacros: + - 'for_each_pointer_in_pset' + - 'for_each_pointer_in_cpset' + - 'for_each_connector_in_drmdev' + - 'for_each_encoder_in_drmdev' + - 'for_each_crtc_in_drmdev' + - 'for_each_plane_in_drmdev' + - 'for_each_mode_in_connector' + - 'for_each_unreserved_plane_in_atomic_req' + +IncludeBlocks: Regroup +IncludeCategories: + # C standard library headers + - Regex: '^<(assert|complex|ctype|errno|fenv|float|inttypes|iso646|limits|locale|math|setjmp|signal|stdalign|stdarg|stdatomic|stdbool|stddef|stdint|stdio|stdlib|stdnoreturn|string|tgmath|threads|time|uchar|wchar|wctype)\.h>$' + Priority: 1 + - Regex: '^<(ctype|dlfcn|fcntl|glob|limits|locale|poll|pthread|regex|semaphore|unistd|sys/mman|sys/stat|sys/types)\.h>$' + Priority: 2 + - Regex: '^<(elf|features|getopt|langinfo)\.h>$' + Priority: 3 + - Regex: '^<(linux/[^.]*|sys/eventfd)\.h>$' + Priority: 4 + - Regex: '^<(plugins/[^.]*|backing_store[^.]*|collection|compositor_ng|cursor|dmabuf_surface|egl_info|egl|flutter-pi|gbm_surface_backing_store|gl_renderer|gles|jsmn|keyboard|locales|modesetting|notifier_listener|pixel_format|platform_view|platformchannel|pluginregistry|surface[^.]*|texture_registry|tracer|user_input)\.h>$' + Priority: 6 + - Regex: '.*' + Priority: 5 + +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: AfterHash +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +PenaltyBreakAssignment: 60 +PenaltyBreakBeforeFirstCallParameter: 0 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 0 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: CaseInsensitive +SortUsingDeclarations: false +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +StatementMacros: + - 'DECLARE_REF_OPS' + - 'DEFINE_REF_OPS' + - 'DEFINE_STATIC_REF_OPS' + - 'DECLARE_LOCK_OPS' + - 'DEFINE_LOCK_OPS' + - 'DEFINE_STATIC_LOCK_OPS' + - 'DEFINE_INLINE_LOCK_OPS' + - 'UUID' + - 'CONST_UUID' +TabWidth: 4 +TypenameMacros: + - 'BMAP_ELEMENT_TYPE' + - 'MAX_ALIGNMENT' +UseTab: Never diff --git a/.gitignore b/.gitignore index a6af02d6..6a8bc10a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ .vscode -.clang-format build \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a096f824..42e581ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,15 @@ option(BUILD_RAW_KEYBOARD_PLUGIN "Include the raw keyboard plugin in the finishe 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." OFF) option(BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN "Include the gstreamer_video_player plugin in the finished binary. Allows for more stable, hardware accelerated video playback in flutter using gstreamer." ON) +option(ENABLE_OPENGL "Build with EGL/OpenGL rendering support." ON) +option(TRY_ENABLE_OPENGL "Don't throw an error if EGL/OpenGL aren't found, instead just build without EGL/OpenGL support in that case." ON) +option(ENABLE_VULKAN "Build with Vulkan rendering support." OFF) +option(TRY_ENABLE_VULKAN "Don't throw an error if vulkan isn't found, instead just build without vulkan support in that case." OFF) +set(VULKAN_DEBUG "AUTO" CACHE STRING "Enable vulkan validation layers and verbose vulkan logging. (ON/OFF/AUTO)") +set_property(CACHE VULKAN_DEBUG PROPERTY STRINGS ON OFF AUTO) +option(ENABLE_SOFTWARE "Build with software rendering support." ON) option(DUMP_ENGINE_LAYERS "True if flutter-pi should dump the list of rendering layers that the flutter engine sends to flutter-pi on each draw." OFF) +option(WARN_MISSING_FIELD_INITIALIZERS "True of the compiler should be instructed to warn about missing field initializers. This needs some hacky workarounds in the code so gcc won't report spurious warnings, so this should only be enabled if the warnings are explicitly required." OFF) 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) @@ -103,8 +111,6 @@ message(STATUS "PKG_CONFIG_PATH ........ $ENV{PKG_CONFIG_PATH}") 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) @@ -115,7 +121,9 @@ add_executable(flutter-pi src/platformchannel.c src/pluginregistry.c src/texture_registry.c - src/compositor.c + # src/compositor.c + src/gl_renderer.c + src/vk_renderer.c src/modesetting.c src/collection.c src/cursor.c @@ -124,14 +132,19 @@ add_executable(flutter-pi src/locales.c src/notifier_listener.c src/pixel_format.c + src/compositor_ng.c + src/surface.c + src/backing_store.c + src/gbm_surface_backing_store.c + src/vk_gbm_backing_store.c + src/tracer.c + src/dmabuf_surface.c src/plugins/services.c ) target_link_libraries(flutter-pi ${DRM_LDFLAGS} ${GBM_LDFLAGS} - ${EGL_LDFLAGS} - ${GLESV2_LDFLAGS} ${LIBSYSTEMD_LDFLAGS} ${LIBINPUT_LDFLAGS} ${LIBUDEV_LDFLAGS} @@ -145,8 +158,6 @@ target_include_directories(flutter-pi PRIVATE ${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} @@ -156,19 +167,103 @@ target_include_directories(flutter-pi PRIVATE target_compile_options(flutter-pi PRIVATE ${DRM_CFLAGS} ${GBM_CFLAGS} - ${EGL_CFLAGS} - ${GLESV2_CFLAGS} ${LIBSYSTEMD_CFLAGS} ${LIBINPUT_CFLAGS} ${LIBUDEV_CFLAGS} ${LIBXKBCOMMON_CFLAGS} - $<$<CONFIG:Debug>:-O0 -Wall -Wextra -Wno-unused-function -Wno-sign-compare -Wno-missing-field-initializers -Werror -ggdb -DDEBUG> - $<$<CONFIG:RelWithDebInfo>:-O2 -Wall -Wextra -Wno-unused-function -Wno-sign-compare -Wno-missing-field-initializers -ggdb> - $<$<CONFIG:Release>:-O2 -Wall -Wextra -Wno-unused-function -Wno-sign-compare -Wno-missing-field-initializers -ggdb> + $<$<CONFIG:Debug>:-O0 -Wall -Wextra -Werror -Wno-sign-compare -ggdb -DDEBUG> # -Wno-unused-function + $<$<CONFIG:RelWithDebInfo>:-O2 -Wall -Wextra -Wno-unused-function -Wno-sign-compare -ggdb> + $<$<CONFIG:Release>:-O2 -Wall -Wextra -Wno-unused-function -Wno-sign-compare -ggdb> ) # 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(flutter-pi PRIVATE HAS_KMS HAS_GBM HAS_FBDEV) + +if (WARN_MISSING_FIELD_INITIALIZERS) + target_compile_options(flutter-pi PRIVATE -Wmissing-field-initializers -Wno-error=missing-field-initializers) + target_compile_definitions(flutter-pi PRIVATE WARN_MISSING_FIELD_INITIALIZERS) +else() + target_compile_options(flutter-pi PRIVATE -Wno-missing-field-initializers) +endif() + +if (ENABLE_OPENGL) + if (TRY_ENABLE_OPENGL) + pkg_check_modules(EGL egl) + pkg_check_modules(GLESV2 glesv2) + else() + pkg_check_modules(EGL REQUIRED egl) + pkg_check_modules(GLESV2 REQUIRED glesv2) + endif() + + if (EGL_FOUND) + target_compile_definitions(flutter-pi PRIVATE HAS_EGL) + target_include_directories(flutter-pi PRIVATE ${EGL_INCLUDE_DIRS}) + target_compile_options(flutter-pi PRIVATE ${EGL_CFLAGS}) + target_link_libraries(flutter-pi ${EGL_LDFLAGS}) + else() + FetchContent_Declare( + EGL_REGISTRY + GIT_REPOSITORY "https://github.com/KhronosGroup/EGL-Registry" + GIT_TAG "main" + ) + FetchContent_MakeAvailable(EGL_REGISTRY) + target_include_directories(flutter-pi PRIVATE ${EGL_REGISTRY_SOURCE_DIR}/api/) + endif() + + if (GLESV2_FOUND) + target_compile_definitions(flutter-pi PRIVATE HAS_GL) + target_include_directories(flutter-pi PRIVATE ${GLESV2_INCLUDE_DIRS}) + target_compile_options(flutter-pi PRIVATE ${GLESV2_CFLAGS}) + target_link_libraries(flutter-pi ${GLESV2_LDFLAGS}) + else() + FetchContent_Declare( + OGL_REGISTRY + GIT_REPOSITORY "https://github.com/KhronosGroup/OpenGL-Registry" + GIT_TAG "main" + ) + FetchContent_MakeAvailable(OGL_REGISTRY) + target_include_directories(flutter-pi PRIVATE ${OGL_REGISTRY_SOURCE_DIR}/api/) + endif() + + if ( NOT (GLESV2_FOUND AND EGL_FOUND)) + if (TRY_ENABLE_OPENGL) + message("EGL and/or OpenGL was not found. Flutter-pi will build without EGL/OpenGL rendering support.") + else() + message(SEND_ERROR "EGL and/or OpenGL was not found. Try building with `-DTRY_ENABLE_OPENGL=On` if you want to just disable EGL/OpenGL support in that case.") + endif() + endif() +endif() + +if (ENABLE_VULKAN) + if (TRY_ENABLE_VULKAN) + pkg_check_modules(VULKAN vulkan) + else() + pkg_check_modules(VULKAN REQUIRED vulkan) + endif() + + if (VULKAN_FOUND) + target_compile_definitions(flutter-pi PRIVATE HAS_VULKAN) + target_include_directories(flutter-pi PRIVATE ${VULKAN_INCLUDE_DIRS}) + target_compile_options(flutter-pi PRIVATE ${VULKAN_CFLAGS}) + target_link_libraries(flutter-pi ${VULKAN_LDFLAGS}) + else() + FetchContent_Declare( + VULKAN_HEADERS + GIT_REPOSITORY "https://github.com/KhronosGroup/Vulkan-Headers" + GIT_TAG "main" + ) + FetchContent_MakeAvailable(VULKAN_HEADERS) + target_include_directories(flutter-pi PRIVATE ${VULKAN_HEADERS_SOURCE_DIR}/include/) + endif() + + if ( NOT VULKAN_FOUND ) + if (TRY_ENABLE_VULKAN) + message("Vulkan was not found. Flutter-pi will build without vulkan rendering support.") + else() + message(SEND_ERROR "Vulkan was not found. Try building with `-DTRY_ENABLE_VULKAN=On` if you want to just disable vulkan support in that case.") + endif() + endif() +endif() # TODO: We actually don't need the compile definitions anymore, except for # text input and raw keyboard plugin (because those have special treatment @@ -230,9 +325,13 @@ endif() # Needed so dart VM can actually resolve symbols in the same # executable. -target_link_options(flutter-pi PRIVATE - -rdynamic -) +target_link_options(flutter-pi PRIVATE -rdynamic) + +if (VULKAN_DEBUG STREQUAL "AUTO") + target_compile_definitions(flutter-pi PRIVATE $<$<CONFIG:Debug>:VULKAN_DEBUG>) +elseif (VULKAN_DEBUG) + target_compile_definitions(flutter-pi PRIVATE VULKAN_DEBUG) +endif() if (ENABLE_TSAN) target_link_options(flutter-pi PRIVATE -fsanitize=thread) diff --git a/include/backing_store.h b/include/backing_store.h new file mode 100644 index 00000000..5c597d51 --- /dev/null +++ b/include/backing_store.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +/* + * Backing Stores + * + * - implements flutter backing stores (used for the compositor interface) + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_BACKING_STORE_H +#define _FLUTTERPI_INCLUDE_BACKING_STORE_H + +#include <collection.h> +#include <flutter_embedder.h> + +struct surface; +struct backing_store; + +#define CAST_BACKING_STORE_UNCHECKED(ptr) ((struct backing_store*) (ptr)) +#ifdef DEBUG +# define CAST_BACKING_STORE(ptr) __checked_cast_backing_store(ptr) +ATTR_PURE struct backing_store *__checked_cast_backing_store(void *ptr); +#else +# define CAST_BACKING_STORE(ptr) CAST_BACKING_STORE_UNCHECKED(ptr) +#endif + +int backing_store_fill(struct backing_store *store, FlutterBackingStore *fl_store); + +int backing_store_queue_present(struct backing_store *store, const FlutterBackingStore *fl_store); + +#endif // _FLUTTERPI_INCLUDE_BACKING_STORE_H diff --git a/include/backing_store_private.h b/include/backing_store_private.h new file mode 100644 index 00000000..ff874e2a --- /dev/null +++ b/include/backing_store_private.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +/* + * Backing store implementation + * + * - private implementation for backing stores + * - needed for implementing specific kinds of backing stores + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_BACKING_STORE_PRIVATE_H +#define _FLUTTERPI_INCLUDE_BACKING_STORE_PRIVATE_H + +#include <flutter_embedder.h> +#include <collection.h> +#include <surface_private.h> +#include <compositor_ng.h> + +struct backing_store { + struct surface surface; + + uuid_t uuid; + struct point size; + int (*fill)(struct backing_store *store, FlutterBackingStore *fl_store); + int (*queue_present)(struct backing_store *store, const FlutterBackingStore *fl_store); +}; + +int backing_store_init(struct backing_store *store, struct tracer *tracer, struct point size); + +void backing_store_deinit(struct surface *s); + +#endif // _FLUTTERPI_INCLUDE_BACKING_STORE_PRIVATE_H + + diff --git a/include/collection.h b/include/collection.h index 3035f010..855e69a5 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." @@ -455,6 +462,37 @@ 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__) || __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)) +#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); } @@ -495,50 +533,84 @@ 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(struct obj_name **objp, struct obj_name *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; \ +} + +#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; \ } #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); \ } @@ -552,4 +624,75 @@ 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 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])) + +#endif diff --git a/include/compositor_ng.h b/include/compositor_ng.h new file mode 100644 index 00000000..e7eb19ba --- /dev/null +++ b/include/compositor_ng.h @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: MIT +/* + * Compositor NG + * + * - newer flutter compositor + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_COMPOSITOR_NG_H +#define _FLUTTERPI_INCLUDE_COMPOSITOR_NG_H + +#include <flutter_embedder.h> +#include <collection.h> +#include <pixel_format.h> +#include <modesetting.h> +#include <flutter-pi.h> +#include <egl.h> + +struct compositor; + +struct point { + double x, y; +}; + +#define POINT(_x, _y) ((struct point) {.x = _x, .y = _y}) + +struct quad { + struct point 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(POINT(_x1, _y1), POINT(_x2, _y2), POINT(_x3, _y3), POINT(_x4, _y4)) + +struct aa_rect { + struct point 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 = POINT(offset_x, offset_y), .size = POINT(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 + }; +} + +ATTR_CONST static inline struct point transform_point(const FlutterTransformation transform, const struct point point) { + return POINT( + 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 FlutterTransformation 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 FlutterTransformation transform, const struct aa_rect rect) { + return transform_quad(transform, get_quad(rect)); +} + +ATTR_CONST static inline struct point point_swap_xy(const struct point point) { + return POINT(point.y, point.x); +} + +struct drm_connector_config { + uint32_t connector_type; + uint32_t connector_type_id; + + bool disable, primary; + + bool has_mode_size; + int mode_width, mode_height; + + bool has_mode_refreshrate; + int mode_refreshrate_n, mode_refreshrate_d; + + bool has_framebuffer_size; + int framebuffer_width, framebuffer_height; + + bool has_physical_dimensions; + int physical_width_mm, physical_height_mm; +}; + +struct drm_device_config { + bool has_path; + const char *path; + + size_t n_connector_configs; + struct drm_connector_config *connector_configs; +}; + +struct fbdev_device_config { + const char *path; + + bool has_physical_dimensions; + int physical_width_mm, physical_height_mm; +}; + +struct device_config { + bool is_drm, is_fbdev; + union { + struct drm_device_config drm_config; + struct fbdev_device_config fbdev_config; + }; +}; + +struct compositor_config { + bool has_use_hardware_cursor, use_hardware_cursor; + + bool has_forced_pixel_format; + enum pixfmt forced_pixel_format; + + size_t n_device_configs; + struct device_config *device_configs; +}; + +struct view_geometry { + struct point view_size, display_size; + FlutterTransformation display_to_view_transform; + FlutterTransformation view_to_display_transform; + double device_pixel_ratio; +}; + +struct clip_rect { + struct quad rect; + bool is_aa; + + struct aa_rect aa_rect; + + bool is_rounded; + struct point upper_left_corner_radius; + struct point upper_right_corner_radius; + struct point lower_right_corner_radius; + struct point lower_left_corner_radius; +}; + +/// TODO: Remove +enum fl_layer_type { + kBackingStore_FlLayerType, + kPlatformView_FlLayerType +}; + +struct fl_layer_props { + /** + * @brief True if the presentation quadrangle (the quadrangle on the target window into which the + * layer should be rendered) is an axis-aligned rectangle. For example, allows us to use a plain + * hardware overlay layer for this layer. + * + * This should always be true for backing stores, but might be false for platform views. + */ + bool is_aa_rect; + + /** + * @brief The coords of the axis aligned rectangle if @ref is_aa_rect is true. + */ + struct aa_rect aa_rect; + + /** + * @brief The quadrangle on the target window into which the layer should be rendered. + */ + struct quad quad; + + /** + * @brief Opacity as a normalized float from 0 (transparent) to 1 (opqaue). + */ + double opacity; + + /** + * @brief Rotation of the buffer in degrees clockwise, normalized to a range 0 - 360. + */ + double rotation; + + /** + * @brief The number of clip rectangles in the @ref clip_rects array. + */ + size_t n_clip_rects; + + /** + * @brief The (possibly rounded) rectangles that the surface should be clipped to. + */ + struct clip_rect *clip_rects; +}; + +struct fl_layer { + struct fl_layer_props props; + struct surface *surface; +}; + +struct fl_layer_composition { + refcount_t n_refs; + size_t n_layers; + struct fl_layer layers[]; +}; + +enum present_mode { + kDoubleBufferedVsync_PresentMode, + kTripleBufferedVsync_PresentMode +}; + +struct drmdev; +struct compositor; + +typedef void (*compositor_frame_begin_cb_t)(void *userdata, uint64_t vblank_ns, uint64_t next_vblank_ns); + +ATTR_MALLOC struct compositor *compositor_new( + struct drmdev *drmdev, + struct tracer *tracer, + struct gl_renderer *renderer, + bool has_rotation, drm_plane_transform_t rotation, + bool has_orientation, enum device_orientation orientation, + bool has_explicit_dimensions, int width_mm, int height_mm, + EGLConfig egl_config, + bool has_forced_pixel_format, + enum pixfmt forced_pixel_format, + bool use_frame_requests, + enum present_mode present_mode +); + +ATTR_MALLOC struct compositor *compositor_new_vulkan( + struct drmdev *drmdev, + struct tracer *tracer, + struct vk_renderer *renderer, + bool has_rotation, + drm_plane_transform_t rotation, + bool has_orientation, + enum device_orientation orientation, + bool has_explicit_dimensions, + int width_mm, + int height_mm, + bool has_forced_pixel_format, + enum pixfmt forced_pixel_format, + bool use_frame_requests, + enum present_mode present_mode +); + +void compositor_destroy(struct compositor *compositor); + +DECLARE_REF_OPS(compositor) + +void compositor_get_view_geometry(struct compositor *compositor, struct view_geometry *view_geometry_out); + +ATTR_PURE double compositor_get_refresh_rate(struct compositor *compositor); + +int compositor_get_next_vblank(struct compositor *compositor, uint64_t *next_vblank_ns_out); + +int compositor_set_platform_view(struct compositor *compositor, int64_t id, struct surface *surface); + +struct surface *compositor_get_view_by_id_locked(struct compositor *compositor, int64_t view_id); + +const FlutterCompositor *compositor_get_flutter_compositor(struct compositor *compositor); + +int compositor_request_frame(struct compositor *compositor, compositor_frame_begin_cb_t cb, void *userdata); + +bool compositor_has_egl_surface(struct compositor *compositor); + +EGLSurface compositor_get_egl_surface(struct compositor *compositor); + +int compositor_get_event_fd(struct compositor *compositor); + +int compositor_on_event_fd_ready(struct compositor *compositor); + +ATTR_PURE EGLConfig egl_choose_config_with_pixel_format(EGLDisplay egl_display, const EGLint *config_attribs, enum pixfmt pixel_format); + +#endif // _FLUTTERPI_INCLUDE_COMPOSITOR_NG_H diff --git a/include/dmabuf_surface.h b/include/dmabuf_surface.h new file mode 100644 index 00000000..dec31cd1 --- /dev/null +++ b/include/dmabuf_surface.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +/* + * linux dmabuf surface + * + * - present dmabufs on screen as optimally as possible + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_DMABUF_SURFACE_H +#define _FLUTTERPI_INCLUDE_DMABUF_SURFACE_H + +#include <pixel_format.h> + +struct dmabuf_surface; + +#define CAST_DMABUF_SURFACE_UNCHECKED(ptr) ((struct dmabuf_surface*) (ptr)) +#ifdef DEBUG +# define CAST_DMABUF_SURFACE(ptr) __checked_cast_dmabuf_surface(ptr) +ATTR_PURE struct dmabuf_surface *__checked_cast_dmabuf_surface(void *ptr); +#else +# define CAST_DMABUF_SURFACE(ptr) CAST_DMABUF_SURFACE_UNCHECKED(ptr) +#endif + +struct dmabuf { + enum pixfmt format; + int width, height; + int fds[4]; + int offsets[4]; + int strides[4]; + bool has_modifiers; + uint64_t modifiers[4]; + void *userdata; +}; + +typedef void (*dmabuf_release_cb_t)(struct dmabuf *buf); + +struct texture_registry; + +ATTR_MALLOC struct dmabuf_surface *dmabuf_surface_new(struct tracer *tracer, struct texture_registry *texture_registry); + +int dmabuf_surface_push_dmabuf(struct dmabuf_surface *s, const struct dmabuf *buf, dmabuf_release_cb_t release_cb); + +ATTR_PURE int64_t dmabuf_surface_get_texture_id(struct dmabuf_surface *s); + +#endif // _FLUTTERPI_INCLUDE_DMABUF_SURFACE_H diff --git a/include/egl.h b/include/egl.h new file mode 100644 index 00000000..189df233 --- /dev/null +++ b/include/egl.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +/* + * Just a shim for including EGL headers, and disabling EGL function prototypes if EGL is not present + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_EGL_H +#define _FLUTTERPI_INCLUDE_EGL_H + +#include <stdbool.h> +#include <string.h> + +#ifdef HAS_EGL + +# include <EGL/egl.h> +# include <EGL/eglext.h> + +#else + +// If the system doesn't have EGL installed, we'll clone the official EGL headers and include them, +// but don't declare the function prototypes so we don't accidentally use one. +# define EGL_EGL_PROTOTYPES 0 +# include <EGL/egl.h> +# include <EGL/eglext.h> + +#endif + +static inline bool check_egl_extension(const char *client_ext_string, const char *display_ext_string, const char *extension) { + size_t len = strlen(extension); + + if (client_ext_string != NULL) { + const char *result = strstr(client_ext_string, extension); + if (result != NULL && (result[len] == ' ' || result[len] == '\0')) { + return true; + } + } + + if (display_ext_string != NULL) { + const char *result = strstr(display_ext_string, extension); + if (result != NULL && (result[len] == ' ' || result[len] == '\0')) { + return true; + } + } + + return false; +} + +static inline const char *egl_strerror(EGLenum result) { + switch (result) { + case EGL_SUCCESS: return "EGL_SUCCESS"; + case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED"; + case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS"; + case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC"; + case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE"; + case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG"; + case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT"; + case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE"; + case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY"; + case EGL_BAD_MATCH: return "EGL_BAD_MATCH"; + case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP"; + case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW"; + case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER"; + case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE"; + case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST"; + default: return "<unknown result code>"; + } +} + +#define LOG_EGL_ERROR(result, fmt, ...) LOG_ERROR(fmt ": %s\n", __VA_ARGS__ egl_strerror(result)) + + +#endif // _FLUTTERPI_INCLUDE_EGL_H diff --git a/include/egl_info.h b/include/egl_info.h new file mode 100644 index 00000000..d550547a --- /dev/null +++ b/include/egl_info.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +/* + * Utility for working with EGL and EGL Extensions + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_EGL_INFO_H +#define _FLUTTERPI_INCLUDE_EGL_INFO_H + +#include <egl.h> + +#define EGL_EXTENSION_LIST(EXT, FUN) \ + EXT(EGL_KHR_no_config_context) \ + EXT(EGL_MESA_drm_image) \ + EXT(EGL_KHR_image) \ + EXT(EGL_KHR_image_base) \ + FUN(EGL_KHR_image_base, PFNEGLCREATEIMAGEKHRPROC, eglCreateImageKHR) \ + FUN(EGL_KHR_image_base, PFNEGLDESTROYIMAGEKHRPROC, eglDestroyImageKHR) \ + EXT(EGL_EXT_image_dma_buf_import_modifiers), \ + FUN(EGL_EXT_image_dma_buf_import_modifiers, PFNEGLQUERYDMABUFFORMATSEXTPROC, eglQueryDmaBufFormatsEXT) \ + FUN(EGL_EXT_image_dma_buf_import_modifiers, PFNEGLQUERYDMABUFMODIFIERSEXTPROC, eglQueryDmaBufModifiersEXT) \ + EXT(EGL_KHR_gl_renderbuffer_image) \ + EXT(EGL_EXT_image_dma_buf_import) \ + EXT(EGL_MESA_image_dma_buf_export) \ + FUN(EGL_MESA_image_dma_buf_export, PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC, eglExportDMABUFImageQueryMESA), \ + FUN(EGL_MESA_image_dma_buf_export, PFNEGLEXPORTDMABUFIMAGEMESAPROC, eglExportDMABUFImageMESA) + +struct egl_display_info { +#define EXT(ext_name_str) bool supports_##ext_name_str; +#define FUN(ext_name_str, function_type, function_name) function_type function_name; +EGL_EXTENSION_LIST(EXT, FuN) +#undef FUN +#undef EXT +}; + +static inline void fill_display_info(struct egl_display_info *info, const char *egl_client_exts, const char *egl_display_exts, PFNEGLGETPROCADDRESSPROC get_proc_address) { + const char *namestr; + bool *supported; + +#define EXT(ext_name_str) \ + info->supports_##ext_name_str = (strstr(egl_client_exts, #ext_name_str) != NULL) || (strstr(egl_display_exts, #ext_name_str) != NULL); +#define FUN(ext_name_str, function_type, function_name) \ + info->function_name = get_proc_address(#function_name); \ + if (info->supports_##ext_name_str && info->function_name == NULL) { \ + LOG_ERROR_UNPREFIXED("EGL Extension " #ext_name_str " is listed as supported but EGL procedure " #function_name " could not be resolved.\n"); \ + } + + EGL_EXTENSION_LIST(EXT, FUN) + +#undef FUN +#undef EXT +} + +#endif // _FLUTTERPI_INCLUDE_EGL_INFO_H diff --git a/include/flutter-pi.h b/include/flutter-pi.h index 1d946490..57ac49c0 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -18,77 +18,33 @@ #include <xf86drmMode.h> #include <libinput.h> #include <systemd/sd-event.h> -#include <EGL/egl.h> -//#define EGL_EGLEXT_PROTOTYPES -#include <EGL/eglext.h> -#include <GLES2/gl2.h> -//#define GL_GLEXT_PROTOTYPES -#include <GLES2/gl2ext.h> +#include <egl.h> +#include <gles.h> #include <flutter_embedder.h> -#include <modesetting.h> #include <collection.h> -#include <keyboard.h> - -#define LOAD_EGL_PROC(flutterpi_struct, name, full_name) \ - do { \ - (flutterpi_struct).egl.name = (void*) eglGetProcAddress(#full_name); \ - if ((flutterpi_struct).egl.name == NULL) { \ - fprintf(stderr, "[flutter-pi] FATAL: Could not resolve EGL procedure " #full_name "\n"); \ - return EINVAL; \ - } \ - } while (false) - -#define LOAD_GL_PROC(flutterpi_struct, name, full_name) \ - do { \ - (flutterpi_struct).gl.name = (void*) eglGetProcAddress(#full_name); \ - if ((flutterpi_struct).gl.name == NULL) { \ - fprintf(stderr, "[flutter-pi] FATAL: Could not resolve GL procedure " #full_name "\n"); \ - return EINVAL; \ - } \ - } while (false) enum device_orientation { kPortraitUp, kLandscapeLeft, kPortraitDown, kLandscapeRight }; -/// TODO: Use flutter_embedder.h proctable instead -struct libflutter_engine { - FlutterEngineResult (*FlutterEngineCreateAOTData)(const FlutterEngineAOTDataSource* source, FlutterEngineAOTData* data_out); - FlutterEngineResult (*FlutterEngineCollectAOTData)(FlutterEngineAOTData data); - FlutterEngineResult (*FlutterEngineRun)(size_t version, const FlutterRendererConfig* config, const FlutterProjectArgs* args, void* user_data, FlutterEngine *engine_out); - FlutterEngineResult (*FlutterEngineShutdown)(FlutterEngine engine); - FlutterEngineResult (*FlutterEngineInitialize)(size_t version, const FlutterRendererConfig* config, const FlutterProjectArgs* args, void* user_data, FlutterEngine *engine_out); - FlutterEngineResult (*FlutterEngineDeinitialize)(FlutterEngine engine); - FlutterEngineResult (*FlutterEngineRunInitialized)(FlutterEngine engine); - FlutterEngineResult (*FlutterEngineSendWindowMetricsEvent)(FlutterEngine engine, const FlutterWindowMetricsEvent* event); - FlutterEngineResult (*FlutterEngineSendPointerEvent)(FlutterEngine engine, const FlutterPointerEvent* events, size_t events_count); - FlutterEngineResult (*FlutterEngineSendPlatformMessage)(FlutterEngine engine, const FlutterPlatformMessage* message); - FlutterEngineResult (*FlutterPlatformMessageCreateResponseHandle)(FlutterEngine engine, FlutterDataCallback data_callback, void* user_data, FlutterPlatformMessageResponseHandle** response_out); - FlutterEngineResult (*FlutterPlatformMessageReleaseResponseHandle)(FlutterEngine engine, FlutterPlatformMessageResponseHandle* response); - FlutterEngineResult (*FlutterEngineSendPlatformMessageResponse)(FlutterEngine engine, const FlutterPlatformMessageResponseHandle* handle, const uint8_t* data, size_t data_length); - FlutterEngineResult (*__FlutterEngineFlushPendingTasksNow)(); - FlutterEngineResult (*FlutterEngineRegisterExternalTexture)(FlutterEngine engine, int64_t texture_identifier); - FlutterEngineResult (*FlutterEngineUnregisterExternalTexture)(FlutterEngine engine, int64_t texture_identifier); - FlutterEngineResult (*FlutterEngineMarkExternalTextureFrameAvailable)(FlutterEngine engine, int64_t texture_identifier); - FlutterEngineResult (*FlutterEngineUpdateSemanticsEnabled)(FlutterEngine engine, bool enabled); - FlutterEngineResult (*FlutterEngineUpdateAccessibilityFeatures)(FlutterEngine engine, FlutterAccessibilityFeature features); - FlutterEngineResult (*FlutterEngineDispatchSemanticsAction)(FlutterEngine engine, uint64_t id, FlutterSemanticsAction action, const uint8_t* data, size_t data_length); - FlutterEngineResult (*FlutterEngineOnVsync)(FlutterEngine engine, intptr_t baton, uint64_t frame_start_time_nanos, uint64_t frame_target_time_nanos); - FlutterEngineResult (*FlutterEngineReloadSystemFonts)(FlutterEngine engine); - void (*FlutterEngineTraceEventDurationBegin)(const char* name); - void (*FlutterEngineTraceEventDurationEnd)(const char* name); - void (*FlutterEngineTraceEventInstant)(const char* name); - FlutterEngineResult (*FlutterEnginePostRenderThreadTask)(FlutterEngine engine, VoidCallback callback, void* callback_data); - uint64_t (*FlutterEngineGetCurrentTime)(); - FlutterEngineResult (*FlutterEngineRunTask)(FlutterEngine engine, const FlutterTask* task); - FlutterEngineResult (*FlutterEngineUpdateLocales)(FlutterEngine engine, const FlutterLocale** locales, size_t locales_count); - bool (*FlutterEngineRunsAOTCompiledDartCode)(void); - FlutterEngineResult (*FlutterEnginePostDartObject)(FlutterEngine engine, FlutterEngineDartPort port, const FlutterEngineDartObject* object); - FlutterEngineResult (*FlutterEngineNotifyLowMemoryWarning)(FlutterEngine engine); - FlutterEngineResult (*FlutterEnginePostCallbackOnAllNativeThreads)(FlutterEngine engine, FlutterNativeThreadCallback callback, void* user_data); - FlutterEngineResult (*FlutterEngineNotifyDisplayUpdate)(FlutterEngine engine, FlutterEngineDisplaysUpdateType update_type, const FlutterEngineDisplay* displays, size_t display_count); -}; +#define ORIENTATION_IS_LANDSCAPE(orientation) ((orientation) == kLandscapeLeft || (orientation) == kLandscapeRight) +#define ORIENTATION_IS_PORTRAIT(orientation) ((orientation) == kPortraitUp || (orientation) == kPortraitDown) +#define ORIENTATION_IS_VALID(orientation) ((orientation) == kPortraitUp || (orientation) == kLandscapeLeft || (orientation) == kPortraitDown || (orientation) == kLandscapeRight) + +#define ORIENTATION_ROTATE_CW(orientation) ( \ + (orientation) == kPortraitUp ? kLandscapeLeft : \ + (orientation) == kLandscapeLeft ? kPortraitDown : \ + (orientation) == kPortraitDown ? kLandscapeRight : \ + (orientation) == kLandscapeRight ? kPortraitUp : (assert(0 && "invalid device orientation"), 0) \ + ) + +#define ORIENTATION_ROTATE_CCW(orientation) ( \ + (orientation) == kPortraitUp ? kLandscapeRight : \ + (orientation) == kLandscapeLeft ? kPortraitUp : \ + (orientation) == kPortraitDown ? kLandscapeLeft : \ + (orientation) == kLandscapeRight ? kPortraitDown : (assert(0 && "invalid device orientation"), 0) \ + ) #define ANGLE_FROM_ORIENTATION(o) \ ((o) == kPortraitUp ? 0 : \ @@ -156,7 +112,7 @@ struct libflutter_engine { .pers0 = a.transX, .pers1 = a.transY, .pers2 = a.pers2, \ }) -static inline void apply_flutter_transformation( +static inline void apply_flutter_transformation_( const FlutterTransformation t, double *px, double *py @@ -195,39 +151,22 @@ static inline void apply_flutter_transformation( #define LIBINPUT_EVENT_IS_KEYBOARD(event_type) (\ ((event_type) == LIBINPUT_EVENT_KEYBOARD_KEY)) -enum frame_state { - kFramePending, - kFrameRendering, - kFrameRendered -}; - -struct frame { - /// The current state of the frame. - /// - Pending, when the frame was requested using the FlutterProjectArgs' vsync_callback. - /// - Rendering, when the baton was returned to the engine - /// - Rendered, when the frame has been / is visible on the display. - enum frame_state state; - - /// The baton to be returned to the flutter engine when the frame can be rendered. - intptr_t baton; -}; - -struct compositor; enum flutter_runtime_mode { - kDebug, kRelease + kDebug, kProfile, kRelease }; +struct compositor; struct plugin_registry; struct texture_registry; +struct drmdev; +struct locales; +struct vk_renderer; struct flutterpi { /// graphics stuff struct { struct drmdev *drmdev; - drmEventContext evctx; - sd_event_source *drm_pageflip_event_source; - bool platform_supports_get_sequence_ioctl; } drm; struct { @@ -237,42 +176,9 @@ struct flutterpi { uint64_t modifier; } gbm; - struct { - EGLDisplay display; - EGLConfig config; - EGLContext root_context; - EGLContext flutter_render_context; - EGLContext flutter_resource_uploading_context; - EGLContext compositor_context; - - /// Used to lock the @ref temp_context, to be sure we only try to make it current on - /// one thread. - pthread_mutex_t temp_context_lock; - - /// An EGL context that's only made current to create new contexts, - /// for example when some native code calls @ref flutterpi_create_egl_context - /// to get a new context. - EGLContext temp_context; - - EGLSurface surface; - - char *renderer; - - PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplay; - PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC createPlatformWindowSurface; - PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC createPlatformPixmapSurface; - PFNEGLCREATEDRMIMAGEMESAPROC createDRMImageMESA; - PFNEGLEXPORTDRMIMAGEMESAPROC exportDRMImageMESA; - } egl; - - struct { - PFNGLEGLIMAGETARGETTEXTURE2DOESPROC EGLImageTargetTexture2DOES; - PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC EGLImageTargetRenderbufferStorageOES; - } gl; - struct { /// width & height of the display in pixels. - int width, height; + // int width, height; /// physical width & height of the display in millimeters /// the physical size can only be queried for HDMI displays (and even then, most displays will @@ -280,63 +186,63 @@ struct flutterpi { /// for DSI displays, the physical size of the official 7-inch display will be set in init_display. /// init_display will only update width_mm and height_mm if they are set to zero, allowing you /// to hardcode values for you individual display. - int width_mm, height_mm; + // int width_mm, height_mm; - int refresh_rate; + // int refresh_rate; /// The pixel ratio used by flutter. /// This is computed inside init_display using width_mm and height_mm. /// flutter only accepts pixel ratios >= 1.0 - double pixel_ratio; + // double pixel_ratio; } display; struct { /// This is false when the value in the "orientation" field is /// unset. (Since we can't just do orientation = null) - bool has_orientation; + // bool has_orientation; /// The current device orientation. /// The initial device orientation is based on the width & height data from drm, /// or given as command line arguments. - enum device_orientation orientation; + // enum device_orientation orientation; - bool has_rotation; + // bool has_rotation; /// The angle between the initial device orientation and the current device orientation in degrees. /// (applied as a rotation to the flutter window in transformation_callback, and also /// is used to determine if width/height should be swapped when sending a WindowMetrics event to flutter) - int rotation; + // int rotation; /// width & height of the flutter view. These are the dimensions send to flutter using /// [FlutterEngineSendWindowMetricsEvent]. (So, for example, with rotation == 90, these /// dimensions are swapped compared to the display dimensions) - int width, height; + // int width, height; - int width_mm, height_mm; + // int width_mm, height_mm; /// Used by flutter to transform the flutter view to fill the display. /// Matrix that transforms flutter view coordinates to display coordinates. - FlutterTransformation view_to_display_transform; + // FlutterTransformation view_to_display_transform; /// Used by the touch input to transform raw display coordinates into flutter view coordinates. /// Matrix that transforms display coordinates into flutter view coordinates - FlutterTransformation display_to_view_transform; + // FlutterTransformation display_to_view_transform; } view; - struct concurrent_queue frame_queue; - + struct tracer *tracer; struct compositor *compositor; + sd_event_source *compositor_event_source; /// IO - sd_event_source *user_input_event_source; struct user_input *user_input; + sd_event_source *user_input_event_source; /// Locales struct locales *locales; /// flutter stuff struct { - char *asset_bundle_path; + const char *asset_bundle_path; char *kernel_blob_path; char *app_elf_path; void *app_elf_handle; @@ -348,8 +254,10 @@ struct flutterpi { int engine_argc; char **engine_argv; enum flutter_runtime_mode runtime_mode; - struct libflutter_engine libflutter_engine; + FlutterEngineProcTable procs; FlutterEngine engine; + + bool next_frame_request_is_secondary; } flutter; /// main event loop @@ -361,6 +269,8 @@ struct flutterpi { /// flutter-pi internal stuff struct plugin_registry *plugin_registry; struct texture_registry *texture_registry; + struct gl_renderer *gl_renderer; + struct vk_renderer *vk_renderer; }; struct platform_task { @@ -410,6 +320,7 @@ int flutterpi_sd_event_add_io( ); int flutterpi_send_platform_message( + struct flutterpi *flutterpi, const char *channel, const uint8_t *restrict message, size_t message_size, @@ -436,9 +347,9 @@ int flutterpi_schedule_exit(void); struct gbm_device *flutterpi_get_gbm_device(struct flutterpi *flutterpi); -EGLDisplay flutterpi_get_egl_display(struct flutterpi *flutterpi); +bool flutterpi_has_gl_renderer(struct flutterpi *flutterpi); -EGLContext flutterpi_create_egl_context(struct flutterpi *flutterpi); +struct gl_renderer *flutterpi_get_gl_renderer(struct flutterpi *flutterpi); void flutterpi_trace_event_instant(struct flutterpi *flutterpi, const char *name); diff --git a/include/gbm_surface_backing_store.h b/include/gbm_surface_backing_store.h new file mode 100644 index 00000000..ff665ba0 --- /dev/null +++ b/include/gbm_surface_backing_store.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +/* + * GBM Surface backing store + * + * - public interface for backing stores which are based on GBM surfaces + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_GBM_SURFACE_BACKING_STORE_H +#define _FLUTTERPI_INCLUDE_GBM_SURFACE_BACKING_STORE_H + +#include <collection.h> +#include <pixel_format.h> +#include <compositor_ng.h> + +struct backing_store; +struct gbm_device; +struct gbm_surface_backing_store; + +#define CAST_GBM_SURFACE_BACKING_STORE_UNCHECKED(ptr) ((struct gbm_surface_backing_store*) (ptr)) +#ifdef DEBUG +# define CAST_GBM_SURFACE_BACKING_STORE(ptr) __checked_cast_gbm_surface_backing_store(ptr) +ATTR_PURE struct gbm_surface_backing_store *__checked_cast_gbm_surface_backing_store(void *ptr); +#else +# define CAST_GBM_SURFACE_BACKING_STORE(ptr) CAST_GBM_SURFACE_BACKING_STORE_UNCHECKED(ptr) +#endif + +ATTR_MALLOC struct gbm_surface_backing_store *gbm_surface_backing_store_new_with_egl_config( + struct tracer *tracer, + struct point size, + struct gbm_device *device, + struct gl_renderer *renderer, + enum pixfmt pixel_format, + EGLConfig egl_config +); + +ATTR_MALLOC struct gbm_surface_backing_store *gbm_surface_backing_store_new( + struct tracer *tracer, + struct point size, + struct gbm_device *device, + struct gl_renderer *renderer, + enum pixfmt pixel_format +); + +ATTR_PURE EGLSurface gbm_surface_backing_store_get_egl_surface(struct gbm_surface_backing_store *s); + +ATTR_PURE EGLConfig gbm_surface_backing_store_get_egl_config(struct gbm_surface_backing_store *s); + +#endif // _FLUTTERPI_INCLUDE_GBM_SURFACE_BACKING_STORE_H diff --git a/include/gl_renderer.h b/include/gl_renderer.h new file mode 100644 index 00000000..18a8e17e --- /dev/null +++ b/include/gl_renderer.h @@ -0,0 +1,72 @@ + +// SPDX-License-Identifier: MIT +/* + * EGL/OpenGL renderer + * + * Utilities for rendering with EGL/OpenGL. + * - creating/binding EGL contexts + * - using EGL extensions + * - setting up / cleaning up render threads + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_EGL_GL_RENDERER_H +#define _FLUTTERPI_INCLUDE_EGL_GL_RENDERER_H + +#include <collection.h> +#include <pixel_format.h> +#include <egl.h> + +struct gl_renderer *gl_renderer_new_from_gbm_device( + struct tracer *tracer, + struct gbm_device *gbm_device, + bool has_forced_pixel_format, + enum pixfmt pixel_format +); + +void gl_renderer_destroy(struct gl_renderer *renderer); + +DECLARE_REF_OPS(gl_renderer) + + +bool gl_renderer_has_forced_pixel_format(struct gl_renderer *renderer); + +enum pixfmt gl_renderer_get_forced_pixel_format(struct gl_renderer *renderer); + +bool gl_renderer_has_forced_egl_config(struct gl_renderer *renderer); + +EGLConfig gl_renderer_get_forced_egl_config(struct gl_renderer *renderer); + +struct gbm_device *gl_renderer_get_gbm_device(struct gl_renderer *renderer); + +int gl_renderer_make_flutter_rendering_context_current(struct gl_renderer *renderer, EGLSurface surface); + +int gl_renderer_make_flutter_resource_uploading_context_current(struct gl_renderer *renderer); + +int gl_renderer_make_flutter_setup_context_current(struct gl_renderer *renderer); + +int gl_renderer_clear_current(struct gl_renderer *renderer); + +EGLContext gl_renderer_create_context(struct gl_renderer *renderer); + +void *gl_renderer_get_proc_address(struct gl_renderer *renderer, const char* name); + +EGLDisplay gl_renderer_get_egl_display(struct gl_renderer *renderer); + +bool gl_renderer_supports_egl_extension(struct gl_renderer *renderer, const char *name); + +bool gl_renderer_supports_gl_extension(struct gl_renderer *renderer, const char *name); + +bool gl_renderer_is_llvmpipe(struct gl_renderer *renderer); + +int gl_renderer_make_this_a_render_thread(struct gl_renderer *renderer); + +void gl_renderer_cleanup_this_render_thread(); + +ATTR_PURE EGLConfig gl_renderer_choose_config(struct gl_renderer *renderer, bool has_desired_pixel_format, enum pixfmt desired_pixel_format); + +ATTR_PURE EGLConfig gl_renderer_choose_config_direct(struct gl_renderer *renderer, enum pixfmt pixel_format); + +#endif // _FLUTTERPI_INCLUDE_EGL_GL_RENDERER_H diff --git a/include/gles.h b/include/gles.h new file mode 100644 index 00000000..18a3ee80 --- /dev/null +++ b/include/gles.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +/* + * Just a shim for including EGL headers, and disabling EGL function prototypes if EGL is not present + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_GLES_H +#define _FLUTTERPI_INCLUDE_GLES_H + +#ifdef HAS_GL + +# include <GLES2/gl2.h> +# include <GLES2/gl2ext.h> + +#else + +// If the system doesn't have EGL installed, we'll clone the official EGL headers, +// but don't declare the function prototypes, so we don't accidentally use one. +# define GL_GLES_PROTOTYPES 0 +# include <GLES2/gl2.h> +# include <GLES2/gl2ext.h> + +#endif + +#endif // _FLUTTERPI_INCLUDE_GLES_H diff --git a/include/modesetting.h b/include/modesetting.h index 8ea1a766..7f241f5b 100644 --- a/include/modesetting.h +++ b/include/modesetting.h @@ -1,85 +1,424 @@ -#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 <hanneswinkler2000@web.de> + */ + +#ifndef _FLUTTERPI_INCLUDE_MODESETTING_H +#define _FLUTTERPI_INCLUDE_MODESETTING_H #include <stdbool.h> + #include <pthread.h> #include <xf86drm.h> #include <xf86drmMode.h> #include <collection.h> +#include <pixel_format.h> + +#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 +}; +*/ + +enum drm_plane_type { + kPrimary_DrmPlaneType = DRM_PLANE_TYPE_PRIMARY, + kOverlay_DrmPlaneType = DRM_PLANE_TYPE_OVERLAY, + kCursor_DrmPlaneType = DRM_PLANE_TYPE_CURSOR +}; + +struct drm_mode_blob { + int drm_fd; + uint32_t blob_id; + drmModeModeInfo mode; +}; + +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, + kSPI_DrmConnectorType = DRM_MODE_CONNECTOR_SPI }; -struct drmdev { - int fd; +enum drm_connection_state { + kConnected_DrmConnectionState = DRM_MODE_CONNECTED, + kDisconnected_DrmConnectionState = DRM_MODE_DISCONNECTED, + kUnknown_DrmConnectionState = DRM_MODE_UNKNOWNCONNECTION +}; + +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 +}; - pthread_mutex_t mutex; - bool supports_atomic_modesetting; +struct drm_connector { + uint32_t id; - size_t n_connectors; - struct drm_connector *connectors; + enum drm_connector_type type; + uint32_t type_id; - size_t n_encoders; - struct drm_encoder *encoders; + struct drm_connector_prop_ids ids; - size_t n_crtcs; - struct drm_crtc *crtcs; + int n_encoders; + uint32_t encoders[32]; - size_t n_planes; - struct drm_plane *planes; + 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; - drmModeRes *res; - drmModePlaneRes *plane_res; + struct { + uint32_t crtc_id; + uint32_t encoder_id; + } committed_state; +}; - 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_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 is supported if supported_formats[kARGB8888] 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; +}; + +struct drmdev; +struct _drmModeModeInfo; + +int drmdev_new_from_fd(struct drmdev **drmdev_out, int fd); + +int drmdev_new_from_path(struct drmdev **drmdev_out, const char *path); + +void drmdev_destroy(struct drmdev *drmdev); + +DECLARE_REF_OPS(drmdev) int drmdev_configure( struct drmdev *drmdev, @@ -89,219 +428,167 @@ int drmdev_configure( const drmModeModeInfo *mode ); -int drmdev_plane_get_type( - struct drmdev *drmdev, - uint32_t plane_id -); +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); -int drmdev_plane_supports_setting_rotation_value( - struct drmdev *drmdev, - uint32_t plane_id, - int drm_rotation, - bool *result -); +struct gbm_device *drmdev_get_gbm_device(struct drmdev *drmdev); -int drmdev_plane_get_min_zpos_value( +uint32_t drmdev_add_fb( struct drmdev *drmdev, - uint32_t plane_id, - int64_t *min_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, + uint32_t flags ); -int drmdev_plane_get_max_zpos_value( +uint32_t drmdev_add_fb_multiplanar( 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_handles[4], + uint32_t pitches[4], + uint32_t offsets[4], + bool has_modifiers, + uint64_t modifiers[4], + uint32_t flags ); -int drmdev_plane_supports_setting_zpos( +uint32_t drmdev_add_fb_from_dmabuf( struct drmdev *drmdev, - uint32_t plane_id, - 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, + uint32_t flags ); -int drmdev_plane_supports_setting_zpos_value( +uint32_t drmdev_add_fb_from_dmabuf_multiplanar( struct drmdev *drmdev, - uint32_t plane_id, - int64_t zpos, - 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], + uint32_t flags ); -int drmdev_new_atomic_req( - struct drmdev *drmdev, - struct drmdev_atomic_req **req_out -); +int drmdev_rm_fb(struct drmdev *drmdev, uint32_t fb_id); -void drmdev_destroy_atomic_req( - struct drmdev_atomic_req *req -); +int drmdev_get_last_vblank(struct drmdev *drmdev, uint32_t crtc_id, uint64_t *last_vblank_ns_out); -int drmdev_atomic_req_put_connector_property( - struct drmdev_atomic_req *req, - const char *name, - uint64_t value -); +static inline double mode_get_vrefresh(const drmModeModeInfo *mode) { + return mode->clock * 1000.0 / (mode->htotal * mode->vtotal); +} -int drmdev_atomic_req_put_crtc_property( - struct drmdev_atomic_req *req, - const char *name, - uint64_t value -); +typedef void (*kms_scanout_cb_t)(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata); -int drmdev_atomic_req_put_plane_property( - struct drmdev_atomic_req *req, - uint32_t plane_id, - const char *name, - uint64_t value -); +struct kms_fb_layer { + uint32_t drm_fb_id; + enum pixfmt format; + bool has_modifier; + uint64_t modifier; -int drmdev_atomic_req_put_modeset_props( - struct drmdev_atomic_req *req, - uint32_t *flags -); + int32_t src_x, src_y, src_w, src_h; + int32_t dst_x, dst_y, dst_w, dst_h; -inline static int drmdev_atomic_req_reserve_plane( - struct drmdev_atomic_req *req, - struct drm_plane *plane -) { - return pset_remove(&req->available_planes, plane); -} + bool has_rotation; + drm_plane_transform_t rotation; -int drmdev_atomic_req_commit( - struct drmdev_atomic_req *req, - uint32_t flags, - void *userdata -); + bool has_in_fence_fd; + int in_fence_fd; +}; -int drmdev_legacy_set_mode_and_fb( - struct drmdev *drmdev, - uint32_t fb_id -); +typedef void (*kms_fb_release_cb_t)(void *userdata); -/** - * @brief Do a nonblocking, vblank-synced framebuffer swap. - */ -int drmdev_legacy_primary_plane_pageflip( - struct drmdev *drmdev, - uint32_t fb_id, +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, 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 -); +int kms_req_builder_add_scanout_callback(struct kms_req_builder *builder, kms_scanout_cb_t callback, void *userdata); -int drmdev_legacy_set_crtc_property( - struct drmdev *drmdev, - const char *name, - uint64_t value -); +void kms_req_builder_call_release_callbacks(struct kms_req_builder *builder); -int drmdev_legacy_set_plane_property( - struct drmdev *drmdev, - uint32_t plane_id, - const char *name, - uint64_t value -); +struct kms_req; -float mode_get_vrefresh(const drmModeModeInfo *mode); +DECLARE_REF_OPS(kms_req); -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; - } - } +struct kms_req *kms_req_builder_build(struct kms_req_builder *builder); - return NULL; -} +int kms_req_commit(struct kms_req *req, bool blocking); -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; -} +void kms_req_call_scanout_callbacks(struct kms_req *req, uint64_t vblank_ns); -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; -} +void kms_req_call_release_callbacks(struct kms_req *req); -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; -} +void kms_req_call_post_release_callbacks(struct kms_req *req, uint64_t vblank_ns); -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; -} +struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector); + +struct drm_encoder *__next_encoder(const struct drmdev *drmdev, const struct drm_encoder *encoder); + +struct drm_crtc *__next_crtc(const struct drmdev *drmdev, const struct drm_crtc *crtc); + +struct drm_plane *__next_plane(const struct drmdev *drmdev, const struct drm_plane *plane); + +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/pixel_format.h b/include/pixel_format.h index cf03fc1c..f47b56a3 100644 --- a/include/pixel_format.h +++ b/include/pixel_format.h @@ -24,6 +24,8 @@ struct fbdev_pixfmt { #include <drm_fourcc.h> #endif +#include <vulkan.h> + /** * @brief A specific pixel format. Use @ref get_pixfmt_info to get information * about this pixel format. @@ -31,29 +33,63 @@ struct fbdev_pixfmt { */ enum pixfmt { kRGB565, + kARGB4444, + kXRGB4444, + kARGB1555, + kXRGB1555, kARGB8888, kXRGB8888, kBGRA8888, + kBGRX8888, kRGBA8888, - kMax_PixFmt = kRGBA8888, + kRGBX8888, + kMax_PixFmt = kRGBX8888, 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); +COMPILE_ASSERT(kMax_PixFmt == kRGBX8888); + + +// 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, /*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, /*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, /*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, /*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, /*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, /*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, /*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, /*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, /*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, /*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, /*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, /*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, /*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, /*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, /*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, /*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) { + return kXRGB8888; + } else if (format == kARGB4444) { + return kXRGB4444; + } else if (format == kARGB1555) { + return kXRGB1555; + } else if (format == kBGRA8888) { + return kBGRX8888; + } else if (format == kRGBA8888) { + return kRGBX8888; + } + + /// TODO: We're potentially returning a non-opaque format here. + return format; +} + /** * @brief Information about a pixel format. * @@ -84,6 +120,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 +145,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 +164,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 == 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/include/platform_view.h b/include/platform_view.h new file mode 100644 index 00000000..55f8a7dc --- /dev/null +++ b/include/platform_view.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +/* + * Platform Views + * + * - implements flutter platform views (for the compositor interface) + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_PLATFORM_VIEW_H +#define _FLUTTERPI_INCLUDE_PLATFORM_VIEW_H + +#include <collection.h> + +struct platform_view; +struct kms_req_builder; +struct fbdev_commit_builder; + +struct platform_view *platform_view_new(); + +void platform_view_destroy(struct platform_view *view); + +DECLARE_REF_OPS(platform_view) + +int platform_view_register(struct platform_view *view); + +void platform_view_unregister(struct platform_view *view); + +int platform_view_present_kms( + struct platform_view *view, + const struct fl_layer *layer, + struct kms_req_builder *builder +); + +int platform_view_present_fbdev( + struct platform_view *view, + const struct fl_layer *layer, + struct fbdev_commit_builder *builder +); + +#endif // _FLUTTERPI_INCLUDE_PLATFORM_VIEW_H diff --git a/include/platformchannel.h b/include/platformchannel.h index acbe3805..67ef18c2 100644 --- a/include/platformchannel.h +++ b/include/platformchannel.h @@ -91,10 +91,6 @@ struct std_value { }; }; -struct std_value_v2 { - enum std_value_type type; -}; - #define STDVALUE_IS_NULL(value) ((value).type == kStdNull) #define STDNULL ((struct std_value) {.type = kStdNull}) @@ -116,7 +112,25 @@ struct std_value_v2 { #define STDVALUE_IS_STRING(value) ((value).type == kStdString) #define STDVALUE_AS_STRING(value) ((value).string_value) -#define STDSTRING(str) ((struct std_value) {.type = kStdString, .string_value = (str)}) + + +#if defined(WARN_MISSING_FIELD_INITIALIZERS) && (defined(__GNUC__) || defined(__clang__)) +# define STDSTRING(str) (*({ \ + struct std_value *value = alloca(sizeof(struct std_value)); \ + memset(value, 0, sizeof *value); \ + value->type = kStdString; \ + value->string_value = (str); \ + value; \ + })) +#else +// somehow GCC warns about field "values" being uninitialized, even though it isn't. +// so if we haven't enabled warning about missing field initializers, we can use this, otherwise +// we somehow need to silence it. +# define STDSTRING(str) ((struct std_value) {.type = kStdString, .string_value = (str)}) +# ifdef WARN_MISSING_FIELD_INITIALIZERS +# pragma warning "Warning about missing field initializers but neither clang nor gcc is used - spurious warnings will ocurr." +# endif +#endif #define STDVALUE_IS_LIST(value) ((value).type == kStdList) #define STDVALUE_IS_SIZE(value, _size) ((value).size == (_size)) @@ -593,6 +607,8 @@ struct std_value_v2 { } \ }) +#pragma GCC diagnostic pop + /// codec of an abstract channel object /// These tell this API how it should encode ChannelObjects -> platform messages /// and how to decode platform messages -> ChannelObjects. diff --git a/include/plugins/gstreamer_video_player.h b/include/plugins/gstreamer_video_player.h index b9316235..c3eef686 100644 --- a/include/plugins/gstreamer_video_player.h +++ b/include/plugins/gstreamer_video_player.h @@ -2,10 +2,8 @@ #define _FLUTTERPI_INCLUDE_PLUGINS_OMXPLAYER_VIDEO_PLUGIN_H #include <collection.h> -#include <EGL/egl.h> -#include <EGL/eglext.h> -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> +#include <egl.h> +#include <gles.h> enum format_hint { kNoFormatHint, @@ -198,6 +196,7 @@ struct notifier *gstplayer_get_error_notifier(struct gstplayer *player); struct video_frame; +struct gl_renderer; struct frame_interface { struct gbm_device *gbm_device; @@ -216,7 +215,7 @@ struct frame_interface { refcount_t n_refs; }; -struct frame_interface *frame_interface_new(); +struct frame_interface *frame_interface_new(struct gl_renderer *renderer); DEFINE_INLINE_LOCK_OPS(frame_interface, context_lock) diff --git a/include/surface.h b/include/surface.h new file mode 100644 index 00000000..b8d40a0a --- /dev/null +++ b/include/surface.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +/* + * Surface + * + * - rendering / scanout surface interface + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_SURFACE_H +#define _FLUTTERPI_INCLUDE_SURFACE_H + +#include <collection.h> + +struct surface; +struct compositor; +struct fl_layer_props; +struct kms_req_builder; +struct fbdev_commit_builder; + +#define CAST_SURFACE_UNCHECKED(ptr) ((struct surface*) (ptr)) +#ifdef DEBUG +# define CAST_SURFACE(ptr) __checked_cast_surface(ptr) +ATTR_PURE struct surface *__checked_cast_surface(void *ptr); +#else +# define CAST_SURFACE(ptr) CAST_SURFACE_UNCHECKED(ptr) +#endif + +void surface_destroy(struct surface *s); + +DECLARE_LOCK_OPS(surface) + +DECLARE_REF_OPS(surface) + +static inline void surface_unref_void(void *ptr) { + DEBUG_ASSERT_NOT_NULL(ptr); + return surface_unref(CAST_SURFACE(ptr)); +} + +ATTR_PURE static inline struct surface *surface_from_id(int64_t id) { + return CAST_SURFACE(int64_to_ptr(id)); +} + +ATTR_PURE int64_t surface_get_revision(struct surface *s); + +int surface_present_kms( + struct surface *s, + const struct fl_layer_props *props, + struct kms_req_builder *builder +); + +int surface_present_fbdev( + struct surface *s, + const struct fl_layer_props *props, + struct fbdev_commit_builder *builder +); + +#endif // _FLUTTERPI_INCLUDE_SURFACE_H diff --git a/include/surface_private.h b/include/surface_private.h new file mode 100644 index 00000000..5c666ddd --- /dev/null +++ b/include/surface_private.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +/* + * Surface Implementation details + * + * - should be included for expanding the surface (i.e. for a backing store or platform view surface type) + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_SURFACE_PRIVATE_H +#define _FLUTTERPI_INCLUDE_SURFACE_PRIVATE_H + +#include <stdint.h> +#include <pthread.h> +#include <collection.h> + +struct fl_layer_props; +struct kms_req_builder; +struct fbdev_commit_builder; +struct tracer; + +struct surface { + uuid_t uuid; + refcount_t n_refs; + pthread_mutex_t lock; + struct tracer *tracer; + int64_t revision; + + int (*present_kms)(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); + int (*present_fbdev)(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); + void (*deinit)(struct surface *s); +}; + +int surface_init(struct surface *s, struct tracer *tracer); + +void surface_deinit(struct surface *s); + +#endif // _FLUTTERPI_INCLUDE_SURFACE_PRIVATE_H diff --git a/include/texture_registry.h b/include/texture_registry.h index cf578ae4..8f77d9d4 100644 --- a/include/texture_registry.h +++ b/include/texture_registry.h @@ -1,7 +1,7 @@ #ifndef _TEXTURE_REGISTRY_H #define _TEXTURE_REGISTRY_H -#include <GLES2/gl2.h> +#include <gles.h> #include <flutter_embedder.h> struct flutter_external_texture_interface { diff --git a/include/thirdparty/cJSON.c b/include/thirdparty/cJSON.c new file mode 100644 index 00000000..524ba464 --- /dev/null +++ b/include/thirdparty/cJSON.c @@ -0,0 +1,3119 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <stdlib.h> +#include <limits.h> +#include <ctype.h> +#include <float.h> + +#ifdef ENABLE_LOCALES +#include <locale.h> +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0/0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return NULL; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 15) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = NULL; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + { + return NULL; + } + if (strlen(valuestring) <= strlen(object->valuestring)) + { + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == NULL) + { + return NULL; + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = sprintf((char*)number_buffer, "null"); + } + else if(d == (double)item->valueint) + { + length = sprintf((char*)number_buffer, "%d", item->valueint); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) + { + hooks->deallocate(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) + { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return false; + } + + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL)) + { + return NULL; + } + + if (item != parent->child) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + else if (item->next == NULL) + { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0) + { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + return add_item_to_array(array, newitem); + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (parent->child == item) + { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } + parent->child = replacement; + } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (replacement->next == NULL) + { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return false; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (replacement->string == NULL) + { + return false; + } + + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); +} diff --git a/include/thirdparty/cJSON.h b/include/thirdparty/cJSON.h new file mode 100644 index 00000000..95a9cf69 --- /dev/null +++ b/include/thirdparty/cJSON.h @@ -0,0 +1,300 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 15 + +#include <stddef.h> + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable address area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/ +#define cJSON_SetBoolValue(object, boolValue) ( \ + (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \ + (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \ + cJSON_Invalid\ +) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/tracer.h b/include/tracer.h new file mode 100644 index 00000000..2e225cb8 --- /dev/null +++ b/include/tracer.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +/* + * Tracer - simple object for tracing using flutter event tracing interface. + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_TRACER_H +#define _FLUTTERPI_INCLUDE_TRACER_H + +#include <flutter_embedder.h> + +struct tracer; + +struct tracer *tracer_new_with_cbs( + FlutterEngineTraceEventDurationBeginFnPtr trace_begin, + FlutterEngineTraceEventDurationEndFnPtr trace_end, + FlutterEngineTraceEventInstantFnPtr trace_instant +); + +struct tracer *tracer_new_with_stubs(); + +DECLARE_REF_OPS(tracer); + +void __tracer_begin(struct tracer *tracer, const char *name); + +void __tracer_end(struct tracer *tracer, const char *name); + +void __tracer_instant(struct tracer *tracer, const char *name); + +void tracer_set_cbs( + struct tracer *tracer, + FlutterEngineTraceEventDurationBeginFnPtr trace_begin, + FlutterEngineTraceEventDurationEndFnPtr trace_end, + FlutterEngineTraceEventInstantFnPtr trace_instant +); + +#ifdef DEBUG +#define TRACER_BEGIN(tracer, name) __tracer_begin(tracer, name) +#define TRACER_END(tracer, name) __tracer_end(tracer, name) +#define TRACER_INSTANT(tracer, name) __tracer_instant(tracer, name) +#else +#define TRACER_BEGIN(tracer, name) do {} while (0) +#define TRACER_END(tracer, name) do {} while (0) +#define TRACER_INSTANT(tracer, name) do {} while (0) +#endif + +#define DECLARE_STATIC_TRACING_CALLS(obj_type_name, obj_var_name) \ +static void trace_begin(struct obj_type_name *obj_var_name, const char *name); \ +static void trace_end(struct obj_type_name *obj_var_name, const char *name); \ +static void trace_instant(struct obj_type_name *obj_var_name, const char *name); + +#define DEFINE_STATIC_TRACING_CALLS(obj_type_name, obj_var_name, tracer_member_name) \ +static void trace_begin(struct obj_type_name *obj_var_name, const char *name) { \ + return tracer_begin(obj_var_name->tracer_member_name, name); \ +} \ +static void trace_end(struct obj_type_name *obj_var_name, const char *name) { \ + return tracer_end(obj_var_name->tracer_member_name, name); \ +} \ +static void trace_instant(struct obj_type_name *obj_var_name, const char *name) { \ + return tracer_instant(obj_var_name->tracer_member_name, name); \ +} + +#endif // _FLUTTERPI_INCLUDE_TRACER_H diff --git a/include/vk_gbm_backing_store.h b/include/vk_gbm_backing_store.h new file mode 100644 index 00000000..66490c4d --- /dev/null +++ b/include/vk_gbm_backing_store.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +/* + * Vulkan GBM backing store + * + * - used as a render target for flutter vulkan rendering + * - can be scanned out using KMS + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_VK_GBM_BACKING_STORE_H +#define _FLUTTERPI_INCLUDE_VK_GBM_BACKING_STORE_H + +#include <collection.h> +#include <pixel_format.h> +#include <compositor_ng.h> + +struct tracer; +struct gbm_device; +struct vk_gbm_backing_store; + +#define CAST_VK_GBM_BACKING_STORE_UNCHECKED(ptr) ((struct vk_gbm_backing_store*) (ptr)) +#ifdef DEBUG +# define CAST_VK_GBM_BACKING_STORE(ptr) __checked_cast_vk_gbm_backing_store (ptr) +ATTR_PURE struct vk_gbm_backing_store *__checked_cast_vk_gbm_backing_store(void *ptr); +#else +# define CAST_VK_GBM_BACKING_STORE(ptr) CAST_VK_GBM_BACKING_STORE_UNCHECKED(ptr) +#endif + +ATTR_MALLOC struct vk_gbm_backing_store *vk_gbm_backing_store_new( + struct tracer *tracer, + struct point size, + struct gbm_device *device, + struct vk_renderer *renderer, + enum pixfmt pixel_format +); + +#endif // _FLUTTERPI_INCLUDE_VK_GBM_BACKING_STORE_H diff --git a/include/vk_renderer.h b/include/vk_renderer.h new file mode 100644 index 00000000..e1a9dfb7 --- /dev/null +++ b/include/vk_renderer.h @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT +/* + * Vulkan Renderer + * + * - provides a vulkan renderer object + * - a vulkan renderer object is basically a combination of: + * - a vulkan instance (VkInstance) + * - a vulkan physical device (VkPhysicalDevice) + * - a vulkan logical device (VkDevice) + * - a vulkan graphics queue (VkQueue) + * - a vulkan command buffer pool (VkCommandPool or something) + * - and utilities for using those + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_VK_RENDERER_H +#define _FLUTTERPI_INCLUDE_VK_RENDERER_H + +#include <vulkan.h> +#include <collection.h> + +struct vk_renderer; + +/** + * @brief Create a new vulkan renderer with some reasonable defaults. + * + * Creates a vulkan instance with: + * - app name `flutter-pi`, version 1.0.0 + * - engine `flutter-pi`, version 1.0.0 + * - vulkan version 1.1.0 + * - khronos validation layers and debug utils enabled, if supported and VULKAN_DEBUG is defined + * + * Selects a good physical device (dedicated GPU > integrated GPU > software) that has a + * graphics queue family and supports the following device extensions: + * - `VK_KHR_external_memory` + * - `VK_KHR_external_memory_fd` + * - `VK_KHR_external_semaphore` + * - `VK_KHR_external_semaphore_fd` + * - `VK_EXT_external_memory_dma_buf` + * - `VK_KHR_image_format_list` + * - `VK_EXT_image_drm_format_modifier` + * + * Those extensions will also be enabled when create the logical device of course. + * + * Will also create a graphics queue. + * + * @return New vulkan renderer instance. + */ +ATTR_MALLOC struct vk_renderer *vk_renderer_new(); + +void vk_renderer_destroy(); + +DECLARE_REF_OPS(vk_renderer) + +/** + * @brief Get the vulkan version of this renderer. This is unconditionally VK_MAKE_VERSION(1, 1, 0) for now. + * + * @param renderer renderer instance + * @return VK_MAKE_VERSION(1, 1, 0) + */ +ATTR_CONST uint32_t vk_renderer_get_vk_version(struct vk_renderer *renderer); + +/** + * @brief Get the vulkan instance of this renderer. See @ref vk_renderer_new for details on this instance. + * + * @param renderer renderer instance + * @return vulkan instance + */ +ATTR_PURE VkInstance vk_renderer_get_instance(struct vk_renderer *renderer); + +/** + * @brief Get the physical device that's used by this renderer. See @ref vk_renderer_new for details. + * + * @param renderer renderer instance + * @return vulkan physical device + */ +ATTR_PURE VkPhysicalDevice vk_renderer_get_physical_device(struct vk_renderer *renderer); + +/** + * @brief Get the logical device that's used by this renderer. See @ref vk_renderer_new for details. + * + * @param renderer renderer instance + * @return vulkan logical device + */ +ATTR_PURE VkDevice vk_renderer_get_device(struct vk_renderer *renderer); + +/** + * @brief Get the index of the graphics queue family. + * + * @param renderer renderer instance + * @return instance of the graphics queue family. + */ +ATTR_PURE uint32_t vk_renderer_get_queue_family_index(struct vk_renderer *renderer); + +/** + * @brief Get the graphics queue of this renderer. + * + * @param renderer renderer instance + * @return graphics queue + */ +ATTR_PURE VkQueue vk_renderer_get_queue(struct vk_renderer *renderer); + +ATTR_PURE int vk_renderer_get_enabled_instance_extension_count(struct vk_renderer *renderer); + +ATTR_PURE const char **vk_renderer_get_enabled_instance_extensions(struct vk_renderer *renderer); + +ATTR_PURE int vk_renderer_get_enabled_device_extension_count(struct vk_renderer *renderer); + +ATTR_PURE const char **vk_renderer_get_enabled_device_extensions(struct vk_renderer *renderer); + +/** + * @brief Find the index of a memory type for which the following conditions are true: + * - (1 < 32) & @param req_bits is not 0 + * - the memory types property flags support the flags that are given in @param flags + * + * @param renderer renderer instance + * @param flags Which property flags the memory type should support. + * @param req_bits Which memory types are allowed to choose from. + * @return index of the found memory type or -1 if none was found. + */ +ATTR_PURE int vk_renderer_find_mem_type(struct vk_renderer *renderer, VkMemoryPropertyFlags flags, uint32_t req_bits); + +#endif // _FLUTTERPI_INCLUDE_VK_RENDERER_H diff --git a/include/vulkan.h b/include/vulkan.h new file mode 100644 index 00000000..abcfe25f --- /dev/null +++ b/include/vulkan.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +/* + * Just a shim for including vulkan headers, and disabling vulkan function prototypes if vulkan is not present + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#ifndef _FLUTTERPI_INCLUDE_VULKAN_H +#define _FLUTTERPI_INCLUDE_VULKAN_H + +#ifdef HAS_VULKAN + +# include <vulkan/vulkan.h> + +#else + +// If the system doesn't have vulkan installed, we'll clone the official vulkan headers, +// but don't declare the function prototypes, so we don't accidentally use one. +# define VK_NO_PROTOTYPES 1 +# include <vulkan/vulkan.h> + +#endif + +static inline const char *vk_strerror(VkResult result) { + switch (result) { + case VK_SUCCESS: return "VK_SUCCESS"; + case VK_NOT_READY: return "VK_NOT_READY"; + case VK_TIMEOUT: return "VK_TIMEOUT"; + case VK_EVENT_SET: return "VK_EVENT_SET"; + case VK_EVENT_RESET: return "VK_EVENT_RESET"; + case VK_INCOMPLETE: return "VK_INCOMPLETE"; + case VK_ERROR_OUT_OF_HOST_MEMORY: return "VK_ERROR_OUT_OF_HOST_MEMORY"; + case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "VK_ERROR_OUT_OF_DEVICE_MEMORY"; + case VK_ERROR_INITIALIZATION_FAILED: return "VK_ERROR_INITIALIZATION_FAILED"; + case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST"; + case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED"; + case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT"; + case VK_ERROR_EXTENSION_NOT_PRESENT: return "VK_ERROR_EXTENSION_NOT_PRESENT"; + case VK_ERROR_FEATURE_NOT_PRESENT: return "VK_ERROR_FEATURE_NOT_PRESENT"; + case VK_ERROR_INCOMPATIBLE_DRIVER: return "VK_ERROR_INCOMPATIBLE_DRIVER"; + case VK_ERROR_TOO_MANY_OBJECTS: return "VK_ERROR_TOO_MANY_OBJECTS"; + case VK_ERROR_FORMAT_NOT_SUPPORTED: return "VK_ERROR_FORMAT_NOT_SUPPORTED"; + case VK_ERROR_FRAGMENTED_POOL: return "VK_ERROR_FRAGMENTED_POOL"; + case VK_ERROR_UNKNOWN: return "VK_ERROR_UNKNOWN"; + case VK_ERROR_OUT_OF_POOL_MEMORY: return "VK_ERROR_OUT_OF_POOL_MEMORY"; + case VK_ERROR_INVALID_EXTERNAL_HANDLE: return "VK_ERROR_INVALID_EXTERNAL_HANDLE"; + case VK_ERROR_FRAGMENTATION: return "VK_ERROR_FRAGMENTATION"; + case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: return "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; + case VK_ERROR_SURFACE_LOST_KHR: return "VK_ERROR_SURFACE_LOST_KHR"; + case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR"; + case VK_SUBOPTIMAL_KHR: return "VK_SUBOPTIMAL_KHR"; + case VK_ERROR_OUT_OF_DATE_KHR: return "VK_ERROR_OUT_OF_DATE_KHR"; + case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR"; + case VK_ERROR_VALIDATION_FAILED_EXT: return "VK_ERROR_VALIDATION_FAILED_EXT"; + case VK_ERROR_INVALID_SHADER_NV: return "VK_ERROR_INVALID_SHADER_NV"; + case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT"; + case VK_ERROR_NOT_PERMITTED_EXT: return "VK_ERROR_NOT_PERMITTED_EXT"; + case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"; + case VK_THREAD_IDLE_KHR: return "VK_THREAD_IDLE_KHR"; + case VK_THREAD_DONE_KHR: return "VK_THREAD_DONE_KHR"; + case VK_OPERATION_DEFERRED_KHR: return "VK_OPERATION_DEFERRED_KHR"; + case VK_OPERATION_NOT_DEFERRED_KHR: return "VK_OPERATION_NOT_DEFERRED_KHR"; + case VK_PIPELINE_COMPILE_REQUIRED_EXT: return "VK_PIPELINE_COMPILE_REQUIRED_EXT"; + default: return "<unknown result code>"; + } +} + +#define LOG_VK_ERROR(result, fmt, ...) LOG_ERROR(fmt ": %s\n", __VA_ARGS__ vk_strerror(result)) + +#endif // _FLUTTERPI_INCLUDE_VULKAN_H diff --git a/src/backing_store.c b/src/backing_store.c new file mode 100644 index 00000000..72a7195b --- /dev/null +++ b/src/backing_store.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +/* + * backing stores + * + * - simple flutter backing store implementation + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#include <stdlib.h> +#include <stdatomic.h> +#include <stdint.h> + +#include <collection.h> +#include <surface.h> +#include <backing_store.h> +#include <backing_store_private.h> +#include <compositor_ng.h> +#include <tracer.h> + +FILE_DESCR("flutter backing store") + +// just so we can be sure &backing_store->surface is the same as (struct surface*) backing_store +COMPILE_ASSERT(offsetof(struct backing_store, surface) == 0); + +static const uuid_t uuid = CONST_UUID(0x78, 0x70, 0x45, 0x13, 0xa8, 0xf3, 0x43, 0x34, 0xa0, 0xa3, 0xae, 0x90, 0xf1, 0x11, 0x41, 0xe0); + +void backing_store_deinit(struct surface *s); + +int backing_store_init(struct backing_store *store, struct tracer *tracer, struct point size) { + int ok; + + ok = surface_init(&store->surface, tracer); + if (ok != 0) { + return ok; + } + + store->surface.deinit = backing_store_deinit; + store->surface.present_kms = NULL; + store->surface.present_fbdev = NULL; + uuid_copy(&store->uuid, uuid); + store->size = size; + store->fill = NULL; + store->queue_present = NULL; + return 0; +} + +void backing_store_deinit(struct surface *s) { + surface_deinit(s); +} + +int backing_store_fill(struct backing_store *store, FlutterBackingStore *fl_store) { + int ok; + + DEBUG_ASSERT_NOT_NULL(store); + DEBUG_ASSERT_NOT_NULL(fl_store); + DEBUG_ASSERT_NOT_NULL(store->fill); + + DEBUG_ASSERT_EQUALS(fl_store->user_data, NULL); + DEBUG_ASSERT_EQUALS(fl_store->did_update, false); + + TRACER_BEGIN(store->surface.tracer, "backing_store_fill"); + ok = store->fill(store, fl_store); + TRACER_END(store->surface.tracer, "backing_store_fill"); + + DEBUG_ASSERT_EQUALS(fl_store->user_data, NULL); + DEBUG_ASSERT_EQUALS(fl_store->did_update, false); + + return ok; +} + +int backing_store_queue_present(struct backing_store *store, const FlutterBackingStore *fl_store) { + int ok; + + DEBUG_ASSERT_NOT_NULL(store); + DEBUG_ASSERT_NOT_NULL(fl_store); + DEBUG_ASSERT_NOT_NULL(store->queue_present); + + TRACER_BEGIN(store->surface.tracer, "backing_store_queue_present"); + ok = store->queue_present(store, fl_store); + TRACER_END(store->surface.tracer, "backing_store_queue_present"); + + return ok; +} + +#ifdef DEBUG +ATTR_PURE struct backing_store *__checked_cast_backing_store(void *ptr) { + struct backing_store *store; + + store = CAST_BACKING_STORE_UNCHECKED(ptr); + DEBUG_ASSERT(uuid_equals(store->uuid, uuid)); + return store; +} +#endif diff --git a/src/collection.c b/src/collection.c index 225fccb3..2d2a4f84 100644 --- a/src/collection.c +++ b/src/collection.c @@ -41,6 +41,9 @@ int queue_enqueue( ) { size_t new_size; + DEBUG_ASSERT_NOT_NULL(queue); + DEBUG_ASSERT_NOT_NULL(p_element); + if (queue->size == queue->length) { // expand the queue or wait for an element to be dequeued. @@ -79,15 +82,19 @@ int queue_dequeue( struct queue *queue, void *element_out ) { + DEBUG_ASSERT_NOT_NULL(queue); + if (queue->length == 0) { return EAGAIN; } - memcpy( - element_out, - ((char*) queue->elements) + (queue->element_size*queue->start_index), - queue->element_size - ); + if (element_out != NULL) { + memcpy( + element_out, + ((char*) queue->elements) + (queue->element_size*queue->start_index), + queue->element_size + ); + } queue->start_index = (queue->start_index + 1) & (queue->size - 1); queue->length--; @@ -99,6 +106,8 @@ int queue_peek( struct queue *queue, void **pelement_out ) { + DEBUG_ASSERT_NOT_NULL(queue); + if (queue->length == 0) { if (pelement_out != NULL) { *pelement_out = NULL; @@ -107,7 +116,7 @@ int queue_peek( } if (pelement_out != NULL) { - *pelement_out = ((char*) queue->elements) + (queue->element_size*queue->start_index); + *pelement_out = ((char*) (queue->elements)) + (queue->element_size*queue->start_index); } return 0; diff --git a/src/compositor_ng.c b/src/compositor_ng.c new file mode 100644 index 00000000..24163fda --- /dev/null +++ b/src/compositor_ng.c @@ -0,0 +1,1972 @@ +// SPDX-License-Identifier: MIT +/* + * compositor-ng + * + * - a reimplementation of the flutter compositor + * - takes flutter layers as input, composits them into multiple hw planes, outputs them to the modesetting interface + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + +#include <inttypes.h> +#include <math.h> +#include <stdlib.h> + +#include <pthread.h> +#include <semaphore.h> + +#ifdef HAS_GBM +# include <gbm.h> +#endif +#include <flutter_embedder.h> +#include <systemd/sd-event.h> + +#include <backing_store.h> +#include <collection.h> +#include <compositor_ng.h> +#include <egl.h> +#include <flutter-pi.h> +#include <gbm_surface_backing_store.h> +#include <vk_gbm_backing_store.h> +#include <gl_renderer.h> +#include <vk_renderer.h> +#include <modesetting.h> +#include <notifier_listener.h> +#include <pixel_format.h> +#include <surface.h> +#include <tracer.h> + +FILE_DESCR("compositor-ng") + +/** + * @brief A nicer, ref-counted version of the FlutterLayer's passed by the engine to the present layer callback. + * + * Differences to the FlutterLayer's passed to the present layer callback: + * - for platform views: + * - struct platform_view* object as the platform view instead of int64_t view id + * - position is given as a quadrilateral or axis-aligned rectangle instead of a bunch of (broken) transforms + * - same for clip rects + * - opacity and rotation as individual numbers + * - for backing stores: + * - struct backing_store* object instead of FlutterBackingStore + * - offset & size as as a struct aa_rect + * - refcounted + */ + +static struct fl_layer_composition *fl_layer_composition_new(size_t n_layers) { + struct fl_layer_composition *composition; + struct fl_layer *layers; + + composition = malloc((sizeof *composition) + (n_layers * sizeof *layers)); + if (composition == NULL) { + return NULL; + } + + composition->n_refs = REFCOUNT_INIT_1; + composition->n_layers = n_layers; + return composition; +} + +static size_t fl_layer_composition_get_n_layers(struct fl_layer_composition *composition) { + DEBUG_ASSERT_NOT_NULL(composition); + return composition->n_layers; +} + +static struct fl_layer *fl_layer_composition_peek_layer(struct fl_layer_composition *composition, int layer) { + DEBUG_ASSERT_NOT_NULL(composition); + DEBUG_ASSERT_NOT_NULL(composition->layers); + DEBUG_ASSERT(layer >= 0 && layer < composition->n_layers); + return composition->layers + layer; +} + +static void fl_layer_composition_destroy(struct fl_layer_composition *composition) { + DEBUG_ASSERT_NOT_NULL(composition); + + for (int i = 0; i < composition->n_layers; i++) { + surface_unref(composition->layers[i].surface); + if (composition->layers[i].props.clip_rects != NULL) { + free(composition->layers[i].props.clip_rects); + } + } + + free(composition); +} + +DEFINE_STATIC_REF_OPS(fl_layer_composition, n_refs) + +struct frame_req { + compositor_frame_begin_cb_t cb; + void *userdata; +#ifdef DEBUG + bool signaled; +#endif +}; + +/** + * @brief A single display / screen / window that flutter-pi can display flutter contents on. For example, this + * would be a drmdev with a specific CRTC, mode and connector, or a single fbdev. + * + */ +struct window { + struct compositor *compositor; + + pthread_mutex_t lock; + + /// Event tracing interface. + struct tracer *tracer; + + /// DRM device for showing the output. + struct drmdev *drmdev; + + /// GBM Device for allocating the graphics buffers. + struct gbm_device *gbm_device; + + /// The last presented composition. + struct fl_layer_composition *composition; + + /// @brief The main backing store. + /// (Due to the flutter embedder API architecture, we always need to have + /// a primary surface, other backing stores can only be framebuffers.) + struct backing_store *backing_store; + + /// @brief The EGL/GL compatible backing store if this is a normal egl/gl window. + struct gbm_surface_backing_store *egl_backing_store; + + /// @brief The vulkan compatible backing store if this is a vulkan window. + struct vk_gbm_backing_store *vk_backing_store; + + /// The frame request queue. + /// Normally, flutter would request a frame using the flutter engine vsync callback + /// supplied by the embedder, and the embedder would queue it and respond to it when + /// the engine can start rendering. However, since that callback is broken + /// (sometimes multiple frames are requested but engine won't start rendering), + /// instead we'll request and wait for frame begin in the window_push_composition function. + /// That way we make sure we don't accidentally present two frames per vblank period. + /// + /// @ref use_frame_requests is true when flutter engine requests frames (so it's always false + /// right now), false when we should request them ourselves. + struct queue frame_req_queue; + + /// The selected connector, encoder, crtc and mode that we should present the flutter graphics on. + struct drm_connector *selected_connector; + struct drm_encoder *selected_encoder; + struct drm_crtc *selected_crtc; + drmModeModeInfo *selected_mode; + + /// Refresh rate of the selected mode. + double refresh_rate; + + /// Flutter device pixel ratio (in the horizontal axis) + /// Number of physical pixels per logical pixel. + /// There are always 38 logical pixels per cm, or 96 per inch. + /// This is roughly equivalent to DPI / 100. + /// A device pixel ratio of 1.0 is roughly a dpi of 96, which is the most common + /// dpi for full-hd desktop displays. + /// To calculate this, the physical dimensions of the display are required. + /// If there are no physical dimensions this will default to 1.0. + double pixel_ratio; + + /// Whether we have physical screen dimensions and @ref width_mm and @ref height_mm + /// contain usable values. + bool has_dimensions; + + /// Width, height of the screen in millimeters. + int width_mm, height_mm; + + /// The rotation we should apply to the flutter layers to present them on screen. + drm_plane_transform_t rotation; + + /// The current device orientation and the original (startup) device orientation. + /// @ref original_orientation is kLandscapeLeft for displays that are more wide than high, + /// and kPortraitUp for displays that are more high than wide. + /// + /// @ref orientation should always equal to rotating @ref original_orientation clock-wise by the + /// angle in the @ref rotation field. + enum device_orientation orientation, original_orientation; + + /// @brief Matrix for transforming display coordinates to view coordinates. + /// For example for transforming pointer events (which are in the display coordinate space) + /// to flutter coordinates. + /// Useful if for example flutter has specified a custom device orientation (for example kPortraitDown), + /// in that case we of course also need to transform the touch coords. + FlutterTransformation display_to_view_transform; + + /// @brief Matrix for transforming view coordinates to display coordinates. + /// Can be used as a root surface transform, for fitting the flutter view into + /// the desired display frame. + /// Useful if for example flutter has specified a custom device orientation (for example kPortraitDown), + /// because we need to rotate the flutter view in that case. + FlutterTransformation view_to_display_transform; + + /// @brief True if flutter will request frames before attempting to render something. + /// False if the window should sync to vblank internally. + bool use_frame_requests; + + /// @brief True if we should set the mode on the next KMS req. + bool set_mode; + + /// @brief True if we should set @ref set_mode to false on the next commit. + bool set_set_mode; + + /// @brief True if we should use a specific pixel format. + bool has_forced_pixel_format; + + /// @brief The forced pixel format if @ref has_forced_pixel_format is true. + enum pixfmt forced_pixel_format; + + /// @brief The EGLConfig to use for creating any EGL surfaces. + /// Can be EGL_NO_CONFIG_KHR if backing stores should automatically select one. + EGLConfig egl_config; + + /// @brief How many buffers to use and how we should schedule frames. + /// Doesn't change after initialization. + enum present_mode present_mode; + + /// @brief If we're using triple buffering, this is the frame we're going to commit + /// next, once we can commit a frame again. (I.e., once the previous frame is + /// being scanned out) + /// Can be NULL if we don't have a frame we can commit next yet. + struct kms_req *next_frame; + + /// @brief If using triple buffering, this is true if the previous frame is already + /// being shown on screen and the next frame can be immediately queued / committed to + /// be shown on screen. + /// If this is false, @ref next_frame should be set to the next frame and @ref window_on_pageflip + /// will commit the next frame. + bool present_immediately; + + /// @brief The EGL/OpenGL renderer used to create any GL backing stores. + struct gl_renderer *renderer; + + /// @brief The vulkan renderer if this is a vulkan window. + struct vk_renderer *vk_renderer; +}; + +static void fill_view_matrices( + drm_plane_transform_t transform, + int display_width, + int display_height, + FlutterTransformation *display_to_view_transform_out, + FlutterTransformation *view_to_display_transform_out +) { + DEBUG_ASSERT(PLANE_TRANSFORM_IS_ONLY_ROTATION(transform)); + + if (transform.rotate_0) { + *view_to_display_transform_out = FLUTTER_TRANSLATION_TRANSFORMATION(0, 0); + + *display_to_view_transform_out = FLUTTER_TRANSLATION_TRANSFORMATION(0, 0); + } else if (transform.rotate_90) { + *view_to_display_transform_out = FLUTTER_ROTZ_TRANSFORMATION(90); + view_to_display_transform_out->transX = display_width; + + *display_to_view_transform_out = FLUTTER_ROTZ_TRANSFORMATION(-90); + display_to_view_transform_out->transY = display_width; + } else if (transform.rotate_180) { + *view_to_display_transform_out = FLUTTER_ROTZ_TRANSFORMATION(180); + view_to_display_transform_out->transX = display_width; + view_to_display_transform_out->transY = display_height; + + *display_to_view_transform_out = FLUTTER_ROTZ_TRANSFORMATION(-180); + display_to_view_transform_out->transX = display_width; + display_to_view_transform_out->transY = display_height; + } else if (transform.rotate_270) { + *view_to_display_transform_out = FLUTTER_ROTZ_TRANSFORMATION(270); + view_to_display_transform_out->transY = display_height; + + *display_to_view_transform_out = FLUTTER_ROTZ_TRANSFORMATION(-270); + display_to_view_transform_out->transX = display_height; + } +} + +static int select_mode( + struct drmdev *drmdev, + struct drm_connector **connector_out, + struct drm_encoder **encoder_out, + struct drm_crtc **crtc_out, + drmModeModeInfo **mode_out +) { + struct drm_connector *connector; + struct drm_encoder *encoder; + struct drm_crtc *crtc; + drmModeModeInfo *mode, *mode_iter; + + // find any connected connector + for_each_connector_in_drmdev(drmdev, connector) { + if (connector->variable_state.connection_state == kConnected_DrmConnectionState) { + break; + } + } + + if (connector == NULL) { + LOG_ERROR("Could not find a connected connector!\n"); + return EINVAL; + } + + // 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))) { + mode = mode_iter; + } + } + } + + if (mode == NULL) { + LOG_ERROR("Could not find a preferred output mode!\n"); + return EINVAL; + } + + // Find the encoder that's linked to the connector right now + for_each_encoder_in_drmdev(drmdev, encoder) { + if (encoder->encoder->encoder_id == connector->committed_state.encoder_id) { + break; + } + } + + // Otherwise use use any encoder that the connector supports linking to + if (encoder == NULL) { + for (int i = 0; i < connector->n_encoders; i++, encoder = NULL) { + for_each_encoder_in_drmdev(drmdev, encoder) { + if (encoder->encoder->encoder_id == connector->encoders[i]) { + break; + } + } + + if (encoder->encoder->possible_crtcs) { + // only use this encoder if there's a crtc we can use with it + break; + } + } + } + + if (encoder == NULL) { + LOG_ERROR("Could not find a suitable DRM encoder.\n"); + return EINVAL; + } + + // Find the CRTC that's currently linked to this encoder + for_each_crtc_in_drmdev(drmdev, crtc) { + if (crtc->id == encoder->encoder->crtc_id) { + break; + } + } + + // Otherwise use any CRTC that this encoder supports linking to + if (crtc == NULL) { + for_each_crtc_in_drmdev(drmdev, crtc) { + if (encoder->encoder->possible_crtcs & crtc->bitmask) { + // find a CRTC that is possible to use with this encoder + break; + } + } + } + + if (crtc == NULL) { + LOG_ERROR("Could not find a suitable DRM CRTC.\n"); + return EINVAL; + } + + *connector_out = connector; + *encoder_out = encoder; + *crtc_out = crtc; + *mode_out = mode; + return 0; +} + +static int window_init( + struct window *window, + struct compositor *compositor, + struct tracer *tracer, + struct drmdev *drmdev, + struct gl_renderer *renderer, + bool has_rotation, + drm_plane_transform_t rotation, + bool has_orientation, + enum device_orientation orientation, + bool has_explicit_dimensions, + int width_mm, + int height_mm, + bool use_frame_requests, + bool has_forced_pixel_format, + enum pixfmt forced_pixel_format, + EGLConfig egl_config, + enum present_mode present_mode +) { + enum device_orientation original_orientation; + struct drm_connector *selected_connector; + struct drm_encoder *selected_encoder; + struct drm_crtc *selected_crtc; + drmModeModeInfo *selected_mode; + double pixel_ratio; + bool has_dimensions; + int ok; + + DEBUG_ASSERT_NOT_NULL(window); + DEBUG_ASSERT_NOT_NULL(compositor); + DEBUG_ASSERT_NOT_NULL(tracer); + DEBUG_ASSERT_NOT_NULL(drmdev); + DEBUG_ASSERT_NOT_NULL(renderer); + DEBUG_ASSERT(!has_rotation || PLANE_TRANSFORM_IS_ONLY_ROTATION(rotation)); + DEBUG_ASSERT(!has_orientation || ORIENTATION_IS_VALID(orientation)); + DEBUG_ASSERT(!has_explicit_dimensions || (width_mm > 0 && height_mm > 0)); + //DEBUG_ASSERT_EQUALS_MSG(present_mode, kDoubleBufferedVsync_PresentMode, "Only double buffered vsync supported right now."); + + ok = queue_init(&window->frame_req_queue, sizeof(struct frame_req), QUEUE_DEFAULT_MAX_SIZE); + if (ok != 0) { + return ENOMEM; + } + + ok = select_mode(drmdev, &selected_connector, &selected_encoder, &selected_crtc, &selected_mode); + if (ok != 0) { + goto fail_deinit_queue; + } + + if (has_explicit_dimensions) { + has_dimensions = true; + } else if (selected_connector->variable_state.width_mm % 10 || selected_connector->variable_state.height_mm % 10) { + has_dimensions = true; + width_mm = selected_connector->variable_state.width_mm; + height_mm = selected_connector->variable_state.height_mm; + } else if (selected_connector->type == DRM_MODE_CONNECTOR_DSI + && selected_connector->variable_state.width_mm == 0 + && selected_connector->variable_state.height_mm == 0) { + has_dimensions = true; + width_mm = 155; + height_mm = 86; + } else { + has_dimensions = false; + } + + if (has_dimensions == false) { + LOG_DEBUG( + "WARNING: display didn't provide valid physical dimensions. The device-pixel ratio will default " + "to 1.0, which may not be the fitting device-pixel ratio for your display. \n" + "Use the `-d` commandline parameter to specify the physical dimensions of your display.\n" + ); + pixel_ratio = 1.0; + } else { + pixel_ratio = (10.0 * selected_mode->hdisplay) / (width_mm * 38.0); + + int horizontal_dpi = (int) (selected_mode->hdisplay / (width_mm / 25.4)); + int vertical_dpi = (int) (selected_mode->vdisplay / (height_mm / 25.4)); + + if (horizontal_dpi != vertical_dpi) { + // See https://github.com/flutter/flutter/issues/71865 for current status of this issue. + LOG_DEBUG("INFO: display has non-square pixels. Non-square-pixels are not supported by flutter.\n"); + } + } + + DEBUG_ASSERT(!has_rotation || PLANE_TRANSFORM_IS_ONLY_ROTATION(rotation)); + + if (selected_mode->hdisplay > selected_mode->vdisplay) { + original_orientation = kLandscapeLeft; + } else { + original_orientation = kPortraitUp; + } + + if (!has_rotation && !has_orientation) { + rotation = PLANE_TRANSFORM_ROTATE_0; + orientation = original_orientation; + has_rotation = true; + has_orientation = true; + } else if (!has_orientation) { + drm_plane_transform_t r = rotation; + orientation = original_orientation; + while (r.u64 != PLANE_TRANSFORM_ROTATE_0.u64) { + orientation = ORIENTATION_ROTATE_CW(orientation); + r = PLANE_TRANSFORM_ROTATE_CCW(r); + } + has_orientation = true; + } else if (!has_rotation) { + enum device_orientation o = orientation; + rotation = PLANE_TRANSFORM_ROTATE_0; + while (o != original_orientation) { + rotation = PLANE_TRANSFORM_ROTATE_CW(rotation); + o = ORIENTATION_ROTATE_CCW(o); + } + has_rotation = true; + } else { + enum device_orientation o = orientation; + drm_plane_transform_t r = rotation; + while (r.u64 != PLANE_TRANSFORM_ROTATE_0.u64) { + r = PLANE_TRANSFORM_ROTATE_CCW(r); + o = ORIENTATION_ROTATE_CCW(o); + } + + if (ORIENTATION_IS_LANDSCAPE(o) && !(selected_mode->hdisplay >= selected_mode->vdisplay)) { + LOG_DEBUG( + "Explicit orientation and rotation given, but orientation is inconsistent with orientation. (display " + "is more high than wide, but de-rotated orientation is landscape)\n" + ); + } else if (ORIENTATION_IS_PORTRAIT(o) && !(selected_mode->vdisplay >= selected_mode->hdisplay)) { + LOG_DEBUG( + "Explicit orientation and rotation given, but orientation is inconsistent with orientation. (display " + "is more wide than high, but de-rotated orientation is portrait)\n" + ); + } + + original_orientation = o; + } + + DEBUG_ASSERT(has_orientation && has_rotation); + + fill_view_matrices( + rotation, + selected_mode->hdisplay, + selected_mode->vdisplay, + &window->display_to_view_transform, + &window->view_to_display_transform + ); + + LOG_DEBUG_UNPREFIXED( + "===================================\n" + "display mode:\n" + " resolution: %" PRIu16 " x %" PRIu16 + "\n" + " refresh rate: %fHz\n" + " physical size: %dmm x %dmm\n" + " flutter device pixel ratio: %f\n" + "===================================\n", + selected_mode->hdisplay, + selected_mode->vdisplay, + mode_get_vrefresh(selected_mode), + width_mm, + height_mm, + pixel_ratio + ); + + window->compositor = compositor; + pthread_mutex_init(&window->lock, NULL); + window->tracer = tracer_ref(tracer); + window->drmdev = drmdev_ref(drmdev); + window->gbm_device = drmdev_get_gbm_device(drmdev); + window->composition = NULL; + window->backing_store = NULL; + window->egl_backing_store = NULL; + window->vk_backing_store = NULL; + window->selected_connector = selected_connector; + window->selected_crtc = selected_crtc; + window->selected_encoder = selected_encoder; + window->selected_mode = selected_mode; + window->refresh_rate = mode_get_vrefresh(selected_mode); + window->has_dimensions = has_dimensions; + window->width_mm = width_mm; + window->height_mm = height_mm; + window->pixel_ratio = pixel_ratio; + window->use_frame_requests = use_frame_requests; + window->rotation = rotation; + window->orientation = orientation; + window->original_orientation = original_orientation; + window->set_mode = true; + window->set_set_mode = true; /* doesn't really matter */ + window->has_forced_pixel_format = has_forced_pixel_format; + window->forced_pixel_format = forced_pixel_format; + window->egl_config = egl_config; + window->present_mode = present_mode; + window->present_immediately = true; + window->next_frame = NULL; + window->renderer = gl_renderer_ref(renderer); + window->vk_renderer = NULL; + window->vk_backing_store = NULL; + return 0; + +fail_deinit_queue: + queue_deinit(&window->frame_req_queue); + return ok; +} + +static int window_init_vulkan( + struct window *window, + struct compositor *compositor, + struct tracer *tracer, + struct drmdev *drmdev, + struct vk_renderer *renderer, + bool has_rotation, drm_plane_transform_t rotation, + bool has_orientation, enum device_orientation orientation, + bool has_explicit_dimensions, int width_mm, int height_mm, + bool use_frame_requests, + bool has_forced_pixel_format, + enum pixfmt forced_pixel_format, + enum present_mode present_mode +) { + enum device_orientation original_orientation; + struct drm_connector *selected_connector; + struct drm_encoder *selected_encoder; + struct drm_crtc *selected_crtc; + drmModeModeInfo *selected_mode; + double pixel_ratio; + bool has_dimensions; + int ok; + + DEBUG_ASSERT_NOT_NULL(window); + DEBUG_ASSERT_NOT_NULL(compositor); + DEBUG_ASSERT_NOT_NULL(tracer); + DEBUG_ASSERT_NOT_NULL(drmdev); + DEBUG_ASSERT_NOT_NULL(renderer); + DEBUG_ASSERT(!has_rotation || PLANE_TRANSFORM_IS_ONLY_ROTATION(rotation)); + DEBUG_ASSERT(!has_orientation || ORIENTATION_IS_VALID(orientation)); + DEBUG_ASSERT(!has_explicit_dimensions || (width_mm > 0 && height_mm > 0)); + //DEBUG_ASSERT_EQUALS_MSG(present_mode, kDoubleBufferedVsync_PresentMode, "Only double buffered vsync supported right now."); + + ok = queue_init(&window->frame_req_queue, sizeof(struct frame_req), QUEUE_DEFAULT_MAX_SIZE); + if (ok != 0) { + return ENOMEM; + } + + ok = select_mode(drmdev, &selected_connector, &selected_encoder, &selected_crtc, &selected_mode); + if (ok != 0) { + goto fail_deinit_queue; + } + + if (has_explicit_dimensions) { + has_dimensions = true; + } else if (selected_connector->variable_state.width_mm % 10 || selected_connector->variable_state.height_mm % 10) { + has_dimensions = true; + width_mm = selected_connector->variable_state.width_mm; + height_mm = selected_connector->variable_state.height_mm; + } else if (selected_connector->type == DRM_MODE_CONNECTOR_DSI + && selected_connector->variable_state.width_mm == 0 + && selected_connector->variable_state.height_mm == 0) { + has_dimensions = true; + width_mm = 155; + height_mm = 86; + } else { + has_dimensions = false; + } + + if (has_dimensions == false) { + LOG_DEBUG( + "WARNING: display didn't provide valid physical dimensions. The device-pixel ratio will default " + "to 1.0, which may not be the fitting device-pixel ratio for your display. \n" + "Use the `-d` commandline parameter to specify the physical dimensions of your display.\n" + ); + pixel_ratio = 1.0; + } else { + pixel_ratio = (10.0 * selected_mode->hdisplay) / (width_mm * 38.0); + + int horizontal_dpi = (int) (selected_mode->hdisplay / (width_mm / 25.4)); + int vertical_dpi = (int) (selected_mode->vdisplay / (height_mm / 25.4)); + + if (horizontal_dpi != vertical_dpi) { + // See https://github.com/flutter/flutter/issues/71865 for current status of this issue. + LOG_DEBUG("INFO: display has non-square pixels. Non-square-pixels are not supported by flutter.\n"); + } + } + + DEBUG_ASSERT(!has_rotation || PLANE_TRANSFORM_IS_ONLY_ROTATION(rotation)); + + if (selected_mode->hdisplay > selected_mode->vdisplay) { + original_orientation = kLandscapeLeft; + } else { + original_orientation = kPortraitUp; + } + + if (!has_rotation && !has_orientation) { + rotation = PLANE_TRANSFORM_ROTATE_0; + orientation = original_orientation; + has_rotation = true; + has_orientation = true; + } else if (!has_orientation) { + drm_plane_transform_t r = rotation; + orientation = original_orientation; + while (r.u64 != PLANE_TRANSFORM_ROTATE_0.u64) { + orientation = ORIENTATION_ROTATE_CW(orientation); + r = PLANE_TRANSFORM_ROTATE_CCW(r); + } + has_orientation = true; + } else if (!has_rotation) { + enum device_orientation o = orientation; + rotation = PLANE_TRANSFORM_ROTATE_0; + while (o != original_orientation) { + rotation = PLANE_TRANSFORM_ROTATE_CW(rotation); + o = ORIENTATION_ROTATE_CCW(o); + } + has_rotation = true; + } else { + enum device_orientation o = orientation; + drm_plane_transform_t r = rotation; + while (r.u64 != PLANE_TRANSFORM_ROTATE_0.u64) { + r = PLANE_TRANSFORM_ROTATE_CCW(r); + o = ORIENTATION_ROTATE_CCW(o); + } + + if (ORIENTATION_IS_LANDSCAPE(o) && !(selected_mode->hdisplay >= selected_mode->vdisplay)) { + LOG_DEBUG( + "Explicit orientation and rotation given, but orientation is inconsistent with orientation. (display " + "is more high than wide, but de-rotated orientation is landscape)\n" + ); + } else if (ORIENTATION_IS_PORTRAIT(o) && !(selected_mode->vdisplay >= selected_mode->hdisplay)) { + LOG_DEBUG( + "Explicit orientation and rotation given, but orientation is inconsistent with orientation. (display " + "is more wide than high, but de-rotated orientation is portrait)\n" + ); + } + + original_orientation = o; + } + + DEBUG_ASSERT(has_orientation && has_rotation); + + fill_view_matrices( + rotation, + selected_mode->hdisplay, + selected_mode->vdisplay, + &window->display_to_view_transform, + &window->view_to_display_transform + ); + + LOG_DEBUG_UNPREFIXED( + "===================================\n" + "display mode:\n" + " resolution: %" PRIu16 " x %" PRIu16 + "\n" + " refresh rate: %fHz\n" + " physical size: %dmm x %dmm\n" + " flutter device pixel ratio: %f\n" + "===================================\n", + selected_mode->hdisplay, + selected_mode->vdisplay, + mode_get_vrefresh(selected_mode), + width_mm, + height_mm, + pixel_ratio + ); + + window->compositor = compositor; + pthread_mutex_init(&window->lock, NULL); + window->tracer = tracer_ref(tracer); + window->drmdev = drmdev_ref(drmdev); + window->gbm_device = drmdev_get_gbm_device(drmdev); + window->composition = NULL; + window->backing_store = NULL; + window->egl_backing_store = NULL; + window->vk_backing_store = NULL; + window->selected_connector = selected_connector; + window->selected_crtc = selected_crtc; + window->selected_encoder = selected_encoder; + window->selected_mode = selected_mode; + window->refresh_rate = mode_get_vrefresh(selected_mode); + window->has_dimensions = has_dimensions; + window->width_mm = width_mm; + window->height_mm = height_mm; + window->pixel_ratio = pixel_ratio; + window->use_frame_requests = use_frame_requests; + window->rotation = rotation; + window->orientation = orientation; + window->original_orientation = original_orientation; + window->set_mode = true; + window->set_set_mode = true; /* doesn't really matter */ + window->has_forced_pixel_format = true; + window->forced_pixel_format = has_forced_pixel_format ? forced_pixel_format : kARGB8888; + window->egl_config = EGL_NO_CONFIG_KHR; + window->present_mode = present_mode; + window->present_immediately = true; + window->next_frame = NULL; + window->renderer = NULL; + window->vk_renderer = vk_renderer_ref(renderer); + return 0; + + fail_deinit_queue: + queue_deinit(&window->frame_req_queue); + return ok; +} + +MAYBE_UNUSED static void window_deinit(struct window *window) { + struct kms_req_builder *builder; + struct kms_req *req; + int ok; + + builder = drmdev_create_request_builder(window->drmdev, window->selected_crtc->id); + DEBUG_ASSERT_NOT_NULL(builder); + + ok = kms_req_builder_unset_mode(builder); + DEBUG_ASSERT_EQUALS(ok, 0); + + req = kms_req_builder_build(builder); + DEBUG_ASSERT_NOT_NULL(req); + + kms_req_builder_unref(builder); + + ok = kms_req_commit(req, true); + DEBUG_ASSERT_EQUALS(ok, 0); + (void) ok; + + kms_req_unref(req); + drmdev_unref(window->drmdev); + queue_deinit(&window->frame_req_queue); + tracer_unref(window->tracer); +} + +static struct window *window_ref(struct window *w) { + DEBUG_ASSERT_NOT_NULL(w); + compositor_ref(w->compositor); + return w; +} + +static void window_unref(struct window *w) { + DEBUG_ASSERT_NOT_NULL(w); + compositor_unref(w->compositor); +} + +MAYBE_UNUSED static void window_unrefp(struct window **w) { + DEBUG_ASSERT_NOT_NULL(w); + window_unref(*w); + *w = NULL; +} + +DEFINE_STATIC_LOCK_OPS(window, lock) + +static void window_get_view_geometry(struct window *window, struct view_geometry *view_geometry_out) { + struct point display_size; + + display_size = POINT(window->selected_mode->hdisplay, window->selected_mode->vdisplay); + + *view_geometry_out = (struct view_geometry){ + .view_size = (window->rotation.rotate_90 || window->rotation.rotate_270) + ? point_swap_xy(display_size) + : display_size, + .display_size = display_size, + .display_to_view_transform = window->display_to_view_transform, + .view_to_display_transform = window->view_to_display_transform, + .device_pixel_ratio = window->pixel_ratio, + }; +} + +ATTR_PURE static double window_get_refresh_rate(struct window *window) { + DEBUG_ASSERT_NOT_NULL(window); + return window->refresh_rate; +} + +static int window_get_next_vblank(struct window *window, uint64_t *next_vblank_ns_out) { + uint64_t last_vblank; + int ok; + + DEBUG_ASSERT_NOT_NULL(window); + DEBUG_ASSERT_NOT_NULL(next_vblank_ns_out); + + ok = drmdev_get_last_vblank(window->drmdev, window->selected_crtc->id, &last_vblank); + if (ok != 0) { + return ok; + } + + *next_vblank_ns_out = last_vblank + (uint64_t) (1000000000.0 / window->refresh_rate); + return 0; +} + +static int window_on_pageflip(struct window *window, uint64_t vblank_ns, uint64_t next_vblank_ns); + +static void on_scanout(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata) { + struct window *window; + int ok; + + DEBUG_ASSERT_NOT_NULL(drmdev); + DEBUG_ASSERT_NOT_NULL(userdata); + (void) drmdev; + window = userdata; + + ok = window_on_pageflip( + window, + vblank_ns, + vblank_ns + (uint64_t) (1000000000.0 / mode_get_vrefresh(window->selected_mode)) + ); + if (ok != 0) { + LOG_ERROR("Error handling pageflip event. window_on_pageflip: %s\n", strerror(ok)); + } + + window_unref(window); +} + +static int window_request_frame_and_wait_for_begin(struct window *window); + +static int window_on_rendering_complete(struct window *window); + +static int window_push_composition(struct window *window, struct fl_layer_composition *composition) { + struct kms_req_builder *builder; + struct kms_req *req; + int ok; + + DEBUG_ASSERT_NOT_NULL(window); + DEBUG_ASSERT_NOT_NULL(composition); + + // If flutter won't request frames (because the vsync callback is broken), + // we'll wait here for the previous frame to be presented / rendered. + // Otherwise the surface_swap_buffers at the bottom might allocate an + // additional buffer and we'll potentially use more buffers than we're + // trying to use. + if (!window->use_frame_requests) { + TRACER_BEGIN(window->tracer, "window_request_frame_and_wait_for_begin"); + ok = window_request_frame_and_wait_for_begin(window); + TRACER_END(window->tracer, "window_request_frame_and_wait_for_begin"); + if (ok != 0) { + LOG_ERROR("Could not wait for frame begin.\n"); + return ok; + } + } + + window_lock(window); + + /// TODO: If we don't have new revisions, we don't need to scanout anything. + + /// TODO: Should we do this at the end of the function? + fl_layer_composition_swap_ptrs(&window->composition, composition); + + builder = drmdev_create_request_builder(window->drmdev, window->selected_crtc->id); + if (builder == NULL) { + ok = ENOMEM; + goto fail_unlock; + } + + // We only set the mode once, at the first atomic request. + if (window->set_mode) { + ok = kms_req_builder_set_connector(builder, window->selected_connector->id); + if (ok != 0) { + LOG_ERROR("Couldn't select connector.\n"); + goto fail_unref_builder; + } + + ok = kms_req_builder_set_mode(builder, window->selected_mode); + if (ok != 0) { + LOG_ERROR("Couldn't apply output mode.\n"); + goto fail_unref_builder; + } + + window->set_set_mode = true; + } + + for (size_t i = 0; i < fl_layer_composition_get_n_layers(composition); i++) { + struct fl_layer *layer = fl_layer_composition_peek_layer(composition, i); + (void) layer; + + ok = surface_present_kms(layer->surface, &layer->props, builder); + if (ok != 0) { + LOG_ERROR("Couldn't present flutter layer on screen. surface_present_kms: %s\n", strerror(ok)); + goto fail_release_layers; + } + } + + /// TODO: Fix this + /// make kms_req_builder keep a ref on the buffers + /// delete the release callbacks + ok = kms_req_builder_add_scanout_callback(builder, on_scanout, window_ref(window)); + if (ok != 0) { + LOG_ERROR("Couldn't register scanout callback.\n"); + goto fail_unref_window; + } + + req = kms_req_builder_build(builder); + if (req == NULL) { + goto fail_unref_window; + } + + kms_req_builder_unref(builder); + builder = NULL; + + if (window->present_mode == kDoubleBufferedVsync_PresentMode) { + TRACER_BEGIN(window->tracer, "kms_req_builder_commit"); + ok = kms_req_commit(req, /* blocking: */ false); + TRACER_END(window->tracer, "kms_req_builder_commit"); + + if (ok != 0) { + LOG_ERROR("Could not commit frame request.\n"); + goto fail_unref_window2; + } + + if (window->set_set_mode) { + window->set_mode = false; + window->set_set_mode = false; + } + } else { + DEBUG_ASSERT_EQUALS(window->present_mode, kTripleBufferedVsync_PresentMode); + + if (window->present_immediately) { + TRACER_BEGIN(window->tracer, "kms_req_builder_commit"); + ok = kms_req_commit(req, /* blocking: */ false); + TRACER_END(window->tracer, "kms_req_builder_commit"); + + if (ok != 0) { + LOG_ERROR("Could not commit frame request.\n"); + goto fail_unref_window2; + } + + if (window->set_set_mode) { + window->set_mode = false; + window->set_set_mode = false; + } + + window->present_immediately = false; + } else { + if (window->next_frame != NULL) { + /// FIXME: Call the release callbacks when the kms_req is destroyed, not when it's unrefed. + /// Not sure this here will lead to the release callbacks being called multiple times. + kms_req_call_release_callbacks(window->next_frame); + kms_req_unref(window->next_frame); + } + + window->next_frame = kms_req_ref(req); + window->set_set_mode = window->set_mode; + } + } + + window_unlock(window); + + // KMS Req is committed now and drmdev keeps a ref + // on it internally, so we don't need to keep this one. + kms_req_unref(req); + + window_on_rendering_complete(window); + + return 0; + + +fail_unref_window2: + window_unref(window); + kms_req_call_release_callbacks(req); + kms_req_unref(req); + goto fail_unlock; + +fail_unref_window: + window_unref(window); + +fail_release_layers: + // like above, kms_req_builder_unref won't call the layer release callbacks. + kms_req_builder_call_release_callbacks(builder); + +fail_unref_builder: + kms_req_builder_unref(builder); + +fail_unlock: + window_unlock(window); + return ok; +} + +static struct backing_store *window_create_backing_store(struct window *window, struct point size) { + struct backing_store *store; + + DEBUG_ASSERT_NOT_NULL(window); + + /// TODO: Make pixel format configurable or automatically select one + /// TODO: Only create one real GBM Surface backing store, otherwise create + /// custom GBM BO backing stores + window_lock(window); + + if (window->egl_backing_store == NULL && window->renderer != NULL) { + struct gbm_surface_backing_store *egl_store; + + egl_store = gbm_surface_backing_store_new_with_egl_config( + window->tracer, + size, + window->gbm_device, + window->renderer, + window->has_forced_pixel_format ? window->forced_pixel_format : kARGB8888, + window->egl_config + ); + if (egl_store == NULL) { + window_unlock(window); + return NULL; + } + + window->egl_backing_store = CAST_GBM_SURFACE_BACKING_STORE_UNCHECKED(surface_ref(CAST_SURFACE_UNCHECKED(egl_store))); + window->backing_store = CAST_BACKING_STORE_UNCHECKED(surface_ref(CAST_SURFACE_UNCHECKED(egl_store))); + store = CAST_BACKING_STORE_UNCHECKED(egl_store); + } else if (window->vk_backing_store == NULL && window->vk_renderer != NULL) { + struct vk_gbm_backing_store *vk_store; + + vk_store = vk_gbm_backing_store_new( + window->tracer, + size, + window->gbm_device, + window->vk_renderer, + window->forced_pixel_format + ); + if (vk_store == NULL) { + window_unlock(window); + return NULL; + } + + window->vk_backing_store = CAST_VK_GBM_BACKING_STORE_UNCHECKED(surface_ref(CAST_SURFACE_UNCHECKED(vk_store))); + window->backing_store = CAST_BACKING_STORE_UNCHECKED(surface_ref(CAST_SURFACE_UNCHECKED(vk_store))); + store = CAST_BACKING_STORE_UNCHECKED(vk_store); + } else { + DEBUG_ASSERT((window->egl_backing_store || window->vk_backing_store) && window->backing_store); + store = CAST_BACKING_STORE_UNCHECKED(surface_ref(CAST_SURFACE_UNCHECKED(window->backing_store))); + } + + window_unlock(window); + + return CAST_BACKING_STORE_UNCHECKED(store); +} + +static bool window_has_egl_surface(struct window *window) { + bool result; + + DEBUG_ASSERT_NOT_NULL(window); + + window_lock(window); + result = window->egl_backing_store != NULL; + window_unlock(window); + + return result; +} + +static EGLSurface window_get_egl_surface(struct window *window) { + struct gbm_surface_backing_store *store; + + DEBUG_ASSERT_NOT_NULL(window); + + window_lock(window); + + if (window->egl_backing_store == NULL) { + if (window->renderer == NULL) { + LOG_DEBUG("EGL Surface was requested but there's not EGL/GL renderer configured.\n"); + window_unlock(window); + return EGL_NO_SURFACE; + } + + LOG_DEBUG("EGL Surface was requested before flutter supplied the surface dimensions.\n"); + + store = gbm_surface_backing_store_new_with_egl_config( + window->tracer, + POINT(window->selected_mode->hdisplay, window->selected_mode->vdisplay), + drmdev_get_gbm_device(window->drmdev), + window->renderer, + window->has_forced_pixel_format ? window->forced_pixel_format : kARGB8888, + window->egl_config + ); + if (store == NULL) { + window_unlock(window); + return EGL_NO_SURFACE; + } + + window->egl_backing_store = CAST_GBM_SURFACE_BACKING_STORE_UNCHECKED(surface_ref(CAST_SURFACE_UNCHECKED(store))); + window->backing_store = CAST_BACKING_STORE_UNCHECKED(store); + } + + window_unlock(window); + + return gbm_surface_backing_store_get_egl_surface(window->egl_backing_store); +} + +static void on_frame_begin_signal_semaphore(void *userdata, uint64_t vblank_ns, uint64_t next_vblank_ns) { + sem_t *sem; + + DEBUG_ASSERT_NOT_NULL(userdata); + sem = userdata; + (void) vblank_ns; + (void) next_vblank_ns; + + sem_post(sem); +} + +static int window_begin_frame_locked(struct window *window, uint64_t vblank_ns, uint64_t next_vblank_ns) { + struct frame_req *req; + int ok; + + DEBUG_ASSERT_NOT_NULL(window); + + ok = queue_peek(&window->frame_req_queue, (void **) &req); + if (ok != 0) { + return ok; + } + + DEBUG_ASSERT(req->signaled == false); + + req->cb(req->userdata, vblank_ns, next_vblank_ns); + +#ifdef DEBUG + req->signaled = true; +#endif + + return 0; +} + +MAYBE_UNUSED static int window_begin_frame(struct window *window, uint64_t vblank_ns, uint64_t next_vblank_ns) { + int ok; + + DEBUG_ASSERT_NOT_NULL(window); + + window_lock(window); + ok = window_begin_frame_locked(window, vblank_ns, next_vblank_ns); + window_unlock(window); + + return ok; +} + +static int window_pop_frame_locked(struct window *window) { + return queue_dequeue(&window->frame_req_queue, NULL); +} + +static int window_request_frame_locked(struct window *window, compositor_frame_begin_cb_t cb, void *userdata) { + struct frame_req req; + bool signal_immediately; + int ok; + + DEBUG_ASSERT_NOT_NULL(window); + DEBUG_ASSERT_NOT_NULL(cb); + + req.cb = cb; + req.userdata = userdata; +#ifdef DEBUG + req.signaled = false; +#endif + + // if no frame is queued right now, we can immediately start the frame. + signal_immediately = queue_peek(&window->frame_req_queue, NULL) == EAGAIN; + + ok = queue_enqueue(&window->frame_req_queue, &req); + if (ok != 0) { + return ok; + } + + if (signal_immediately) { + ok = window_begin_frame_locked( + window, + get_monotonic_time(), + get_monotonic_time() + (uint64_t) (1000000000.0 / window->refresh_rate) + ); + if (ok != 0) { + return ok; + } + } + + return 0; +} + +static int window_request_frame(struct window *window, compositor_frame_begin_cb_t cb, void *userdata) { + int ok; + + DEBUG_ASSERT_NOT_NULL(window); + + window_lock(window); + ok = window_request_frame_locked(window, cb, userdata); + window_unlock(window); + + return ok; +} + +static int window_request_frame_and_wait_for_begin(struct window *window) { + sem_t sem; + int ok; + + ok = sem_init(&sem, 0, 0); + if (ok != 0) { + ok = errno; + LOG_ERROR("Could not initialize semaphore for waiting for frame begin. sem_init: %s\n", strerror(ok)); + return ok; + } + + ok = window_request_frame(window, on_frame_begin_signal_semaphore, &sem); + if (ok != 0) { + sem_destroy(&sem); + return ok; + } + + while (1) { + ok = sem_wait(&sem); + if ((ok < 0) && (errno != EINTR)) { + ok = errno; + LOG_ERROR("Could not blockingly wait for frame begin. sem_wait: %s\n", strerror(ok)); + sem_destroy(&sem); + return ok; + } else if (ok == 0) { + break; + } + } + + ok = sem_destroy(&sem); + DEBUG_ASSERT(ok == 0); + (void) ok; + + return 0; +} + +MAYBE_UNUSED static int window_on_rendering_complete(struct window *window) { + int ok; + + // if we're using triple buffering, we can immediately start the next frame. + // for double buffering we need to wait till we have a buffer again, so after + // the next pageflip + if (window->present_mode == kTripleBufferedVsync_PresentMode) { + window_lock(window); + + ok = window_pop_frame_locked(window); + if (ok != EAGAIN) { + goto fail_unlock; + } + + uint64_t time = get_monotonic_time(); + + ok = window_begin_frame_locked(window, time, time + (uint64_t) (1000000000.0 / window->refresh_rate)); + if (ok != EAGAIN) { + goto fail_unlock; + } + + window_unlock(window); + } + + return 0; + +fail_unlock: + window_unlock(window); + return ok; +} + +static int window_on_pageflip(struct window *window, uint64_t vblank_ns, uint64_t next_vblank_ns) { + int ok; + + TRACER_INSTANT(window->tracer, "window_on_pageflip"); + + // Whenever the pageflip was completed, + // we have a new buffer available that we can render into. + // This only applies to double-buffering though, for triple buffering + // we will have started the next frame at an earlier point. + if (window->present_mode == kDoubleBufferedVsync_PresentMode) { + window_lock(window); + + ok = window_pop_frame_locked(window); + if (ok != 0 && ok != EAGAIN) { + goto fail_unlock; + } + + ok = window_begin_frame_locked(window, vblank_ns, next_vblank_ns); + if (ok != 0 && ok != EAGAIN) { + goto fail_unlock; + } + + window_unlock(window); + } else if (window->present_mode == kTripleBufferedVsync_PresentMode) { + window_lock(window); + + if (window->next_frame != NULL) { + TRACER_BEGIN(window->tracer, "kms_req_builder_commit"); + ok = kms_req_commit(window->next_frame, false); + TRACER_END(window->tracer, "kms_req_builder_commit"); + + if (ok != 0) { + LOG_ERROR("Could not present frame. kms_req_builder_commit: %s\n", strerror(ok)); + goto fail_unlock; + } + + if (window->set_set_mode) { + window->set_mode = false; + window->set_set_mode = false; + } + + kms_req_unrefp(&window->next_frame); + } else { + window->present_immediately = true; + } + + window_unlock(window); + } + + return 0; + +fail_unlock: + window_unlock(window); + return ok; +} + +/** + * @brief The flutter compositor. Responsible for taking the FlutterLayers, processing them into a struct fl_layer_composition*, then passing + * those to the window so it can show it on screen. + * + * Right now this is only supports a single output screen only, but in the future we might add multi-screen support. + * (Possibly one Flutter Engine per view) + */ +struct compositor { + refcount_t n_refs; + pthread_mutex_t mutex; + + struct tracer *tracer; + struct window main_window; + struct fl_layer_composition *composition; + struct pointer_set platform_views; + + FlutterCompositor flutter_compositor; +}; + +struct platform_view_with_id { + int64_t id; + struct surface *surface; +}; + +static bool on_flutter_present_layers(const FlutterLayer **layers, size_t layers_count, void *userdata); + +static bool on_flutter_create_backing_store( + const FlutterBackingStoreConfig *config, + FlutterBackingStore *backing_store_out, + void *userdata +); + +static bool on_flutter_collect_backing_store(const FlutterBackingStore *fl_store, void *userdata); + +ATTR_MALLOC struct compositor *compositor_new( + struct drmdev *drmdev, + struct tracer *tracer, + struct gl_renderer *renderer, + bool has_rotation, + drm_plane_transform_t rotation, + bool has_orientation, + enum device_orientation orientation, + bool has_explicit_dimensions, + int width_mm, + int height_mm, + EGLConfig egl_config, + bool has_forced_pixel_format, + enum pixfmt forced_pixel_format, + bool use_frame_requests, + enum present_mode present_mode +) { + struct compositor *compositor; + int ok; + + DEBUG_ASSERT_MSG( + egl_config == EGL_NO_CONFIG_KHR || has_forced_pixel_format == true, + "If an explicit EGLConfig is given, a pixel format must be given too." + ); + + LOG_DEBUG("Has forced pixel format: %s\n", has_forced_pixel_format ? "yes" : "no"); + + compositor = malloc(sizeof *compositor); + if (compositor == NULL) { + goto fail_return_null; + } + + ok = pthread_mutex_init(&compositor->mutex, NULL); + if (ok != 0) { + goto fail_free_compositor; + } + + ok = pset_init(&compositor->platform_views, PSET_DEFAULT_MAX_SIZE); + if (ok != 0) { + goto fail_destroy_mutex; + } + + ok = window_init( + &compositor->main_window, + compositor, + tracer, + drmdev, + renderer, + has_rotation, + rotation, + has_orientation, + orientation, + has_explicit_dimensions, + width_mm, + height_mm, + use_frame_requests, + has_forced_pixel_format, + forced_pixel_format, + egl_config, + present_mode + ); + if (ok != 0) { + LOG_ERROR("Could not initialize main window.\n"); + goto fail_deinit_platform_views; + } + + compositor->n_refs = REFCOUNT_INIT_1; + compositor->composition = NULL; + // just so we get an error if the FlutterCompositor struct was updated + COMPILE_ASSERT(sizeof(FlutterCompositor) == 24); + compositor->flutter_compositor = (FlutterCompositor + ){ .struct_size = sizeof(FlutterCompositor), + .user_data = compositor, + .create_backing_store_callback = on_flutter_create_backing_store, + .collect_backing_store_callback = on_flutter_collect_backing_store, + .present_layers_callback = on_flutter_present_layers, + .avoid_backing_store_cache = true }; + compositor->tracer = tracer_ref(tracer); + return compositor; + +fail_deinit_platform_views: + pset_deinit(&compositor->platform_views); + +fail_destroy_mutex: + pthread_mutex_destroy(&compositor->mutex); + +fail_free_compositor: + free(compositor); + +fail_return_null: + return NULL; +} + +ATTR_MALLOC struct compositor *compositor_new_vulkan( + struct drmdev *drmdev, + struct tracer *tracer, + struct vk_renderer *renderer, + bool has_rotation, + drm_plane_transform_t rotation, + bool has_orientation, + enum device_orientation orientation, + bool has_explicit_dimensions, + int width_mm, + int height_mm, + bool has_forced_pixel_format, + enum pixfmt forced_pixel_format, + bool use_frame_requests, + enum present_mode present_mode +) { + struct compositor *compositor; + int ok; + + LOG_DEBUG("Has forced pixel format: %s\n", has_forced_pixel_format ? "yes" : "no"); + + compositor = malloc(sizeof *compositor); + if (compositor == NULL) { + goto fail_return_null; + } + + ok = pthread_mutex_init(&compositor->mutex, NULL); + if (ok != 0) { + goto fail_free_compositor; + } + + ok = pset_init(&compositor->platform_views, PSET_DEFAULT_MAX_SIZE); + if (ok != 0) { + goto fail_destroy_mutex; + } + + ok = window_init_vulkan( + &compositor->main_window, + compositor, + tracer, + drmdev, + renderer, + has_rotation, rotation, + has_orientation, orientation, + has_explicit_dimensions, width_mm, height_mm, + use_frame_requests, + has_forced_pixel_format, forced_pixel_format, + present_mode + ); + if (ok != 0) { + LOG_ERROR("Could not initialize main window.\n"); + goto fail_deinit_platform_views; + } + + compositor->n_refs = REFCOUNT_INIT_1; + compositor->composition = NULL; + // just so we get an error if the FlutterCompositor struct was updated + COMPILE_ASSERT(sizeof(FlutterCompositor) == 24); + compositor->flutter_compositor = (FlutterCompositor){ + .struct_size = sizeof(FlutterCompositor), + .user_data = compositor, + .create_backing_store_callback = on_flutter_create_backing_store, + .collect_backing_store_callback = on_flutter_collect_backing_store, + .present_layers_callback = on_flutter_present_layers, + .avoid_backing_store_cache = true, + }; + compositor->tracer = tracer_ref(tracer); + return compositor; + +fail_deinit_platform_views: + pset_deinit(&compositor->platform_views); + +fail_destroy_mutex: + pthread_mutex_destroy(&compositor->mutex); + +fail_free_compositor: + free(compositor); + +fail_return_null: + return NULL; +} + +void compositor_destroy(struct compositor *compositor) { + struct platform_view_with_id *view; + + queue_deinit(&compositor->main_window.frame_req_queue); + for_each_pointer_in_pset(&compositor->platform_views, view) { + surface_unref(view->surface); + free(view); + } + pset_deinit(&compositor->platform_views); + pthread_mutex_destroy(&compositor->mutex); + free(compositor); +} + +DEFINE_REF_OPS(compositor, n_refs) + +DEFINE_STATIC_LOCK_OPS(compositor, mutex) + +void compositor_get_view_geometry(struct compositor *compositor, struct view_geometry *view_geometry_out) { + return window_get_view_geometry(&compositor->main_window, view_geometry_out); +} + +ATTR_PURE double compositor_get_refresh_rate(struct compositor *compositor) { + DEBUG_ASSERT_NOT_NULL(compositor); + return window_get_refresh_rate(&compositor->main_window); +} + +int compositor_get_next_vblank(struct compositor *compositor, uint64_t *next_vblank_ns_out) { + DEBUG_ASSERT_NOT_NULL(compositor); + DEBUG_ASSERT_NOT_NULL(next_vblank_ns_out); + return window_get_next_vblank(&compositor->main_window, next_vblank_ns_out); +} + +static int compositor_push_composition(struct compositor *compositor, struct fl_layer_composition *composition) { + int ok; + + fl_layer_composition_ref(composition); + if (compositor->composition) { + fl_layer_composition_unref(compositor->composition); + } + compositor->composition = composition; + + TRACER_BEGIN(compositor->tracer, "window_push_composition"); + ok = window_push_composition(&compositor->main_window, composition); + TRACER_END(compositor->tracer, "window_push_composition"); + + return ok; +} + +static void fill_platform_view_layer_props( + struct fl_layer_props *props_out, + const FlutterPoint *offset, + const FlutterSize *size, + const FlutterPlatformViewMutation **mutations, + size_t n_mutations, + const FlutterTransformation *display_to_view_transform, + const FlutterTransformation *view_to_display_transform, + double device_pixel_ratio +) { + (void) view_to_display_transform; + + /** + * inversion for + * ``` + * const auto transformed_layer_bounds = + * root_surface_transformation_.mapRect(layer_bounds); + * ``` + */ + + struct quad quad = transform_aa_rect( + *display_to_view_transform, + (struct aa_rect){ .offset.x = offset->x, .offset.y = offset->y, .size.x = size->width, .size.y = size->height } + ); + + struct aa_rect rect = get_aa_bounding_rect(quad); + + /** + * inversion for + * ``` + * const auto layer_bounds = + * SkRect::MakeXYWH(params.finalBoundingRect().x(), + * params.finalBoundingRect().y(), + * params.sizePoints().width() * device_pixel_ratio_, + * params.sizePoints().height() * device_pixel_ratio_ + * ); + * ``` + */ + + rect.size.x /= device_pixel_ratio; + rect.size.y /= device_pixel_ratio; + + // okay, now we have the params.finalBoundingRect().x() in aa_back_transformed.x and + // params.finalBoundingRect().y() in aa_back_transformed.y. + // those are flutter view coordinates, so we still need to transform them to display coordinates. + + // However, there are also calculated as a side-product of calculating the size of the quadrangle. + // So we'll avoid calculating them for now. Calculation of the size may fail when the offset + // given to `SceneBuilder.addPlatformView` (https://api.flutter.dev/flutter/dart-ui/SceneBuilder/addPlatformView.html) + // is not zero. (Don't really know what to do in that case) + + rect.offset.x = 0; + rect.offset.y = 0; + quad = get_quad(rect); + + double rotation = 0, opacity = 1; + for (int i = n_mutations - 1; i >= 0; i--) { + if (mutations[i]->type == kFlutterPlatformViewMutationTypeTransformation) { + quad = transform_quad(mutations[i]->transformation, quad); + + double rotz = atan2(mutations[i]->transformation.skewX, mutations[i]->transformation.scaleX) * 180.0 / M_PI; + if (rotz < 0) { + rotz += 360; + } + + rotation += rotz; + } else if (mutations[i]->type == kFlutterPlatformViewMutationTypeOpacity) { + opacity *= mutations[i]->opacity; + } + } + + rotation = fmod(rotation, 360.0); + + /// TODO: Implement axis aligned rectangle detection + props_out->is_aa_rect = false; + props_out->aa_rect = AA_RECT_FROM_COORDS(0, 0, 0, 0); + props_out->quad = quad; + props_out->opacity = 0; + props_out->rotation = rotation; + + /// TODO: Implement clip rects + props_out->n_clip_rects = 0; + props_out->clip_rects = NULL; +} + +static int compositor_push_fl_layers(struct compositor *compositor, size_t n_fl_layers, const FlutterLayer **fl_layers) { + struct fl_layer_composition *composition; + int ok; + + composition = fl_layer_composition_new(n_fl_layers); + if (composition == NULL) { + return ENOMEM; + } + + compositor_lock(compositor); + + for (int i = 0; i < n_fl_layers; i++) { + const FlutterLayer *fl_layer = fl_layers[i]; + struct fl_layer *layer = fl_layer_composition_peek_layer(composition, i); + + if (fl_layer->type == kFlutterLayerContentTypeBackingStore) { + /// TODO: Implement + layer->surface = surface_ref(CAST_SURFACE(fl_layer->backing_store->user_data)); + + // Tell the surface that flutter has rendered into this framebuffer / texture / image. + // It'll also read the did_update field and not update the surface revision in that case. + backing_store_queue_present(CAST_BACKING_STORE(layer->surface), fl_layer->backing_store); + + layer->props.is_aa_rect = true; + layer->props.aa_rect = + AA_RECT_FROM_COORDS(fl_layer->offset.y, fl_layer->offset.y, fl_layer->size.width, fl_layer->size.height); + layer->props.quad = get_quad(layer->props.aa_rect); + layer->props.opacity = 1.0; + layer->props.rotation = 0.0; + layer->props.n_clip_rects = 0; + layer->props.clip_rects = NULL; + } else { + DEBUG_ASSERT_EQUALS(fl_layer->type, kFlutterLayerContentTypePlatformView); + + /// TODO: Maybe always check if the ID is valid? +#if DEBUG + // if we're in debug mode, we actually check if the ID is a valid, + // registered ID. + /// TODO: Implement + layer->surface = + surface_ref(compositor_get_view_by_id_locked(compositor, fl_layer->platform_view->identifier)); +#else + // in release mode, we just assume the id is valid. + // Since the surface constructs the ID by just casting the surface pointer to an int64_t, + // we can easily cast it back without too much trouble. + // Only problem is if the id is garbage, we won't notice and the returned surface is garbage too. + layer->surface = surface_ref(surface_from_id(fl_layer->platform_view->identifier)); +#endif + + // The coordinates flutter gives us are a bit buggy, so calculating the right geometry is really a problem on its own + /// TODO: Don't unconditionally take the geometry from the main window. + fill_platform_view_layer_props( + &layer->props, + &fl_layer->offset, + &fl_layer->size, + fl_layer->platform_view->mutations, + fl_layer->platform_view->mutations_count, + &compositor->main_window.display_to_view_transform, + &compositor->main_window.view_to_display_transform, + compositor->main_window.pixel_ratio + ); + } + } + + compositor_unlock(compositor); + + TRACER_BEGIN(compositor->tracer, "compositor_push_composition"); + ok = compositor_push_composition(compositor, composition); + TRACER_END(compositor->tracer, "compositor_push_composition"); + + fl_layer_composition_unref(composition); + + return 0; + + //fail_free_composition: + //fl_layer_composition_unref(composition); + return ok; +} + +static bool on_flutter_present_layers(const FlutterLayer **layers, size_t layers_count, void *userdata) { + struct compositor *compositor; + int ok; + + DEBUG_ASSERT_NOT_NULL(layers); + DEBUG_ASSERT(layers_count > 0); + DEBUG_ASSERT_NOT_NULL(userdata); + compositor = userdata; + + TRACER_BEGIN(compositor->tracer, "compositor_push_fl_layers"); + ok = compositor_push_fl_layers(compositor, layers_count, layers); + TRACER_END(compositor->tracer, "compositor_push_fl_layers"); + + if (ok != 0) { + return false; + } + + return true; +} + +int compositor_set_platform_view(struct compositor *compositor, int64_t id, struct surface *surface) { + struct platform_view_with_id *data; + int ok; + + DEBUG_ASSERT_NOT_NULL(compositor); + DEBUG_ASSERT(id != 0); + DEBUG_ASSERT_NOT_NULL(surface); + + compositor_lock(compositor); + + for_each_pointer_in_pset(&compositor->platform_views, data) { + if (data->id == id) { + break; + } + } + + if (data == NULL) { + if (surface == NULL) { + data = malloc(sizeof *data); + if (data == NULL) { + ok = ENOMEM; + goto fail_unlock; + } + + data->id = id; + data->surface = surface; + pset_put(&compositor->platform_views, data); + } + } else { + DEBUG_ASSERT_NOT_NULL(data->surface); + if (surface == NULL) { + pset_remove(&compositor->platform_views, data); + surface_unref(data->surface); + free(data); + } else { + surface_ref(surface); + surface_unref(data->surface); + data->surface = surface; + } + } + + compositor_unlock(compositor); + return 0; + +fail_unlock: + compositor_unlock(compositor); + return ok; +} + +struct surface *compositor_get_view_by_id_locked(struct compositor *compositor, int64_t view_id) { + struct platform_view_with_id *data; + + for_each_pointer_in_pset(&compositor->platform_views, data) { + if (data->id == view_id) { + return data->surface; + } + } + + return NULL; +} + +static struct backing_store *compositor_create_backing_store(struct compositor *compositor, struct point size) { + DEBUG_ASSERT_NOT_NULL(compositor); + return window_create_backing_store(&compositor->main_window, size); +} + +bool compositor_has_egl_surface(struct compositor *compositor) { + return window_has_egl_surface(&compositor->main_window); +} + +EGLSurface compositor_get_egl_surface(struct compositor *compositor) { + return window_get_egl_surface(&compositor->main_window); +} + +int compositor_request_frame(struct compositor *compositor, compositor_frame_begin_cb_t cb, void *userdata) { + DEBUG_ASSERT_NOT_NULL(compositor); + return window_request_frame(&compositor->main_window, cb, userdata); +} + +static bool on_flutter_create_backing_store( + const FlutterBackingStoreConfig *config, + FlutterBackingStore *backing_store_out, + void *userdata +) { + struct backing_store *store; + struct compositor *compositor; + int ok; + + DEBUG_ASSERT_NOT_NULL(config); + DEBUG_ASSERT_NOT_NULL(backing_store_out); + DEBUG_ASSERT_NOT_NULL(userdata); + compositor = userdata; + + // we have a reference on this store. + // i.e. when we don't use it, we need to unref it. + store = compositor_create_backing_store(compositor, POINT(config->size.width, config->size.height)); + if (store == NULL) { + LOG_ERROR("Couldn't create backing store for flutter to render into.\n"); + return false; + } + + COMPILE_ASSERT(sizeof(FlutterBackingStore) == 56); + memset(backing_store_out, 0, sizeof *backing_store_out); + backing_store_out->struct_size = sizeof(FlutterBackingStore); + + // backing_store_fill asserts that the user_data is null so it can make sure + // any concrete backing_store_fill implementation doesn't try to set the user_data. + // so we set the user_data after the fill + ok = backing_store_fill(store, backing_store_out); + if (ok != 0) { + LOG_ERROR("Couldn't fill flutter backing store with concrete OpenGL framebuffer/texture or Vulkan image.\n"); + surface_unref(CAST_SURFACE_UNCHECKED(store)); + return false; + } + + // now we can set the user_data. + backing_store_out->user_data = store; + + return true; +} + +static bool on_flutter_collect_backing_store(const FlutterBackingStore *fl_store, void *userdata) { + struct compositor *compositor; + struct surface *s; + + DEBUG_ASSERT_NOT_NULL(fl_store); + DEBUG_ASSERT_NOT_NULL(userdata); + s = CAST_SURFACE(fl_store->user_data); + compositor = userdata; + + (void) compositor; + + surface_unref(s); + return true; +} + +const FlutterCompositor *compositor_get_flutter_compositor(struct compositor *compositor) { + DEBUG_ASSERT_NOT_NULL(compositor); + return &compositor->flutter_compositor; +} + +int compositor_get_event_fd(struct compositor *compositor) { + DEBUG_ASSERT_NOT_NULL(compositor); + return drmdev_get_event_fd(compositor->main_window.drmdev); +} + +int compositor_on_event_fd_ready(struct compositor *compositor) { + DEBUG_ASSERT_NOT_NULL(compositor); + return drmdev_on_event_fd_ready(compositor->main_window.drmdev); +} diff --git a/src/config.c b/src/config.c new file mode 100644 index 00000000..d0e0953e --- /dev/null +++ b/src/config.c @@ -0,0 +1,198 @@ +#include "../include/thirdparty/cJSON.h" + +#include <stdio.h> +#include <stdbool.h> +/* + Example JSON config: + { + "enableHwCursor": true, + "windows": [ + { + "drmdev": "/dev/dri/card0", + "mode": "1920x1080@60p", + "dimensions": "160x90", + "framebufferSize": "960x540", + "pixelformat": "RGB565" + }, + { ... } + ] + } +*/ + +struct window { + char *drmdev; + char *mode; + char *dimensions; + char *framebufferSize; + char *pixelformat; +}; + +struct configuration { + bool enableHwCursor; + struct window *windows; + size_t n_windows; +}; + +struct configuration *configuration_new(void) { + struct configuration *c; + c = malloc(sizeof *c); + if (c == NULL) { + return NULL; + } + c->enableHwCursor = false; + c->windows = NULL; + c->n_windows = 0; + return c; +} + +void configuration_destroy(struct configuration *c) { + size_t index; + for (index = 0; index < c->n_windows; index++) { + free(c->windows[index].drmdev); + free(c->windows[index].mode); + free(c->windows[index].dimensions); + free(c->windows[index].framebufferSize); + free(c->windows[index].pixelformat); + } + free(c->windows); + free(c); +} + +void configuration_dump(struct configuration *c) { + size_t window; + for (window = 0; window < c->n_windows; window++) { + printf("%s %s %s %s %s\n", c->windows[window].drmdev, c->windows[window].mode, c->windows[window].dimensions, c->windows[window].framebufferSize, c->windows[window].pixelformat); + } +} + +struct configuration *configuration_parse(const char *json) { + cJSON *root, *windows, *window, *item; + struct configuration *c; + size_t index; + char *value; + root = cJSON_Parse(json); + if (root == NULL) { + return NULL; + } + c = configuration_new(); + if (c == NULL) { + cJSON_Delete(root); + return NULL; + } + item = cJSON_GetObjectItem(root, "enableHwCursor"); + if (item != NULL && cJSON_IsBool(item)) { + c->enableHwCursor = cJSON_IsTrue(item); + } + windows = cJSON_GetObjectItem(root, "windows"); + if (windows != NULL) { + c->n_windows = cJSON_GetArraySize(windows); + c->windows = malloc(c->n_windows * sizeof *c->windows); + if (c->windows == NULL) { + configuration_destroy(c); + cJSON_Delete(root); + return NULL; + } + for (index = 0; index < c->n_windows; index++) { + window = cJSON_GetArrayItem(windows, index); + c->windows[index].drmdev = NULL; + c->windows[index].mode = NULL; + c->windows[index].dimensions = NULL; + c->windows[index].framebufferSize = NULL; + c->windows[index].pixelformat = NULL; + item = cJSON_GetObjectItem(window, "drmdev"); + if (item != NULL) { + value = item->valuestring; + c->windows[index].drmdev = strdup(value); + } + item = cJSON_GetObjectItem(window, "mode"); + if (item != NULL) { + value = item->valuestring; + c->windows[index].mode = strdup(value); + } + item = cJSON_GetObjectItem(window, "dimensions"); + if (item != NULL) { + value = item->valuestring; + c->windows[index].dimensions = strdup(value); + } + item = cJSON_GetObjectItem(window, "framebufferSize"); + if (item != NULL) { + value = item->valuestring; + c->windows[index].framebufferSize = strdup(value); + } + item = cJSON_GetObjectItem(window, "pixelformat"); + if (item != NULL) { + value = item->valuestring; + c->windows[index].pixelformat = strdup(value); + } + } + } + cJSON_Delete(root); + return c; +} + + +#ifdef TEST +int main(int argc, char *argv[]) { + struct configuration *c; + c = configuration_parse(argv[1]); + configuration_dump(c); + configuration_destroy(c); + return 0; +} +#endif + +// Load config json from path +struct configuration *configuration_load(const char *path) { + FILE *f; + char *json; + size_t size; + struct configuration *c; + f = fopen(path, "r"); + if (f == NULL) { + return NULL; + } + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + json = malloc(size + 1); + if (json == NULL) { + fclose(f); + return NULL; + } + if (fread(json, 1, size, f) != size) { + fclose(f); + free(json); + return NULL; + } + json[size] = '\0'; + fclose(f); + c = configuration_parse(json); + free(json); + return c; +} + +// xdg path +char *configuration_path(void) { + char *path; + path = getenv("XDG_CONFIG_HOME"); + if (path == NULL) { + path = getenv("HOME"); + if (path == NULL) { + return NULL; + } + path = malloc(strlen(path) + strlen("/.config/") + 1); + if (path == NULL) { + return NULL; + } + strcpy(path, path); + strcat(path, "/.config/"); + } else { + path = malloc(strlen(path) + strlen("/config.json") + 1); + if (path == NULL) { + return NULL; + } + strcpy(path, path); + strcat(path, "/config.json"); + } + return path; +} \ No newline at end of file diff --git a/src/dmabuf_surface.c b/src/dmabuf_surface.c new file mode 100644 index 00000000..8280af57 --- /dev/null +++ b/src/dmabuf_surface.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: MIT +/* + * linux-dmabuf rendering surface + * + * A surface: + * - that plugins can push linux dmabufs into (for example, for video playback) + * - that'll expose both a texture and a platform view + * + * the exposed flutter texture: (cold path) + * - is an imported EGL Image, which is created from the dmabuf using the EGL_EXT_image_dma_buf_import extension (if supported) + * - if that extension is not supported, copy the dmabuf contents into the texture + * - using a texture is slower than a hardware overlay + * - (because with a texture, texture contents must be converted into the right pixel format and composited into a single framebuffer, + * before the frame can be scanned out => additional memory copy, but with hardware overlay that will be done in realtime, on-the-fly, + * while the picture is being scanned out) + * + * the platform view: (hot path) + * - on KMS present, will add a hardware overlay plane to scanout that fb, if that's possible + * (if the rectangle is axis-aligned and pixel format, alpha value etc is supported by KMS) + * - otherwise, fall back to the cold path (textures) + * - this needs integration from the dart side, because only the dart side can decide whether to use + * texture or platform view + * + * The surface should have a specific counterpart on the dart side, which will decide whether to use + * texture or platform view. That decision is hard to make consistent, i.e. when dart-side decides on + * platform view, it's not 100% guaranteed this surface will actually succeed in adding the hw overlay plane. + * + * So best we can do is guess. Not sure how to implement the switching between hot path / cold path though. + * Maybe, if we fail in adding the hw overlay plane, we could signal that somehow to the + * dart-side and make it use a texture for the next frame. But also, it could be adding the overlay plane succeeds, + * and adding a later plane fails. In that case we don't notice the error, but we should still fallback to texture rendering. + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#include <stdlib.h> + +#include <collection.h> + +#include <stdlib.h> +#include <stdatomic.h> +#include <stdint.h> + +#include <collection.h> +#include <surface.h> +#include <surface_private.h> +#include <dmabuf_surface.h> +#include <compositor_ng.h> +#include <texture_registry.h> + +FILE_DESCR("dmabuf surface") + +struct refcounted_dmabuf { + refcount_t n_refs; + struct dmabuf buf; + dmabuf_release_cb_t release_callback; + + struct drmdev *drmdev; + uint32_t drm_fb_id; +}; + +void refcounted_dmabuf_destroy(struct refcounted_dmabuf *dmabuf) { + dmabuf->release_callback(&dmabuf->buf); + if (DRM_ID_IS_VALID(dmabuf->drm_fb_id)) { + drmdev_rm_fb(dmabuf->drmdev, dmabuf->drm_fb_id); + } + drmdev_unref(dmabuf->drmdev); + free(dmabuf); +} + +DEFINE_STATIC_REF_OPS(refcounted_dmabuf, n_refs); + +struct dmabuf_surface { + struct surface surface; + + uuid_t uuid; + EGLDisplay egl_display; + struct texture *texture; + struct refcounted_dmabuf *next_buf; +}; + +COMPILE_ASSERT(offsetof(struct dmabuf_surface, surface) == 0); + +static const uuid_t uuid = CONST_UUID(0x68, 0xed, 0xe5, 0x8a, 0x4a, 0x2b, 0x40, 0x76, 0xa1, 0xb8, 0x89, 0x2e, 0x81, 0xfa, 0xe2, 0xb7); + +#define CAST_THIS(ptr) CAST_DMABUF_SURFACE(ptr) +#define CAST_THIS_UNCHECKED(ptr) CAST_DMABUF_SURFACE_UNCHECKED(ptr) + +#ifdef DEBUG +ATTR_PURE struct dmabuf_surface *__checked_cast_dmabuf_surface(void *ptr) { + struct dmabuf_surface *s; + + s = CAST_DMABUF_SURFACE_UNCHECKED(ptr); + DEBUG_ASSERT(uuid_equals(s->uuid, uuid)); + return s; +} +#endif + +static void dmabuf_surface_deinit(struct surface *s); +static int dmabuf_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); +static int dmabuf_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); + +int dmabuf_surface_init(struct dmabuf_surface *s, struct tracer *tracer, struct texture_registry *texture_registry) { + struct texture *texture; + int ok; + + texture = texture_new(texture_registry); + if (texture == NULL) { + return EIO; + } + + ok = surface_init(&s->surface, tracer); + if (ok != 0) { + return ok; + } + + s->surface.deinit = dmabuf_surface_deinit; + s->surface.present_kms = dmabuf_surface_present_kms; + s->surface.present_fbdev = dmabuf_surface_present_fbdev; + uuid_copy(&s->uuid, uuid); + s->egl_display = EGL_NO_DISPLAY; + s->texture = texture; + s->next_buf = NULL; + return 0; +} + +static void dmabuf_surface_deinit(struct surface *s) { + texture_destroy(CAST_THIS_UNCHECKED(s)->texture); + surface_deinit(s); +} + +/** + * @brief Create a new dmabuf surface. + * + * @return struct dmabuf_surface* + */ +ATTR_MALLOC struct dmabuf_surface *dmabuf_surface_new(struct tracer *tracer, struct texture_registry *texture_registry) { + struct dmabuf_surface *s; + int ok; + + s = malloc(sizeof *s); + if (s == NULL) { + goto fail_return_null; + } + + ok = dmabuf_surface_init(s, tracer, texture_registry); + if (ok != 0) { + goto fail_free_surface; + } + + return s; + + + fail_free_surface: + free(s); + + fail_return_null: + return NULL; +} + +int dmabuf_surface_push_dmabuf(struct dmabuf_surface *s, const struct dmabuf *buf, dmabuf_release_cb_t release_cb) { + struct refcounted_dmabuf *b; + + DEBUG_ASSERT_NOT_NULL(s); + DEBUG_ASSERT_NOT_NULL(buf); + DEBUG_ASSERT_NOT_NULL(release_cb); + +#ifdef HAS_EGL + DEBUG_ASSERT(eglGetCurrentContext() != EGL_NO_CONTEXT); +#endif + + UNIMPLEMENTED(); + + b = malloc(sizeof *b); + if (b == NULL) { + return ENOMEM; + } + + b->n_refs = REFCOUNT_INIT_1; + b->buf = *buf; + b->release_callback = release_cb; + b->drmdev = NULL; + b->drm_fb_id = DRM_ID_NONE; + + surface_lock(CAST_SURFACE_UNCHECKED(s)); + + if (s->next_buf != NULL) { + refcounted_dmabuf_unref(s->next_buf); + } + s->next_buf = b; + + surface_unlock(CAST_SURFACE_UNCHECKED(s)); + + return 0; +} + +ATTR_PURE int64_t dmabuf_surface_get_texture_id(struct dmabuf_surface *s) { + DEBUG_ASSERT_NOT_NULL(s); + return texture_get_id(s->texture); +} + +static void on_release_layer(void *userdata) { + DEBUG_ASSERT_NOT_NULL(userdata); + refcounted_dmabuf_unref((struct refcounted_dmabuf*) userdata); +} + +static int dmabuf_surface_present_kms(struct surface *_s, const struct fl_layer_props *props, struct kms_req_builder *builder) { + struct dmabuf_surface *s; + uint32_t fb_id; + + DEBUG_ASSERT_MSG(props->is_aa_rect, "Only axis-aligned rectangles supported right now."); + s = CAST_THIS(_s); + (void) s; + (void) props; + (void) builder; + + surface_lock(_s); + + if (DRM_ID_IS_VALID(s->next_buf->drm_fb_id)) { + DEBUG_ASSERT_EQUALS_MSG(s->next_buf->drmdev, kms_req_builder_get_drmdev(builder), "Only 1 KMS instance per dmabuf supported right now."); + fb_id = s->next_buf->drm_fb_id; + } else { + fb_id = drmdev_add_fb_from_dmabuf( + kms_req_builder_get_drmdev(builder), + s->next_buf->buf.width, + s->next_buf->buf.height, + s->next_buf->buf.format, + s->next_buf->buf.fds[0], + s->next_buf->buf.strides[0], + s->next_buf->buf.offsets[0], + s->next_buf->buf.has_modifiers, + s->next_buf->buf.modifiers[0], + 0 + ); + if (!DRM_ID_IS_VALID(fb_id)) { + LOG_ERROR("Couldn't add dmabuf as framebuffer.\n"); + } + + s->next_buf->drm_fb_id = fb_id; + s->next_buf->drmdev = drmdev_ref(kms_req_builder_get_drmdev(builder)); + } + + kms_req_builder_push_fb_layer( + builder, + &(struct kms_fb_layer) { + .drm_fb_id = fb_id, + .format = s->next_buf->buf.format, + + .has_modifier = s->next_buf->buf.has_modifiers, + .modifier = s->next_buf->buf.modifiers[0], + + .src_x = 0, + .src_y = 0, + .src_w = DOUBLE_TO_FP1616_ROUNDED(s->next_buf->buf.width), + .src_h = DOUBLE_TO_FP1616_ROUNDED(s->next_buf->buf.height), + + .dst_x = props->aa_rect.offset.x, + .dst_y = props->aa_rect.offset.y, + .dst_w = props->aa_rect.size.x, + .dst_h = props->aa_rect.size.y, + + .has_rotation = false, + .rotation = PLANE_TRANSFORM_ROTATE_0, + .has_in_fence_fd = false, + .in_fence_fd = 0, + }, + on_release_layer, + refcounted_dmabuf_ref(s->next_buf) + ); + + surface_unlock(_s); + + return 0; +} + +static int dmabuf_surface_present_fbdev(struct surface *_s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder) { + struct dmabuf_surface *s; + + s = CAST_THIS(_s); + (void) s; + (void) props; + (void) builder; + + return 0; +} diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 46ab4707..b60e06e7 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -33,12 +33,8 @@ #include <xf86drmMode.h> #include <drm_fourcc.h> #include <gbm.h> -#include <EGL/egl.h> -#define EGL_EGLEXT_PROTOTYPES -#include <EGL/eglext.h> -#include <GLES2/gl2.h> -#define GL_GLEXT_PROTOTYPES -#include <GLES2/gl2ext.h> +#include <egl.h> +#include <gles.h> #include <libinput.h> #include <libudev.h> #include <systemd/sd-event.h> @@ -46,15 +42,19 @@ #include <flutter-pi.h> #include <pixel_format.h> -#include <compositor.h> +#include <gl_renderer.h> +#include <compositor_ng.h> #include <keyboard.h> #include <user_input.h> #include <locales.h> #include <platformchannel.h> #include <pluginregistry.h> #include <texture_registry.h> +#include <modesetting.h> +#include <tracer.h> #include <plugins/text_input.h> #include <plugins/raw_keyboard.h> +#include <vk_renderer.h> #ifdef ENABLE_MTRACE # include <mcheck.h> @@ -62,6 +62,26 @@ FILE_DESCR("flutter-pi") +#define LOAD_EGL_PROC(flutterpi_struct, name, full_name) \ + do { \ + (flutterpi_struct).egl.name = (void*) eglGetProcAddress(#full_name); \ + if ((flutterpi_struct).egl.name == NULL) { \ + LOG_ERROR("FATAL: Could not resolve EGL procedure " #full_name "\n"); \ + return EINVAL; \ + } \ + } while (false) + +#define LOAD_GL_PROC(flutterpi_struct, name, full_name) \ + do { \ + (flutterpi_struct).gl.name = (void*) eglGetProcAddress(#full_name); \ + if ((flutterpi_struct).gl.name == NULL) { \ + LOG_ERROR("FATAL: Could not resolve GL procedure " #full_name "\n"); \ + return EINVAL; \ + } \ + } while (false) + +#define PIXFMT_ARG_NAME(_name, _arg_name, ...) _arg_name ", " + const char *const usage ="\ flutter-pi - run flutter apps on your Raspberry Pi.\n\ \n\ @@ -69,11 +89,15 @@ USAGE:\n\ flutter-pi [options] <asset bundle path> [flutter engine options]\n\ \n\ OPTIONS:\n\ + --config-file=<path> Path to the configuration file.\n\ +\n\ --release Run the app in release mode. The AOT snapshot\n\ of the app (\"app.so\") must be located inside the\n\ asset bundle directory.\n\ This also requires a libflutter_engine.so that was\n\ built with --runtime-mode=release.\n\ +\n\ + --vulkan Use vulkan for rendering.\n\ \n\ -o, --orientation <orientation> Start the app in this orientation. Valid\n\ for <orientation> are: portrait_up, landscape_left,\n\ @@ -98,8 +122,9 @@ OPTIONS:\n\ in turn basically \"scales\" the UI.\n\ \n\ --pixelformat <format> Selects the pixel format to use for the framebuffers.\n\ - Available pixel formats:\n\ - RGB565, ARGB8888, XRGB8888, BGRA8888, RGBA8888\n\ + If this is not specified, a good pixel format will\n\ + be selected automatically.\n\ + Available pixel formats: " PIXFMT_LIST(PIXFMT_ARG_NAME) "\n\ \n\ -i, --input <glob pattern> Appends all files matching this glob pattern to the\n\ list of input (touchscreen, mouse, touchpad, \n\ @@ -110,9 +135,9 @@ OPTIONS:\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\ + If that fails flutter-pi will fallback to using\n\ + all devices matching \"/dev/input/event*\" as \n\ + 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\ @@ -138,12 +163,9 @@ SEE ALSO:\n\ For instructions on how to build an asset bundle or an AOT snapshot\n\ of your app, please see the linked github repository.\n\ For a list of options you can pass to the flutter engine, look here:\n\ - https://github.com/flutter/engine/blob/master/shell/common/switches.h\n\ + https://github.com/flutter/engine/blob/main/shell/common/switches.h\n\ "; -// If this fails, update the accepted value list for --pixelformat above too. -COMPILE_ASSERT(kCount_PixFmt == 5); - struct flutterpi flutterpi; /*static int flutterpi_post_platform_task( @@ -159,33 +181,47 @@ static bool runs_platform_tasks_on_current_thread(void *userdata); /// Called on some flutter internal thread when the flutter /// rendering EGLContext should be made current. static bool on_make_current(void* userdata) { - EGLint egl_error; - - (void) userdata; + struct flutterpi *flutterpi; + EGLSurface surface; + int ok; + + DEBUG_ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + DEBUG_ASSERT_NOT_NULL(flutterpi->gl_renderer); - eglGetError(); + /// TODO: Test if this works + + if (compositor_has_egl_surface(flutterpi->compositor) || true) { + surface = compositor_get_egl_surface(flutterpi->compositor); + if (surface == EGL_NO_SURFACE) { + /// TODO: Should we allow this? + LOG_ERROR("Couldn't get an EGL surface from the compositor.\n"); + return false; + } + } else { + surface = EGL_NO_SURFACE; + } - eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.flutter_render_context); - if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { - LOG_ERROR("Could not make the flutter rendering EGL context current. eglMakeCurrent: 0x%08X\n", egl_error); + ok = gl_renderer_make_flutter_rendering_context_current(flutterpi->gl_renderer, surface); + if (ok != 0) { return false; } - + return true; } /// Called on some flutter internal thread to /// clear the EGLContext. static bool on_clear_current(void* userdata) { - EGLint egl_error; - - (void) userdata; + struct flutterpi *flutterpi; + int ok; - eglGetError(); + DEBUG_ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + DEBUG_ASSERT_NOT_NULL(flutterpi->gl_renderer); - eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { - LOG_ERROR("Could not clear the flutter EGL context. eglMakeCurrent: 0x%08X\n", egl_error); + ok = gl_renderer_clear_current(flutterpi->gl_renderer); + if (ok != 0) { return false; } @@ -206,170 +242,89 @@ static bool on_present(void *userdata) { /// (Won't be called since we're supplying a compositor, /// still needs to be present) static uint32_t fbo_callback(void* userdata) { - (void) userdata; + struct flutterpi *flutterpi; + + DEBUG_ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + + TRACER_INSTANT(flutterpi->tracer, "fbo_callback"); return 0; } /// Called on some flutter internal thread when the flutter /// resource uploading EGLContext should be made current. static bool on_make_resource_current(void *userdata) { - EGLint egl_error; - - (void) userdata; - - eglGetError(); + struct flutterpi *flutterpi; + int ok; - eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, flutterpi.egl.flutter_resource_uploading_context); - if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { - LOG_ERROR("Could not make the flutter resource uploading EGL context current. eglMakeCurrent: 0x%08X\n", egl_error); + DEBUG_ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + DEBUG_ASSERT_NOT_NULL(flutterpi->gl_renderer); + + ok = gl_renderer_make_flutter_resource_uploading_context_current(flutterpi->gl_renderer); + if (ok != 0) { return false; } return true; } -/// Cut a word from a string, mutating "string" -static void cut_word_from_string( - char* string, - const char* word +/// Called by flutter +static void *proc_resolver( + void* userdata, + const char* name ) { - size_t word_length = strlen(word); - char* word_in_str = strstr(string, word); + struct flutterpi *flutterpi; - // check if the given word is surrounded by spaces in the string - if (word_in_str - && ((word_in_str == string) || (word_in_str[-1] == ' ')) - && ((word_in_str[word_length] == 0) || (word_in_str[word_length] == ' ')) - ) { - if (word_in_str[word_length] == ' ') word_length++; + DEBUG_ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + DEBUG_ASSERT_NOT_NULL(flutterpi->gl_renderer); - int i = 0; - do { - word_in_str[i] = word_in_str[i+word_length]; - } while (word_in_str[i++ + word_length] != 0); - } + return gl_renderer_get_proc_address(flutterpi->gl_renderer, name); } -/// An override for glGetString since the real glGetString -/// won't work. -static const GLubyte *hacked_glGetString(GLenum name) { - static GLubyte *extensions = NULL; - if (name != GL_EXTENSIONS) - return glGetString(name); - - if (extensions == NULL) { - GLubyte *orig_extensions = (GLubyte *) glGetString(GL_EXTENSIONS); - - extensions = malloc(strlen((const char*)orig_extensions) + 1); - if (!extensions) { - return NULL; - } - - strcpy((char*)extensions, (const char*)orig_extensions); - - /* - * working (apparently) - */ - //cut_word_from_string(extensions, "GL_EXT_blend_minmax"); - //cut_word_from_string(extensions, "GL_EXT_multi_draw_arrays"); - //cut_word_from_string(extensions, "GL_EXT_texture_format_BGRA8888"); - //cut_word_from_string(extensions, "GL_OES_compressed_ETC1_RGB8_texture"); - //cut_word_from_string(extensions, "GL_OES_depth24"); - //cut_word_from_string(extensions, "GL_OES_texture_npot"); - //cut_word_from_string(extensions, "GL_OES_vertex_half_float"); - //cut_word_from_string(extensions, "GL_OES_EGL_image"); - //cut_word_from_string(extensions, "GL_OES_depth_texture"); - //cut_word_from_string(extensions, "GL_AMD_performance_monitor"); - //cut_word_from_string(extensions, "GL_OES_EGL_image_external"); - //cut_word_from_string(extensions, "GL_EXT_occlusion_query_boolean"); - //cut_word_from_string(extensions, "GL_KHR_texture_compression_astc_ldr"); - //cut_word_from_string(extensions, "GL_EXT_compressed_ETC1_RGB8_sub_texture"); - //cut_word_from_string(extensions, "GL_EXT_draw_elements_base_vertex"); - //cut_word_from_string(extensions, "GL_EXT_texture_border_clamp"); - //cut_word_from_string(extensions, "GL_OES_draw_elements_base_vertex"); - //cut_word_from_string(extensions, "GL_OES_texture_border_clamp"); - //cut_word_from_string(extensions, "GL_KHR_texture_compression_astc_sliced_3d"); - //cut_word_from_string(extensions, "GL_MESA_tile_raster_order"); - - /* - * should be working, but isn't - */ - cut_word_from_string((char*)extensions, "GL_EXT_map_buffer_range"); - - /* - * definitely broken - */ - cut_word_from_string((char*)extensions, "GL_OES_element_index_uint"); - cut_word_from_string((char*)extensions, "GL_OES_fbo_render_mipmap"); - cut_word_from_string((char*)extensions, "GL_OES_mapbuffer"); - cut_word_from_string((char*)extensions, "GL_OES_rgb8_rgba8"); - cut_word_from_string((char*)extensions, "GL_OES_stencil8"); - cut_word_from_string((char*)extensions, "GL_OES_texture_3D"); - cut_word_from_string((char*)extensions, "GL_OES_packed_depth_stencil"); - cut_word_from_string((char*)extensions, "GL_OES_get_program_binary"); - cut_word_from_string((char*)extensions, "GL_APPLE_texture_max_level"); - cut_word_from_string((char*)extensions, "GL_EXT_discard_framebuffer"); - cut_word_from_string((char*)extensions, "GL_EXT_read_format_bgra"); - cut_word_from_string((char*)extensions, "GL_EXT_frag_depth"); - cut_word_from_string((char*)extensions, "GL_NV_fbo_color_attachments"); - cut_word_from_string((char*)extensions, "GL_OES_EGL_sync"); - cut_word_from_string((char*)extensions, "GL_OES_vertex_array_object"); - cut_word_from_string((char*)extensions, "GL_EXT_unpack_subimage"); - cut_word_from_string((char*)extensions, "GL_NV_draw_buffers"); - cut_word_from_string((char*)extensions, "GL_NV_read_buffer"); - cut_word_from_string((char*)extensions, "GL_NV_read_depth"); - cut_word_from_string((char*)extensions, "GL_NV_read_depth_stencil"); - cut_word_from_string((char*)extensions, "GL_NV_read_stencil"); - cut_word_from_string((char*)extensions, "GL_EXT_draw_buffers"); - cut_word_from_string((char*)extensions, "GL_KHR_debug"); - cut_word_from_string((char*)extensions, "GL_OES_required_internalformat"); - cut_word_from_string((char*)extensions, "GL_OES_surfaceless_context"); - cut_word_from_string((char*)extensions, "GL_EXT_separate_shader_objects"); - cut_word_from_string((char*)extensions, "GL_KHR_context_flush_control"); - cut_word_from_string((char*)extensions, "GL_KHR_no_error"); - cut_word_from_string((char*)extensions, "GL_KHR_parallel_shader_compile"); - } - - return extensions; -} - -/// Called by flutter -static void *proc_resolver( +static void* on_get_vulkan_proc_address( void* userdata, + FlutterVulkanInstanceHandle instance, const char* name ) { - static int is_VC4 = -1; - void *address; + DEBUG_ASSERT_NOT_NULL(userdata); + DEBUG_ASSERT_NOT_NULL(name); - (void) userdata; + return (void*) vkGetInstanceProcAddr((VkInstance) instance, name); +} - /* - * The mesa V3D driver reports some OpenGL ES extensions as supported and working - * even though they aren't. hacked_glGetString is a workaround for this, which will - * cut out the non-working extensions from the list of supported extensions. - */ +static FlutterVulkanImage on_get_next_vulkan_image( + void *userdata, + const FlutterFrameInfo *frameinfo +) { + struct flutterpi *flutterpi; - if (name == NULL) - return NULL; + DEBUG_ASSERT_NOT_NULL(userdata); + DEBUG_ASSERT_NOT_NULL(frameinfo); + flutterpi = userdata; + + (void) flutterpi; + (void) frameinfo; - // first detect if we're running on a VideoCore 4 / using the VC4 driver. - if ((is_VC4 == -1) && (is_VC4 = strcmp(flutterpi.egl.renderer, "VC4 V3D 2.1") == 0)) { - printf( "detected VideoCore IV as underlying graphics chip, and VC4 as the driver.\n" - "Reporting modified GL_EXTENSIONS string that doesn't contain non-working extensions.\n"); - is_VC4 = 0; - } + UNIMPLEMENTED(); +} - // if we do, and the symbol to resolve is glGetString, we return our hacked_glGetString. - if (is_VC4 && (strcmp(name, "glGetString") == 0)) - return hacked_glGetString; +static bool on_present_vulkan_image( + void *userdata, + const FlutterVulkanImage *image +) { + struct flutterpi *flutterpi; - if ((address = dlsym(RTLD_DEFAULT, name)) || (address = eglGetProcAddress(name))) - return address; + DEBUG_ASSERT_NOT_NULL(userdata); + DEBUG_ASSERT_NOT_NULL(image); + flutterpi = userdata; - LOG_ERROR("proc_resolver: Could not resolve symbol \"%s\"\n", name); + (void) flutterpi; + (void) image; - return NULL; + UNIMPLEMENTED(); } static void on_platform_message( @@ -386,104 +341,148 @@ static void on_platform_message( } } -/// Called on the main thread when a new frame request may have arrived. -/// Uses [drmCrtcGetSequence] or [FlutterEngineGetCurrentTime] to complete -/// the frame request. -static int on_execute_frame_request( - void *userdata -) { - FlutterEngineResult result; - struct frame *peek; +static bool flutterpi_runs_platfrom_tasks_on_current_thread(struct flutterpi *flutterpi) { + DEBUG_ASSERT_NOT_NULL(flutterpi); + return pthread_equal(pthread_self(), flutterpi->event_loop_thread) != 0; +} + +struct frame_req { + struct flutterpi *flutterpi; + intptr_t baton; + uint64_t vblank_ns, next_vblank_ns; +}; + +static int on_deferred_begin_frame(void *userdata) { + FlutterEngineResult engine_result; + struct frame_req *req; + + DEBUG_ASSERT_NOT_NULL(userdata); + req = userdata; + + DEBUG_ASSERT(flutterpi_runs_platfrom_tasks_on_current_thread(req->flutterpi)); + + TRACER_INSTANT(req->flutterpi->tracer, "FlutterEngineOnVsync"); + engine_result = req->flutterpi->flutter.procs.OnVsync( + req->flutterpi->flutter.engine, + req->baton, + req->vblank_ns, req->next_vblank_ns + ); + + free(req); + + if (engine_result != kSuccess) { + LOG_ERROR("Couldn't signal frame begin to flutter engine. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); + return EIO; + } + + return 0; +} + +MAYBE_UNUSED static void on_begin_frame(void *userdata, uint64_t vblank_ns, uint64_t next_vblank_ns) { + FlutterEngineResult engine_result; + struct frame_req *req; int ok; - (void) userdata; - cqueue_lock(&flutterpi.frame_queue); - - ok = cqueue_peek_locked(&flutterpi.frame_queue, (void**) &peek); - if (ok == 0) { - if (peek->state == kFramePending) { - 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); - if (ok < 0) { - perror("[flutter-pi] Couldn't get last vblank timestamp. drmCrtcGetSequence"); - cqueue_unlock(&flutterpi.frame_queue); - return errno; - } - } else { - ns = flutterpi.flutter.libflutter_engine.FlutterEngineGetCurrentTime(); - } - - result = flutterpi.flutter.libflutter_engine.FlutterEngineOnVsync( - flutterpi.flutter.engine, - peek->baton, - ns, - ns + (1000000000 / flutterpi.display.refresh_rate) - ); - if (result != kSuccess) { - LOG_ERROR("Could not reply to frame request. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(result)); - cqueue_unlock(&flutterpi.frame_queue); - return EIO; - } + DEBUG_ASSERT_NOT_NULL(userdata); + req = userdata; - peek->state = kFrameRendering; + if (flutterpi_runs_platfrom_tasks_on_current_thread(req->flutterpi)) { + TRACER_INSTANT(req->flutterpi->tracer, "FlutterEngineOnVsync"); + + engine_result = req->flutterpi->flutter.procs.OnVsync( + req->flutterpi->flutter.engine, + req->baton, + vblank_ns, next_vblank_ns + ); + if (engine_result != kSuccess) { + LOG_ERROR("Couldn't signal frame begin to flutter engine. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); + goto fail_free_req; + } + + free(req); + } else { + req->vblank_ns = vblank_ns; + req->next_vblank_ns = next_vblank_ns; + ok = flutterpi_post_platform_task(on_deferred_begin_frame, req); + if (ok != 0) { + LOG_ERROR("Couldn't defer signalling frame begin.\n"); + goto fail_free_req; } - } else if (ok == EAGAIN) { - // do nothing - } else if (ok != 0) { - LOG_ERROR("Could not get peek of frame queue. cqueue_peek_locked: %s\n", strerror(ok)); - cqueue_unlock(&flutterpi.frame_queue); - return ok; } - cqueue_unlock(&flutterpi.frame_queue); + return; - return 0; + fail_free_req: + free(req); + return; } /// 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 ) { - struct frame *peek; + FlutterEngineResult engine_result; + struct flutterpi *flutterpi; + struct frame_req *req; int ok; - (void) userdata; - cqueue_lock(&flutterpi.frame_queue); + DEBUG_ASSERT_NOT_NULL(userdata); + flutterpi = userdata; - ok = cqueue_peek_locked(&flutterpi.frame_queue, (void**) &peek); - if ((ok == 0) || (ok == EAGAIN)) { - bool reply_instantly = ok == EAGAIN; + TRACER_INSTANT(flutterpi->tracer, "on_frame_request"); - ok = cqueue_try_enqueue_locked(&flutterpi.frame_queue, &(struct frame) { - .state = kFramePending, - .baton = baton - }); - if (ok != 0) { - LOG_ERROR("Could not enqueue frame request. cqueue_try_enqueue_locked: %s\n", strerror(ok)); - cqueue_unlock(&flutterpi.frame_queue); - return; + req = malloc(sizeof *req); + if (req == NULL) { + LOG_ERROR("Out of memory\n"); + return; + } + + req->flutterpi = flutterpi; + req->baton = baton; + req->vblank_ns = get_monotonic_time(); + req->next_vblank_ns = req->vblank_ns + (1000000000.0 / compositor_get_refresh_rate(flutterpi->compositor)); + + if (flutterpi_runs_platfrom_tasks_on_current_thread(req->flutterpi)) { + TRACER_INSTANT(req->flutterpi->tracer, "FlutterEngineOnVsync"); + + engine_result = req->flutterpi->flutter.procs.OnVsync( + req->flutterpi->flutter.engine, + req->baton, + req->vblank_ns, + req->next_vblank_ns + ); + if (engine_result != kSuccess) { + LOG_ERROR("Couldn't signal frame begin to flutter engine. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); + goto fail_free_req; } - if (reply_instantly) { - flutterpi_post_platform_task( - on_execute_frame_request, - NULL - ); + free(req); + } else { + ok = flutterpi_post_platform_task(on_deferred_begin_frame, req); + if (ok != 0) { + LOG_ERROR("Couldn't defer signalling frame begin.\n"); + goto fail_free_req; } - } else if (ok != 0) { - LOG_ERROR("Could not get peek of frame queue. cqueue_peek_locked: %s\n", strerror(ok)); } - cqueue_unlock(&flutterpi.frame_queue); + return; + + fail_free_req: + free(req); } static FlutterTransformation on_get_transformation(void *userdata) { - (void) userdata; - return flutterpi.view.view_to_display_transform; + struct view_geometry geometry; + struct flutterpi *flutterpi; + + DEBUG_ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + + compositor_get_view_geometry(flutterpi->compositor, &geometry); + + return geometry.view_to_display_transform; } atomic_int_least64_t platform_task_counter = 0; @@ -712,7 +711,7 @@ static int on_execute_flutter_task( task = userdata; - result = flutterpi.flutter.libflutter_engine.FlutterEngineRunTask(flutterpi.flutter.engine, task); + result = flutterpi.flutter.procs.RunTask(flutterpi.flutter.engine, task); if (result != kSuccess) { LOG_ERROR("Error running platform task. FlutterEngineRunTask: %d\n", result); free(task); @@ -761,9 +760,9 @@ static int on_send_platform_message( msg = userdata; if (msg->is_response) { - result = flutterpi.flutter.libflutter_engine.FlutterEngineSendPlatformMessageResponse(flutterpi.flutter.engine, msg->target_handle, msg->message, msg->message_size); + result = flutterpi.flutter.procs.SendPlatformMessageResponse(flutterpi.flutter.engine, msg->target_handle, msg->message, msg->message_size); } else { - result = flutterpi.flutter.libflutter_engine.FlutterEngineSendPlatformMessage( + result = flutterpi.flutter.procs.SendPlatformMessage( flutterpi.flutter.engine, &(FlutterPlatformMessage) { .struct_size = sizeof(FlutterPlatformMessage), @@ -793,6 +792,7 @@ static int on_send_platform_message( } int flutterpi_send_platform_message( + struct flutterpi *flutterpi, const char *channel, const uint8_t *restrict message, size_t message_size, @@ -802,9 +802,9 @@ int flutterpi_send_platform_message( FlutterEngineResult result; int ok; - if (runs_platform_tasks_on_current_thread(NULL)) { - result = flutterpi.flutter.libflutter_engine.FlutterEngineSendPlatformMessage( - flutterpi.flutter.engine, + if (runs_platform_tasks_on_current_thread(flutterpi)) { + result = flutterpi->flutter.procs.SendPlatformMessage( + flutterpi->flutter.engine, &(const FlutterPlatformMessage) { .struct_size = sizeof(FlutterPlatformMessage), .channel = channel, @@ -871,8 +871,8 @@ int flutterpi_respond_to_platform_message( FlutterEngineResult result; int ok; - if (runs_platform_tasks_on_current_thread(NULL)) { - result = flutterpi.flutter.libflutter_engine.FlutterEngineSendPlatformMessageResponse( + if (runs_platform_tasks_on_current_thread(&flutterpi)) { + result = flutterpi.flutter.procs.SendPlatformMessageResponse( flutterpi.flutter.engine, handle, message, @@ -935,56 +935,33 @@ const char *flutterpi_get_asset_bundle_path( /// TODO: Make this refcounted if we're gonna use it from multiple threads. struct gbm_device *flutterpi_get_gbm_device(struct flutterpi *flutterpi) { - return flutterpi->gbm.device; + return drmdev_get_gbm_device(flutterpi->drm.drmdev); } -EGLDisplay flutterpi_get_egl_display(struct flutterpi *flutterpi) { - return flutterpi->egl.display; +bool flutterpi_has_gl_renderer(struct flutterpi *flutterpi) { + DEBUG_ASSERT_NOT_NULL(flutterpi); + return flutterpi->gl_renderer != NULL; } -EGLContext flutterpi_create_egl_context(struct flutterpi *flutterpi) { - EGLContext context; - - pthread_mutex_lock(&flutterpi->egl.temp_context_lock); - - context = eglCreateContext( - flutterpi->egl.display, - flutterpi->egl.config, - flutterpi->egl.temp_context, - (EGLint[]) { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - } - ); - if (context == EGL_NO_CONTEXT) { - LOG_ERROR("Could not create new EGL context from temp context. eglCreateContext: %" PRId32 "\n", eglGetError()); - goto fail_unlock_mutex; - } - - pthread_mutex_unlock(&flutterpi->egl.temp_context_lock); - - return context; - - fail_unlock_mutex: - pthread_mutex_unlock(&flutterpi->egl.temp_context_lock); - return EGL_NO_CONTEXT; +struct gl_renderer *flutterpi_get_gl_renderer(struct flutterpi *flutterpi) { + DEBUG_ASSERT_NOT_NULL(flutterpi); + return flutterpi->gl_renderer; } void flutterpi_trace_event_instant(struct flutterpi *flutterpi, const char *name) { - flutterpi->flutter.libflutter_engine.FlutterEngineTraceEventInstant(name); + flutterpi->flutter.procs.TraceEventInstant(name); } void flutterpi_trace_event_begin(struct flutterpi *flutterpi, const char *name) { - flutterpi->flutter.libflutter_engine.FlutterEngineTraceEventDurationBegin(name); + flutterpi->flutter.procs.TraceEventDurationBegin(name); } void flutterpi_trace_event_end(struct flutterpi *flutterpi, const char *name) { - flutterpi->flutter.libflutter_engine.FlutterEngineTraceEventDurationEnd(name); + flutterpi->flutter.procs.TraceEventDurationEnd(name); } static bool runs_platform_tasks_on_current_thread(void* userdata) { - (void) userdata; - return pthread_equal(pthread_self(), flutterpi.event_loop_thread) != 0; + return flutterpi_runs_platfrom_tasks_on_current_thread(userdata); } static int run_main_loop(void) { @@ -1131,186 +1108,17 @@ static int init_main_loop(void) { /************************** * DISPLAY INITIALIZATION * **************************/ -/// Called on the main thread when a pageflip ocurred. -void on_pageflip_event( - int fd, - unsigned int frame, - unsigned int sec, - unsigned int usec, - void *userdata -) { - FlutterEngineResult result; - struct frame presented_frame, *peek; - int ok; - - (void) fd; - (void) frame; - (void) userdata; - - flutterpi.flutter.libflutter_engine.FlutterEngineTraceEventInstant("pageflip"); - - cqueue_lock(&flutterpi.frame_queue); - - ok = cqueue_try_dequeue_locked(&flutterpi.frame_queue, &presented_frame); - if (ok != 0) { - LOG_ERROR("Could not dequeue completed frame from frame queue: %s\n", strerror(ok)); - goto fail_unlock_frame_queue; - } - - ok = cqueue_peek_locked(&flutterpi.frame_queue, (void**) &peek); - if (ok == EAGAIN) { - // no frame queued after the one that was completed right now. - // do nothing here. - } else if (ok != 0) { - LOG_ERROR("Could not get frame queue peek. cqueue_peek_locked: %s\n", strerror(ok)); - goto fail_unlock_frame_queue; - } else { - if (peek->state == kFramePending) { - uint64_t ns = (sec * 1000000000ll) + (usec * 1000ll); - - result = flutterpi.flutter.libflutter_engine.FlutterEngineOnVsync( - flutterpi.flutter.engine, - peek->baton, - ns, - ns + (1000000000ll / flutterpi.display.refresh_rate) - ); - if (result != kSuccess) { - LOG_ERROR("Could not reply to frame request. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(result)); - goto fail_unlock_frame_queue; - } - - peek->state = kFrameRendering; - } else { - LOG_ERROR("frame queue in inconsistent state. aborting\n"); - abort(); - } - } - - cqueue_unlock(&flutterpi.frame_queue); - - ok = compositor_on_page_flip(sec, usec); - if (ok != 0) { - LOG_ERROR("Error notifying compositor about page flip. compositor_on_page_flip: %s\n", strerror(ok)); - } - - return; - - - fail_unlock_frame_queue: - cqueue_unlock(&flutterpi.frame_queue); -} - -static int on_drm_fd_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - int ok; +static int on_compositor_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + struct compositor *compositor; (void) s; + (void) fd; (void) revents; - (void) userdata; - - ok = drmHandleEvent(fd, &flutterpi.drm.evctx); - if (ok < 0) { - perror("[flutter-pi] Could not handle DRM event. drmHandleEvent"); - return -errno; - } - - return 0; -} - -int flutterpi_fill_view_properties( - bool has_orientation, - enum device_orientation orientation, - bool has_rotation, - int rotation -) { - enum device_orientation default_orientation = flutterpi.display.width >= flutterpi.display.height ? kLandscapeLeft : kPortraitUp; - - if (flutterpi.view.has_orientation) { - if (flutterpi.view.has_rotation == false) { - flutterpi.view.rotation = ANGLE_BETWEEN_ORIENTATIONS(default_orientation, flutterpi.view.orientation); - flutterpi.view.has_rotation = true; - } - } else if (flutterpi.view.has_rotation) { - for (int i = kPortraitUp; i <= kLandscapeRight; i++) { - if (ANGLE_BETWEEN_ORIENTATIONS(default_orientation, i) == flutterpi.view.rotation) { - flutterpi.view.orientation = i; - flutterpi.view.has_orientation = true; - break; - } - } - } else { - flutterpi.view.orientation = default_orientation; - flutterpi.view.has_orientation = true; - flutterpi.view.rotation = 0; - flutterpi.view.has_rotation = true; - } - if (has_orientation) { - flutterpi.view.rotation += ANGLE_BETWEEN_ORIENTATIONS(flutterpi.view.orientation, orientation); - if (flutterpi.view.rotation >= 360) { - flutterpi.view.rotation -= 360; - } - - flutterpi.view.orientation = orientation; - } else if (has_rotation) { - for (int i = kPortraitUp; i <= kLandscapeRight; i++) { - if (ANGLE_BETWEEN_ORIENTATIONS(default_orientation, i) == rotation) { - flutterpi.view.orientation = i; - flutterpi.view.rotation = rotation; - break; - } - } - } - - if ((flutterpi.view.rotation <= 45) || ((flutterpi.view.rotation >= 135) && (flutterpi.view.rotation <= 225)) || (flutterpi.view.rotation >= 315)) { - flutterpi.view.width = flutterpi.display.width; - flutterpi.view.height = flutterpi.display.height; - flutterpi.view.width_mm = flutterpi.display.width_mm; - flutterpi.view.height_mm = flutterpi.display.height_mm; - } else { - flutterpi.view.width = flutterpi.display.height; - flutterpi.view.height = flutterpi.display.width; - flutterpi.view.width_mm = flutterpi.display.height_mm; - flutterpi.view.height_mm = flutterpi.display.width_mm; - } - - if (flutterpi.view.rotation == 0) { - flutterpi.view.view_to_display_transform = FLUTTER_TRANSLATION_TRANSFORMATION(0, 0); - - flutterpi.view.display_to_view_transform = FLUTTER_TRANSLATION_TRANSFORMATION(0, 0); - } else if (flutterpi.view.rotation == 90) { - flutterpi.view.view_to_display_transform = FLUTTER_ROTZ_TRANSFORMATION(90); - flutterpi.view.view_to_display_transform.transX = flutterpi.display.width; - - flutterpi.view.display_to_view_transform = FLUTTER_ROTZ_TRANSFORMATION(-90); - flutterpi.view.display_to_view_transform.transY = flutterpi.display.width; - } else if (flutterpi.view.rotation == 180) { - flutterpi.view.view_to_display_transform = FLUTTER_ROTZ_TRANSFORMATION(180); - flutterpi.view.view_to_display_transform.transX = flutterpi.display.width; - flutterpi.view.view_to_display_transform.transY = flutterpi.display.height; - - flutterpi.view.display_to_view_transform = FLUTTER_ROTZ_TRANSFORMATION(-180); - flutterpi.view.display_to_view_transform.transX = flutterpi.display.width; - flutterpi.view.display_to_view_transform.transY = flutterpi.display.height; - } else if (flutterpi.view.rotation == 270) { - flutterpi.view.view_to_display_transform = FLUTTER_ROTZ_TRANSFORMATION(270); - flutterpi.view.view_to_display_transform.transY = flutterpi.display.height; - - flutterpi.view.display_to_view_transform = FLUTTER_ROTZ_TRANSFORMATION(-270); - flutterpi.view.display_to_view_transform.transX = flutterpi.display.height; - } - - if (flutterpi.user_input != NULL) { - // update the user input with the new transforms - user_input_set_transform( - flutterpi.user_input, - &flutterpi.view.display_to_view_transform, - &flutterpi.view.view_to_display_transform, - flutterpi.display.width, - flutterpi.display.height - ); - } + DEBUG_ASSERT_NOT_NULL(userdata); + compositor = userdata; - return 0; + return compositor_on_event_fd_ready(compositor); } static const FlutterLocale* on_compute_platform_resolved_locales(const FlutterLocale **locales, size_t n_locales) { @@ -1339,42 +1147,46 @@ static bool on_gl_external_texture_frame_callback( ); } -static int load_egl_gl_procs(void) { - LOAD_EGL_PROC(flutterpi, getPlatformDisplay, eglGetPlatformDisplayEXT); - LOAD_EGL_PROC(flutterpi, createPlatformWindowSurface, eglCreatePlatformWindowSurface); - LOAD_EGL_PROC(flutterpi, createPlatformPixmapSurface, eglCreatePlatformPixmapSurface); - flutterpi.egl.createDRMImageMESA = (PFNEGLCREATEDRMIMAGEMESAPROC) eglGetProcAddress("eglCreateDRMImageMESA"); - flutterpi.egl.exportDRMImageMESA = (PFNEGLEXPORTDRMIMAGEMESAPROC) eglGetProcAddress("eglExportDRMImageMESA"); - flutterpi.gl.EGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) eglGetProcAddress("glEGLImageTargetTexture2DOES"); - flutterpi.gl.EGLImageTargetRenderbufferStorageOES = (PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC) eglGetProcAddress("glEGLImageTargetRenderbufferStorageOES"); - return 0; -} +// clang-format off +static int init_display( + struct flutterpi *flutterpi, + sd_event *event_loop, + bool has_pixel_format, enum pixfmt pixel_format, + bool has_rotation, drm_plane_transform_t rotation, + bool has_orientation, enum device_orientation orientation, + bool has_explicit_dimensions, int width_mm, int height_mm, + bool use_vulkan +) { + // clang-format on -static int init_display(void) { /********************** * DRM INITIALIZATION * **********************/ - const struct drm_connector *connector; - const struct drm_encoder *encoder; - const struct drm_crtc *crtc; - const drmModeModeInfo *mode, *mode_iter; + struct compositor *compositor; + struct gbm_device *gbm_device; + sd_event_source *compositor_event_source; + struct drmdev *drmdev; + struct tracer *tracer; drmDevicePtr devices[64]; - EGLint egl_error; - int ok, num_devices; + struct gl_renderer *gl_renderer; + struct vk_renderer *vk_renderer; + int ok, n_devices; /********************** * DRM INITIALIZATION * **********************/ - num_devices = drmGetDevices2(0, devices, sizeof(devices)/sizeof(*devices)); - if (num_devices < 0) { - LOG_ERROR("Could not query DRM device list: %s\n", strerror(-num_devices)); - return -num_devices; + ok = drmGetDevices2(0, devices, sizeof(devices)/sizeof(*devices)); + if (ok < 0) { + LOG_ERROR("Could not query DRM device list: %s\n", strerror(-ok)); + return -ok; } + + n_devices = ok; // find a GPU that has a primary node - flutterpi.drm.drmdev = NULL; - for (int i = 0; i < num_devices; i++) { + drmdev = NULL; + for (int i = 0; i < n_devices; i++) { drmDevicePtr device; device = devices[i]; @@ -1384,7 +1196,7 @@ static int init_display(void) { continue; } - ok = drmdev_new_from_path(&flutterpi.drm.drmdev, device->nodes[DRM_NODE_PRIMARY]); + ok = drmdev_new_from_path(&drmdev, device->nodes[DRM_NODE_PRIMARY]); if (ok != 0) { LOG_ERROR("Could not create drmdev from device at \"%s\". Continuing.\n", device->nodes[DRM_NODE_PRIMARY]); continue; @@ -1393,418 +1205,146 @@ static int init_display(void) { break; } - if (flutterpi.drm.drmdev == NULL) { + if (drmdev == NULL) { LOG_ERROR("flutter-pi couldn't find a usable DRM device.\n" "Please make sure you've enabled the Fake-KMS driver in raspi-config.\n" "If you're not using a Raspberry Pi, please make sure there's KMS support for your graphics chip.\n"); return ENOENT; } - // find a connected connector - for_each_connector_in_drmdev(flutterpi.drm.drmdev, connector) { - if (connector->connector->connection == DRM_MODE_CONNECTED) { - // 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 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)) { - // don't change anything. - } else { - flutterpi.display.width_mm = connector->connector->mmWidth; - flutterpi.display.height_mm = connector->connector->mmHeight; - } - } - - break; - } + gbm_device = drmdev_get_gbm_device(drmdev); + if (gbm_device == NULL) { + LOG_ERROR("Couldn't create GBM device.\n"); + goto fail_unref_drmdev; } - if (connector == NULL) { - LOG_ERROR("Could not find a connected connector!\n"); - return EINVAL; + tracer = tracer_new_with_stubs(); + if (tracer == NULL) { + LOG_ERROR("Couldn't create event tracer.\n"); + ok = EIO; + goto fail_unref_drmdev; } - // 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))) { - mode = mode_iter; - } + if (use_vulkan) { + gl_renderer = NULL; + vk_renderer = vk_renderer_new(); + if (vk_renderer == NULL) { + LOG_ERROR("Couldn't create vulkan renderer.\n"); + ok = EIO; + goto fail_unref_tracer; } - } - - if (mode == NULL) { - LOG_ERROR("Could not find a preferred output mode!\n"); - return EINVAL; - } - flutterpi.display.width = mode->hdisplay; - flutterpi.display.height = mode->vdisplay; - flutterpi.display.refresh_rate = mode->vrefresh; - - if ((flutterpi.display.width_mm == 0) || (flutterpi.display.height_mm == 0)) { - LOG_ERROR("WARNING: display didn't provide valid physical dimensions. The device-pixel ratio will default to 1.0, which may not be the fitting device-pixel ratio for your display.\n"); - flutterpi.display.pixel_ratio = 1.0; - } else { - flutterpi.display.pixel_ratio = (10.0 * flutterpi.display.width) / (flutterpi.display.width_mm * 38.0); - - int horizontal_dpi = (int) (flutterpi.display.width / (flutterpi.display.width_mm / 25.4)); - int vertical_dpi = (int) (flutterpi.display.height / (flutterpi.display.height_mm / 25.4)); - - if (horizontal_dpi != vertical_dpi) { - // See https://github.com/flutter/flutter/issues/71865 for current status of this issue. - LOG_ERROR("WARNING: display has non-square pixels. Non-square-pixels are not supported by flutter.\n"); - } - } - - for_each_encoder_in_drmdev(flutterpi.drm.drmdev, encoder) { - if (encoder->encoder->encoder_id == connector->connector->encoder_id) { - break; - } - } - - if (encoder == NULL) { - for (int i = 0; i < connector->connector->count_encoders; i++, encoder = NULL) { - for_each_encoder_in_drmdev(flutterpi.drm.drmdev, encoder) { - if (encoder->encoder->encoder_id == connector->connector->encoders[i]) { - break; - } - } - - if (encoder->encoder->possible_crtcs) { - // only use this encoder if there's a crtc we can use with it - break; - } + compositor = compositor_new_vulkan( + drmdev, + tracer, + vk_renderer, + has_rotation, rotation, + has_orientation, orientation, + has_explicit_dimensions, width_mm, height_mm, + has_pixel_format, pixel_format, + false, + kTripleBufferedVsync_PresentMode + ); + if (compositor == NULL) { + LOG_ERROR("Couldn't create compositor.\n"); + ok = EIO; + goto fail_unref_renderer; } - } - - if (encoder == NULL) { - LOG_ERROR("Could not find a suitable DRM encoder.\n"); - return EINVAL; - } - - for_each_crtc_in_drmdev(flutterpi.drm.drmdev, crtc) { - if (crtc->crtc->crtc_id == encoder->encoder->crtc_id) { - break; + } else { + vk_renderer = NULL; + gl_renderer = gl_renderer_new_from_gbm_device(tracer, gbm_device, has_pixel_format, pixel_format); + if (gl_renderer == NULL) { + LOG_ERROR("Couldn't create EGL/OpenGL renderer.\n"); + ok = EIO; + goto fail_unref_tracer; } - } - if (crtc == NULL) { - for_each_crtc_in_drmdev(flutterpi.drm.drmdev, crtc) { - if (encoder->encoder->possible_crtcs & crtc->bitmask) { - // find a CRTC that is possible to use with this encoder - break; - } + // it seems that after some Raspbian update, regular users are sometimes no longer allowed + // to use the direct-rendering infrastructure; i.e. the open the devices inside /dev/dri/ + // as read-write. flutter-pi must be run as root then. + // sometimes it works fine without root, sometimes it doesn't. + if (gl_renderer_is_llvmpipe(gl_renderer)) { + LOG_ERROR_UNPREFIXED( + "WARNING: Detected llvmpipe (ie. software rendering) as the OpenGL ES renderer.\n" + " Check that flutter-pi has permission to use the 3D graphics hardware,\n" + " or try running it as root.\n" + " This warning will probably result in a \"failed to set mode\" error\n" + " later on in the initialization.\n" + ); + ok = EINVAL; + goto fail_unref_renderer; } - } - - if (crtc == NULL) { - LOG_ERROR("Could not find a suitable DRM CRTC.\n"); - 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; - // 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); - int _errno = errno; - - if ((ok == 0) && (ns != 0)) { - flutterpi.drm.platform_supports_get_sequence_ioctl = true; - } else { - flutterpi.drm.platform_supports_get_sequence_ioctl = false; - if (ok != 0) { - LOG_ERROR("WARNING: Error getting last vblank timestamp. drmCrtcGetSequence: %s\n", strerror(_errno)); - } else { - LOG_ERROR("WARNING: Kernel didn't return a valid vblank timestamp. (timestamp == 0)\n"); - } - LOG_ERROR("VSync will be disabled.\nSee https://github.com/ardera/flutter-pi/issues/38 for more info.\n"); + compositor = compositor_new( + drmdev, + tracer, + gl_renderer, + has_rotation, rotation, + has_orientation, orientation, + has_explicit_dimensions, width_mm, height_mm, + gl_renderer_get_forced_egl_config(gl_renderer), + has_pixel_format, pixel_format, + false, + kTripleBufferedVsync_PresentMode + ); + if (compositor == NULL) { + LOG_ERROR("Couldn't create compositor.\n"); + ok = EIO; + goto fail_unref_renderer; } } - memset(&flutterpi.drm.evctx, 0, sizeof(drmEventContext)); - flutterpi.drm.evctx.version = 4; - flutterpi.drm.evctx.page_flip_handler = on_pageflip_event; - ok = sd_event_add_io( - flutterpi.event_loop, - &flutterpi.drm.drm_pageflip_event_source, - flutterpi.drm.drmdev->fd, + event_loop, + &compositor_event_source, + compositor_get_event_fd(compositor), EPOLLIN | EPOLLHUP | EPOLLPRI, - on_drm_fd_ready, - NULL + on_compositor_ready, + compositor_ref(compositor) ); if (ok < 0) { - LOG_ERROR("Could not add DRM pageflip event listener. sd_event_add_io: %s\n", strerror(-ok)); - return -ok; - } - - printf( - "===================================\n" - "display mode:\n" - " resolution: %u x %u\n" - " refresh rate: %uHz\n" - " physical size: %umm x %umm\n" - " flutter device pixel ratio: %f\n" - "===================================\n", - flutterpi.display.width, flutterpi.display.height, - flutterpi.display.refresh_rate, - flutterpi.display.width_mm, flutterpi.display.height_mm, - flutterpi.display.pixel_ratio - ); - - /********************** - * GBM INITIALIZATION * - **********************/ - flutterpi.gbm.device = gbm_create_device(flutterpi.drm.drmdev->fd); - flutterpi.gbm.format = DRM_FORMAT_ARGB8888; - 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); - if (flutterpi.gbm.surface == NULL) { - perror("[flutter-pi] Could not create GBM Surface. gbm_surface_create_with_modifiers"); - return errno; - } - - /********************** - * EGL INITIALIZATION * - **********************/ - EGLint major, minor; - - static const EGLint context_attribs[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - - const EGLint config_attribs[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_SAMPLES, 0, - EGL_NONE - }; - - const char *egl_exts_client, *egl_exts_dpy, *gl_exts; - - egl_exts_client = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); - - ok = load_egl_gl_procs(); - if (ok != 0) { - LOG_ERROR("Could not load EGL / GL ES procedure addresses! error: %s\n", strerror(ok)); - return ok; - } - - eglGetError(); - -#ifdef EGL_KHR_platform_gbm - flutterpi.egl.display = flutterpi.egl.getPlatformDisplay(EGL_PLATFORM_GBM_KHR, flutterpi.gbm.device, NULL); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not get EGL display! eglGetPlatformDisplay: 0x%08X\n", egl_error); - return EIO; - } -#else - flutterpi.egl.display = eglGetDisplay((void*) flutterpi.gbm.device); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not get EGL display! eglGetDisplay: 0x%08X\n", egl_error); - return EIO; - } -#endif - - eglInitialize(flutterpi.egl.display, &major, &minor); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Failed to initialize EGL! eglInitialize: 0x%08X\n", egl_error); - return EIO; - } - - egl_exts_dpy = eglQueryString(flutterpi.egl.display, EGL_EXTENSIONS); - - printf("EGL information:\n"); - printf(" version: %s\n", eglQueryString(flutterpi.egl.display, EGL_VERSION)); - printf(" vendor: \"%s\"\n", eglQueryString(flutterpi.egl.display, EGL_VENDOR)); - printf(" client extensions: \"%s\"\n", egl_exts_client); - printf(" display extensions: \"%s\"\n", egl_exts_dpy); - printf("===================================\n"); - - eglBindAPI(EGL_OPENGL_ES_API); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Failed to bind OpenGL ES API! eglBindAPI: 0x%08X\n", egl_error); - return EIO; + ok = -ok; + LOG_ERROR("Could not add DRM pageflip event listener. sd_event_add_io: %s\n", strerror(ok)); + goto fail_unref_compositor; } - - EGLint count = 0, matched = 0; - EGLConfig *configs; - bool _found_matching_config = false; - eglGetConfigs(flutterpi.egl.display, NULL, 0, &count); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not get the number of EGL framebuffer configurations. eglGetConfigs: 0x%08X\n", egl_error); - return EIO; - } - - configs = malloc(count * sizeof(EGLConfig)); - if (!configs) return ENOMEM; - - eglChooseConfig(flutterpi.egl.display, config_attribs, configs, count, &matched); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not query EGL framebuffer configurations with fitting attributes. eglChooseConfig: 0x%08X\n", egl_error); - return EIO; - } - - if (matched == 0) { - LOG_ERROR("No fitting EGL framebuffer configuration found.\n"); - return EIO; - } - - for (int i = 0; i < count; i++) { - EGLint native_visual_id; - - eglGetConfigAttrib(flutterpi.egl.display, configs[i], EGL_NATIVE_VISUAL_ID, &native_visual_id); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not query native visual ID of EGL config. eglGetConfigAttrib: 0x%08X\n", egl_error); - continue; - } - - if (native_visual_id == flutterpi.gbm.format) { - flutterpi.egl.config = configs[i]; - _found_matching_config = true; - break; - } - } - free(configs); - - if (_found_matching_config == false) { - LOG_ERROR("Could not find EGL framebuffer configuration with appropriate attributes & native visual ID.\n"); - return EIO; - } - - /**************************** - * OPENGL ES INITIALIZATION * - ****************************/ - flutterpi.egl.root_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, EGL_NO_CONTEXT, context_attribs); - if (flutterpi.egl.root_context == EGL_NO_CONTEXT) { - LOG_ERROR("Could not create OpenGL ES root context. eglCreateContext: 0x%08X\n", egl_error); - return EIO; - } - - flutterpi.egl.flutter_render_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, flutterpi.egl.root_context, context_attribs); - if (flutterpi.egl.flutter_render_context == EGL_NO_CONTEXT) { - LOG_ERROR("Could not create OpenGL ES context for flutter rendering. eglCreateContext: 0x%08X\n", egl_error); - return EIO; - } - - flutterpi.egl.flutter_resource_uploading_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, flutterpi.egl.root_context, context_attribs); - if (flutterpi.egl.flutter_resource_uploading_context == EGL_NO_CONTEXT) { - LOG_ERROR("Could not create OpenGL ES context for flutter resource uploads. eglCreateContext: 0x%08X\n", egl_error); - return EIO; - } - - flutterpi.egl.compositor_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, flutterpi.egl.root_context, context_attribs); - if (flutterpi.egl.compositor_context == EGL_NO_CONTEXT) { - LOG_ERROR("Could not create OpenGL ES context for compositor. eglCreateContext: 0x%08X\n", egl_error); - return EIO; - } - - pthread_mutex_init(&flutterpi.egl.temp_context_lock, NULL); - - flutterpi.egl.temp_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, flutterpi.egl.root_context, context_attribs); - if (flutterpi.egl.temp_context == EGL_NO_CONTEXT) { - LOG_ERROR("Could not create OpenGL ES context for creating new contexts. eglCreateContext: 0x%08X\n", egl_error); - return EIO; - } - - flutterpi.egl.surface = eglCreateWindowSurface(flutterpi.egl.display, flutterpi.egl.config, (EGLNativeWindowType) flutterpi.gbm.surface, NULL); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not create EGL window surface. eglCreateWindowSurface: 0x%08X\n", egl_error); - return EIO; - } - - eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.root_context); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not make OpenGL ES root context current to get OpenGL information. eglMakeCurrent: 0x%08X\n", egl_error); - return EIO; - } + flutterpi->tracer = tracer; + flutterpi->compositor = compositor; + flutterpi->compositor_event_source = compositor_event_source; + flutterpi->gl_renderer = gl_renderer; + flutterpi->vk_renderer = vk_renderer; + return 0; - flutterpi.egl.renderer = (char*) glGetString(GL_RENDERER); - - gl_exts = (char*) glGetString(GL_EXTENSIONS); - printf("OpenGL ES information:\n"); - printf(" version: \"%s\"\n", glGetString(GL_VERSION)); - printf(" shading language version: \"%s\"\n", glGetString(GL_SHADING_LANGUAGE_VERSION)); - printf(" vendor: \"%s\"\n", glGetString(GL_VENDOR)); - printf(" renderer: \"%s\"\n", flutterpi.egl.renderer); - printf(" extensions: \"%s\"\n", gl_exts); - printf("===================================\n"); - - // it seems that after some Raspbian update, regular users are sometimes no longer allowed - // to use the direct-rendering infrastructure; i.e. the open the devices inside /dev/dri/ - // as read-write. flutter-pi must be run as root then. - // sometimes it works fine without root, sometimes it doesn't. - if (strncmp(flutterpi.egl.renderer, "llvmpipe", sizeof("llvmpipe")-1) == 0) { - printf("WARNING: Detected llvmpipe (ie. software rendering) as the OpenGL ES renderer.\n" - " Check that flutter-pi has permission to use the 3D graphics hardware,\n" - " or try running it as root.\n" - " This warning will probably result in a \"failed to set mode\" error\n" - " later on in the initialization.\n"); - } - - eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not clear OpenGL ES context. eglMakeCurrent: 0x%08X\n", egl_error); - return EIO; - } + fail_unref_compositor: + compositor_unref(compositor); - /// miscellaneous initialization - /// initialize the compositor - ok = compositor_initialize(flutterpi.drm.drmdev); - if (ok != 0) { - return ok; + fail_unref_renderer: + if (gl_renderer) { + gl_renderer_unref(gl_renderer); } - - /// initialize the frame queue - ok = cqueue_init(&flutterpi.frame_queue, sizeof(struct frame), QUEUE_DEFAULT_MAX_SIZE); - if (ok != 0) { - return ok; + if (vk_renderer) { + vk_renderer_unref(vk_renderer); } - /// We're starting without any rotation by default. - flutterpi_fill_view_properties(false, 0, false, 0); + fail_unref_tracer: + tracer_unref(tracer); - return 0; + fail_unref_drmdev: + drmdev_unref(drmdev); + return ok; } /************************** * FLUTTER INITIALIZATION * **************************/ static int init_application(void) { + FlutterEngineResult (*get_proc_addresses)(FlutterEngineProcTable* table); FlutterEngineAOTDataSource aot_source; - struct libflutter_engine *libflutter_engine; struct texture_registry *texture_registry; struct plugin_registry *plugin_registry; + FlutterEngineProcTable *procs; FlutterRendererConfig renderer_config = {0}; + struct view_geometry geometry; FlutterEngineAOTData aot_data; FlutterEngineResult engine_result; FlutterProjectArgs project_args = {0}; @@ -1833,53 +1373,27 @@ static int init_application(void) { } } - libflutter_engine = &flutterpi.flutter.libflutter_engine; - -# define LOAD_LIBFLUTTER_ENGINE_PROC(name) \ - do { \ - libflutter_engine->name = dlsym(libflutter_engine_handle, #name); \ - if (!libflutter_engine->name) {\ - perror("[flutter-pi] Could not resolve libflutter_engine procedure " #name ". dlsym"); \ - return EINVAL; \ - } \ - } while (false) - - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineCreateAOTData); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineCollectAOTData); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineRun); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineShutdown); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineInitialize); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineDeinitialize); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineRunInitialized); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineSendWindowMetricsEvent); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineSendPointerEvent); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineSendPlatformMessage); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterPlatformMessageCreateResponseHandle); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterPlatformMessageReleaseResponseHandle); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineSendPlatformMessageResponse); - LOAD_LIBFLUTTER_ENGINE_PROC(__FlutterEngineFlushPendingTasksNow); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineRegisterExternalTexture); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineUnregisterExternalTexture); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineMarkExternalTextureFrameAvailable); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineUpdateSemanticsEnabled); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineUpdateAccessibilityFeatures); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineDispatchSemanticsAction); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineOnVsync); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineReloadSystemFonts); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineTraceEventDurationBegin); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineTraceEventDurationEnd); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineTraceEventInstant); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEnginePostRenderThreadTask); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineGetCurrentTime); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineRunTask); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineUpdateLocales); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineRunsAOTCompiledDartCode); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEnginePostDartObject); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineNotifyLowMemoryWarning); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEnginePostCallbackOnAllNativeThreads); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineNotifyDisplayUpdate); - -# undef LOAD_LIBFLUTTER_ENGINE_PROC + procs = &flutterpi.flutter.procs; + + get_proc_addresses = dlsym(libflutter_engine_handle, "FlutterEngineGetProcAddresses"); + if (get_proc_addresses == NULL) { + LOG_ERROR("Could not resolve flutter engine function FlutterEngineGetProcAddresses.\n"); + return EINVAL; + } + + procs->struct_size = sizeof(FlutterEngineProcTable); + engine_result = get_proc_addresses(procs); + if (engine_result != kSuccess) { + LOG_ERROR("Could not resolve flutter engine proc addresses. FlutterEngineGetProcAddresses: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); + return EINVAL; + } + + tracer_set_cbs( + flutterpi.tracer, + procs->TraceEventDurationBegin, + procs->TraceEventDurationEnd, + procs->TraceEventInstant + ); plugin_registry = plugin_registry_new(&flutterpi); if (plugin_registry == NULL) { @@ -1901,21 +1415,47 @@ static int init_application(void) { return EIO; } + + // configure flutter rendering - renderer_config = (FlutterRendererConfig) { - .type = kOpenGL, - .open_gl = { - .struct_size = sizeof(FlutterOpenGLRendererConfig), - .make_current = on_make_current, - .clear_current = on_clear_current, - .present = on_present, - .fbo_callback = fbo_callback, - .make_resource_current = on_make_resource_current, - .gl_proc_resolver = proc_resolver, - .surface_transformation = on_get_transformation, - .gl_external_texture_frame_callback = on_gl_external_texture_frame_callback, - } - }; + if (flutterpi.vk_renderer) { + renderer_config = (FlutterRendererConfig) { + .type = kVulkan, + .vulkan = { + .struct_size = sizeof(FlutterVulkanRendererConfig), + .version = vk_renderer_get_vk_version(flutterpi.vk_renderer), + .instance = vk_renderer_get_instance(flutterpi.vk_renderer), + .physical_device = vk_renderer_get_physical_device(flutterpi.vk_renderer), + .device = vk_renderer_get_device(flutterpi.vk_renderer), + .queue_family_index = vk_renderer_get_queue_family_index(flutterpi.vk_renderer), + .queue = vk_renderer_get_queue(flutterpi.vk_renderer), + .enabled_instance_extension_count = vk_renderer_get_enabled_instance_extension_count(flutterpi.vk_renderer), + .enabled_instance_extensions = vk_renderer_get_enabled_instance_extensions(flutterpi.vk_renderer), + .enabled_device_extension_count = vk_renderer_get_enabled_device_extension_count(flutterpi.vk_renderer), + .enabled_device_extensions = vk_renderer_get_enabled_device_extensions(flutterpi.vk_renderer), + .get_instance_proc_address_callback = on_get_vulkan_proc_address, + .get_next_image_callback = on_get_next_vulkan_image, + .present_image_callback = on_present_vulkan_image, + }, + }; + } else { + renderer_config = (FlutterRendererConfig) { + .type = kOpenGL, + .open_gl = { + .struct_size = sizeof(FlutterOpenGLRendererConfig), + .make_current = on_make_current, + .clear_current = on_clear_current, + .present = on_present, + .fbo_callback = fbo_callback, + .make_resource_current = on_make_resource_current, + .gl_proc_resolver = proc_resolver, + .surface_transformation = on_get_transformation, + .gl_external_texture_frame_callback = on_gl_external_texture_frame_callback, + } + }; + } + + COMPILE_ASSERT(sizeof(FlutterProjectArgs) == 144); // configure the project project_args = (FlutterProjectArgs) { @@ -1936,31 +1476,33 @@ static int init_application(void) { .root_isolate_create_callback = NULL, .update_semantics_node_callback = NULL, .update_semantics_custom_action_callback = NULL, - .persistent_cache_path = NULL, + .persistent_cache_path = flutterpi.flutter.asset_bundle_path, .is_persistent_cache_read_only = false, - .vsync_callback = NULL /* on_frame_request - broken since 2.2 */, + .vsync_callback = on_frame_request, /* broken since 2.2, kinda */ .custom_dart_entrypoint = NULL, .custom_task_runners = &(FlutterCustomTaskRunners) { .struct_size = sizeof(FlutterCustomTaskRunners), .platform_task_runner = &(FlutterTaskRunnerDescription) { .struct_size = sizeof(FlutterTaskRunnerDescription), - .user_data = NULL, + .user_data = &flutterpi, .runs_task_on_current_thread_callback = runs_platform_tasks_on_current_thread, .post_task_callback = on_post_flutter_task }, - .render_task_runner = NULL + .render_task_runner = NULL, + .thread_priority_setter = NULL }, .shutdown_dart_vm_when_done = true, - .compositor = &flutter_compositor, + .compositor = compositor_get_flutter_compositor(flutterpi.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, - .log_tag = NULL + .log_tag = NULL, + .on_pre_engine_restart_callback = NULL }; - bool engine_is_aot = libflutter_engine->FlutterEngineRunsAOTCompiledDartCode(); + bool engine_is_aot = procs->RunsAOTCompiledDartCode(); if ((engine_is_aot == true) && (flutterpi.flutter.runtime_mode != kRelease)) { LOG_ERROR( "The flutter engine was built for release (AOT) mode, but flutter-pi was not started up in release mode.\n" @@ -1983,7 +1525,7 @@ static int init_application(void) { .type = kFlutterEngineAOTDataSourceTypeElfPath }; - engine_result = libflutter_engine->FlutterEngineCreateAOTData(&aot_source, &aot_data); + engine_result = procs->CreateAOTData(&aot_source, &aot_data); if (engine_result != kSuccess) { LOG_ERROR("Could not load AOT data. FlutterEngineCreateAOTData: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); return EIO; @@ -1992,42 +1534,46 @@ static int init_application(void) { project_args.aot_data = aot_data; } + flutterpi.flutter.next_frame_request_is_secondary = false; + + LOG_DEBUG("initializing flutter engine...\n"); // spin up the engine - engine_result = libflutter_engine->FlutterEngineInitialize(FLUTTER_ENGINE_VERSION, &renderer_config, &project_args, &flutterpi, &flutterpi.flutter.engine); + engine_result = procs->Initialize(FLUTTER_ENGINE_VERSION, &renderer_config, &project_args, &flutterpi, &flutterpi.flutter.engine); if (engine_result != kSuccess) { LOG_ERROR("Could not initialize the flutter engine. FlutterEngineInitialize: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); return EINVAL; } - - engine_result = libflutter_engine->FlutterEngineRunInitialized(flutterpi.flutter.engine); + + LOG_DEBUG("running flutter engine...\n"); + engine_result = procs->RunInitialized(flutterpi.flutter.engine); if (engine_result != kSuccess) { LOG_ERROR("Could not run the flutter engine. FlutterEngineRunInitialized: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); return EINVAL; } - ok = locales_add_to_fl_engine(flutterpi.locales, flutterpi.flutter.engine, libflutter_engine->FlutterEngineUpdateLocales); + ok = locales_add_to_fl_engine(flutterpi.locales, flutterpi.flutter.engine, procs->UpdateLocales); if (ok != 0) { return ok; } texture_registry = texture_registry_new( &(const struct flutter_external_texture_interface) { - .register_external_texture = libflutter_engine->FlutterEngineRegisterExternalTexture, - .unregister_external_texture = libflutter_engine->FlutterEngineUnregisterExternalTexture, - .mark_external_texture_frame_available = libflutter_engine->FlutterEngineMarkExternalTextureFrameAvailable, + .register_external_texture = procs->RegisterExternalTexture, + .unregister_external_texture = procs->UnregisterExternalTexture, + .mark_external_texture_frame_available = procs->MarkExternalTextureFrameAvailable, .engine = flutterpi.flutter.engine } ); flutterpi.texture_registry = texture_registry; - engine_result = libflutter_engine->FlutterEngineNotifyDisplayUpdate( + engine_result = procs->NotifyDisplayUpdate( flutterpi.flutter.engine, kFlutterEngineDisplaysUpdateTypeStartup, &(FlutterEngineDisplay) { .struct_size = sizeof(FlutterEngineDisplay), .display_id = 0, .single_display = true, - .refresh_rate = flutterpi.display.refresh_rate + .refresh_rate = compositor_get_refresh_rate(flutterpi.compositor) }, 1 ); @@ -2036,14 +1582,25 @@ static int init_application(void) { return EINVAL; } + compositor_get_view_geometry(flutterpi.compositor, &geometry); + + // just so we get an error if the window metrics event was expanded without us noticing + COMPILE_ASSERT(sizeof(FlutterWindowMetricsEvent) == 64); + // update window size - engine_result = libflutter_engine->FlutterEngineSendWindowMetricsEvent( + engine_result = procs->SendWindowMetricsEvent( flutterpi.flutter.engine, &(FlutterWindowMetricsEvent) { .struct_size = sizeof(FlutterWindowMetricsEvent), - .width = flutterpi.view.width, - .height = flutterpi.view.height, - .pixel_ratio = flutterpi.display.pixel_ratio + .width = geometry.view_size.x, + .height = geometry.view_size.y, + .pixel_ratio = geometry.device_pixel_ratio, + .left = 0, + .top = 0, + .physical_view_inset_top = 0, + .physical_view_inset_right = 0, + .physical_view_inset_bottom = 0, + .physical_view_inset_left = 0 } ); if (engine_result != kSuccess) { @@ -2084,14 +1641,17 @@ static void on_flutter_pointer_event(void *userdata, const FlutterPointerEvent * FlutterEngineResult engine_result; struct flutterpi *flutterpi; + DEBUG_ASSERT_NOT_NULL(userdata); flutterpi = userdata; - engine_result = flutterpi->flutter.libflutter_engine.FlutterEngineSendPointerEvent( + /// TODO: make this atomic + flutterpi->flutter.next_frame_request_is_secondary = true; + + engine_result = flutterpi->flutter.procs.SendPointerEvent( flutterpi->flutter.engine, events, n_events ); - if (engine_result != kSuccess) { LOG_ERROR("Error sending touchscreen / mouse events to flutter. FlutterEngineSendPointerEvent: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); //flutterpi_schedule_exit(flutterpi); @@ -2165,7 +1725,12 @@ static void on_set_cursor_enabled(void *userdata, bool enabled) { int ok; flutterpi = userdata; + (void) flutterpi; + (void) ok; + (void) enabled; + /// TODO: Implement + /* ok = compositor_apply_cursor_state( enabled, flutterpi->view.rotation, @@ -2174,6 +1739,7 @@ static void on_set_cursor_enabled(void *userdata, bool enabled) { if (ok != 0) { LOG_ERROR("Error enabling / disabling mouse cursor. compositor_apply_cursor_state: %s\n", strerror(ok)); } + */ } static void on_move_cursor(void *userdata, unsigned int x, unsigned int y) { @@ -2181,12 +1747,19 @@ static void on_move_cursor(void *userdata, unsigned int x, unsigned int y) { int ok; flutterpi = userdata; + (void) ok; (void) flutterpi; + (void) x; + (void) y; + + /// TODO: Implement + /* ok = compositor_set_cursor_pos(x, y); if (ok != 0) { LOG_ERROR("Error moving mouse cursor. compositor_set_cursor_pos: %s\n", strerror(ok)); } + */ } static const struct user_input_interface user_input_interface = { @@ -2210,26 +1783,29 @@ static int on_user_input_fd_ready(sd_event_source *s, int fd, uint32_t revents, return user_input_on_fd_ready(input); } -static int init_user_input(void) { +static int init_user_input(struct flutterpi *flutterpi, struct compositor *compositor, struct sd_event *event_loop) { + struct view_geometry geometry; struct user_input *input; sd_event_source *event_source; int ok; + + compositor_get_view_geometry(compositor, &geometry); event_source = NULL; input = user_input_new( &user_input_interface, - &flutterpi, - &flutterpi.view.display_to_view_transform, - &flutterpi.view.view_to_display_transform, - flutterpi.display.width, - flutterpi.display.height + flutterpi, + &geometry.display_to_view_transform, + &geometry.view_to_display_transform, + geometry.display_size.x, + geometry.display_size.y ); if (input == NULL) { LOG_ERROR("Couldn't initialize user input. flutter-pi will run without user input.\n"); } else { ok = sd_event_add_io( - flutterpi.event_loop, + event_loop, &event_source, user_input_get_fd(input), EPOLLIN | EPOLLRDHUP | EPOLLPRI, @@ -2243,18 +1819,15 @@ static int init_user_input(void) { } } - flutterpi.user_input = input; - flutterpi.user_input_event_source = event_source; - + flutterpi->user_input = input; + flutterpi->user_input_event_source = event_source; return 0; } - static bool setup_paths(void) { char *kernel_blob_path, *icu_data_path, *app_elf_path; - #define PATH_EXISTS(path) (access((path),R_OK)==0) - if (!PATH_EXISTS(flutterpi.flutter.asset_bundle_path)) { + if (access(flutterpi.flutter.asset_bundle_path, R_OK) != 0) { LOG_ERROR("Asset Bundle Directory \"%s\" does not exist\n", flutterpi.flutter.asset_bundle_path); return false; } @@ -2263,19 +1836,19 @@ static bool setup_paths(void) { asprintf(&app_elf_path, "%s/app.so", flutterpi.flutter.asset_bundle_path); if (flutterpi.flutter.runtime_mode == kDebug) { - if (!PATH_EXISTS(kernel_blob_path)) { + if (access(kernel_blob_path, R_OK) != 0) { LOG_ERROR("Could not find \"kernel.blob\" file inside \"%s\", which is required for debug mode.\n", flutterpi.flutter.asset_bundle_path); return false; } } else if (flutterpi.flutter.runtime_mode == kRelease) { - if (!PATH_EXISTS(app_elf_path)) { + if (access(app_elf_path, R_OK) != 0) { LOG_ERROR("Could not find \"app.so\" file inside \"%s\", which is required for release and profile mode.\n", flutterpi.flutter.asset_bundle_path); return false; } } asprintf(&icu_data_path, "/usr/lib/icudtl.dat"); - if (!PATH_EXISTS(icu_data_path)) { + if (access(icu_data_path, R_OK) != 0) { LOG_ERROR("Could not find \"icudtl.dat\" file inside \"/usr/lib/\".\n"); return false; } @@ -2285,17 +1858,41 @@ static bool setup_paths(void) { flutterpi.flutter.app_elf_path = app_elf_path; return true; - - #undef PATH_EXISTS } -static bool parse_cmd_args(int argc, char **argv) { +struct cmd_args { + bool has_orientation; + enum device_orientation orientation; + + bool has_rotation; + int rotation; + + bool has_physical_dimensions; + int width_mm, height_mm; + + bool has_pixel_format; + enum pixfmt pixel_format; + + bool has_runtime_mode; + enum flutter_runtime_mode runtime_mode; + + const char *asset_bundle_path; + + int engine_argc; + char **engine_argv; + + bool use_vulkan; +}; + +static bool parse_cmd_args(int argc, char **argv, struct cmd_args *result_out) { bool finished_parsing_options; int runtime_mode_int = kDebug; + int vulkan_int = false; int longopt_index = 0; int opt, ok; struct option long_options[] = { + {"config", required_argument, NULL, 'c'}, {"release", no_argument, &runtime_mode_int, kRelease}, {"input", required_argument, NULL, 'i'}, {"orientation", required_argument, NULL, 'o'}, @@ -2303,9 +1900,21 @@ static bool parse_cmd_args(int argc, char **argv) { {"dimensions", required_argument, NULL, 'd'}, {"help", no_argument, 0, 'h'}, {"pixelformat", required_argument, NULL, 'p'}, + {"vulkan", no_argument, &vulkan_int, true}, {0, 0, 0, 0} }; + memset(result_out, 0, sizeof *result_out); + + result_out->has_orientation = false; + result_out->has_rotation = false; + result_out->has_physical_dimensions = false; + result_out->has_pixel_format = false; + result_out->has_runtime_mode = false; + result_out->asset_bundle_path = NULL; + result_out->engine_argc = 0; + result_out->engine_argv = NULL; + finished_parsing_options = false; while (!finished_parsing_options) { longopt_index = 0; @@ -2315,20 +1924,19 @@ static bool parse_cmd_args(int argc, char **argv) { case 0: // flag was encountered. just continue break; - case 'o': if (STREQ(optarg, "portrait_up")) { - flutterpi.view.orientation = kPortraitUp; - flutterpi.view.has_orientation = true; + result_out->orientation = kPortraitUp; + result_out->has_orientation = true; } else if (STREQ(optarg, "landscape_left")) { - flutterpi.view.orientation = kLandscapeLeft; - flutterpi.view.has_orientation = true; + result_out->orientation = kLandscapeLeft; + result_out->has_orientation = true; } else if (STREQ(optarg, "portrait_down")) { - flutterpi.view.orientation = kPortraitDown; - flutterpi.view.has_orientation = true; + result_out->orientation = kPortraitDown; + result_out->has_orientation = true; } else if (STREQ(optarg, "landscape_right")) { - flutterpi.view.orientation = kLandscapeRight; - flutterpi.view.has_orientation = true; + result_out->orientation = kLandscapeRight; + result_out->has_orientation = true; } else { LOG_ERROR( "ERROR: Invalid argument for --orientation passed.\n" @@ -2353,8 +1961,8 @@ static bool parse_cmd_args(int argc, char **argv) { return false; } - flutterpi.view.rotation = rotation; - flutterpi.view.has_rotation = true; + result_out->rotation = rotation; + result_out->has_rotation = true; break; case 'd': ; @@ -2366,29 +1974,27 @@ static bool parse_cmd_args(int argc, char **argv) { return false; } - flutterpi.display.width_mm = width_mm; - flutterpi.display.height_mm = height_mm; + result_out->width_mm = width_mm; + result_out->height_mm = height_mm; break; case 'p': - for (int i = 0; i < n_pixfmt_infos; i++) { + for (unsigned i = 0; i < n_pixfmt_infos; i++) { if (strcmp(optarg, pixfmt_infos[i].arg_name) == 0) { - flutterpi.gbm.format = pixfmt_infos[i].gbm_format; + result_out->has_pixel_format = true; + result_out->pixel_format = pixfmt_infos[i].format; goto valid_format; } } LOG_ERROR( "ERROR: Invalid argument for --pixelformat passed.\n" - "Valid values are: RGB565, ARGB8888, XRGB8888, BGRA8888, RGBA8888\n" + "Valid values are: " PIXFMT_LIST(PIXFMT_ARG_NAME) "\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); + return false; valid_format: break; @@ -2418,28 +2024,32 @@ static bool parse_cmd_args(int argc, char **argv) { return false; } - flutterpi.flutter.asset_bundle_path = strdup(argv[optind]); - flutterpi.flutter.runtime_mode = runtime_mode_int; + result_out->asset_bundle_path = strdup(argv[optind]); + result_out->runtime_mode = runtime_mode_int; argv[optind] = argv[0]; - flutterpi.flutter.engine_argc = argc - optind; - flutterpi.flutter.engine_argv = argv + optind; + result_out->engine_argc = argc - optind; + result_out->engine_argv = argv + optind; + + result_out->use_vulkan = vulkan_int; return true; } int init(int argc, char **argv) { + struct cmd_args cmd_args; int ok; -#ifdef ENABLE_MTRACE - mtrace(); -#endif - - ok = parse_cmd_args(argc, argv); + ok = parse_cmd_args(argc, argv, &cmd_args); if (ok == false) { return EINVAL; } + flutterpi.flutter.runtime_mode = cmd_args.has_runtime_mode ? cmd_args.runtime_mode : kDebug; + flutterpi.flutter.asset_bundle_path = cmd_args.asset_bundle_path; + flutterpi.flutter.engine_argc = cmd_args.engine_argc; + flutterpi.flutter.engine_argv = cmd_args.engine_argv; + ok = setup_paths(); if (ok == false) { return EINVAL; @@ -2456,12 +2066,29 @@ int init(int argc, char **argv) { return EINVAL; } - ok = init_user_input(); + ok = init_display( + &flutterpi, + flutterpi.event_loop, + cmd_args.has_pixel_format, cmd_args.pixel_format, + cmd_args.has_rotation, + cmd_args.rotation == 0 ? PLANE_TRANSFORM_ROTATE_0 : + cmd_args.rotation == 90 ? PLANE_TRANSFORM_ROTATE_90 : + cmd_args.rotation == 180 ? PLANE_TRANSFORM_ROTATE_180 : + cmd_args.rotation == 270 ? PLANE_TRANSFORM_ROTATE_270 : + (assert(0 && "invalid rotation"), PLANE_TRANSFORM_ROTATE_0), + cmd_args.has_orientation, cmd_args.orientation, + cmd_args.has_physical_dimensions, cmd_args.width_mm, cmd_args.height_mm, + cmd_args.use_vulkan + ); if (ok != 0) { return ok; } - ok = init_display(); + ok = init_user_input( + &flutterpi, + flutterpi.compositor, + flutterpi.event_loop + ); if (ok != 0) { return ok; } diff --git a/src/gbm_surface_backing_store.c b/src/gbm_surface_backing_store.c new file mode 100644 index 00000000..ec6da992 --- /dev/null +++ b/src/gbm_surface_backing_store.c @@ -0,0 +1,667 @@ +// SPDX-License-Identifier: MIT +/* + * GBM Surface Backing Stores + * + * - implements backing store using a gbm surface + * - ideal way to create backing stores right now + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#include <stdlib.h> + +#include <collection.h> +#include <pixel_format.h> +#include <modesetting.h> +#include <egl.h> +#include <gles.h> +#include <surface.h> +#include <backing_store.h> +#include <backing_store_private.h> +#include <gbm_surface_backing_store.h> +#include <tracer.h> +#include <gl_renderer.h> + +FILE_DESCR("gbm surface backing store") + +#ifndef HAS_EGL +# error "EGL is needed for GBM surface backing store." +#endif + +struct gbm_surface_backing_store; + +struct locked_fb { + atomic_flag is_locked; + struct gbm_surface_backing_store *store; + struct gbm_bo *bo; + refcount_t n_refs; +}; + +struct gbm_surface_backing_store { + union { + struct surface surface; + struct backing_store backing_store; + }; + + uuid_t uuid; + enum pixfmt pixel_format; + struct gbm_device *gbm_device; + struct gbm_surface *gbm_surface; + struct gbm_bo *front_buffer; + EGLDisplay egl_display; + EGLSurface egl_surface; + EGLConfig egl_config; + struct gl_renderer *renderer; + + // Internally mesa supports 4 GBM BOs per surface, so we don't need + // more than 4 here either. + struct locked_fb locked_fbs[4]; + struct locked_fb *locked_front_fb; +#ifdef DEBUG + atomic_int n_locked_fbs; +#endif +}; + +COMPILE_ASSERT(offsetof(struct gbm_surface_backing_store, surface) == 0); +COMPILE_ASSERT(offsetof(struct gbm_surface_backing_store, backing_store.surface) == 0); + +static const uuid_t uuid = CONST_UUID(0xf9, 0xc2, 0x5d, 0xad, 0x2e, 0x3b, 0x4e, 0x2c, 0x9d, 0x26, 0x64, 0x70, 0xfa, 0x9a, 0x25, 0xd9); + +#define CAST_THIS(ptr) CAST_GBM_SURFACE_BACKING_STORE(ptr) +#define CAST_THIS_UNCHECKED(ptr) CAST_GBM_SURFACE_BACKING_STORE_UNCHECKED(ptr) + +static void locked_fb_destroy(struct locked_fb *fb) { + struct gbm_surface_backing_store *store; + + store = fb->store; + fb->store = NULL; + gbm_surface_release_buffer(store->gbm_surface, fb->bo); +#ifdef DEBUG + atomic_fetch_sub(&store->n_locked_fbs, 1); +#endif + atomic_flag_clear(&fb->is_locked); + surface_unref(CAST_SURFACE(store)); +} + +DEFINE_STATIC_REF_OPS(locked_fb, n_refs) + +#ifdef DEBUG +ATTR_PURE struct gbm_surface_backing_store *__checked_cast_gbm_surface_backing_store(void *ptr) { + struct gbm_surface_backing_store *store; + + store = CAST_GBM_SURFACE_BACKING_STORE_UNCHECKED(ptr); + DEBUG_ASSERT(uuid_equals(store->uuid, uuid)); + return store; +} +#endif + +void gbm_surface_backing_store_deinit(struct surface *s); +static int gbm_surface_backing_store_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); +static int gbm_surface_backing_store_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); +static int gbm_surface_backing_store_fill(struct backing_store *store, FlutterBackingStore *fl_store); +static int gbm_surface_backing_store_queue_present(struct backing_store *store, const FlutterBackingStore *fl_store); + +int gbm_surface_backing_store_init( + struct gbm_surface_backing_store *store, + struct tracer *tracer, + struct point size, + struct gbm_device *gbm_device, + struct gl_renderer *renderer, + enum pixfmt pixel_format, + EGLConfig egl_config +) { + struct gbm_surface *gbm_surface; + EGLDisplay egl_display; + EGLSurface egl_surface; + EGLBoolean egl_ok; + int ok; + + DEBUG_ASSERT_NOT_NULL(renderer); + DEBUG_ASSERT_PIXFMT_VALID(pixel_format); + egl_display = gl_renderer_get_egl_display(renderer); + DEBUG_ASSERT_NOT_NULL(egl_display); + +#ifdef DEBUG + if (egl_config != EGL_NO_CONFIG_KHR) { + EGLint value = 0; + + egl_ok = eglGetConfigAttrib(egl_display, egl_config, EGL_NATIVE_VISUAL_ID, &value); + if (egl_ok == EGL_FALSE) { + LOG_ERROR("Couldn't query pixel format of EGL framebuffer config. eglGetConfigAttrib: 0x%08X\n", eglGetError()); + return EIO; + } + + DEBUG_ASSERT_EQUALS_MSG(value, get_pixfmt_info(pixel_format)->gbm_format, "EGL framebuffer config pixel format doesn't match the argument pixel format."); + } +#endif + /// TODO: Think about allowing different tilings / modifiers here + gbm_surface = gbm_surface_create( + gbm_device, + (uint32_t) size.x, (uint32_t) size.y, + get_pixfmt_info(pixel_format)->gbm_format, + GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT + ); + if (gbm_surface == NULL) { + ok = errno; + LOG_ERROR("Couldn't create GBM surface for rendering. gbm_surface_create_with_modifiers: %s\n", strerror(ok)); + return ok; + } + + if (egl_config == EGL_NO_CONFIG_KHR) { + // choose a config + egl_config = gl_renderer_choose_config_direct(renderer, pixel_format); + if (egl_config == EGL_NO_CONFIG_KHR) { + LOG_ERROR("EGL doesn't supported the specified pixel format %s. Try a different one (ARGB8888 should always work).\n", get_pixfmt_info(pixel_format)->name); + ok = EINVAL; + goto fail_destroy_gbm_surface; + } + } + + static const EGLAttrib surface_attribs[] = { + /* EGL_GL_COLORSPACE, GL_LINEAR / GL_SRGB */ + /* EGL_RENDER_BUFFER, EGL_BACK_BUFFER / EGL_SINGLE_BUFFER */ + /* EGL_VG_ALPHA_FORMAT, EGL_VG_ALPHA_FORMAT_NONPRE / EGL_VG_ALPHA_FORMAT_PRE */ + /* EGL_VG_COLORSPACE, EGL_VG_COLORSPACE_sRGB / EGL_VG_COLORSPACE_LINEAR */ + EGL_NONE + }; + + (void) surface_attribs; + + egl_ok = eglBindAPI(EGL_OPENGL_ES_API); + if (egl_ok == EGL_FALSE) { + LOG_ERROR("Couldn't bind OpenGL ES API to EGL. eglBindAPI: 0x%08X\n", eglGetError()); + ok = EIO; + goto fail_destroy_gbm_surface; + } + + egl_surface = eglCreatePlatformWindowSurface( + egl_display, + egl_config, + gbm_surface, + NULL + ); + if (egl_surface == EGL_NO_SURFACE) { + LOG_ERROR("Could not create EGL rendering surface. eglCreatePlatformWindowSurface: 0x%08X\n", eglGetError()); + ok = EIO; + goto fail_destroy_gbm_surface; + } + + /// TODO: Implement + ok = backing_store_init(CAST_BACKING_STORE_UNCHECKED(store), tracer, size); + if (ok != 0) { + goto fail_destroy_egl_surface; + } + + store->surface.present_kms = gbm_surface_backing_store_present_kms; + store->surface.present_fbdev = gbm_surface_backing_store_present_fbdev; + store->surface.deinit = gbm_surface_backing_store_deinit; + store->backing_store.fill = gbm_surface_backing_store_fill; + store->backing_store.queue_present = gbm_surface_backing_store_queue_present; + uuid_copy(&store->uuid, uuid); + store->pixel_format = pixel_format; + store->gbm_device = gbm_device; + store->gbm_surface = gbm_surface; + store->egl_display = egl_display; + store->egl_surface = egl_surface; + store->egl_config = egl_config; + store->renderer = gl_renderer_ref(renderer); + for (int i = 0; i < ARRAY_SIZE(store->locked_fbs); i++) { + store->locked_fbs->is_locked = (atomic_flag) ATOMIC_FLAG_INIT; + } + store->locked_front_fb = NULL; + return 0; + + + fail_destroy_egl_surface: +#ifdef HAS_EGL + eglDestroySurface(egl_display, egl_surface); + + fail_destroy_gbm_surface: +#endif + gbm_surface_destroy(gbm_surface); + return ok; +} + +/** + * @brief Create a new gbm_surface based backing store, with an explicit EGL Config for the created EGLSurface. + * + * @param compositor The compositor that this surface will be registered to when calling surface_register. + * @param size The size of the backing store. + * @param device The GBM device used to allocate the surface. + * @param renderer The EGL/OpenGL used to create any GL surfaces. + * @param pixel_format The pixel format to be used by the framebuffers of the surface. + * @param egl_config The EGLConfig used for creating the EGLSurface. + * @return struct gbm_surface_backing_store* + */ +ATTR_MALLOC struct gbm_surface_backing_store *gbm_surface_backing_store_new_with_egl_config( + struct tracer *tracer, + struct point size, + struct gbm_device *device, + struct gl_renderer *renderer, + enum pixfmt pixel_format, + EGLConfig egl_config +) { + struct gbm_surface_backing_store *store; + int ok; + + store = malloc(sizeof *store); + if (store == NULL) { + goto fail_return_null; + } + + ok = gbm_surface_backing_store_init(store, tracer, size, device, renderer, pixel_format, egl_config); + if (ok != 0) { + goto fail_free_store; + } + + return store; + + + fail_free_store: + free(store); + + fail_return_null: + return NULL; +} + +/** + * @brief Create a new gbm_surface based backing store. + * + * @param compositor The compositor that this surface will be registered to when calling surface_register. + * @param size The size of the backing store. + * @param device The GBM device used to allocate the surface. + * @param renderer The EGL/OpenGL used to create any GL surfaces. + * @param pixel_format The pixel format to be used by the framebuffers of the surface. + * @return struct gbm_surface_backing_store* + */ +ATTR_MALLOC struct gbm_surface_backing_store *gbm_surface_backing_store_new( + struct tracer *tracer, + struct point size, + struct gbm_device *device, + struct gl_renderer *renderer, + enum pixfmt pixel_format +) { + return gbm_surface_backing_store_new_with_egl_config( + tracer, + size, + device, + renderer, + pixel_format, + EGL_NO_CONFIG_KHR + ); +} + +void gbm_surface_backing_store_deinit(struct surface *s) { + struct gbm_surface_backing_store *store; + + store = CAST_GBM_SURFACE_BACKING_STORE(s); + + gl_renderer_unref(store->renderer); + backing_store_deinit(s); +} + +struct gbm_bo_meta { + struct drmdev *drmdev; + uint32_t fb_id; + + bool has_opaque_fb; + enum pixfmt opaque_pixel_format; + uint32_t opaque_fb_id; +}; + +static void on_destroy_gbm_bo_meta(struct gbm_bo *bo, void *meta_void) { + struct gbm_bo_meta *meta; + int ok; + + DEBUG_ASSERT_NOT_NULL(bo); + DEBUG_ASSERT_NOT_NULL(meta_void); + meta = meta_void; + + ok = drmdev_rm_fb(meta->drmdev, meta->fb_id); + if (ok != 0) { + LOG_ERROR("Couldn't remove DRM framebuffer.\n"); + } + + if (meta->has_opaque_fb && meta->opaque_fb_id != meta->fb_id) { + ok = drmdev_rm_fb(meta->drmdev, meta->opaque_fb_id); + if (ok != 0) { + LOG_ERROR("Couldn't remove DRM framebuffer.\n"); + } + } + + drmdev_unref(meta->drmdev); + free(meta); +} + +static void on_release_layer(void *userdata) { + struct locked_fb *fb; + + DEBUG_ASSERT_NOT_NULL(userdata); + + fb = userdata; + locked_fb_unref(fb); +} + +static int gbm_surface_backing_store_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder) { + struct gbm_surface_backing_store *store; + struct gbm_bo_meta *meta; + struct drmdev *drmdev; + struct gbm_bo *bo; + enum pixfmt pixel_format, opaque_pixel_format; + uint32_t fb_id, opaque_fb_id; + int ok; + + /// TODO: Implement by adding the current front bo as a KMS fb (if that's not already done) + store = CAST_THIS(s); + + /// TODO: Implement non axis-aligned fl_layer_props + DEBUG_ASSERT_MSG(props->is_aa_rect, "only axis aligned view geometry is supported right now"); + + surface_lock(s); + + DEBUG_ASSERT_NOT_NULL_MSG(store->locked_front_fb, "There's no framebuffer available for scanout right now. Make sure you called backing_store_swap_buffers() before presenting."); + + bo = store->locked_front_fb->bo; + meta = gbm_bo_get_user_data(bo); + if (meta == NULL) { + bool has_opaque_fb; + + meta = malloc(sizeof *meta); + if (meta == NULL) { + ok = ENOMEM; + goto fail_unlock; + } + + drmdev = kms_req_builder_get_drmdev(builder); + DEBUG_ASSERT_NOT_NULL(drmdev); + + TRACER_BEGIN(store->surface.tracer, "drmdev_add_fb (non-opaque)"); + fb_id = drmdev_add_fb( + drmdev, + gbm_bo_get_width(bo), + gbm_bo_get_height(bo), + store->pixel_format, + gbm_bo_get_handle(bo).u32, + gbm_bo_get_stride(bo), + gbm_bo_get_offset(bo, 0), + true, gbm_bo_get_modifier(bo), + 0 + ); + TRACER_END(store->surface.tracer, "drmdev_add_fb (non-opaque)"); + + if (fb_id == 0) { + ok = EIO; + LOG_ERROR("Couldn't add GBM buffer as DRM framebuffer.\n"); + goto fail_free_meta; + } + + if (get_pixfmt_info(store->pixel_format)->is_opaque == false) { + has_opaque_fb = false; + opaque_pixel_format = pixfmt_opaque(store->pixel_format); + if (get_pixfmt_info(opaque_pixel_format)->is_opaque) { + + TRACER_BEGIN(store->surface.tracer, "drmdev_add_fb (opaque)"); + opaque_fb_id = drmdev_add_fb( + drmdev, + gbm_bo_get_width(bo), + gbm_bo_get_height(bo), + opaque_pixel_format, + gbm_bo_get_handle(bo).u32, + gbm_bo_get_stride(bo), + gbm_bo_get_offset(bo, 0), + true, gbm_bo_get_modifier(bo), + 0 + ); + TRACER_END(store->surface.tracer, "drmdev_add_fb (opaque)"); + + if (opaque_fb_id != 0) { + has_opaque_fb = true; + } + } + } else { + has_opaque_fb = true; + opaque_fb_id = fb_id; + opaque_pixel_format = store->pixel_format; + } + + meta->drmdev = drmdev_ref(drmdev); + meta->fb_id = fb_id; + meta->has_opaque_fb = has_opaque_fb; + meta->opaque_pixel_format = opaque_pixel_format; + meta->opaque_fb_id = opaque_fb_id; + gbm_bo_set_user_data(bo, meta, on_destroy_gbm_bo_meta); + } else { + // We can only add this GBM BO to a single KMS device as an fb right now. + DEBUG_ASSERT_EQUALS_MSG(meta->drmdev, kms_req_builder_get_drmdev(builder), "Currently GBM BOs can only be scanned out on a single KMS device for their whole lifetime."); + } + + /* + LOG_DEBUG( + "gbm_surface_backing_store_present_kms:\n" + " src_x, src_y, src_w, src_h: %f %f %f %f\n" + " dst_x, dst_y, dst_w, dst_h: %f %f %f %f\n", + 0.0, 0.0, + store->backing_store.size.x, + store->backing_store.size.y, + props->aa_rect.offset.x, + props->aa_rect.offset.y, + props->aa_rect.size.x, + props->aa_rect.size.y + ); + */ + + // The bottom-most layer should preferably be an opaque layer. + // For example, on Pi 4, even though ARGB8888 is listed as supported for the primary plane, + // rendering is completely off. + // So we just cast our fb to an XRGB8888 framebuffer and scanout that instead. + if (kms_req_builder_prefer_next_layer_opaque(builder)) { + if (meta->has_opaque_fb) { + fb_id = meta->opaque_fb_id; + pixel_format = meta->opaque_pixel_format; + } else { + LOG_DEBUG("Bottom-most framebuffer layer should be opaque, but an opaque framebuffer couldn't be created.\n"); + LOG_DEBUG("Using non-opaque framebuffer instead, which can result in visual glitches.\n"); + fb_id = meta->fb_id; + pixel_format = store->pixel_format; + } + } else { + fb_id = meta->fb_id; + pixel_format = store->pixel_format; + } + + TRACER_BEGIN(store->surface.tracer, "kms_req_builder_push_fb_layer"); + ok = kms_req_builder_push_fb_layer( + builder, + &(const struct kms_fb_layer) { + .drm_fb_id = fb_id, + .format = pixel_format, + .has_modifier = false, + .modifier = 0, + + .dst_x = (int32_t) props->aa_rect.offset.x, + .dst_y = (int32_t) props->aa_rect.offset.y, + .dst_w = (uint32_t) props->aa_rect.size.x, + .dst_h = (uint32_t) props->aa_rect.size.y, + + .src_x = 0, + .src_y = 0, + .src_w = DOUBLE_TO_FP1616_ROUNDED(store->backing_store.size.x), + .src_h = DOUBLE_TO_FP1616_ROUNDED(store->backing_store.size.y), + + .has_rotation = false, + .rotation = PLANE_TRANSFORM_ROTATE_0, + + .has_in_fence_fd = false, + .in_fence_fd = 0 + }, + on_release_layer, + locked_fb_ref(store->locked_front_fb) + ); + TRACER_END(store->surface.tracer, "kms_req_builder_push_fb_layer"); + if (ok != 0) { + goto fail_unref_locked_fb; + } + + surface_unlock(s); + return ok; + + fail_unref_locked_fb: + locked_fb_unref(store->locked_front_fb); + goto fail_unlock; + + fail_free_meta: + free(meta); + + fail_unlock: + surface_unlock(s); + return ok; +} + +static int gbm_surface_backing_store_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder) { + struct gbm_surface_backing_store *store; + + /// TODO: Implement by mmapping the current front bo, copy it into the fbdev + /// TODO: Print a warning here if we're not using explicit linear tiling and use glReadPixels instead of gbm_bo_map in that case + + store = CAST_THIS(s); + (void) store; + (void) props; + (void) builder; + + UNIMPLEMENTED(); + + return 0; +} + +static int gbm_surface_backing_store_fill(struct backing_store *s, FlutterBackingStore *fl_store) { +#if HAS_EGL + fl_store->type = kFlutterBackingStoreTypeOpenGL; + fl_store->open_gl = (FlutterOpenGLBackingStore) { + .type = kFlutterOpenGLTargetTypeFramebuffer, + .framebuffer = { + /* for some reason flutter wants this to be GL_BGRA8_EXT, contrary to what the docs say */ + .target = GL_BGRA8_EXT, + + /* 0 refers to the window surface, instead of to an FBO */ + .name = 0, + + /* + * even though the compositor will call surface_ref too to fill the FlutterBackingStore.user_data, + * we need to ref two times because flutter will call both this destruction callback and the + * compositor collect callback + */ + .user_data = surface_ref(CAST_SURFACE_UNCHECKED(s)), + .destruction_callback = surface_unref_void + } + }; + return 0; +#else + (void) s; + (void) fl_store; + LOG_ERROR("OpenGL backing stores are not supported if flutter-pi was built without EGL support."); + return EINVAL; +#endif +} + +static int gbm_surface_backing_store_queue_present(struct backing_store *s, const FlutterBackingStore *fl_store) { + MAYBE_UNUSED struct gbm_surface_backing_store *store; + struct gbm_bo *bo; + MAYBE_UNUSED EGLBoolean egl_ok; + int i, ok; + + store = CAST_THIS(s); + (void) fl_store; + + surface_lock(CAST_SURFACE(s)); + + /// TODO: Handle fl_store->did_update == false here + + // Unref the old front fb so potentially one of the locked_fbs entries gets freed + if (store->locked_front_fb != NULL) { + locked_fb_unrefp(&store->locked_front_fb); + } + + DEBUG_ASSERT(gbm_surface_has_free_buffers(store->gbm_surface)); + + // create the in fence here +#ifdef HAS_EGL + TRACER_BEGIN(s->surface.tracer, "eglSwapBuffers"); + egl_ok = eglSwapBuffers(store->egl_display, store->egl_surface); + TRACER_END(s->surface.tracer, "eglSwapBuffers"); + + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Couldn't flush rendering. eglSwapBuffers"); + return EIO; + } +#endif + + TRACER_BEGIN(s->surface.tracer, "gbm_surface_lock_front_buffer"); + bo = gbm_surface_lock_front_buffer(store->gbm_surface); + TRACER_END(s->surface.tracer, "gbm_surface_lock_front_buffer"); + + if (bo == NULL) { + ok = errno; + LOG_ERROR("Couldn't lock GBM front buffer. gbm_surface_lock_front_buffer: %s\n", strerror(ok)); + goto fail_unlock; + } + + // Try to find & lock a locked_fb we can use. + // Note we use atomics here even though we hold the surfaces' mutex because + // releasing a locked_fb is possibly done without the mutex. + for (i = 0; i < ARRAY_SIZE(store->locked_fbs); i++) { + if (atomic_flag_test_and_set(&store->locked_fbs[i].is_locked) == false) { + goto locked; + } + } + + // If we reached this point, we couldn't find lock one of the 4 locked_fbs. + // Which shouldn't happen except we have an application bug. + DEBUG_ASSERT_MSG(false, "Couldn't find a free slot to lock the surfaces front framebuffer."); + ok = EIO; + goto fail_release_bo; + + locked: + /// TODO: Remove this once we're using triple buffering + //DEBUG_ASSERT_MSG(atomic_fetch_add(&store->n_locked_fbs, 1) <= 1, "sanity check failed: too many locked fbs for double-buffered vsync"); + store->locked_fbs[i].bo = bo; + store->locked_fbs[i].store = CAST_GBM_SURFACE_BACKING_STORE(surface_ref(CAST_SURFACE(s))); + store->locked_fbs[i].n_refs = REFCOUNT_INIT_1; + store->locked_front_fb = store->locked_fbs + i; + surface_unlock(CAST_SURFACE(s)); + return 0; + + + fail_release_bo: + gbm_surface_release_buffer(store->gbm_surface, bo); + + fail_unlock: + surface_unlock(CAST_SURFACE(s)); + return ok; +} + +/** + * @brief Get the EGL Surface for rendering into this backing store. + * + * Flutter doesn't really support backing stores to be EGL Surfaces, so we have to hack around this, kinda. + * + * @param s + * @return EGLSurface The EGLSurface associated with this backing store. Only valid for the lifetime of this gbm_surface_backing_store. + */ +ATTR_PURE EGLSurface gbm_surface_backing_store_get_egl_surface(struct gbm_surface_backing_store *s) { + return s->egl_surface; +} + +/** + * @brief Get the EGLConfig that was used to create the EGLSurface for this backing store. + * + * If the display doesn't support EGL_KHR_no_config_context, we need to create the EGL rendering context with + * the same EGLConfig as every EGLSurface we want to bind to it. So we can just let gbm_surface_backing_store choose a config + * and let flutter-pi query that config when creating the rendering contexts in that case. + * + * @param s + * @return EGLConfig The chosen EGLConfig. Valid forever. + */ +ATTR_PURE EGLConfig gbm_surface_backing_store_get_egl_config(struct gbm_surface_backing_store *s) { + return s->egl_config; +} diff --git a/src/gl_renderer.c b/src/gl_renderer.c new file mode 100644 index 00000000..fe9265d2 --- /dev/null +++ b/src/gl_renderer.c @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: MIT +/* + * Utilities for rendering using OpenGL + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + +#define _GNU_SOURCE +#include <stdlib.h> +#include <pthread.h> +#include <dlfcn.h> + +#include <collection.h> +#include <egl.h> +#include <gles.h> +#include <tracer.h> +#include <pixel_format.h> +#include <gl_renderer.h> + +FILE_DESCR("EGL/GL renderer") + +struct gl_renderer { + refcount_t n_refs; + + struct tracer *tracer; + struct gbm_device *gbm_device; + EGLDisplay egl_display; + + EGLContext root_context, flutter_rendering_context, flutter_resource_uploading_context, flutter_setup_context; + pthread_mutex_t root_context_lock; + + bool has_forced_pixel_format; + enum pixfmt pixel_format; + + /** + * @brief If EGL doesn't support EGL_KHR_no_config_context, we need to specify an EGLConfig (basically the framebuffer format) + * for the context. And since all shared contexts need to have the same EGL Config, we basically have to choose a single global config + * for the display. + * + * If this field is not EGL_NO_CONFIG_KHR, all contexts we create need to have exactly this EGL Config. + */ + EGLConfig forced_egl_config; + + EGLint major, minor; + const char *egl_client_exts; + const char *egl_display_exts; + const char *gl_renderer; + const char *gl_exts; +}; + +static ATTR_PURE EGLConfig choose_config_with_pixel_format(EGLDisplay display, const EGLint *attrib_list, enum pixfmt pixel_format) { + EGLConfig *matching; + EGLBoolean egl_ok; + EGLint value, n_matched; + + DEBUG_ASSERT(display != EGL_NO_DISPLAY); + + LOG_DEBUG("Choosing EGL config with pixel format %s...\n", get_pixfmt_info(pixel_format)->name); + + n_matched = 0; + egl_ok = eglChooseConfig(display, attrib_list, NULL, 0, &n_matched); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not query number of EGL framebuffer configurations with fitting attributes. eglChooseConfig: 0x%08X\n", eglGetError()); + return EGL_NO_CONFIG_KHR; + } + + matching = alloca(n_matched * sizeof *matching); + + egl_ok = eglChooseConfig(display, attrib_list, matching, n_matched, &n_matched); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not query EGL framebuffer configurations with fitting attributes. eglChooseConfig: 0x%08X\n", eglGetError()); + return EGL_NO_CONFIG_KHR; + } + + for (int i = 0; i < n_matched; i++) { + egl_ok = eglGetConfigAttrib(display, matching[i], EGL_NATIVE_VISUAL_ID, &value); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not query pixel format of EGL framebuffer config. eglGetConfigAttrib: 0x%08X\n", eglGetError()); + return EGL_NO_CONFIG_KHR; + } + + if (int32_to_uint32(value) == get_pixfmt_info(pixel_format)->gbm_format) { + // found a config with matching pixel format. + return matching[i]; + } + } + + return EGL_NO_CONFIG_KHR; +} + +struct gl_renderer *gl_renderer_new_from_gbm_device( + struct tracer *tracer, + struct gbm_device *gbm_device, + bool has_forced_pixel_format, + enum pixfmt pixel_format +) { + struct gl_renderer *renderer; + const char *egl_client_exts, *egl_display_exts; + const char *gl_renderer, *gl_exts; + EGLContext root_context, flutter_render_context, flutter_resource_uploading_context, flutter_setup_context; + EGLDisplay egl_display; + EGLBoolean egl_ok; + EGLConfig forced_egl_config; + EGLint major, minor; + int ok; + + renderer = malloc(sizeof *renderer); + if (renderer == NULL) { + goto fail_return_null; + } + + egl_client_exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (egl_client_exts == NULL) { + LOG_ERROR("Couldn't query EGL client extensions. eglQueryString: 0x%08X\n", eglGetError()); + goto fail_free_renderer; + } + + egl_display = eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm_device, NULL); + if (egl_display == EGL_NO_DISPLAY) { + LOG_ERROR("Could not get EGL display from GBM device. eglGetPlatformDisplay: 0x%08X\n", eglGetError()); + goto fail_free_renderer; + } + + egl_ok = eglInitialize(egl_display, &major, &minor); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Failed to initialize EGL! eglInitialize: 0x%08X\n", eglGetError()); + goto fail_free_renderer; + } + + egl_display_exts = eglQueryString(egl_display, EGL_EXTENSIONS); + if (egl_display_exts == NULL) { + LOG_ERROR("Couldn't query EGL display extensions. eglQueryString: 0x%08X\n", eglGetError()); + goto fail_terminate_display; + } + + if (!check_egl_extension(egl_client_exts, egl_display_exts, "EGL_KHR_surfaceless_context")) { + LOG_ERROR("EGL doesn't support the EGL_KHR_surfaceless_context extension, which is required by flutter-pi.\n"); + goto fail_destroy_flutter_resource_uploading_context; + } + + egl_ok = eglBindAPI(EGL_OPENGL_ES_API); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Couldn't bind OpenGL ES API to EGL. eglBindAPI: 0x%08X\n", eglGetError()); + goto fail_terminate_display; + } + + if (check_egl_extension(egl_client_exts, egl_display_exts, "EGL_KHR_no_config_context")) { + // EGL supports creating contexts without an EGLConfig, which is nice. + // Just create a context without selecting a config and let the backing stores (when they're created) select + // the framebuffer config instead. + forced_egl_config = EGL_NO_CONFIG_KHR; + } else { + // choose a config + const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_SAMPLES, 0, + EGL_NONE + }; + + if (has_forced_pixel_format == false) { + has_forced_pixel_format = true; + pixel_format = kARGB8888; + } + + forced_egl_config = choose_config_with_pixel_format(egl_display, config_attribs, pixel_format); + if (forced_egl_config == EGL_NO_CONFIG_KHR) { + LOG_ERROR("No fitting EGL framebuffer configuration found.\n"); + goto fail_terminate_display; + } + } + + static const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + root_context = eglCreateContext(egl_display, forced_egl_config, EGL_NO_CONTEXT, context_attribs); + if (root_context == EGL_NO_CONTEXT) { + LOG_ERROR("Could not create EGL context for OpenGL ES. eglCreateContext: 0x%08X\n", eglGetError()); + goto fail_terminate_display; + } + + flutter_render_context = eglCreateContext(egl_display, forced_egl_config, root_context, context_attribs); + if (flutter_render_context == EGL_NO_CONTEXT) { + LOG_ERROR("Could not create EGL OpenGL ES context for flutter rendering. eglCreateContext: 0x%08X\n", eglGetError()); + goto fail_destroy_root_context; + } + + flutter_resource_uploading_context = eglCreateContext(egl_display, forced_egl_config, root_context, context_attribs); + if (flutter_resource_uploading_context == EGL_NO_CONTEXT) { + LOG_ERROR("Could not create EGL OpenGL ES context for flutter resource uploads. eglCreateContext: 0x%08X\n", eglGetError()); + goto fail_destroy_flutter_render_context; + } + + flutter_setup_context = eglCreateContext(egl_display, forced_egl_config, root_context, context_attribs); + if (flutter_setup_context == EGL_NO_CONTEXT) { + LOG_ERROR("Could not create EGL OpenGL ES context for flutter initialization. eglCreateContext: 0x%08X\n", eglGetError()); + goto fail_destroy_flutter_resource_uploading_context; + } + + egl_ok = eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, root_context); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not make EGL OpenGL ES root context current to query OpenGL information. eglMakeCurrent: 0x%08X\n", eglGetError()); + goto fail_destroy_flutter_setup_context; + } + + gl_renderer = (const char*) glGetString(GL_RENDERER); + if (gl_renderer == NULL) { + LOG_ERROR("Couldn't query OpenGL ES renderer information.\n"); + goto fail_clear_current; + } + + gl_exts = (const char*) glGetString(GL_EXTENSIONS); + if (gl_exts == NULL) { + LOG_ERROR("Couldn't query supported OpenGL ES extensions.\n"); + goto fail_clear_current; + } + + egl_ok = eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not clear EGL OpenGL ES context. eglMakeCurrent: 0x%08X\n", eglGetError()); + goto fail_destroy_flutter_resource_uploading_context; + } + + ok = pthread_mutex_init(&renderer->root_context_lock, NULL); + if (ok < 0) { + goto fail_destroy_flutter_resource_uploading_context; + } + + renderer->n_refs = REFCOUNT_INIT_1; + renderer->tracer = tracer_ref(tracer); + renderer->gbm_device = gbm_device; + renderer->egl_display = egl_display; + renderer->root_context = root_context; + renderer->flutter_rendering_context = flutter_render_context; + renderer->flutter_resource_uploading_context = flutter_resource_uploading_context; + renderer->flutter_setup_context = flutter_setup_context; + renderer->has_forced_pixel_format = has_forced_pixel_format; + renderer->pixel_format = pixel_format; + renderer->forced_egl_config = forced_egl_config; + renderer->major = major; + renderer->minor = minor; + renderer->egl_client_exts = egl_client_exts; + renderer->egl_display_exts = egl_display_exts; + renderer->gl_renderer = gl_renderer; + renderer->gl_exts = gl_exts; + + return renderer; + + + fail_clear_current: + eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + fail_destroy_flutter_setup_context: + eglDestroyContext(egl_display, flutter_setup_context); + + fail_destroy_flutter_resource_uploading_context: + eglDestroyContext(egl_display, flutter_resource_uploading_context); + + fail_destroy_flutter_render_context: + eglDestroyContext(egl_display, flutter_render_context); + + fail_destroy_root_context: + eglDestroyContext(egl_display, root_context); + + fail_terminate_display: + eglTerminate(egl_display); + + fail_free_renderer: + free(renderer); + + fail_return_null: + return NULL; +} + +void gl_renderer_destroy(struct gl_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + pthread_mutex_destroy(&renderer->root_context_lock); + eglDestroyContext(renderer->egl_display, renderer->flutter_resource_uploading_context); + eglDestroyContext(renderer->egl_display, renderer->flutter_rendering_context); + eglDestroyContext(renderer->egl_display, renderer->root_context); + eglTerminate(renderer->egl_display); + free(renderer); +} + +DEFINE_REF_OPS(gl_renderer, n_refs) + +bool gl_renderer_has_forced_pixel_format(struct gl_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return renderer->has_forced_pixel_format; +} + +enum pixfmt gl_renderer_get_forced_pixel_format(struct gl_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + DEBUG_ASSERT(renderer->has_forced_pixel_format); + return renderer->pixel_format; +} + +bool gl_renderer_has_forced_egl_config(struct gl_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return renderer->forced_egl_config != EGL_NO_CONFIG_KHR; +} + +EGLConfig gl_renderer_get_forced_egl_config(struct gl_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return renderer->forced_egl_config; +} + +struct gbm_device *gl_renderer_get_gbm_device(struct gl_renderer *renderer) { + return renderer->gbm_device; +} + +int gl_renderer_make_flutter_setup_context_current(struct gl_renderer *renderer) { + EGLBoolean egl_ok; + + DEBUG_ASSERT_NOT_NULL(renderer); + + TRACER_BEGIN(renderer->tracer, "gl_renderer_make_flutter_rendering_context_current"); + egl_ok = eglMakeCurrent( + renderer->egl_display, + EGL_NO_SURFACE, EGL_NO_SURFACE, + renderer->flutter_setup_context + ); + TRACER_END(renderer->tracer, "gl_renderer_make_flutter_rendering_context_current"); + + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not make the flutter setup EGL context current. eglMakeCurrent: 0x%08X\n", eglGetError()); + return EIO; + } + + return 0; +} + +int gl_renderer_make_flutter_rendering_context_current(struct gl_renderer *renderer, EGLSurface surface) { + EGLBoolean egl_ok; + + DEBUG_ASSERT_NOT_NULL(renderer); + /// NOTE: Allow this for now + /// DEBUG_ASSERT(surface != EGL_NO_SURFACE); + + TRACER_BEGIN(renderer->tracer, "gl_renderer_make_flutter_rendering_context_current"); + egl_ok = eglMakeCurrent( + renderer->egl_display, + surface, surface, + renderer->flutter_rendering_context + ); + TRACER_END(renderer->tracer, "gl_renderer_make_flutter_rendering_context_current"); + + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not make the flutter rendering EGL context current. eglMakeCurrent: 0x%08X\n", eglGetError()); + return EIO; + } + + return 0; +} + +int gl_renderer_make_flutter_resource_uploading_context_current(struct gl_renderer *renderer) { + EGLBoolean egl_ok; + + DEBUG_ASSERT_NOT_NULL(renderer); + + TRACER_BEGIN(renderer->tracer, "gl_renderer_make_flutter_resource_uploading_context_current"); + egl_ok = eglMakeCurrent( + renderer->egl_display, + EGL_NO_SURFACE, EGL_NO_SURFACE, + renderer->flutter_resource_uploading_context + ); + TRACER_END(renderer->tracer, "gl_renderer_make_flutter_resource_uploading_context_current"); + + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not make the flutter resource uploading EGL context current. eglMakeCurrent: 0x%08X\n", eglGetError()); + return EIO; + } + + return 0; +} + +int gl_renderer_clear_current(struct gl_renderer *renderer) { + EGLBoolean egl_ok; + + DEBUG_ASSERT_NOT_NULL(renderer); + + TRACER_BEGIN(renderer->tracer, "gl_renderer_clear_current"); + egl_ok = eglMakeCurrent(renderer->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + TRACER_END(renderer->tracer, "gl_renderer_clear_current"); + + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not clear the flutter EGL context. eglMakeCurrent: 0x%08X\n", eglGetError()); + return EIO; + } + + return 0; +} + +void *gl_renderer_get_proc_address(MAYBE_UNUSED struct gl_renderer *renderer, const char* name) { + void *address; + + address = eglGetProcAddress(name); + if (address) { + return address; + } + + address = dlsym(RTLD_DEFAULT, name); + if (address) { + return address; + } + + LOG_ERROR("Could not resolve EGL/GL symbol \"%s\"\n", name); + return NULL; +} + +EGLDisplay gl_renderer_get_egl_display(struct gl_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return renderer->egl_display; +} + +EGLContext gl_renderer_create_context(struct gl_renderer *renderer) { + EGLContext context; + + DEBUG_ASSERT_NOT_NULL(renderer); + + pthread_mutex_lock(&renderer->root_context_lock); + context = eglCreateContext(renderer->egl_display, renderer->forced_egl_config, renderer->root_context, NULL); + pthread_mutex_unlock(&renderer->root_context_lock); + + return context; +} + +bool gl_renderer_supports_egl_extension(struct gl_renderer *renderer, const char *name) { + DEBUG_ASSERT_NOT_NULL(renderer); + DEBUG_ASSERT_NOT_NULL(name); + return check_egl_extension(renderer->egl_client_exts, renderer->egl_display_exts, name); +} + +bool gl_renderer_supports_gl_extension(struct gl_renderer *renderer, const char *name) { + DEBUG_ASSERT_NOT_NULL(renderer); + DEBUG_ASSERT_NOT_NULL(name); + return check_egl_extension(renderer->gl_exts, NULL, name); +} + +bool gl_renderer_is_llvmpipe(struct gl_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return strstr(renderer->gl_renderer, "llvmpipe") != NULL; +} + +#ifdef DEBUG +static __thread bool is_render_thread = false; +#endif + +int gl_renderer_make_this_a_render_thread(struct gl_renderer *renderer) { + EGLContext context; + EGLBoolean egl_ok; + + DEBUG_ASSERT_NOT_NULL(renderer); + DEBUG_ASSERT(is_render_thread == false); + + pthread_mutex_lock(&renderer->root_context_lock); + context = eglCreateContext(renderer->egl_display, renderer->forced_egl_config, renderer->root_context, NULL); + pthread_mutex_unlock(&renderer->root_context_lock); + + if (context == EGL_NO_CONTEXT) { + LOG_ERROR("Couldn't create a new EGL context.\n"); + return EIO; + } + + egl_ok = eglMakeCurrent(renderer->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, context); + if (egl_ok == EGL_FALSE) { + LOG_ERROR("Couldn't make EGL context current to make this an EGL thread.\n"); + return EIO; + } + +#ifdef DEBUG + is_render_thread = true; +#endif + + return 0; +} + +void gl_renderer_cleanup_this_render_thread() { + EGLDisplay display; + EGLContext context; + EGLBoolean egl_ok; + + DEBUG_ASSERT(is_render_thread); + + context = eglGetCurrentContext(); + DEBUG_ASSERT(context != EGL_NO_CONTEXT); + + display = eglGetCurrentDisplay(); + DEBUG_ASSERT(display != EGL_NO_CONTEXT); + + egl_ok = eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (egl_ok == EGL_FALSE) { + LOG_ERROR("Couldn't clear EGL context to cleanup this EGL thread.\n"); + return; + } + + egl_ok = eglDestroyContext(display, context); + if (egl_ok == EGL_FALSE) { + LOG_ERROR("Couldn't destroy EGL context to cleanup this EGL thread.\n"); + return; + } +} + +ATTR_PURE EGLConfig gl_renderer_choose_config(struct gl_renderer *renderer, bool has_desired_pixel_format, enum pixfmt desired_pixel_format) { + DEBUG_ASSERT_NOT_NULL(renderer); + + if (renderer->forced_egl_config != EGL_NO_CONFIG_KHR) { + return renderer->forced_egl_config; + } + + const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_SAMPLES, 0, + EGL_NONE + }; + + return choose_config_with_pixel_format( + renderer->egl_display, + config_attribs, + renderer->has_forced_pixel_format ? renderer->pixel_format : + has_desired_pixel_format ? desired_pixel_format : + kARGB8888 + ); +} + +ATTR_PURE EGLConfig gl_renderer_choose_config_direct(struct gl_renderer *renderer, enum pixfmt pixel_format) { + DEBUG_ASSERT_NOT_NULL(renderer); + DEBUG_ASSERT_PIXFMT_VALID(pixel_format); + + const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_SAMPLES, 0, + EGL_NONE + }; + + return choose_config_with_pixel_format( + renderer->egl_display, + config_attribs, + pixel_format + ); +} \ No newline at end of file diff --git a/src/modesetting.c b/src/modesetting.c index b22269ba..f371f130 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -1,99 +1,275 @@ -#include <fcntl.h> -#include <stdio.h> +#include <errno.h> +#include <inttypes.h> #include <stdint.h> -#include <string.h> +#include <stdio.h> #include <stdlib.h> -#include <errno.h> +#include <string.h> + +#include <fcntl.h> #include <unistd.h> +#include <sys/epoll.h> #include <xf86drm.h> #include <xf86drmMode.h> #include <modesetting.h> +#include <pixel_format.h> + +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; + 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]; + + int n_scanout_callbacks; + struct { + kms_scanout_cb_t callback; + void *userdata; + } scanout_cbs[32]; + + int n_post_release_cbs; + struct { + kms_scanout_cb_t callback; + void *userdata; + } post_release_cbs[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; -static int drmdev_lock(struct drmdev *drmdev) { - return pthread_mutex_lock(&drmdev->mutex); + size_t n_crtcs; + struct drm_crtc *crtcs; + + size_t n_planes; + struct drm_plane *planes; + + drmModeRes *res; + drmModePlaneRes *plane_res; + + /* + 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 gbm_device *gbm_device; + + int event_fd; + struct kms_req *last_flipped; +}; + +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; + } + + modes = calloc(connector->count_modes, sizeof *modes); + if (modes == NULL) { + goto fail_free_props; + } + + memcpy(modes, connector->modes, connector->count_modes * sizeof *modes); + + 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_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; + } } *connectors_out = connectors; *n_connectors_out = drmdev->res->count_connectors; - 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_connectors: free(connectors); - *connectors_out = NULL; *n_connectors_out = 0; return ok; @@ -101,18 +277,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 +316,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 +338,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 +450,483 @@ 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 & (1 << (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; - for (int j = 0; j < props->count_props; j++) { - props_info[j] = drmModeGetProperty(drmdev->fd, props->props[j]); - if (props_info[j] == NULL) { + 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)); + + 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, + 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; } -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 -) { +int drmdev_new_from_fd(struct drmdev **drmdev_out, int fd) { + struct gbm_device *gbm_device; struct drmdev *drmdev; - int ok; + bool supports_atomic_modesetting; + int ok, event_fd; + + assert_rotations_work(); drmdev = calloc(1, sizeof *drmdev); if (drmdev == NULL) { return ENOMEM; } - drmdev->fd = fd; - - ok = drmSetClientCap(drmdev->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + ok = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); if (ok < 0) { ok = errno; - perror("[modesetting] Could not set DRM client universal planes capable. drmSetClientCap"); + LOG_ERROR("Could not set DRM client universal planes capable. drmSetClientCap: %s\n", strerror(ok)); goto fail_free_drmdev; } - - ok = drmSetClientCap(drmdev->fd, DRM_CLIENT_CAP_ATOMIC, 1); + + ok = drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1); if ((ok < 0) && (errno == EOPNOTSUPP)) { - drmdev->supports_atomic_modesetting = false; + supports_atomic_modesetting = false; } else if (ok < 0) { ok = errno; - perror("[modesetting] Could not set DRM client atomic capable. drmSetClientCap"); + LOG_ERROR("Could not set DRM client atomic capable. drmSetClientCap: %s\n", strerror(ok)); goto fail_free_drmdev; } else { - drmdev->supports_atomic_modesetting = true; + supports_atomic_modesetting = true; } - 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,42 +947,73 @@ 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; + } + + drmdev->fd = fd; + drmdev->supports_atomic_modesetting = supports_atomic_modesetting; + drmdev->gbm_device = gbm_device; + drmdev->event_fd = event_fd; + drmdev->last_flipped = NULL; + + *drmdev_out = drmdev; + + return 0; + +fail_close_event_fd: + close(event_fd); +fail_destroy_gbm_device: + gbm_device_destroy(gbm_device); - fail_free_crtcs: +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_free_drmdev: free(drmdev); return ok; } -int drmdev_new_from_path( - struct drmdev **drmdev_out, - const char *path -) { +int drmdev_new_from_path(struct drmdev **drmdev_out, const char *path) { int ok, fd; fd = open(path, O_RDWR); if (fd < 0) { - perror("[modesetting] Could not open DRM device. open"); - return errno; + ok = errno; + LOG_ERROR("Could not open DRM device. open: %s\n", strerror(ok)); + return ok; } ok = drmdev_new_from_fd(drmdev_out, fd); @@ -487,782 +1025,1135 @@ int drmdev_new_from_path( return 0; } -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)); + /// TODO: Implement + UNIMPLEMENTED(); +} + +DEFINE_REF_OPS(drmdev, n_refs) + +int drmdev_get_fd(struct drmdev *drmdev) { + DEBUG_ASSERT_NOT_NULL(drmdev); + return drmdev->fd; +} + +int drmdev_get_event_fd(struct drmdev *drmdev) { + DEBUG_ASSERT_NOT_NULL(drmdev); + return drmdev->event_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 drm_crtc *crtc; - uint32_t mode_id; - int ok; + struct kms_req_builder *builder; + struct kms_req *req; - 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) { - break; - } - } + (void) fd; + (void) sequence; + (void) crtc_id; - if (connector == NULL) { - drmdev_unlock(drmdev); - return EINVAL; - } + uint64_t vblank_ns = tv_sec * 1000000000ull + tv_usec * 1000ull; - for_each_encoder_in_drmdev(drmdev, encoder) { - if (encoder->encoder->encoder_id == encoder_id) { - break; - } + if (builder->drmdev->last_flipped != NULL) { + kms_req_call_release_callbacks(builder->drmdev->last_flipped); + kms_req_call_post_release_callbacks(builder->drmdev->last_flipped, vblank_ns); + DEBUG_ASSERT(refcount_is_one(&((struct kms_req_builder*) builder->drmdev->last_flipped)->n_refs)); + kms_req_unref(builder->drmdev->last_flipped); } - if (encoder == NULL) { - drmdev_unlock(drmdev); - return EINVAL; - } + //DEBUG_ASSERT(refcount_is_one(&builder->n_refs)); + builder->drmdev->last_flipped = req; - for_each_crtc_in_drmdev(drmdev, crtc) { - if (crtc->crtc->crtc_id == crtc_id) { - break; - } - } + /// TODO: Fix this + drmdev_unlock(builder->drmdev); + kms_req_call_scanout_callbacks(req, vblank_ns); + drmdev_lock(builder->drmdev); +} - if (crtc == NULL) { - drmdev_unlock(drmdev); - return EINVAL; +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->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, + uint32_t flags ) { - 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) { + 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, + flags + ); + 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, + flags + ); + 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], + uint32_t flags ) { - 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, + flags + ); + 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, + flags + ); + 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, + uint32_t flags ) { - 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, flags); } -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], + uint32_t flags ) { - 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, + flags + ); +} - 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; - } - - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_IMMUTABLE) { - *result = false; - return 0; - } +MAYBE_UNUSED static struct drm_plane *get_plane_by_id(struct drmdev *drmdev, uint32_t plane_id) { + struct drm_plane *plane; - if (plane->props_info[prop_index]->count_values != 2) { - *result = false; - return 0; + plane = NULL; + for (int i = 0; i < drmdev->n_planes; i++) { + if (drmdev->planes[i].id == plane_id) { + plane = drmdev->planes + i; + break; + } } - 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 plane; +} - if ((min <= zpos) && (max >= zpos)) { - *result = true; - return 0; - } else { - *result = false; - return 0; +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; } - } 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]; + } - if ((min <= zpos) && (max >= zpos)) { - *result = true; - return 0; - } else { - *result = false; - return 0; + return NULL; +} + +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; } - } else { - *result = false; - return 0; } - - return 0; + + return NULL; } -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 prop_index = get_plane_property_index_by_name(plane, "zpos"); - if (prop_index == -1) { - return EINVAL; +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; + } } - if (plane->props_info[prop_index]->count_values != 2) { - return EINVAL; + return NULL; +} + +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; + } } - 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]; + return NULL; +} - *min_zpos_out = (int64_t) min; - return 0; - } else { - return EINVAL; +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; + } } - - return EINVAL; + + return NULL; } -int drmdev_plane_get_max_zpos_value( - struct drmdev *drmdev, - uint32_t plane_id, - int64_t *max_zpos_out +static bool plane_qualifies( + 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 ) { - 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; - } - - if (plane->props_info[prop_index]->count_values != 2) { - return EINVAL; + 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; + } - 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]; + // 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; + } + } - *max_zpos_out = (int64_t) max; - return 0; + // not found in the supported modified format list + return false; } else { - return EINVAL; + // 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; + } } - - return EINVAL; -} -int drmdev_plane_supports_setting_zpos( - struct drmdev *drmdev, - uint32_t plane_id, - bool *result -) { - struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); - if (plane == NULL) { - 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; + } } - - int prop_index = get_plane_property_index_by_name(plane, "zpos"); - if (prop_index == -1) { - *result = false; - return 0; + 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; +} - if (plane->props_info[prop_index]->count_values != 2) { - *result = false; - return 0; +static struct drm_plane *allocate_plane( + 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 +) { + 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 it doesn't, look for the next one + if (!qualifies) { + continue; + } + + // we found one, mark it as used and return it + BMAP_CLEAR(builder->planes, i); + return plane; + } } - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_IMMUTABLE) { - *result = false; - return 0; + // we didn't find an available plane matching our criteria + return NULL; +} + +static void release_plane(struct kms_req_builder *builder, uint32_t plane_id) { + struct drm_plane *plane; + int index; + + index = 0; + for_each_plane_in_drmdev(builder->drmdev, plane) { + if (plane->id == plane_id) { + break; + } + index++; } - if (!(plane->props_info[prop_index]->flags & (DRM_MODE_PROP_RANGE | DRM_MODE_PROP_SIGNED_RANGE))) { - *result = false; - return 0; + if (plane == NULL) { + LOG_ERROR("Could not release invalid plane %" PRIu32 ".\n", plane_id); + return; } - *result = true; - return 0; + DEBUG_ASSERT(!BMAP_IS_SET(builder->planes, index)); + BMAP_SET(builder->planes, index); } -int drmdev_new_atomic_req( - struct drmdev *drmdev, - struct drmdev_atomic_req **req_out -) { - struct drmdev_atomic_req *req; - struct drm_plane *plane; +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; - if (drmdev->supports_atomic_modesetting == false) { - return EOPNOTSUPP; + DEBUG_ASSERT_NOT_NULL(drmdev); + DEBUG_ASSERT(crtc_id != 0 && crtc_id != 0xFFFFFFFF); + + for_each_crtc_in_drmdev(drmdev, crtc) { + if (crtc->id == crtc_id) { + break; + } } - req = calloc(1, sizeof *req); - if (req == NULL) { - return ENOMEM; + if (crtc == NULL) { + LOG_ERROR("Invalid CRTC id: %" PRId32 "\n", crtc_id); + return NULL; } - req->drmdev = drmdev; + builder = malloc(sizeof *builder); + if (builder == NULL) { + return NULL; + } - req->atomic_req = drmModeAtomicAlloc(); - if (req->atomic_req == NULL) { - free(req); - return ENOMEM; + if (drmdev->supports_atomic_modesetting) { + req = drmModeAtomicAlloc(); + if (req == NULL) { + free(builder); + return NULL; + } + } else { + req = NULL; } - req->available_planes = PSET_INITIALIZER_STATIC(req->available_planes_storage, 32); + min_zpos = INT64_MAX; + BMAP_ZERO(builder->planes); + for (int i = 0; i < drmdev->n_planes; i++) { + struct drm_plane *plane = drmdev->planes + i; - for_each_plane_in_drmdev(drmdev, plane) { - if (plane->plane->possible_crtcs & drmdev->selected_crtc->bitmask) { - pset_put(&req->available_planes, plane); + 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; + } } } - *req_out = req; - - 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->n_scanout_callbacks = 0; + builder->n_post_release_cbs = 0; + builder->has_mode = false; + builder->unset_mode = false; + return builder; } -void drmdev_destroy_atomic_req( - struct drmdev_atomic_req *req -) { - drmModeAtomicFree(req->atomic_req); - free(req); +void kms_req_builder_destroy(struct kms_req_builder *builder) { + /// TODO: Is this complete? + free(builder); } -int drmdev_atomic_req_put_connector_property( - struct drmdev_atomic_req *req, - const char *name, - uint64_t value -) { - int ok; +DEFINE_REF_OPS(kms_req_builder, n_refs) - drmdev_lock(req->drmdev); +struct drmdev *kms_req_builder_get_drmdev(struct kms_req_builder *builder) { + return builder->drmdev; +} - 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; - } +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); - return 0; - } - } +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; +} - drmdev_unlock(req->drmdev); - return EINVAL; +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; } -int drmdev_atomic_req_put_crtc_property( - struct drmdev_atomic_req *req, - const char *name, - uint64_t value -) { - int ok; +int kms_req_builder_set_connector(struct kms_req_builder *builder, uint32_t connector_id) { + struct drm_connector *conn; - drmdev_lock(req->drmdev); + DEBUG_ASSERT_NOT_NULL(builder); + DEBUG_ASSERT(DRM_ID_IS_VALID(connector_id)); - 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 - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not add crtc property to atomic request. drmModeAtomicAddProperty"); - drmdev_unlock(req->drmdev); - return ok; - } - - drmdev_unlock(req->drmdev); - return 0; + for_each_connector_in_drmdev(builder->drmdev, conn) { + if (conn->id == connector_id) { + break; } } - drmdev_unlock(req->drmdev); - return EINVAL; + if (conn == NULL) { + LOG_ERROR("Could not find connector with id %" PRIu32 "\n", connector_id); + return EINVAL; + } + + builder->connector = conn; + return 0; } -int drmdev_atomic_req_put_plane_property( - struct drmdev_atomic_req *req, - uint32_t plane_id, - const char *name, - uint64_t value +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, + void *userdata ) { struct drm_plane *plane; - int ok; - - drmdev_lock(req->drmdev); + 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); + + 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; + } - 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; - break; + 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 + ); } } if (plane == NULL) { - drmdev_unlock(req->drmdev); - return EINVAL; + 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; + } } - 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; + 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); } - } - drmdev_unlock(req->drmdev); - return EINVAL; -} + if (layer->has_rotation && plane->has_rotation && !plane->has_hardcoded_rotation) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.rotation, layer->rotation.u64); + } -int drmdev_atomic_req_put_modeset_props( - struct drmdev_atomic_req *req, - uint32_t *flags -) { - struct drmdev_atomic_req *augment; - int ok; + if (index == 0) { + if (plane->has_alpha) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.alpha, DRM_BLEND_ALPHA_OPAQUE); + } - ok = drmdev_new_atomic_req(req->drmdev, &augment); - if (ok != 0) { - return ok; + if (plane->has_blend_mode && plane->supported_blend_modes[kNone_DrmBlendMode]) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.pixel_blend_mode, kNone_DrmBlendMode); + } + } } - 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; + // 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; + } } - 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; - } + /// 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].release_callback_userdata = userdata; + return 0; - ok = drmdev_atomic_req_put_crtc_property(req, "ACTIVE", 1); - if (ok != 0) { - drmdev_destroy_atomic_req(augment); - return ok; - } +fail_release_plane: + release_plane(builder, plane->id); + return ok; +} - 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; - } +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; +} - drmdev_destroy_atomic_req(augment); +int kms_req_builder_add_scanout_callback(struct kms_req_builder *builder, kms_scanout_cb_t callback, void *userdata) { + int i; - if (flags != NULL) { - *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; - } + DEBUG_ASSERT_NOT_NULL(builder); + DEBUG_ASSERT_NOT_NULL(callback); + DEBUG_ASSERT(builder->n_scanout_callbacks < ARRAY_SIZE(builder->scanout_cbs)); + i = builder->n_scanout_callbacks++; + builder->scanout_cbs[i].callback = callback; + builder->scanout_cbs[i].userdata = userdata; return 0; } -int drmdev_atomic_req_commit( - struct drmdev_atomic_req *req, - uint32_t flags, +int kms_req_builder_add_post_release_callback( + struct kms_req_builder *builder, + kms_scanout_cb_t callback, void *userdata ) { - int ok; + int i; - drmdev_lock(req->drmdev); + DEBUG_ASSERT_NOT_NULL(builder); + DEBUG_ASSERT_NOT_NULL(callback); + DEBUG_ASSERT(builder->n_post_release_cbs < ARRAY_SIZE(builder->post_release_cbs)); - 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; - } - - drmdev_unlock(req->drmdev); + i = builder->n_post_release_cbs++; + builder->post_release_cbs[i].callback = callback; + builder->post_release_cbs[i].userdata = userdata; return 0; } -int drmdev_legacy_set_mode_and_fb( - struct drmdev *drmdev, - uint32_t fb_id -) { - int ok; +void kms_req_builder_call_release_callbacks(struct kms_req_builder *builder) { + DEBUG_ASSERT_NOT_NULL(builder); + 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); + } + } +} - drmdev_lock(drmdev); +struct kms_req *kms_req_builder_build(struct kms_req_builder *builder) { + return (struct kms_req *) kms_req_builder_ref(builder); +} - 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; - } +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); +} - drmdev_unlock(drmdev); +MAYBE_UNUSED void kms_req_unref(struct kms_req *req) { + return kms_req_builder_unref((struct kms_req_builder *) req); +} - return 0; +MAYBE_UNUSED void kms_req_unrefp(struct kms_req **req) { + return kms_req_builder_unrefp((struct kms_req_builder **) req); } -int drmdev_legacy_primary_plane_pageflip( - struct drmdev *drmdev, - uint32_t fb_id, - void *userdata -) { - int ok; +void kms_req_call_scanout_callbacks(struct kms_req *req, uint64_t vblank_ns) { + struct kms_req_builder *builder; - drmdev_lock(drmdev); + DEBUG_ASSERT_NOT_NULL(req); + builder = (struct kms_req_builder *) req; - 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; + for (int i = 0; i < builder->n_scanout_callbacks; i++) { + DEBUG_ASSERT_NOT_NULL(builder->scanout_cbs[i].callback); + builder->scanout_cbs[i].callback(builder->drmdev, vblank_ns, builder->scanout_cbs[i].userdata); } +} - drmdev_unlock(drmdev); +void kms_req_call_release_callbacks(struct kms_req *req) { + struct kms_req_builder *builder; - return 0; + DEBUG_ASSERT_NOT_NULL(req); + builder = (struct kms_req_builder *) req; + + return kms_req_builder_call_release_callbacks(builder); } -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; +void kms_req_call_post_release_callbacks(struct kms_req *req, uint64_t vblank_ns) { + struct kms_req_builder *builder; - drmdev_lock(drmdev); + DEBUG_ASSERT_NOT_NULL(req); + builder = (struct kms_req_builder *) req; - 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; + for (int i = 0; i < builder->n_post_release_cbs; i++) { + DEBUG_ASSERT_NOT_NULL(builder->post_release_cbs[i].callback); + builder->post_release_cbs[i].callback(builder->drmdev, vblank_ns, builder->post_release_cbs[i].userdata); } - - drmdev_unlock(drmdev); - - return 0; } -int drmdev_legacy_set_connector_property( - struct drmdev *drmdev, - const char *name, - uint64_t value -) { +int kms_req_commit(struct kms_req *req, bool blocking) { + struct kms_req_builder *builder; + struct drm_mode_blob *mode_blob; + uint32_t flags; + bool update_mode; int ok; - drmdev_lock(drmdev); - - 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 + update_mode = false; + mode_blob = NULL; + + DEBUG_ASSERT_NOT_NULL(req); + builder = (struct kms_req_builder *) req; + + drmdev_lock(builder->drmdev); + + // only change the mode if the new mode differs from the old one + /// TODO: Maybe let the user of this API track whether the mode changes + /// and set mode unconditionally here? + /// TOOD: If this is not a standard mode reported by connector/CRTC, + /// is there a way to verify if it is valid? + if (builder->has_mode && + (!builder->crtc->committed_state.has_mode || + memcmp(&builder->crtc->committed_state.mode, &builder->mode, sizeof(drmModeModeInfo)) != 0)) { + 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->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 connector property. drmModeConnectorSetProperty"); - drmdev_unlock(drmdev); - return ok; + LOG_ERROR("Could not commit display update. drmModeSetCrtc: %s\n", strerror(ok)); + goto fail_maybe_destroy_mode_blob; } - - drmdev_unlock(drmdev); - return 0; - } - } - - drmdev_unlock(drmdev); - return EINVAL; -} - -int drmdev_legacy_set_crtc_property( - struct drmdev *drmdev, - const char *name, - uint64_t value -) { - int ok; - - drmdev_lock(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 + } else { + ok = drmModePageFlip( + builder->drmdev->fd, + builder->crtc->id, + builder->layers[0].layer.drm_fb_id, + DRM_MODE_PAGE_FLIP_EVENT, + kms_req_builder_ref(builder) ); - 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. drmModePageFlip: %s\n", strerror(ok)); + goto fail_unref_builder; } - - drmdev_unlock(drmdev); - return 0; } - } - drmdev_unlock(drmdev); - return EINVAL; -} + // 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." + ); -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; + /// TODO: Call drmModeSetPlane for all other layers + /// TODO: Assert here - drmdev_lock(drmdev); + } 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); + if (update_mode) { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } - 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; + /// 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->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; } } - if (plane == NULL) { - 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); + } + + // 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; + } } - 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; + 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; } } - drmdev_unlock(drmdev); - return EINVAL; -} \ No newline at end of file + 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; +} diff --git a/src/pixel_format.c b/src/pixel_format.c index e7c19e06..dff89d1b 100644 --- a/src/pixel_format.c +++ b/src/pixel_format.c @@ -1,4 +1,5 @@ #include <pixel_format.h> +#include <vulkan.h> #ifdef HAS_FBDEV # define FBDEV_FORMAT_FIELD_INITIALIZER(r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset) \ @@ -26,13 +27,15 @@ # 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) \ +#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 = _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 +53,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/platformchannel.c b/src/platformchannel.c index 3fe3a27c..b37d5832 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -93,10 +93,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); \ } @@ -1179,7 +1179,7 @@ int platch_send(char *channel, struct platch_obj *object, enum platch_codec resp handlerdata->on_response = on_response; handlerdata->userdata = userdata; - result = flutterpi.flutter.libflutter_engine.FlutterPlatformMessageCreateResponseHandle(flutterpi.flutter.engine, platch_on_response_internal, handlerdata, &response_handle); + result = flutterpi.flutter.procs.PlatformMessageCreateResponseHandle(flutterpi.flutter.engine, platch_on_response_internal, handlerdata, &response_handle); if (result != kSuccess) { fprintf(stderr, "[flutter-pi] Error create platform message response handle. FlutterPlatformMessageCreateResponseHandle: %s\n", FLUTTER_RESULT_TO_STRING(result)); goto fail_free_handlerdata; @@ -1187,6 +1187,7 @@ int platch_send(char *channel, struct platch_obj *object, enum platch_codec resp } ok = flutterpi_send_platform_message( + &flutterpi, channel, buffer, size, @@ -1198,7 +1199,7 @@ int platch_send(char *channel, struct platch_obj *object, enum platch_codec resp // TODO: This won't work if we're not on the main thread if (on_response) { - result = flutterpi.flutter.libflutter_engine.FlutterPlatformMessageReleaseResponseHandle(flutterpi.flutter.engine, response_handle); + result = flutterpi.flutter.procs.PlatformMessageReleaseResponseHandle(flutterpi.flutter.engine, response_handle); if (result != kSuccess) { fprintf(stderr, "[flutter-pi] Error releasing platform message response handle. FlutterPlatformMessageReleaseResponseHandle: %s\n", FLUTTER_RESULT_TO_STRING(result)); ok = EIO; @@ -1215,7 +1216,7 @@ int platch_send(char *channel, struct platch_obj *object, enum platch_codec resp fail_release_handle: if (on_response) { - flutterpi.flutter.libflutter_engine.FlutterPlatformMessageReleaseResponseHandle(flutterpi.flutter.engine, response_handle); + flutterpi.flutter.procs.PlatformMessageReleaseResponseHandle(flutterpi.flutter.engine, response_handle); } fail_free_buffer: diff --git a/src/pluginregistry.c b/src/pluginregistry.c index 5210ac1c..14ea79a1 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -93,7 +93,7 @@ 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( +MAYBE_UNUSED static struct platch_obj_cb_data *get_cb_data_by_channel( struct plugin_registry *registry, const char *channel ) { @@ -227,12 +227,16 @@ int plugin_registry_ensure_plugins_initialized(struct plugin_registry *registry) struct plugin_instance *instance; enum plugin_init_result result; struct pointer_set initialized_plugins; + void **initialized_plugins_storage; int n_pointers; cpset_lock(®istry->plugins); n_pointers = cpset_get_count_pointers_locked(®istry->plugins); - initialized_plugins = PSET_INITIALIZER_STATIC(alloca(sizeof(void*) * n_pointers), n_pointers); + + initialized_plugins_storage = alloca(sizeof(*initialized_plugins_storage) * n_pointers); + memset(initialized_plugins_storage, 0, sizeof(*initialized_plugins_storage) * n_pointers); + initialized_plugins = PSET_INITIALIZER_STATIC(initialized_plugins_storage, n_pointers); LOG_DEBUG("Registered plugins: "); for_each_pointer_in_cpset(®istry->plugins, instance) { diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index a349143b..977a477b 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -11,6 +11,7 @@ #include <flutter-pi.h> #include <texture_registry.h> +#include <gl_renderer.h> #include <plugins/gstreamer_video_player.h> FILE_DESCR("gstreamer video_player") @@ -33,8 +34,9 @@ struct video_frame { struct gl_texture_frame gl_frame; }; -struct frame_interface *frame_interface_new(struct flutterpi *flutterpi) { +struct frame_interface *frame_interface_new(struct gl_renderer *renderer) { struct frame_interface *interface; + struct gbm_device *gbm_device; EGLBoolean egl_ok; EGLContext context; EGLDisplay display; @@ -44,40 +46,46 @@ struct frame_interface *frame_interface_new(struct flutterpi *flutterpi) { return NULL; } - display = flutterpi_get_egl_display(flutterpi); + display = gl_renderer_get_egl_display(renderer); if (display == EGL_NO_DISPLAY) { goto fail_free; } - context = flutterpi_create_egl_context(flutterpi); + context = gl_renderer_create_context(renderer); if (context == EGL_NO_CONTEXT) { goto fail_free; } - PFNEGLCREATEIMAGEKHRPROC create_image = (PFNEGLCREATEIMAGEKHRPROC) eglGetProcAddress("eglCreateImageKHR"); + PFNEGLCREATEIMAGEKHRPROC create_image = (PFNEGLCREATEIMAGEKHRPROC) gl_renderer_get_proc_address(renderer, "eglCreateImageKHR"); if (create_image == NULL) { - LOG_ERROR("Could not resolve eglCreateImageKHR egl procedure.\n"); + LOG_ERROR("Could not resolve eglCreateImageKHR EGL procedure.\n"); goto fail_destroy_context; } - PFNEGLDESTROYIMAGEKHRPROC destroy_image = (PFNEGLDESTROYIMAGEKHRPROC) eglGetProcAddress("eglDestroyImageKHR"); + PFNEGLDESTROYIMAGEKHRPROC destroy_image = (PFNEGLDESTROYIMAGEKHRPROC) gl_renderer_get_proc_address(renderer, "eglDestroyImageKHR"); if (destroy_image == NULL) { - LOG_ERROR("Could not resolve eglDestroyImageKHR egl procedure.\n"); + LOG_ERROR("Could not resolve eglDestroyImageKHR EGL procedure.\n"); goto fail_destroy_context; } - PFNGLEGLIMAGETARGETTEXTURE2DOESPROC gl_egl_image_target_texture2d = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) eglGetProcAddress("glEGLImageTargetTexture2DOES"); + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC gl_egl_image_target_texture2d = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) gl_renderer_get_proc_address(renderer, "glEGLImageTargetTexture2DOES"); if (gl_egl_image_target_texture2d == NULL) { - LOG_ERROR("Could not resolve glEGLImageTargetTexture2DOES egl procedure.\n"); + LOG_ERROR("Could not resolve glEGLImageTargetTexture2DOES EGL procedure.\n"); goto fail_destroy_context; } // These two are optional. // Might be useful in the future. - PFNEGLQUERYDMABUFFORMATSEXTPROC egl_query_dmabuf_formats = (PFNEGLQUERYDMABUFFORMATSEXTPROC) eglGetProcAddress("eglQueryDmaBufFormatsEXT"); - PFNEGLQUERYDMABUFMODIFIERSEXTPROC egl_query_dmabuf_modifiers = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); + PFNEGLQUERYDMABUFFORMATSEXTPROC egl_query_dmabuf_formats = (PFNEGLQUERYDMABUFFORMATSEXTPROC) gl_renderer_get_proc_address(renderer, "eglQueryDmaBufFormatsEXT"); + PFNEGLQUERYDMABUFMODIFIERSEXTPROC egl_query_dmabuf_modifiers = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC) gl_renderer_get_proc_address(renderer, "eglQueryDmaBufModifiersEXT"); - interface->gbm_device = flutterpi_get_gbm_device(flutterpi); + gbm_device = gl_renderer_get_gbm_device(renderer); + if (gbm_device == NULL) { + LOG_ERROR("GL Render doesn't have a GBM device associated with it, which is necessary for importing the video frames.\n"); + goto fail_destroy_context; + } + + interface->gbm_device = gbm_device; interface->display = display; pthread_mutex_init(&interface->context_lock, NULL); interface->context = context; diff --git a/src/plugins/gstreamer_video_player/player.c b/src/plugins/gstreamer_video_player/player.c index 4722513a..257f1fd9 100644 --- a/src/plugins/gstreamer_video_player/player.c +++ b/src/plugins/gstreamer_video_player/player.c @@ -1003,7 +1003,7 @@ static struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char * texture = flutterpi_create_texture(flutterpi); if (texture == NULL) goto fail_free_player; - frame_interface = frame_interface_new(flutterpi); + frame_interface = frame_interface_new(flutterpi_get_gl_renderer(flutterpi)); if (frame_interface == NULL) goto fail_destroy_texture; texture_id = texture_get_id(texture); diff --git a/src/plugins/gstreamer_video_player/plugin.c b/src/plugins/gstreamer_video_player/plugin.c index b9a37af5..1ed3220a 100644 --- a/src/plugins/gstreamer_video_player/plugin.c +++ b/src/plugins/gstreamer_video_player/plugin.c @@ -193,7 +193,7 @@ static int get_player_from_map_arg( return 0; } -static int get_player_and_meta_from_map_arg( +MAYBE_UNUSED static int get_player_and_meta_from_map_arg( struct std_value *arg, struct gstplayer **player_out, struct gstplayer_meta **meta_out, @@ -258,7 +258,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/services.c b/src/plugins/services.c index 6cffe42f..12068890 100644 --- a/src/plugins/services.c +++ b/src/plugins/services.c @@ -3,7 +3,7 @@ #include <flutter-pi.h> #include <pluginregistry.h> -#include <compositor.h> +//#include <compositor.h> #include <plugins/services.h> static struct { @@ -72,6 +72,9 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter * } */ + /// TODO: Implement + + /* value = &object->json_arg; if ((value->type != kJsonArray) || (value->size == 0)) { @@ -140,6 +143,7 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter responsehandle, "Expected `arg` to contain at least one element." ); + */ } else if (strcmp(object->method, "SystemChrome.setApplicationSwitcherDescription") == 0) { /* * SystemChrome.setApplicationSwitcherDescription(Map description) diff --git a/src/plugins/text_input.c b/src/plugins/text_input.c index 9a295f10..949eb46f 100644 --- a/src/plugins/text_input.c +++ b/src/plugins/text_input.c @@ -491,7 +491,7 @@ static int on_request_autofill( ); } -static int on_set_editable_size_and_transform( +MAYBE_UNUSED static int on_set_editable_size_and_transform( struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle ) { @@ -823,7 +823,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; @@ -838,7 +838,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; diff --git a/src/surface.c b/src/surface.c new file mode 100644 index 00000000..2b019cad --- /dev/null +++ b/src/surface.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +/* + * Window Surface + * + * - provides an object that can be composited by flutter-pi + * - (by calling present_kms or present_fbdev on it) + * - == basically the thing that stores the graphics of a FlutterLayer + * - backing stores are special kinds of scanout surfaces that flutter can render into + * - every scanout surface can be registered as a platform view to display it + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + +#include <stddef.h> +#include <stdlib.h> + +#include <collection.h> +#include <compositor_ng.h> +#include <surface.h> +#include <surface_private.h> +#include <tracer.h> + +FILE_DESCR("rendering surfaces") + +void surface_deinit(struct surface *s); + +static const uuid_t uuid = CONST_UUID(0xce, 0x35, 0x87, 0x0c, 0x82, 0x08, 0x46, 0x09, 0xbd, 0xab, 0x80, 0x67, 0x28, 0x15, 0x45, 0xb5); + +#ifdef DEBUG +ATTR_PURE struct surface *__checked_cast_surface(void *ptr) { + struct surface *s; + + s = CAST_SURFACE_UNCHECKED(ptr); + DEBUG_ASSERT(uuid_equals(s->uuid, uuid)); + return s; +} +#endif + +int surface_init(struct surface *s, struct tracer *tracer) { + uuid_copy(&s->uuid, uuid); + s->n_refs = REFCOUNT_INIT_1; + pthread_mutex_init(&s->lock, NULL); + s->tracer = tracer_ref(tracer); + s->revision = 1; + s->present_kms = NULL; + s->present_fbdev = NULL; + s->deinit = surface_deinit; + return 0; +} + +void surface_deinit(struct surface *s) { + tracer_unref(s->tracer); +} + +struct surface *surface_new(struct tracer *tracer) { + struct surface *s; + int ok; + + s = malloc(sizeof *s); + if (s == NULL) { + return NULL; + } + + ok = surface_init(s, tracer); + if (ok != 0) { + free(s); + return NULL; + } + + return s; +} + +void surface_destroy(struct surface *s) { + DEBUG_ASSERT_NOT_NULL(s->deinit); + s->deinit(s); + free(s); +} + +DEFINE_LOCK_OPS(surface, lock) + +DEFINE_REF_OPS(surface, n_refs) + +int64_t surface_get_revision(struct surface *s) { + DEBUG_ASSERT_NOT_NULL(s); + return s->revision; +} + +int surface_present_kms( + struct surface *s, + const struct fl_layer_props *props, + struct kms_req_builder *builder +) { + int ok; + + DEBUG_ASSERT_NOT_NULL(s); + DEBUG_ASSERT_NOT_NULL(props); + DEBUG_ASSERT_NOT_NULL(builder); + DEBUG_ASSERT_NOT_NULL(s->present_kms); + + TRACER_BEGIN(s->tracer, "surface_present_kms"); + ok = s->present_kms(s, props, builder); + TRACER_END(s->tracer, "surface_present_kms"); + + return ok; +} + +int surface_present_fbdev( + struct surface *s, + const struct fl_layer_props *props, + struct fbdev_commit_builder *builder +) { + int ok; + + DEBUG_ASSERT_NOT_NULL(s); + DEBUG_ASSERT_NOT_NULL(props); + DEBUG_ASSERT_NOT_NULL(builder); + DEBUG_ASSERT_NOT_NULL(s->present_fbdev); + + TRACER_BEGIN(s->tracer, "surface_present_fbdev"); + ok = s->present_fbdev(s, props, builder); + TRACER_END(s->tracer, "surface_present_fbdev"); + + return ok; +} diff --git a/src/tracer.c b/src/tracer.c new file mode 100644 index 00000000..6b478a47 --- /dev/null +++ b/src/tracer.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT +/* + * Tracer - simple event tracing based on flutter event tracing interface + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#include <stdlib.h> + +#include <flutter_embedder.h> + +#include <collection.h> +#include <tracer.h> + +FILE_DESCR("tracing") + +struct tracer { + refcount_t n_refs; + atomic_bool has_cbs; + FlutterEngineTraceEventDurationBeginFnPtr trace_begin; + FlutterEngineTraceEventDurationEndFnPtr trace_end; + FlutterEngineTraceEventInstantFnPtr trace_instant; + + atomic_bool logged_discarded_events; +}; + +struct tracer *tracer_new_with_cbs( + FlutterEngineTraceEventDurationBeginFnPtr trace_begin, + FlutterEngineTraceEventDurationEndFnPtr trace_end, + FlutterEngineTraceEventInstantFnPtr trace_instant +) { + struct tracer *tracer; + + tracer = malloc(sizeof *tracer); + if (tracer == NULL) { + goto fail_return_null; + } + + tracer->n_refs = REFCOUNT_INIT_1; + tracer->has_cbs = ATOMIC_VAR_INIT(true); + tracer->trace_begin = trace_begin; + tracer->trace_end = trace_end; + tracer->trace_instant = trace_instant; + tracer->logged_discarded_events = ATOMIC_VAR_INIT(false); + return tracer; + + + fail_return_null: + return NULL; +} + +struct tracer *tracer_new_with_stubs() { + struct tracer *tracer; + + tracer = malloc(sizeof *tracer); + if (tracer == NULL) { + goto fail_return_null; + } + + tracer->n_refs = REFCOUNT_INIT_1; + tracer->has_cbs = ATOMIC_VAR_INIT(false); + tracer->trace_begin = NULL; + tracer->trace_end = NULL; + tracer->trace_instant = NULL; + tracer->logged_discarded_events = ATOMIC_VAR_INIT(false); + return tracer; + + + fail_return_null: + return NULL; +} + +void tracer_set_cbs( + struct tracer *tracer, + FlutterEngineTraceEventDurationBeginFnPtr trace_begin, + FlutterEngineTraceEventDurationEndFnPtr trace_end, + FlutterEngineTraceEventInstantFnPtr trace_instant +) { + tracer->trace_begin = trace_begin; + tracer->trace_end = trace_end; + tracer->trace_instant = trace_instant; + + bool already_set_before = atomic_exchange(&tracer->has_cbs, true); + DEBUG_ASSERT_MSG(!already_set_before, "tracing callbacks can only be set once."); +} + +void tracer_destroy(struct tracer *tracer) { + free(tracer); +} + +DEFINE_REF_OPS(tracer, n_refs) + +static void log_discarded_event(struct tracer *tracer, const char *name) { + if (atomic_exchange(&tracer->logged_discarded_events, true) == false) { + LOG_DEBUG("Tracing event was discarded because tracer not initialized yet: %s. This message will only be logged once.\n", name); + } +} + +void __tracer_begin(struct tracer *tracer, const char *name) { + DEBUG_ASSERT_NOT_NULL(tracer); + DEBUG_ASSERT_NOT_NULL(name); + if (atomic_load(&tracer->has_cbs)) { + tracer->trace_begin(name); + } else { + log_discarded_event(tracer, name); + } +} + +void __tracer_end(struct tracer *tracer, const char *name) { + DEBUG_ASSERT_NOT_NULL(tracer); + DEBUG_ASSERT_NOT_NULL(name); + if (atomic_load(&tracer->has_cbs)) { + tracer->trace_end(name); + } else { + log_discarded_event(tracer, name); + } +} + +void __tracer_instant(struct tracer *tracer, const char *name) { + DEBUG_ASSERT_NOT_NULL(tracer); + DEBUG_ASSERT_NOT_NULL(name); + if (atomic_load(&tracer->has_cbs)) { + tracer->trace_instant(name); + } else { + log_discarded_event(tracer, name); + } +} diff --git a/src/user_input.c b/src/user_input.c index 527261b6..aabb4fbb 100644 --- a/src/user_input.c +++ b/src/user_input.c @@ -15,6 +15,7 @@ #include <collection.h> #include <keyboard.h> #include <user_input.h> +#include <compositor_ng.h> FILE_DESCR("user input") @@ -538,9 +539,8 @@ static int on_mouse_motion_event(struct user_input *input, struct libinput_event struct libinput_event_pointer *pointer_event; struct input_device_data *data; struct libinput_device *device; + struct point delta, pos_display, pos_view; uint64_t timestamp; - double new_cursor_x, new_cursor_y; - double dx, dy; DEBUG_ASSERT(input != NULL); DEBUG_ASSERT(event != NULL); @@ -552,37 +552,35 @@ static int on_mouse_motion_event(struct user_input *input, struct libinput_event data->timestamp = timestamp; - dx = libinput_event_pointer_get_dx(pointer_event); - dy = libinput_event_pointer_get_dy(pointer_event); - - // transform the deltas. when the display is rotated - // we want the mouse to move in different directions as well. - // the dx and dy is still in display (not view) coordinates though, - // we only changed the movement direction. - apply_flutter_transformation(input->view_to_display_transform_nontranslating, &dx, &dy); + delta = transform_point( + input->view_to_display_transform_nontranslating, + POINT( + libinput_event_pointer_get_dx(pointer_event), + libinput_event_pointer_get_dy(pointer_event) + ) + ); - new_cursor_x = input->cursor_x + dx; - new_cursor_y = input->cursor_y + dy; + pos_display = POINT(input->cursor_x + delta.x, input->cursor_y + delta.y); // check if we're ran over the display boundaries. - if (new_cursor_x < 0.0) { - new_cursor_x = 0.0; - } else if (new_cursor_x > input->display_width - 1) { - new_cursor_x = input->display_width - 1; + if (pos_display.x < 0.0) { + pos_display.x = 0.0; + } else if (pos_display.x > input->display_width - 1) { + pos_display.x = input->display_width - 1; } - if (new_cursor_y < 0.0) { - new_cursor_y = 0.0; - } else if (new_cursor_y > input->display_height - 1) { - new_cursor_y = input->display_height - 1; + if (pos_display.y < 0.0) { + pos_display.y = 0.0; + } else if (pos_display.y > input->display_height - 1) { + pos_display.y = input->display_height - 1; } - input->cursor_x = new_cursor_x; - input->cursor_y = new_cursor_y; + input->cursor_x = pos_display.x; + input->cursor_y = pos_display.y; // transform the cursor pos to view (flutter) coordinates. - apply_flutter_transformation(input->display_to_view_transform, &new_cursor_x, &new_cursor_y); - + pos_view = transform_point(input->display_to_view_transform, pos_display); + if (data->has_emitted_pointer_events == false) { data->has_emitted_pointer_events = true; input->n_cursor_devices++; @@ -594,8 +592,8 @@ static int on_mouse_motion_event(struct user_input *input, struct libinput_event input, &FLUTTER_POINTER_MOUSE_MOVE_EVENT( timestamp, - new_cursor_x, - new_cursor_y, + pos_view.x, + pos_view.y, data->flutter_device_id_offset, data->buttons ), @@ -615,8 +613,8 @@ static int on_mouse_motion_absolute_event(struct user_input *input, struct libin struct libinput_event_pointer *pointer_event; struct input_device_data *data; struct libinput_device *device; + struct point pos_display, pos_view; uint64_t timestamp; - double x, y; DEBUG_ASSERT(input != NULL); DEBUG_ASSERT(event != NULL); @@ -627,20 +625,22 @@ static int on_mouse_motion_absolute_event(struct user_input *input, struct libin timestamp = libinput_event_pointer_get_time_usec(pointer_event); // get the new mouse position in display coordinates - x = libinput_event_pointer_get_absolute_x_transformed(pointer_event, input->display_width - 1); - y = libinput_event_pointer_get_absolute_y_transformed(pointer_event, input->display_height - 1); + pos_display = POINT( + libinput_event_pointer_get_absolute_x_transformed(pointer_event, input->display_width), + libinput_event_pointer_get_absolute_y_transformed(pointer_event, input->display_height) + ); /// FIXME: Why do we store the coordinates here? - data->x = x; - data->y = y; + data->x = pos_display.x; + data->y = pos_display.y; data->timestamp = timestamp; // update the "global" cursor position - input->cursor_x = x; - input->cursor_y = y; + input->cursor_x = pos_display.x; + input->cursor_y = pos_display.y; // transform x & y to view (flutter) coordinates - apply_flutter_transformation(input->display_to_view_transform, &x, &y); + pos_view = transform_point(input->display_to_view_transform, pos_display); if (data->has_emitted_pointer_events == false) { data->has_emitted_pointer_events = true; @@ -652,7 +652,7 @@ static int on_mouse_motion_absolute_event(struct user_input *input, struct libin input, &FLUTTER_POINTER_MOUSE_MOVE_EVENT( timestamp, - x, y, + pos_view.x, pos_view.y, data->flutter_device_id_offset, data->buttons ), @@ -668,11 +668,11 @@ static int on_mouse_button_event(struct user_input *input, struct libinput_event struct input_device_data *data; struct libinput_device *device; FlutterPointerPhase pointer_phase; + struct point pos_view; uint64_t timestamp; uint16_t evdev_code; int64_t flutter_button; int64_t new_flutter_button_state; - double x, y; DEBUG_ASSERT(input != NULL); DEBUG_ASSERT(event != NULL); @@ -732,19 +732,16 @@ static int on_mouse_button_event(struct user_input *input, struct libinput_event pointer_phase = kMove; } - x = input->cursor_x; - y = input->cursor_y; - // since the stored coords are in display, not view coordinates, // we need to transform them again - apply_flutter_transformation(input->display_to_view_transform, &x, &y); + pos_view = transform_point(input->display_to_view_transform, POINT(input->cursor_x, input->cursor_y)); emit_pointer_events( input, &FLUTTER_POINTER_MOUSE_BUTTON_EVENT( pointer_phase, timestamp, - x, y, + pos_view.x, pos_view.y, data->flutter_device_id_offset, new_flutter_button_state ), @@ -762,6 +759,7 @@ static int on_mouse_axis_event(struct user_input *input, struct libinput_event * struct libinput_event_pointer *pointer_event; struct input_device_data *data; struct libinput_device *device; + struct point pos_view; uint64_t timestamp; DEBUG_ASSERT(input != NULL); @@ -772,12 +770,9 @@ static int on_mouse_axis_event(struct user_input *input, struct libinput_event * data = libinput_device_get_user_data(device); timestamp = libinput_event_pointer_get_time_usec(pointer_event); - double x = input->cursor_x; - double y = input->cursor_y; - // since the stored coords are in display, not view coordinates, // we need to transform them again - apply_flutter_transformation(input->display_to_view_transform, &x, &y); + pos_view = transform_point(input->display_to_view_transform, POINT(input->cursor_x, input->cursor_y)); double scroll_x = libinput_event_pointer_has_axis(pointer_event, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL) ? libinput_event_pointer_get_axis_value(pointer_event, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL) @@ -791,8 +786,8 @@ static int on_mouse_axis_event(struct user_input *input, struct libinput_event * input, &FLUTTER_POINTER_MOUSE_SCROLL_EVENT( timestamp, - x, - y, + pos_view.x, + pos_view.y, data->flutter_device_id_offset, scroll_x / 15.0 * 53.0, scroll_y / 15.0 * 53.0, @@ -807,9 +802,9 @@ static int on_mouse_axis_event(struct user_input *input, struct libinput_event * static int on_touch_down(struct user_input *input, struct libinput_event *event) { struct libinput_event_touch *touch_event; struct input_device_data *data; + struct point pos_view; uint64_t timestamp; int64_t device_id; - double x, y; int slot; DEBUG_ASSERT(input != NULL); @@ -828,18 +823,21 @@ static int on_touch_down(struct user_input *input, struct libinput_event *event) device_id = data->flutter_device_id_offset + slot; - x = libinput_event_touch_get_x_transformed(touch_event, input->display_width - 1); - y = libinput_event_touch_get_y_transformed(touch_event, input->display_height - 1); - // transform the display coordinates to view (flutter) coordinates - apply_flutter_transformation(input->display_to_view_transform, &x, &y); + pos_view = transform_point( + input->display_to_view_transform, + POINT( + libinput_event_touch_get_x_transformed(touch_event, input->display_width), + libinput_event_touch_get_y_transformed(touch_event, input->display_height) + ) + ); // emit the flutter pointer event - emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_DOWN_EVENT(timestamp, x, y, device_id), 1); + emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_DOWN_EVENT(timestamp, pos_view.x, pos_view.y, device_id), 1); // alter our device state - data->x = x; - data->y = y; + data->x = pos_view.x; + data->y = pos_view.y; data->timestamp = timestamp; return 0; @@ -876,9 +874,9 @@ static int on_touch_up(struct user_input *input, struct libinput_event *event) { static int on_touch_motion(struct user_input *input, struct libinput_event *event) { struct libinput_event_touch *touch_event; struct input_device_data *data; + struct point pos_view; uint64_t timestamp; int64_t device_id; - double x, y; int slot; DEBUG_ASSERT(input != NULL); @@ -897,18 +895,21 @@ static int on_touch_motion(struct user_input *input, struct libinput_event *even device_id = data->flutter_device_id_offset + slot; - x = libinput_event_touch_get_x_transformed(touch_event, input->display_width - 1); - y = libinput_event_touch_get_y_transformed(touch_event, input->display_height - 1); - // transform the display coordinates to view (flutter) coordinates - apply_flutter_transformation(input->display_to_view_transform, &x, &y); + pos_view = transform_point( + input->display_to_view_transform, + POINT( + libinput_event_touch_get_x_transformed(touch_event, input->display_width), + libinput_event_touch_get_y_transformed(touch_event, input->display_height) + ) + ); // emit the flutter pointer event - emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_MOVE_EVENT(timestamp, x, y, device_id), 1); + emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_MOVE_EVENT(timestamp, pos_view.x, pos_view.y, device_id), 1); // alter our device state - data->x = x; - data->y = y; + data->x = pos_view.x; + data->y = pos_view.y; data->timestamp = timestamp; return 0; diff --git a/src/vk_gbm_backing_store.c b/src/vk_gbm_backing_store.c new file mode 100644 index 00000000..62795aa0 --- /dev/null +++ b/src/vk_gbm_backing_store.c @@ -0,0 +1,845 @@ +// SPDX-License-Identifier: MIT +/* + * Vulkan GBM Backing Store + * + * - a surface that can be used for filling flutter vulkan backing stores + * - and for scanout using KMS + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + +#include <stdlib.h> + +#include <collection.h> +#include <stdatomic.h> + +#include <tracer.h> +#include <surface.h> +#include <surface_private.h> +#include <backing_store.h> +#include <backing_store_private.h> + +#include <vk_renderer.h> +#include <vulkan.h> + +#include <vk_gbm_backing_store.h> + +FILE_DESCR("vulkan gbm backing store") + +struct vk_gbm_backing_store; +struct vk_renderer; + +struct fb { + struct gbm_bo *bo; + VkDeviceMemory memory; + VkImage image; + FlutterVulkanImage fl_image; +}; + +struct locked_fb { + struct vk_gbm_backing_store *store; + atomic_flag is_locked; + refcount_t n_refs; + struct fb *fb; +}; + +struct vk_gbm_backing_store { + union { + struct surface surface; + struct backing_store backing_store; + }; + + uuid_t uuid; + + /** + * @brief The vulkan renderer we use for talking to vulkan. + * + */ + struct vk_renderer *renderer; + + /** + * @brief Just some vulkan images that are compatible with GBM/DRM/KMS. + * + * 4 framebuffers is good enough for most use-cases. + */ + struct fb fbs[4]; + + /** + * @brief This is just some locking wrapper around the simple fbs above. + * + * Any locked_fb for which is_locked is false can be locked and then freely used for anything. + * Everything that needs that locked_fb for something should keep a reference on it. + * Once the reference count drops to zero, is_locked will be set to false and the fb is ready to be reused again. + * + */ + struct locked_fb locked_fbs[4]; + + /** + * @brief The framebuffer that was last queued to be presented using @ref vk_gbm_backing_store_queue_present_vulkan. + * + * This framebuffer is still locked so we can present it again any time, without worrying about it being acquired by + * flutter using @ref vk_gbm_backing_store_fill_vulkan. Even when @ref vk_gbm_backing_store_present_kms is called, + * we don't set this to NULL. + * + * This is the framebuffer that will be presented on screen when @ref vk_gbm_backing_store_present_kms or + * @ref vk_gbm_backing_store_present_fbdev is called. + * + */ + struct locked_fb *front_fb; + + /** + * @brief The pixel format to use for all framebuffers. + * + */ + enum pixfmt pixel_format; + +#ifdef DEBUG + /** + * @brief The number of framebuffers that are currently locked. + * + */ + atomic_int n_locked_fbs; +#endif +}; + +static void locked_fb_destroy(struct locked_fb *fb) { + struct vk_gbm_backing_store *store; + + store = fb->store; + fb->store = NULL; + +#ifdef DEBUG + atomic_fetch_sub(&store->n_locked_fbs, 1); +#endif + atomic_flag_clear(&fb->is_locked); + surface_unref(CAST_SURFACE(store)); +} + +DEFINE_STATIC_REF_OPS(locked_fb, n_refs) + +COMPILE_ASSERT(offsetof(struct vk_gbm_backing_store, surface) == 0); +COMPILE_ASSERT(offsetof(struct vk_gbm_backing_store, backing_store.surface) == 0); + +static const uuid_t uuid = CONST_UUID(0x26, 0xfe, 0x91, 0x53, 0x75, 0xf2, 0x41, 0x90, 0xa1, 0xf5, 0xba, 0xe1, 0x1b, 0x28, 0xd5, 0xe5); + +#define CAST_THIS(ptr) CAST_VK_GBM_BACKING_STORE(ptr) +#define CAST_THIS_UNCHECKED(ptr) CAST_VK_GBM_BACKING_STORE_UNCHECKED(ptr) + +#ifdef DEBUG +ATTR_PURE struct vk_gbm_backing_store *__checked_cast_vk_gbm_backing_store(void *ptr) { + struct vk_gbm_backing_store *store; + + store = CAST_VK_GBM_BACKING_STORE_UNCHECKED(ptr); + DEBUG_ASSERT(uuid_equals(store->uuid, uuid)); + return store; +} +#endif + +void vk_gbm_backing_store_deinit(struct surface *s); +static int vk_gbm_backing_store_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); +static int vk_gbm_backing_store_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); +static int vk_gbm_backing_store_fill(struct backing_store *store, FlutterBackingStore *fl_store); +static int vk_gbm_backing_store_queue_present(struct backing_store *store, const FlutterBackingStore *fl_store); + +static bool is_srgb_format(VkFormat vk_format) { + return vk_format == VK_FORMAT_R8G8B8A8_SRGB || vk_format == VK_FORMAT_B8G8R8A8_SRGB; +} + +static VkFormat srgb_to_unorm_format(VkFormat vk_format) { + if (vk_format == VK_FORMAT_R8G8B8A8_SRGB) { + return VK_FORMAT_R8G8B8A8_UNORM; + } else if (vk_format == VK_FORMAT_B8G8R8A8_SRGB) { + return VK_FORMAT_B8G8R8A8_UNORM; + } else { + UNREACHABLE(); + } +} + +static int fb_init(struct fb *fb, struct gbm_device *gbm_device, struct vk_renderer *renderer, int width, int height, enum pixfmt pixel_format, uint64_t drm_modifier) { + PFN_vkGetMemoryFdPropertiesKHR get_memory_fd_props; + VkSubresourceLayout layout; + VkDeviceMemory img_device_memory; + struct gbm_bo *bo; + VkFormat vk_format; + VkDevice device; + VkResult ok; + VkImage vkimg; + int fd; + + DEBUG_ASSERT_MSG(get_pixfmt_info(pixel_format)->vk_format != VK_FORMAT_UNDEFINED, "Given pixel format is not compatible with any vulkan sRGB format."); + + device = vk_renderer_get_device(renderer); + + /// FIXME: Right now, using any _SRGB format (for example VK_FORMAT_B8G8R8A8_SRGB) will not work because + /// that'll break some assertions inside flutter / skia. (VK_FORMAT_B8G8R8A8_SRGB maps to GrColorType::kRGBA_8888_SRGB, + /// but some other part of flutter will use GrColorType::kRGBA_8888 so you'll get a mismatch at some point) + /// We're just converting the _SRGB to a _UNORM here, but I'm not really sure that's guaranteed to work. + /// (_UNORM can mean anything basically) + + vk_format = get_pixfmt_info(pixel_format)->vk_format; + if (is_srgb_format(vk_format)) { + vk_format = srgb_to_unorm_format(vk_format); + } + + ok = vkCreateImage( + device, + &(VkImageCreateInfo){ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = vk_format, + .extent = { .width = width, .height = height, .depth = 1 }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + // Tell vulkan that the tiling we want to use is determined by a DRM format modifier + // (in our case DRM_FORMAT_MOD_LINEAR, but could be something else as well, using a device-supported + // modifier is probably faster if that's possible) + .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, + // These are the usage flags flutter will use too internally + .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = 0, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .pNext = + &(VkExternalMemoryImageCreateInfo){ + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + .pNext = + &(VkImageDrmFormatModifierExplicitCreateInfoEXT){ + .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT, + .drmFormatModifierPlaneCount = 1, + .drmFormatModifier = drm_modifier, + .pPlaneLayouts = + (VkSubresourceLayout[1]){ + { + /// These are just dummy values, but they need to be there AFAIK + .offset = 0, + .size = 0, + .rowPitch = 0, + .arrayPitch = 0, + .depthPitch = 0, + }, + }, + }, + }, + }, + NULL, + &vkimg + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not create Vulkan image. vkCreateImage"); + return EIO; + } + + // We _should_ only have one plane in the linear case. + // Query the layout of that plane to check if it matches the GBM BOs layout. + vkGetImageSubresourceLayout( + device, + vkimg, + &(VkImageSubresource){ + .aspectMask = VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT, // For v3dv, this doesn't really matter + .mipLevel = 0, + .arrayLayer = 0, + }, + &layout + ); + + // Create a GBM BO with the actual memory we're going to use + bo = gbm_bo_create_with_modifiers( + gbm_device, + width, + height, + get_pixfmt_info(pixel_format)->gbm_format, + &drm_modifier, + 1 + ); + if (bo == NULL) { + LOG_ERROR("Could not create GBM BO. gbm_bo_create_with_modifiers: %s\n", strerror(errno)); + goto fail_destroy_image; + } + + // Just some paranoid checks that the layout matches (had some issues with that initially) + if (gbm_bo_get_offset(bo, 0) != layout.offset) { + LOG_ERROR("GBM BO layout doesn't match image layout. This is probably a driver / kernel bug.\n"); + goto fail_destroy_bo; + } + + if (gbm_bo_get_stride_for_plane(bo, 0) != layout.rowPitch) { + LOG_ERROR("GBM BO layout doesn't match image layout. This is probably a driver / kernel bug.\n"); + goto fail_destroy_bo; + } + + // gbm_bo_get_fd will dup us a new dmabuf fd. + // So if we don't use it, we need to close it. + fd = gbm_bo_get_fd(bo); + if (fd < 0) { + LOG_ERROR("Couldn't get dmabuf fd for GBM buffer. gbm_bo_get_fd: %s\n", strerror(errno)); + goto fail_destroy_bo; + } + + // find out as which memory types we can import our dmabuf fd + VkMemoryFdPropertiesKHR fd_memory_props = { + .sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR, + .pNext = NULL, + .memoryTypeBits = 0, + }; + + get_memory_fd_props = (PFN_vkGetMemoryFdPropertiesKHR) vkGetDeviceProcAddr(device, "vkGetMemoryFdPropertiesKHR"); + if (get_memory_fd_props == NULL) { + LOG_ERROR("Couldn't resolve vkGetMemoryFdPropertiesKHR.\n"); + goto fail_close_fd; + } + + ok = get_memory_fd_props( + device, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + fd, + &fd_memory_props + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Couldn't get dmabuf memory properties. vkGetMemoryFdPropertiesKHR"); + goto fail_close_fd; + } + + // Find out the memory requirements for our image (the supported memory types for import) + VkMemoryRequirements2 image_memory_reqs = { + .sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, + .memoryRequirements = { 0 }, + .pNext = NULL + }; + + vkGetImageMemoryRequirements2( + device, + &(VkImageMemoryRequirementsInfo2) { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2, + .image = vkimg, + .pNext = NULL, + }, + &image_memory_reqs + ); + + // Find a memory type that fits both to the dmabuf and the image + int mem = vk_renderer_find_mem_type( + renderer, + 0, + image_memory_reqs.memoryRequirements.memoryTypeBits & fd_memory_props.memoryTypeBits + ); + if (mem < 0) { + LOG_ERROR("Couldn't find a memory type that's both supported by the image and the dmabuffer.\n"); + goto fail_close_fd; + } + + // now, create a VkDeviceMemory instance from our dmabuf. + // after successful import, the fd is owned by the device memory object + // and we don't need to close it. + ok = vkAllocateMemory( + device, + &(VkMemoryAllocateInfo) { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = layout.size, + .memoryTypeIndex = mem, + .pNext = &(VkImportMemoryFdInfoKHR) { + .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + .fd = fd, + .pNext = &(VkMemoryDedicatedAllocateInfo) { + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, + .image = vkimg, + .buffer = VK_NULL_HANDLE, + .pNext = NULL, + }, + }, + }, + NULL, + &img_device_memory + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Couldn't import dmabuf as vulkan device memory. vkAllocateMemory"); + goto fail_close_fd; + } + + ok = vkBindImageMemory2( + device, + 1, + &(VkBindImageMemoryInfo){ + .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO, + .image = vkimg, + .memory = img_device_memory, + .memoryOffset = 0, + .pNext = NULL, + } + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Couldn't bind dmabuf-backed vulkan device memory to vulkan image. vkBindImageMemory2"); + goto fail_free_device_memory; + } + + fb->bo = bo; + fb->memory = img_device_memory; + fb->image = vkimg; + + COMPILE_ASSERT(sizeof(FlutterVulkanImage) == 24); + fb->fl_image = (FlutterVulkanImage) { + .struct_size = sizeof(FlutterVulkanImage), + .image = fb->image, + .format = vk_format, + }; + + return 0; + + + fail_free_device_memory: + vkFreeMemory(device, img_device_memory, NULL); + goto fail_destroy_bo; + + fail_close_fd: + close(fd); + + fail_destroy_bo: + gbm_bo_destroy(bo); + + fail_destroy_image: + vkDestroyImage(device, vkimg, NULL); + return EIO; +} + +static void fb_deinit(struct fb *fb, VkDevice device) { + DEBUG_ASSERT_NOT_NULL(fb); + + vkFreeMemory(device, fb->memory, NULL); + gbm_bo_destroy(fb->bo); + vkDestroyImage(device, fb->image, NULL); +} + +int vk_gbm_backing_store_init( + struct vk_gbm_backing_store *store, + struct tracer *tracer, + struct point size, + struct gbm_device *gbm_device, + struct vk_renderer *renderer, + enum pixfmt pixel_format +) { + int ok; + + ok = backing_store_init(CAST_BACKING_STORE_UNCHECKED(store), tracer, size); + if (ok != 0) { + return EIO; + } + + for (int i = 0; i < ARRAY_SIZE(store->fbs); i++) { + ok = fb_init(store->fbs + i, gbm_device, renderer, (int) size.x, (int) size.y, pixel_format, DRM_FORMAT_MOD_LINEAR); + if (ok != 0) { + LOG_ERROR("Could not initialize vulkan GBM framebuffer.\n"); + goto fail_deinit_previous_fbs; + } + + continue; + + + fail_deinit_previous_fbs: + for (int j = 0; j < i; j++) { + fb_deinit(store->fbs + j, vk_renderer_get_device(renderer)); + } + goto fail_deinit_backing_store; + } + + for (int i = 0; i < ARRAY_SIZE(store->locked_fbs); i++) { + store->locked_fbs[i].store = NULL; + store->locked_fbs[i].is_locked = (atomic_flag) ATOMIC_FLAG_INIT; + store->locked_fbs[i].n_refs = REFCOUNT_INIT_0; + store->locked_fbs[i].fb = store->fbs + i; + } + + COMPILE_ASSERT(ARRAY_SIZE(store->fbs) == ARRAY_SIZE(store->locked_fbs)); + + store->surface.present_kms = vk_gbm_backing_store_present_kms; + store->surface.present_fbdev = vk_gbm_backing_store_present_fbdev; + store->surface.deinit = vk_gbm_backing_store_deinit; + store->backing_store.fill = vk_gbm_backing_store_fill; + store->backing_store.queue_present = vk_gbm_backing_store_queue_present; + + uuid_copy(&store->uuid, uuid); + store->renderer = vk_renderer_ref(renderer); + store->front_fb = NULL; + store->pixel_format = pixel_format; +#ifdef DEBUG + store->n_locked_fbs = ATOMIC_VAR_INIT(0); +#endif + return 0; + + + fail_deinit_backing_store: + backing_store_deinit(CAST_SURFACE_UNCHECKED(store)); + return EIO; +} + +ATTR_MALLOC struct vk_gbm_backing_store *vk_gbm_backing_store_new( + struct tracer *tracer, + struct point size, + struct gbm_device *device, + struct vk_renderer *renderer, + enum pixfmt pixel_format +) { + struct vk_gbm_backing_store *store; + int ok; + + store = malloc(sizeof *store); + if (store == NULL) { + goto fail_return_null; + } + + ok = vk_gbm_backing_store_init(store, tracer, size, device, renderer, pixel_format); + if (ok != 0) { + goto fail_free_store; + } + + return store; + + + fail_free_store: + free(store); + + fail_return_null: + return NULL; +} + +void vk_gbm_backing_store_deinit(struct surface *s) { + struct vk_gbm_backing_store *store; + + store = CAST_THIS(s); + (void) store; + + for (int i = 0; i < ARRAY_SIZE(store->fbs); i++) { + fb_deinit(store->fbs + i, vk_renderer_get_device(store->renderer)); + } + + backing_store_deinit(s); +} + +struct gbm_bo_meta { + struct drmdev *drmdev; + uint32_t fb_id; + bool has_opaque_fb; + enum pixfmt opaque_pixel_format; + uint32_t opaque_fb_id; +}; + +static void on_destroy_gbm_bo_meta(struct gbm_bo *bo, void *meta_void) { + struct gbm_bo_meta *meta; + int ok; + + DEBUG_ASSERT_NOT_NULL(bo); + DEBUG_ASSERT_NOT_NULL(meta_void); + meta = meta_void; + + ok = drmdev_rm_fb(meta->drmdev, meta->fb_id); + if (ok != 0) { + LOG_ERROR("Couldn't remove DRM framebuffer.\n"); + } + + if (meta->has_opaque_fb && meta->opaque_fb_id != meta->fb_id) { + ok = drmdev_rm_fb(meta->drmdev, meta->opaque_fb_id); + if (ok != 0) { + LOG_ERROR("Couldn't remove DRM framebuffer.\n"); + } + } + + drmdev_unref(meta->drmdev); + free(meta); +} + +static void on_release_layer(void *userdata) { + struct locked_fb *fb; + + DEBUG_ASSERT_NOT_NULL(userdata); + + fb = userdata; + locked_fb_unref(fb); +} + +static int vk_gbm_backing_store_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder) { + struct vk_gbm_backing_store *store; + struct gbm_bo_meta *meta; + struct drmdev *drmdev; + struct gbm_bo *bo; + enum pixfmt pixel_format, opaque_pixel_format; + uint32_t fb_id, opaque_fb_id; + int ok; + + store = CAST_THIS(s); + (void) store; + (void) props; + (void) builder; + + /// TODO: Implement non axis-aligned fl_layer_props + DEBUG_ASSERT_MSG(props->is_aa_rect, "only axis aligned view geometry is supported right now"); + + surface_lock(s); + + DEBUG_ASSERT_NOT_NULL_MSG(store->front_fb, "There's no framebuffer available for scanout right now. Make sure you called backing_store_swap_buffers() before presenting."); + + bo = store->front_fb->fb->bo; + meta = gbm_bo_get_user_data(bo); + if (meta == NULL) { + bool has_opaque_fb; + + meta = malloc(sizeof *meta); + if (meta == NULL) { + ok = ENOMEM; + goto fail_unlock; + } + + drmdev = kms_req_builder_get_drmdev(builder); + DEBUG_ASSERT_NOT_NULL(drmdev); + + TRACER_BEGIN(store->surface.tracer, "drmdev_add_fb (non-opaque)"); + fb_id = drmdev_add_fb( + drmdev, + gbm_bo_get_width(bo), + gbm_bo_get_height(bo), + store->pixel_format, + gbm_bo_get_handle(bo).u32, + gbm_bo_get_stride(bo), + gbm_bo_get_offset(bo, 0), + true, gbm_bo_get_modifier(bo), + 0 + ); + TRACER_END(store->surface.tracer, "drmdev_add_fb (non-opaque)"); + + if (fb_id == 0) { + ok = EIO; + LOG_ERROR("Couldn't add GBM buffer as DRM framebuffer.\n"); + goto fail_free_meta; + } + + if (get_pixfmt_info(store->pixel_format)->is_opaque == false) { + has_opaque_fb = false; + opaque_pixel_format = pixfmt_opaque(store->pixel_format); + if (get_pixfmt_info(opaque_pixel_format)->is_opaque) { + + TRACER_BEGIN(store->surface.tracer, "drmdev_add_fb (opaque)"); + opaque_fb_id = drmdev_add_fb( + drmdev, + gbm_bo_get_width(bo), + gbm_bo_get_height(bo), + opaque_pixel_format, + gbm_bo_get_handle(bo).u32, + gbm_bo_get_stride(bo), + gbm_bo_get_offset(bo, 0), + true, gbm_bo_get_modifier(bo), + 0 + ); + TRACER_END(store->surface.tracer, "drmdev_add_fb (opaque)"); + + if (opaque_fb_id != 0) { + has_opaque_fb = true; + } + } + } else { + has_opaque_fb = true; + opaque_fb_id = fb_id; + opaque_pixel_format = store->pixel_format; + } + + meta->drmdev = drmdev_ref(drmdev); + meta->fb_id = fb_id; + meta->has_opaque_fb = has_opaque_fb; + meta->opaque_pixel_format = opaque_pixel_format; + meta->opaque_fb_id = opaque_fb_id; + gbm_bo_set_user_data(bo, meta, on_destroy_gbm_bo_meta); + } else { + // We can only add this GBM BO to a single KMS device as an fb right now. + DEBUG_ASSERT_EQUALS_MSG(meta->drmdev, kms_req_builder_get_drmdev(builder), "Currently GBM BOs can only be scanned out on a single KMS device for their whole lifetime."); + } + + /* + LOG_DEBUG( + "vk_gbm_backing_store_present_kms:\n" + " src_x, src_y, src_w, src_h: %f %f %f %f\n" + " dst_x, dst_y, dst_w, dst_h: %f %f %f %f\n", + 0.0, 0.0, + store->backing_store.size.x, + store->backing_store.size.y, + props->aa_rect.offset.x, + props->aa_rect.offset.y, + props->aa_rect.size.x, + props->aa_rect.size.y + ); + */ + + // The bottom-most layer should preferably be an opaque layer. + // For example, on Pi 4, even though ARGB8888 is listed as supported for the primary plane, + // rendering is completely off. + // So we just cast our fb to an XRGB8888 framebuffer and scanout that instead. + if (kms_req_builder_prefer_next_layer_opaque(builder)) { + if (meta->has_opaque_fb) { + fb_id = meta->opaque_fb_id; + pixel_format = meta->opaque_pixel_format; + } else { + LOG_DEBUG("Bottom-most framebuffer layer should be opaque, but an opaque framebuffer couldn't be created.\n"); + LOG_DEBUG("Using non-opaque framebuffer instead, which can result in visual glitches.\n"); + fb_id = meta->fb_id; + pixel_format = store->pixel_format; + } + } else { + fb_id = meta->fb_id; + pixel_format = store->pixel_format; + } + + TRACER_BEGIN(store->surface.tracer, "kms_req_builder_push_fb_layer"); + ok = kms_req_builder_push_fb_layer( + builder, + &(const struct kms_fb_layer) { + .drm_fb_id = fb_id, + .format = pixel_format, + .has_modifier = false, + .modifier = 0, + + .dst_x = (int32_t) props->aa_rect.offset.x, + .dst_y = (int32_t) props->aa_rect.offset.y, + .dst_w = (uint32_t) props->aa_rect.size.x, + .dst_h = (uint32_t) props->aa_rect.size.y, + + .src_x = 0, + .src_y = 0, + .src_w = DOUBLE_TO_FP1616_ROUNDED(store->backing_store.size.x), + .src_h = DOUBLE_TO_FP1616_ROUNDED(store->backing_store.size.y), + + .has_rotation = false, + .rotation = PLANE_TRANSFORM_ROTATE_0, + + .has_in_fence_fd = false, + .in_fence_fd = 0 + }, + on_release_layer, + locked_fb_ref(store->front_fb) + ); + TRACER_END(store->surface.tracer, "kms_req_builder_push_fb_layer"); + if (ok != 0) { + goto fail_unref_locked_fb; + } + + + surface_unlock(s); + return ok; + + + fail_unref_locked_fb: + locked_fb_unref(store->front_fb); + goto fail_unlock; + + fail_free_meta: + free(meta); + + fail_unlock: + surface_unlock(s); + return ok; +} + +static int vk_gbm_backing_store_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder) { + struct vk_gbm_backing_store *store; + + /// TODO: Implement by mmapping the current front bo, copy it into the fbdev + /// TODO: Print a warning here if we're not using explicit linear tiling and use glReadPixels instead of gbm_bo_map in that case + + store = CAST_THIS(s); + (void) store; + (void) props; + (void) builder; + + UNIMPLEMENTED(); + + return 0; +} + + +static int vk_gbm_backing_store_fill(struct backing_store *s, FlutterBackingStore *fl_store) { + struct vk_gbm_backing_store *store; + int i, ok; + + store = CAST_THIS(s); + + surface_lock(CAST_SURFACE_UNCHECKED(s)); + + // Try to find & lock a locked_fb we can use. + // Note we use atomics here even though we hold the surfaces' mutex because + // releasing a locked_fb is possibly done without the mutex. + for (i = 0; i < ARRAY_SIZE(store->locked_fbs); i++) { + if (atomic_flag_test_and_set(&store->locked_fbs[i].is_locked) == false) { + goto locked; + } + } + + // If we reached this point, we couldn't lock one of the 4 locked_fbs. + // Which shouldn't happen except we have an application bug. + DEBUG_ASSERT_MSG(false, "Couldn't find a free slot to lock the surfaces front framebuffer."); + ok = EIO; + goto fail_unlock; + + locked: ; + /// TODO: Remove this once we're using triple buffering +#ifdef DEBUG + atomic_fetch_add(&store->n_locked_fbs, 1); + //DEBUG_ASSERT_MSG(before + 1 <= 3, "sanity check failed: too many locked fbs for double-buffered vsync"); +#endif + store->locked_fbs[i].store = CAST_VK_GBM_BACKING_STORE(surface_ref(CAST_SURFACE_UNCHECKED(s))); + store->locked_fbs[i].n_refs = REFCOUNT_INIT_1; + + COMPILE_ASSERT(sizeof(FlutterVulkanBackingStore) == 16); + fl_store->type = kFlutterBackingStoreTypeVulkan; + fl_store->vulkan = (FlutterVulkanBackingStore) { + .struct_size = sizeof(FlutterVulkanBackingStore), + .image = &store->locked_fbs[i].fb->fl_image, + .user_data = surface_ref(CAST_SURFACE_UNCHECKED(store)), + .destruction_callback = surface_unref_void, + }; + + surface_unlock(CAST_SURFACE_UNCHECKED(s)); + + return 0; + + + fail_unlock: + surface_unlock(CAST_SURFACE_UNCHECKED(s)); + return ok; +} + +static int vk_gbm_backing_store_queue_present(struct backing_store *s, const FlutterBackingStore *fl_store) { + struct vk_gbm_backing_store *store; + struct locked_fb *fb; + + store = CAST_THIS(s); + + DEBUG_ASSERT_EQUALS(fl_store->type, kFlutterBackingStoreTypeVulkan); + /// TODO: Implement handling if fl_store->did_update == false + + surface_lock(CAST_SURFACE_UNCHECKED(s)); + + // find out which fb this image belongs too + fb = NULL; + for (int i = 0; i < ARRAY_SIZE(store->locked_fbs); i++) { + if (store->locked_fbs[i].fb->fl_image.image == fl_store->vulkan.image->image) { + fb = store->locked_fbs + i; + break; + } + } + + if (fb == NULL) { + LOG_ERROR("The vulkan image flutter wants to present is not known to this backing store.\n"); + surface_unlock(CAST_SURFACE_UNCHECKED(s)); + return EINVAL; + } + + // Replace the front fb with the new one + // (will unref the old one if not NULL internally) + locked_fb_swap_ptrs(&store->front_fb, fb); + + // Since flutter no longer uses this fb for rendering, we need to unref it + locked_fb_unref(fb); + + surface_unlock(CAST_SURFACE_UNCHECKED(s)); + return 0; +} + diff --git a/src/vk_renderer.c b/src/vk_renderer.c new file mode 100644 index 00000000..148a4580 --- /dev/null +++ b/src/vk_renderer.c @@ -0,0 +1,570 @@ +// SPDX-License-Identifier: MIT +/* + * Vulkan Renderer Implementation + * + * Copyright (c) 2022, Hannes Winkler <hanneswinkler2000@web.de> + */ + + +#include <stdlib.h> +#include <alloca.h> + +#include <collection.h> +#include <vulkan.h> +#include <vk_renderer.h> + +#define VALIDATION_LAYER_NAME "VK_LAYER_KHRONOS_validation" + +FILE_DESCR("vulkan renderer") + +MAYBE_UNUSED static VkLayerProperties *get_layer_props(int n_layers, VkLayerProperties *layers, const char *layer_name) { + for (int i = 0; i < n_layers; i++) { + if (strcmp(layers[i].layerName, layer_name) == 0) { + return layers + i; + } + } + return NULL; +} + +MAYBE_UNUSED static bool supports_layer(int n_layers, VkLayerProperties *layers, const char *layer_name) { + return get_layer_props(n_layers, layers, layer_name) != NULL; +} + +static VkExtensionProperties *get_extension_props(int n_extensions, VkExtensionProperties *extensions, const char *extension_name) { + for (int i = 0; i < n_extensions; i++) { + if (strcmp(extensions[i].extensionName, extension_name) == 0) { + return extensions + i; + } + } + return NULL; +} + +MAYBE_UNUSED static bool supports_extension(int n_extensions, VkExtensionProperties *extensions, const char *extension_name) { + return get_extension_props(n_extensions, extensions, extension_name) != NULL; +} + +static VkBool32 on_debug_utils_message( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, + MAYBE_UNUSED VkDebugUtilsMessageTypeFlagsEXT types, + const VkDebugUtilsMessengerCallbackDataEXT* data, + MAYBE_UNUSED void* userdata +) { + LOG_DEBUG( + "[%s] (%d, %s) %s (queues: %d, cmdbufs: %d, objects: %d)\n", + severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT ? "VERBOSE" : + severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT ? "INFO" : + severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT ? "WARNING" : + severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT ? "ERROR" : "unknown severity", + data->messageIdNumber, + data->pMessageIdName, + data->pMessage, + data->queueLabelCount, + data->cmdBufLabelCount, + data->objectCount + ); + return VK_TRUE; +} + +static int get_graphics_queue_family_index(VkPhysicalDevice device) { + uint32_t n_queue_families; + + vkGetPhysicalDeviceQueueFamilyProperties(device, &n_queue_families, NULL); + + VkQueueFamilyProperties queue_families[n_queue_families]; + vkGetPhysicalDeviceQueueFamilyProperties(device, &n_queue_families, queue_families); + + for (unsigned i = 0; i < n_queue_families; i++) { + if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + return i; + } + } + + return -1; +} + +static int score_physical_device(VkPhysicalDevice device, const char **required_device_extensions) { + VkPhysicalDeviceProperties props; + VkPhysicalDeviceFeatures features; + VkResult ok; + uint32_t n_available_extensions; + int graphics_queue_fam_index; + int score = 1; + + vkGetPhysicalDeviceProperties(device, &props); + vkGetPhysicalDeviceFeatures(device, &features); + + if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + score += 15; + } else if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) { + score += 10; + } + + graphics_queue_fam_index = get_graphics_queue_family_index(device); + if (graphics_queue_fam_index == -1) { + LOG_ERROR("Physical device does not support a graphics queue.\n"); + return 0; + } + + ok = vkEnumerateDeviceExtensionProperties(device, NULL, &n_available_extensions, NULL); + if (ok != 0) { + LOG_VK_ERROR(ok, "Could not query available physical device extensions. vkEnumerateDeviceExtensionProperties"); + return 0; + } + + VkExtensionProperties available_extensions[n_available_extensions]; + ok = vkEnumerateDeviceExtensionProperties(device, NULL, &n_available_extensions, available_extensions); + if (ok != 0) { + LOG_VK_ERROR(ok, "Could not query available physical device extensions. vkEnumerateDeviceExtensionProperties"); + return 0; + } + + for (const char **cursor = required_device_extensions; *cursor != NULL; cursor++) { + for (unsigned i = 0; i < n_available_extensions; i++) { + if (strcmp(available_extensions[i].extensionName, *cursor) == 0) { + goto found; + } + } + LOG_ERROR("Required extension %s is not supported by vulkan device.\n", *cursor); + return 0; + + found: + continue; + } + + return score; +} + + +struct vk_renderer { + refcount_t n_refs; + + VkInstance instance; + VkPhysicalDevice physical_device; + VkDevice device; + VkQueue graphics_queue; + VkDebugUtilsMessengerEXT debug_utils_messenger; + VkCommandPool graphics_cmd_pool; + + PFN_vkCreateDebugUtilsMessengerEXT create_debug_utils_messenger; + PFN_vkDestroyDebugUtilsMessengerEXT destroy_debug_utils_messenger; + + int n_enabled_layers; + const char **enabled_layers; + + int n_enabled_instance_extensions; + const char **enabled_instance_extensions; + + int n_enabled_device_extensions; + const char **enabled_device_extensions; +}; + +ATTR_MALLOC struct vk_renderer *vk_renderer_new() { + PFN_vkDestroyDebugUtilsMessengerEXT destroy_debug_utils_messenger; + PFN_vkCreateDebugUtilsMessengerEXT create_debug_utils_messenger; + VkDebugUtilsMessengerEXT debug_utils_messenger; + struct vk_renderer *renderer; + VkPhysicalDevice physical_device; + VkCommandPool graphics_cmd_pool; + VkInstance instance; + VkDevice device; + VkResult ok; + VkQueue graphics_queue; + uint32_t n_available_layers, n_available_instance_extensions, n_available_device_extensions, n_physical_devices; + const char **enabled_layers, **enabled_instance_extensions, **enabled_device_extensions; + bool enable_debug_utils_messenger; + int n_enabled_layers, n_enabled_instance_extensions, n_enabled_device_extensions; + int graphics_queue_family_index; + + renderer = malloc(sizeof *renderer); + if (renderer == NULL) { + return NULL; + } + + ok = vkEnumerateInstanceLayerProperties(&n_available_layers, NULL); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not query vulkan instance layers. vkEnumerateInstanceLayerProperties"); + goto fail_free_renderer; + } + + VkLayerProperties *available_layers = alloca(sizeof(VkLayerProperties) * n_available_layers); + ok = vkEnumerateInstanceLayerProperties(&n_available_layers, available_layers); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not query vulkan instance layers. vkEnumerateInstanceLayerProperties"); + goto fail_free_renderer; + } + + n_enabled_layers = 0; + enabled_layers = malloc(n_available_layers * sizeof(*enabled_layers)); + if (enabled_layers == NULL) { + goto fail_free_renderer; + } + +#ifdef VULKAN_DEBUG + if (supports_layer(n_available_layers, available_layers, VALIDATION_LAYER_NAME)) { + enabled_layers[n_enabled_layers] = VALIDATION_LAYER_NAME; + n_enabled_layers++; + } else { + LOG_DEBUG("Vulkan validation layer was not found. Validation will not be enabled.\n"); + } +#endif + + ok = vkEnumerateInstanceExtensionProperties(NULL, &n_available_instance_extensions, NULL); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not query vulkan instance extensions. vkEnumerateInstanceExtensionProperties"); + goto fail_free_enabled_layers; + } + + VkExtensionProperties *available_instance_extensions = alloca(sizeof(VkExtensionProperties) * n_available_instance_extensions); + ok = vkEnumerateInstanceExtensionProperties(NULL, &n_available_instance_extensions, available_instance_extensions); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not query vulkan instance extensions. vkEnumerateInstanceExtensionProperties"); + goto fail_free_enabled_layers; + } + + n_enabled_instance_extensions = 0; + enabled_instance_extensions = malloc(n_available_instance_extensions * sizeof *enabled_instance_extensions); + if (enabled_instance_extensions == NULL) { + goto fail_free_enabled_layers; + } + + enable_debug_utils_messenger = false; +#ifdef VULKAN_DEBUG + if (supports_extension(n_available_instance_extensions, available_instance_extensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { + enabled_instance_extensions[n_enabled_instance_extensions] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + n_enabled_instance_extensions++; + enable_debug_utils_messenger = true; + } else { + LOG_DEBUG("Vulkan debug utils extension was not found. Debug logging will not be enabled.\n"); + } +#endif + + /// TODO: Maybe enable some other useful instance extensions here? + + ok = vkCreateInstance( + &(VkInstanceCreateInfo) { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .flags = 0, + .pApplicationInfo = &(VkApplicationInfo) { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "flutter-pi", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "flutter-pi", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_MAKE_VERSION(1, 1, 0), + .pNext = NULL, + }, + .enabledLayerCount = n_enabled_layers, + .ppEnabledLayerNames = enabled_layers, + .enabledExtensionCount = n_enabled_instance_extensions, + .ppEnabledExtensionNames = enabled_instance_extensions, + .pNext = enable_debug_utils_messenger ? &(VkDebugUtilsMessengerCreateInfoEXT) { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .flags = 0, + .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + .pfnUserCallback = on_debug_utils_message, + .pUserData = NULL, + .pNext = NULL, + } : NULL + }, + NULL, + &instance + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not create instance. vkCreateInstance"); + goto fail_free_enabled_instance_extensions; + } + + if (enable_debug_utils_messenger) { + create_debug_utils_messenger = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (create_debug_utils_messenger == NULL) { + LOG_ERROR("Could not resolve vkCreateDebugUtilsMessengerEXT function.\n"); + goto fail_destroy_instance; + } + + destroy_debug_utils_messenger = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (destroy_debug_utils_messenger == NULL) { + LOG_ERROR("Could not resolve vkDestroyDebugUtilsMessengerEXT function.\n"); + goto fail_destroy_instance; + } + + ok = create_debug_utils_messenger( + instance, + &(VkDebugUtilsMessengerCreateInfoEXT) { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .flags = 0, + .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + .pfnUserCallback = on_debug_utils_message, + .pUserData = NULL, + .pNext = NULL, + }, + NULL, + &debug_utils_messenger + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not create debug utils messenger. vkCreateDebugUtilsMessengerEXT"); + goto fail_destroy_instance; + } + } else { + debug_utils_messenger = VK_NULL_HANDLE; + } + + ok = vkEnumeratePhysicalDevices(instance, &n_physical_devices, NULL); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not enumerate physical devices. vkEnumeratePhysicalDevices"); + goto fail_maybe_destroy_messenger; + } + + VkPhysicalDevice *physical_devices = alloca(sizeof(VkPhysicalDevice) * n_physical_devices); + ok = vkEnumeratePhysicalDevices(instance, &n_physical_devices, physical_devices); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not enumerate physical devices. vkEnumeratePhysicalDevices"); + goto fail_maybe_destroy_messenger; + } + + static const char *required_device_extensions[] = { + VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, + VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, + VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, + VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, + VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME, + VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, + VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME, + NULL + }; + + physical_device = VK_NULL_HANDLE; + int score = 0; + for (unsigned i = 0; i < n_physical_devices; i++) { + VkPhysicalDevice this = physical_devices[i]; + int this_score = score_physical_device(this, required_device_extensions); + + if (this_score > score) { + physical_device = this; + score = this_score; + } + } + + if (physical_device == VK_NULL_HANDLE) { + LOG_ERROR("No suitable physical device found.\n"); + goto fail_maybe_destroy_messenger; + } + + ok = vkEnumerateDeviceExtensionProperties(physical_device, NULL, &n_available_device_extensions, NULL); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not query device extensions. vkEnumerateDeviceExtensionProperties"); + goto fail_maybe_destroy_messenger; + } + + VkExtensionProperties *available_device_extensions = alloca(sizeof(VkExtensionProperties) * n_available_device_extensions); + ok = vkEnumerateDeviceExtensionProperties(physical_device, NULL, &n_available_device_extensions, available_device_extensions); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not query device extensions. vkEnumerateDeviceExtensionProperties"); + goto fail_maybe_destroy_messenger; + } + + n_enabled_device_extensions = 0; + enabled_device_extensions = malloc(n_available_device_extensions * sizeof *enabled_device_extensions); + if (enabled_device_extensions == NULL) { + goto fail_maybe_destroy_messenger; + } + + // add all the required extensions to the list of enabled device extensions + for (const char **cursor = required_device_extensions; cursor != NULL && *cursor != NULL; cursor++) { + enabled_device_extensions[n_enabled_device_extensions] = *cursor; + n_enabled_device_extensions++; + } + + /// TODO: Maybe enable some other useful device extensions here? + + graphics_queue_family_index = get_graphics_queue_family_index(physical_device); + + ok = vkCreateDevice( + physical_device, + &(const VkDeviceCreateInfo) { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .flags = 0, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = (const VkDeviceQueueCreateInfo[1]) { + { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .flags = 0, + .queueFamilyIndex = graphics_queue_family_index, + .queueCount = 1, + .pQueuePriorities = (float[1]) { 1.0f }, + .pNext = NULL, + }, + }, + .enabledLayerCount = n_enabled_layers, + .ppEnabledLayerNames = enabled_layers, + .enabledExtensionCount = n_enabled_device_extensions, + .ppEnabledExtensionNames = enabled_device_extensions, + .pEnabledFeatures = &(const VkPhysicalDeviceFeatures) { 0 }, + .pNext = NULL, + }, + NULL, + &device + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not create logical device. vkCreateDevice"); + goto fail_free_enabled_device_extensions; + } + + vkGetDeviceQueue(device, graphics_queue_family_index, 0, &graphics_queue); + + ok = vkCreateCommandPool( + device, + &(const VkCommandPoolCreateInfo) { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = graphics_queue_family_index, + .pNext = NULL, + }, + NULL, + &graphics_cmd_pool + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not create command pool for allocating graphics command buffers. vkCreateCommandPool"); + goto fail_destroy_device; + } + + renderer->device = device; + renderer->physical_device = physical_device; + renderer->instance = instance; + renderer->graphics_queue = graphics_queue; + renderer->debug_utils_messenger = debug_utils_messenger; + renderer->graphics_cmd_pool = graphics_cmd_pool; + renderer->create_debug_utils_messenger = create_debug_utils_messenger; + renderer->destroy_debug_utils_messenger = destroy_debug_utils_messenger; + renderer->n_enabled_layers = n_enabled_layers; + renderer->enabled_layers = enabled_layers; + renderer->n_enabled_instance_extensions = n_enabled_instance_extensions; + renderer->enabled_instance_extensions = enabled_instance_extensions; + renderer->n_enabled_device_extensions = n_enabled_device_extensions; + renderer->enabled_device_extensions = enabled_device_extensions; + return renderer; + + + fail_destroy_device: + vkDestroyDevice(device, NULL); + + fail_free_enabled_device_extensions: + free(enabled_device_extensions); + + fail_maybe_destroy_messenger: + if (debug_utils_messenger != VK_NULL_HANDLE) { + destroy_debug_utils_messenger(instance, debug_utils_messenger, NULL); + } + + fail_destroy_instance: + vkDestroyInstance(instance, NULL); + + fail_free_enabled_instance_extensions: + free(enabled_instance_extensions); + + fail_free_enabled_layers: + free(enabled_layers); + + fail_free_renderer: + free(renderer); + return NULL; +} + +void vk_renderer_destroy(struct vk_renderer *renderer) { + VkResult ok; + + ok = vkDeviceWaitIdle(renderer->device); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Couldn't wait for vulkan device idle to destroy it. vkDeviceWaitIdle"); + } + + vkDestroyCommandPool(renderer->device, renderer->graphics_cmd_pool, NULL); + vkDestroyDevice(renderer->device, NULL); + free(renderer->enabled_device_extensions); + if (renderer->debug_utils_messenger != VK_NULL_HANDLE) { + renderer->destroy_debug_utils_messenger(renderer->instance, renderer->debug_utils_messenger, NULL); + } + vkDestroyInstance(renderer->instance, NULL); + free(renderer->enabled_instance_extensions); + free(renderer->enabled_layers); + free(renderer); +} + +DEFINE_REF_OPS(vk_renderer, n_refs) + +ATTR_CONST uint32_t vk_renderer_get_vk_version(struct vk_renderer MAYBE_UNUSED *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return VK_MAKE_VERSION(1, 1, 0); +} + +ATTR_PURE VkInstance vk_renderer_get_instance(struct vk_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return renderer->instance; +} + +ATTR_PURE VkPhysicalDevice vk_renderer_get_physical_device(struct vk_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return renderer->physical_device; +} + +ATTR_PURE VkDevice vk_renderer_get_device(struct vk_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return renderer->device; +} + +ATTR_PURE uint32_t vk_renderer_get_queue_family_index(struct vk_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return (uint32_t) get_graphics_queue_family_index(renderer->physical_device); +} + +ATTR_PURE VkQueue vk_renderer_get_queue(struct vk_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return renderer->graphics_queue; +} + +ATTR_PURE int vk_renderer_get_enabled_instance_extension_count(struct vk_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return renderer->n_enabled_instance_extensions; +} + +ATTR_PURE const char **vk_renderer_get_enabled_instance_extensions(struct vk_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return renderer->enabled_instance_extensions; +} + +ATTR_PURE int vk_renderer_get_enabled_device_extension_count(struct vk_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return renderer->n_enabled_device_extensions; +} + +ATTR_PURE const char **vk_renderer_get_enabled_device_extensions(struct vk_renderer *renderer) { + DEBUG_ASSERT_NOT_NULL(renderer); + return renderer->enabled_device_extensions; +} + +ATTR_PURE int vk_renderer_find_mem_type(struct vk_renderer *renderer, VkMemoryPropertyFlags flags, uint32_t req_bits) { + VkPhysicalDeviceMemoryProperties props; + + vkGetPhysicalDeviceMemoryProperties(renderer->physical_device, &props); + + for (unsigned i = 0u; i < props.memoryTypeCount; ++i) { + if (req_bits & (1 << i)) { + if ((props.memoryTypes[i].propertyFlags & flags) == flags) { + return i; + } + } + } + + return -1; +}